build.{plat,res}: add support for connectors.
authorwhitequark <cz@m-labs.hk>
Mon, 3 Jun 2019 15:02:15 +0000 (15:02 +0000)
committerwhitequark <cz@m-labs.hk>
Mon, 3 Jun 2019 15:02:15 +0000 (15:02 +0000)
Fixes #77.

nmigen/build/plat.py
nmigen/build/res.py
nmigen/test/test_build_res.py
nmigen/vendor/fpga/lattice_ice40.py

index 6b4ee3093f13a8bfea121d99ac849ed93d80f1de..fe8d32b3140963f536618de0356a1f6c5764bfed 100644 (file)
@@ -74,11 +74,12 @@ class BuildProducts:
 
 
 class Platform(ConstraintManager, metaclass=ABCMeta):
-    resources = abstractproperty()
-    clocks    = abstractproperty()
+    resources  = abstractproperty()
+    connectors = abstractproperty()
+    clocks     = abstractproperty()
 
     def __init__(self):
-        super().__init__(self.resources, self.clocks)
+        super().__init__(self.resources, self.connectors, self.clocks)
 
         self.extra_files = OrderedDict()
 
index 9cea2376803d9919344936c9e34b24c43f754a69..0b32ded00704d3f8bad27d3139ce67584aee1b2f 100644 (file)
@@ -15,26 +15,43 @@ class ConstraintError(Exception):
 
 
 class ConstraintManager:
-    def __init__(self, resources, clocks):
+    def __init__(self, resources, connectors, clocks):
         self.resources  = OrderedDict()
-        self.requested  = OrderedDict()
+        self.connectors = OrderedDict()
         self.clocks     = OrderedDict()
+
+        self._mapping   = OrderedDict()
+        self._requested = OrderedDict()
         self._ports     = []
 
         self.add_resources(resources)
+        self.add_connectors(connectors)
         for name_number, frequency in clocks:
             if not isinstance(name_number, tuple):
                 name_number = (name_number, 0)
             self.add_clock(*name_number, frequency)
 
     def add_resources(self, resources):
-        for r in resources:
-            if not isinstance(r, Resource):
-                raise TypeError("Object {!r} is not a Resource".format(r))
-            if (r.name, r.number) in self.resources:
+        for res in resources:
+            if not isinstance(res, Resource):
+                raise TypeError("Object {!r} is not a Resource".format(res))
+            if (res.name, res.number) in self.resources:
+                raise NameError("Trying to add {!r}, but {!r} has the same name and number"
+                                .format(res, self.resources[res.name, res.number]))
+            self.resources[res.name, res.number] = res
+
+    def add_connectors(self, connectors):
+        for conn in connectors:
+            if not isinstance(conn, Connector):
+                raise TypeError("Object {!r} is not a Connector".format(conn))
+            if (conn.name, conn.number) in self.connectors:
                 raise NameError("Trying to add {!r}, but {!r} has the same name and number"
-                                .format(r, self.resources[r.name, r.number]))
-            self.resources[r.name, r.number] = r
+                                .format(conn, self.connectors[conn.name, conn.number]))
+            self.connectors[conn.name, conn.number] = conn
+
+            for conn_pin, plat_pin in conn:
+                assert conn_pin not in self._mapping
+                self._mapping[conn_pin] = plat_pin
 
     def add_clock(self, name, number, frequency):
         resource = self.lookup(name, number)
@@ -57,7 +74,7 @@ class ConstraintManager:
 
     def request(self, name, number=0, *, dir=None, xdr=None):
         resource = self.lookup(name, number)
-        if (resource.name, resource.number) in self.requested:
+        if (resource.name, resource.number) in self._requested:
             raise ConstraintError("Resource {}#{} has already been requested"
                                   .format(name, number))
 
@@ -129,48 +146,48 @@ class ConstraintManager:
         value = resolve(resource,
             *merge_options(resource, dir, xdr),
             name="{}_{}".format(resource.name, resource.number))
-        self.requested[resource.name, resource.number] = value
+        self._requested[resource.name, resource.number] = value
         return value
 
     def iter_single_ended_pins(self):
