From ed64880cc4f505245adbf9455805e34df48f0721 Mon Sep 17 00:00:00 2001 From: whitequark Date: Mon, 3 Jun 2019 15:02:15 +0000 Subject: [PATCH] build.{plat,res}: add support for connectors. Fixes #77. --- nmigen/build/plat.py | 7 +-- nmigen/build/res.py | 71 ++++++++++++++++++----------- nmigen/test/test_build_res.py | 37 ++++++++++++++- nmigen/vendor/fpga/lattice_ice40.py | 24 +++++----- 4 files changed, 95 insertions(+), 44 deletions(-) diff --git a/nmigen/build/plat.py b/nmigen/build/plat.py index 6b4ee30..fe8d32b 100644 --- a/nmigen/build/plat.py +++ b/nmigen/build/plat.py @@ -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() diff --git a/nmigen/build/res.py b/nmigen/build/res.py index 9cea237..0b32ded 100644 --- a/nmigen/build/res.py +++ b/nmigen/build/res.py @@ -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" diff --git a/nmigen/test/test_build_res.py b/nmigen/test/test_build_res.py index d81584b..ace1c11 100644 --- a/nmigen/test/test_build_res.py +++ b/nmigen/test/test_build_res.py @@ -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"): diff --git a/nmigen/vendor/fpga/lattice_ice40.py b/nmigen/vendor/fpga/lattice_ice40.py index bf16425..43f5cb3 100644 --- a/nmigen/vendor/fpga/lattice_ice40.py +++ b/nmigen/vendor/fpga/lattice_ice40.py @@ -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 -- 2.30.2