hdl.ir: typecheck `convert(ports=)` more carefully.
authorawygle <awygle@gmail.com>
Fri, 24 Apr 2020 21:15:00 +0000 (14:15 -0700)
committerLuke Kenneth Casson Leighton <lkcl@lkcl.net>
Fri, 31 Dec 2021 13:29:05 +0000 (13:29 +0000)
The `ports` argument to the `convert` functions is a frequent hotspot of
beginner issues. Check to make sure it is either a list or a tuple, and
give an appropriately helpful error message if not.

Fixes #362.

nmigen/hdl/ir.py
nmigen/test/test_hdl_ir.py

index b9963c1591ae79631d5dafe59a99469251cce855..b7161c231bf1d1a4831bf7e8330c859159b2913a 100644 (file)
@@ -532,6 +532,12 @@ class Fragment:
         if ports is None:
             fragment._propagate_ports(ports=(), all_undef_as_ports=True)
         else:
+            if not isinstance(ports, tuple) and not isinstance(ports, list):
+                msg = "`ports` must be either a list or a tuple, not {!r}"\
+                        .format(ports)
+                if isinstance(ports, Value):
+                    msg += " (did you mean `ports=(<signal>,)`, rather than `ports=<signal>`?)"
+                raise TypeError(msg)
             mapped_ports = []
             # Lower late bound signals like ClockSignal() to ports.
             port_lowerer = DomainLowerer(fragment.domains)
index 2ea762a7aea63d64614e609bf8df36515f0ee306..b93537d9d2a73bc70d3e4ba8cd47101d03d93df4 100644 (file)
@@ -304,6 +304,16 @@ class FragmentPortsTestCase(FHDLTestCase):
                 msg="Only signals may be added as ports, not (const 1'd1)"):
             f.prepare(ports=(Const(1),))
 
+    def test_port_not_iterable(self):
+        f = Fragment()
+        with self.assertRaises(TypeError,
+                msg="`ports` must be either a list or a tuple, not 1"):
+            f.prepare(ports=1)
+        with self.assertRaises(TypeError,
+                msg="`ports` must be either a list or a tuple, not (const 1'd1)" +
+                " (did you mean `ports=(<signal>,)`, rather than `ports=<signal>`?)"):
+            f.prepare(ports=Const(1))
+
 class FragmentDomainsTestCase(FHDLTestCase):
     def test_iter_signals(self):
         cd1 = ClockDomain()