-        for resource, pin, port in self._ports:
+        for res, pin, port in self._ports:
             if pin is None:
                 continue
-            if isinstance(resource.io[0], Pins):
-                yield pin, port.io, resource.extras
+            if isinstance(res.io[0], Pins):
+                yield pin, port.io, res.extras
 
     def iter_differential_pins(self):
-        for resource, pin, port in self._ports:
+        for res, pin, port in self._ports:
             if pin is None:
                 continue
-            if isinstance(resource.io[0], DiffPairs):
-                yield pin, port.p, port.n, resource.extras
+            if isinstance(res.io[0], DiffPairs):
+                yield pin, port.p, port.n, res.extras
 
     def iter_ports(self):
-        for resource, pin, port in self._ports:
-            if isinstance(resource.io[0], Pins):
+        for res, pin, port in self._ports:
+            if isinstance(res.io[0], Pins):
                 yield port.io
-            elif isinstance(resource.io[0], DiffPairs):
+            elif isinstance(res.io[0], DiffPairs):
                 yield port.p
                 yield port.n
             else:
                 assert False
 
     def iter_port_constraints(self):
-        for resource, pin, port in self._ports:
-            if isinstance(resource.io[0], Pins):
-                yield port.io.name, resource.io[0].names, resource.extras
-            elif isinstance(resource.io[0], DiffPairs):
-                yield port.p.name, resource.io[0].p.names, resource.extras
-                yield port.n.name, resource.io[0].n.names, resource.extras
+        for res, pin, port in self._ports:
+            if isinstance(res.io[0], Pins):
+                yield port.io.name, list(res.io[0].map_names(self._mapping, res)), res.extras
+            elif isinstance(res.io[0], DiffPairs):
+                yield port.p.name, list(res.io[0].p.map_names(self._mapping, res)), res.extras
+                yield port.n.name, list(res.io[0].n.map_names(self._mapping, res)), res.extras
             else:
                 assert False
 
     def iter_clock_constraints(self):
-        for name, number in self.clocks.keys() & self.requested.keys():
+        for name, number in self.clocks.keys() & self._requested.keys():
             resource = self.resources[name, number]
-            pin      = self.requested[name, number]
             period   = self.clocks[name, number]
+            pin      = self._requested[name, number]
             if pin.dir == "io":
                 raise ConstraintError("Cannot constrain frequency of resource {}#{} because "
                                       "it has been requested as a tristate buffer"
index d81584b76a1c2e2efd6bb9120ac3eba9844a5682..ace1c118ac994c488c6dc43ac802af744b5fe643 100644 (file)
@@ -17,20 +17,26 @@ class ConstraintManagerTestCase(FHDLTestCase):
                 Subsignal("sda", Pins("N11"))
             )
         ]
-        self.cm = ConstraintManager(self.resources, [])
+        self.connectors = [
+            Connector("pmod", 0, "B0 B1 B2 B3 - -"),
+        ]
+        self.cm = ConstraintManager(self.resources, self.connectors, [])
 
     def test_basic(self):
         self.clocks = [
             ("clk100",      100),
             (("clk50", 0),  50),
         ]
-        self.cm = ConstraintManager(self.resources, self.clocks)
+        self.cm = ConstraintManager(self.resources, self.connectors, self.clocks)
         self.assertEqual(self.cm.resources, {
             ("clk100",   0): self.resources[0],
             ("clk50",    0): self.resources[1],
             ("user_led", 0): self.resources[2],
             ("i2c",      0): self.resources[3]
         })
