hdl.dsl: make referencing undefined FSM states an error.
authorwhitequark <whitequark@whitequark.org>
Thu, 6 Feb 2020 17:47:46 +0000 (17:47 +0000)
committerwhitequark <whitequark@whitequark.org>
Thu, 6 Feb 2020 17:47:46 +0000 (17:47 +0000)
Before this commit, doing something like:

    with m.FSM():
        with m.State("FOO"):
            m.next = "bAR"
        with m.State("BAR"):
            m.next = "FOO"

would silently create an empty state `bAR` and get stuck in it until
the module is reset. This was done intentionally (in Migen, this code
would in fact miscompile), but in retrospect was clearly a bad idea;
it turns typos into bugs, while in the rare case that branching to
a completely empty state is desired, it is trivial to define one.

Fixes #315.

nmigen/hdl/dsl.py
nmigen/test/test_hdl_dsl.py

index 96f19231830ecd59146450190ae083b57ce82b77..85ae4722044c2d31c10fcb00154db7089b22bcb0 100644 (file)
@@ -375,6 +375,10 @@ class Module(_ModuleBuilderRoot, Elaboratable):
             self._ctrl_context = "FSM"
             self.domain._depth += 1
             yield fsm
+            for state_name in fsm_data["encoding"]:
+                if state_name not in fsm_data["states"]:
+                    raise NameError("FSM state '{}' is referenced but not defined"
+                                    .format(state_name))
         finally:
             self.domain._depth -= 1
             self._ctrl_context = None
@@ -386,7 +390,7 @@ class Module(_ModuleBuilderRoot, Elaboratable):
         src_loc = tracer.get_src_loc(src_loc_at=1)
         fsm_data = self._get_ctrl("FSM")
         if name in fsm_data["states"]:
-            raise SyntaxError("FSM state '{}' is already defined".format(name))
+            raise NameError("FSM state '{}' is already defined".format(name))
         if name not in fsm_data["encoding"]:
             fsm_data["encoding"][name] = len(fsm_data["encoding"])
         try:
index 9b3a91d28696d8a1538dcbe6aac09aef254465b3..1c5686b0b5ecfa526dbf5e90f044f958defc025d 100644 (file)
@@ -582,12 +582,19 @@ class DSLTestCase(FHDLTestCase):
             with m.FSM(domain="comb"):
                 pass
 
+    def test_FSM_wrong_undefined(self):
+        m = Module()
+        with self.assertRaises(NameError,
+                msg="FSM state 'FOO' is referenced but not defined"):
+            with m.FSM() as fsm:
+                fsm.ongoing("FOO")
+
     def test_FSM_wrong_redefined(self):
         m = Module()
         with m.FSM():
             with m.State("FOO"):
                 pass
-            with self.assertRaises(SyntaxError,
+            with self.assertRaises(NameError,
                     msg="FSM state 'FOO' is already defined"):
                 with m.State("FOO"):
                     pass