build.res: detect physical conflicts earlier.
authorwhitequark <cz@m-labs.hk>
Wed, 3 Jul 2019 15:07:44 +0000 (15:07 +0000)
committerwhitequark <cz@m-labs.hk>
Wed, 3 Jul 2019 15:07:44 +0000 (15:07 +0000)
This is useful for two reasons:
  1. nMigen can provide better error messages than the platform and
     do it earlier in the build pipeline.
  2. Many platforms handle diffpairs by only constraining the P pin;
     the N pin is completely ignored. If this is undetected,
     downstream users (human or software) can rely on this
     information assuming it is correct and introduce more errors.
     (Of course, this will not catch every mistake, but the most
     common is a copy-paste issue, and that will handle it.)

Fixes #124.

nmigen/build/res.py
nmigen/test/test_build_res.py

index a17adf038a62507f9ff06ac75e3f5c023a27ed14..0c9e8518aa938deef62af6ef21c1d97a44a683e6 100644 (file)
@@ -18,6 +18,7 @@ class ResourceManager:
     def __init__(self, resources, connectors):
         self.resources  = OrderedDict()
         self._requested = OrderedDict()
+        self._phys_reqd = OrderedDict()
 
         self.connectors = OrderedDict()
         self._conn_pins = OrderedDict()
@@ -115,14 +116,25 @@ class ResourceManager:
             elif isinstance(resource.ios[0], (Pins, DiffPairs)):
                 phys = resource.ios[0]
                 if isinstance(phys, Pins):
+                    phys_names = phys.names
                     port = Record([("io", len(phys))], name=name)
                 if isinstance(phys, DiffPairs):
+                    phys_names = phys.p.names + phys.n.names
                     port = Record([("p", len(phys)),
                                    ("n", len(phys))], name=name)
                 if dir == "-":
                     pin = None
                 else:
                     pin = Pin(len(phys), dir, xdr, name=name)
+
+                for phys_name in phys_names:
+                    if phys_name in self._phys_reqd:
+                        raise ResourceError("Resource component {} uses physical pin {}, but it "
+                                            "is already used by resource component {} that was "
+                                            "requested earlier"
+                                            .format(name, phys_name, self._phys_reqd[phys_name]))
+                    self._phys_reqd[phys_name] = name
+
                 self._ports.append((resource, pin, port, attrs))
 
                 if pin is not None and resource.clock is not None:
index d6df1ca3ae372dbbe1a2fc92012209ab310a74b4..42e762f28ae7e75ea0f3e8c17f9cd2c3a1cb1899 100644 (file)
@@ -238,6 +238,16 @@ class ResourceManagerTestCase(FHDLTestCase):
             self.cm.request("user_led", 0)
             self.cm.request("user_led", 0)
 
+    def test_wrong_request_duplicate_physical(self):
+        self.cm.add_resources([
+            Resource("clk20", 0, Pins("H1", dir="i")),
+        ])
+        self.cm.request("clk100", 0)
+        with self.assertRaises(ResourceError,
+                msg="Resource component clk20_0 uses physical pin H1, but it is already "
+                    "used by resource component clk100_0 that was requested earlier"):
+            self.cm.request("clk20", 0)
+
     def test_wrong_request_with_dir(self):
         with self.assertRaises(TypeError,
                 msg="Direction must be one of \"i\", \"o\", \"oe\", \"io\", or \"-\", "