+        self.assertEqual(self.cm.connectors, {
+            ("pmod", 0): self.connectors[0],
+        })
         self.assertEqual(self.cm.clocks, {
             ("clk100", 0): 100,
             ("clk50",  0): 50,
@@ -136,6 +142,23 @@ class ConstraintManagerTestCase(FHDLTestCase):
         self.assertIs(ports[0], clk100.p)
         self.assertIs(ports[1], clk100.n)
 
+    def test_request_via_connector(self):
+        self.cm.add_resources([
+            Resource("spi", 0,
+                Subsignal("ss",   Pins("1", conn=("pmod", 0))),
+                Subsignal("clk",  Pins("2", conn=("pmod", 0))),
+                Subsignal("miso", Pins("3", conn=("pmod", 0))),
+                Subsignal("mosi", Pins("4", conn=("pmod", 0))),
+            )
+        ])
+        spi0 = self.cm.request("spi", 0)
+        self.assertEqual(list(sorted(self.cm.iter_port_constraints())), [
+            ("spi_0__clk__io",  ["B1"], {}),
+            ("spi_0__miso__io", ["B2"], {}),
+            ("spi_0__mosi__io", ["B3"], {}),
+            ("spi_0__ss__io",   ["B0"], {}),
+        ])
+
     def test_add_clock(self):
         self.cm.add_clock("clk100", 0, 10e6)
         self.assertEqual(self.cm.clocks["clk100", 0], 10e6)
@@ -158,6 +181,16 @@ class ConstraintManagerTestCase(FHDLTestCase):
                     "(resource user_led 0 (pins o A0) ) has the same name and number"):
             self.cm.add_resources([Resource("user_led", 0, Pins("A1", dir="o"))])
 
+    def test_wrong_connectors(self):
+        with self.assertRaises(TypeError, msg="Object 'wrong' is not a Connector"):
+            self.cm.add_connectors(['wrong'])
+
+    def test_wrong_connectors_duplicate(self):
+        with self.assertRaises(NameError,
+                msg="Trying to add (connector pmod 0 1=>1 2=>2), but "
+                    "(connector pmod 0 1=>B0 2=>B1 3=>B2 4=>B3) has the same name and number"):
+            self.cm.add_connectors([Connector("pmod", 0, "1 2")])
+
     def test_wrong_lookup(self):
         with self.assertRaises(NameError,
                 msg="Resource user_led#1 does not exist"):
index bf1642501c70827c85089234a4e7fb9129d8cdd5..43f5cb3835bced3888ccbf445106d28ce3db16fb 100644 (file)
@@ -109,11 +109,11 @@ class LatticeICE40Platform(TemplatedPlatform):
     ]
 
     def iter_ports(self):
-        for resource, pin, port in self._ports:
-            if isinstance(resource.io[0], Pins):
+        for res, pin, port in self._ports:
+            if isinstance(res.io[0], Pins):
                 yield port.io
-            elif isinstance(resource.io[0], DiffPairs):
-                if resource.extras.get("IO_STANDARD", "SB_LVCMOS") == "SB_LVDS_INPUT":
+            elif isinstance(res.io[0], DiffPairs):
+                if res.extras.get("IO_STANDARD", "SB_LVCMOS") == "SB_LVDS_INPUT":
                     yield port.p
                 else:
                     yield port.p
@@ -122,15 +122,15 @@ class LatticeICE40Platform(TemplatedPlatform):
                 assert False
 
     def iter_port_constraints(self):
-        for resource, pin, port in self._ports:
-            if isinstance(resource.io[0], Pins):
-                yield port.io.name, resource.io[0].names, resource.extras
-            elif isinstance(resource.io[0], DiffPairs):
-                if resource.extras.get("IO_STANDARD", "SB_LVCMOS") == "SB_LVDS_INPUT":
-                    yield port.p.name, resource.io[0].p.names, resource.extras
+        for res, pin, port in self._ports:
+            if isinstance(res.io[0], Pins):
+                yield port.io.name, list(res.io[0].map_names(self._mapping, res)), res.extras
+            elif isinstance(res.io[0], DiffPairs):
+                if res.extras.get("IO_STANDARD", "SB_LVCMOS") == "SB_LVDS_INPUT":
+                    yield port.p.name, list(res.io[0].p.map_names(self._mapping, res)), res.extras
                 else:
-                    yield port.p.name, resource.io[0].p.names, resource.extras
-                    yield port.n.name, resource.io[0].n.names, resource.extras
+                    yield port.p.name, list(res.io[0].p.map_names(self._mapping, res)), res.extras
+                    yield port.n.name, list(res.io[0].n.map_names(self._mapping, res)), res.extras
             else:
                 assert False