From ab3f103e5a26f7f0bb982d08fb96ebd8a0fc7f4b Mon Sep 17 00:00:00 2001 From: whitequark Date: Wed, 5 Jun 2019 07:02:08 +0000 Subject: [PATCH] build.dsl: replace extras= with Attrs(). This change proved more tricky than expected due to downstream dependencies, so it also includes some secondary refactoring. --- nmigen/build/__init__.py | 4 +- nmigen/build/dsl.py | 101 +++++++++++++------------- nmigen/build/plat.py | 76 ++++++++++---------- nmigen/build/res.py | 125 ++++++++++++++++++--------------- nmigen/test/test_build_dsl.py | 87 +++++++++++++---------- nmigen/test/test_build_res.py | 24 ++++--- nmigen/vendor/lattice_ice40.py | 102 +++++++++++---------------- 7 files changed, 264 insertions(+), 255 deletions(-) diff --git a/nmigen/build/__init__.py b/nmigen/build/__init__.py index d13f3cd..26a34ad 100644 --- a/nmigen/build/__init__.py +++ b/nmigen/build/__init__.py @@ -1,3 +1,3 @@ -from .dsl import Pins, DiffPairs, Subsignal, Resource, Connector -from .res import ConstraintError +from .dsl import Pins, DiffPairs, Attrs, Subsignal, Resource, Connector +from .res import ResourceError from .plat import Platform, TemplatedPlatform diff --git a/nmigen/build/dsl.py b/nmigen/build/dsl.py index 0a0fb2b..14cfce3 100644 --- a/nmigen/build/dsl.py +++ b/nmigen/build/dsl.py @@ -1,7 +1,7 @@ from collections import OrderedDict -__all__ = ["Pins", "DiffPairs", "Subsignal", "Resource", "Connector"] +__all__ = ["Pins", "DiffPairs", "Attrs", "Subsignal", "Resource", "Connector"] class Pins: @@ -32,13 +32,15 @@ class Pins: return iter(self.names) def map_names(self, mapping, resource): + mapped_names = [] for name in self.names: while ":" in name: if name not in mapping: raise NameError("Resource {!r} refers to nonexistent connector pin {}" .format(resource, name)) name = mapping[name] - yield name + mapped_names.append(name) + return mapped_names def __repr__(self): return "(pins {} {})".format(self.dir, " ".join(self.names)) @@ -67,65 +69,66 @@ class DiffPairs: self.dir, " ".join(self.p.names), " ".join(self.n.names)) +class Attrs(OrderedDict): + def __init__(self, **attrs): + for attr_key, attr_value in attrs.items(): + if not isinstance(attr_value, str): + raise TypeError("Attribute value must be a string, not {!r}" + .format(attr_value)) + + super().__init__(**attrs) + + def __repr__(self): + return "(attrs {})".format(" ".join("{}={}".format(k, v) + for k, v in self.items())) + + class Subsignal: - def __init__(self, name, *io, extras=None): - self.name = name - - if not io: - raise TypeError("Missing I/O constraints") - for c in io: - if not isinstance(c, (Pins, DiffPairs, Subsignal)): - raise TypeError("I/O constraint must be one of Pins, DiffPairs or Subsignal, " - "not {!r}" - .format(c)) - if isinstance(io[0], (Pins, DiffPairs)) and len(io) > 1: - raise TypeError("Pins and DiffPairs cannot be followed by more I/O constraints, but " - "{!r} is followed by {!r}" - .format(io[0], io[1])) - if isinstance(io[0], Subsignal): - for c in io[1:]: - if not isinstance(c, Subsignal): - raise TypeError("A Subsignal can only be followed by more Subsignals, but " - "{!r} is followed by {!r}" - .format(io[0], c)) - self.io = io - self.extras = {} - - if extras is not None: - if not isinstance(extras, dict): - raise TypeError("Extra constraints must be a dict, not {!r}" - .format(extras)) - for extra_key, extra_value in extras.items(): - if not isinstance(extra_key, str): - raise TypeError("Extra constraint key must be a string, not {!r}" - .format(extra_key)) - if not isinstance(extra_value, str): - raise TypeError("Extra constraint value must be a string, not {!r}" - .format(extra_value)) - self.extras[extra_key] = extra_value - - if isinstance(self.io[0], Subsignal): - for sub in self.io: - sub.extras.update(self.extras) + def __init__(self, name, *args): + self.name = name + self.ios = [] + self.attrs = Attrs() + + if not args: + raise ValueError("Missing I/O constraints") + for arg in args: + if isinstance(arg, (Pins, DiffPairs)): + if not self.ios: + self.ios.append(arg) + else: + raise TypeError("Pins and DiffPairs are incompatible with other location or " + "subsignal constraints, but {!r} appears after {!r}" + .format(arg, self.ios[-1])) + elif isinstance(arg, Subsignal): + if not self.ios or isinstance(self.ios[-1], Subsignal): + self.ios.append(arg) + else: + raise TypeError("Subsignal is incompatible with location constraints, but " + "{!r} appears after {!r}" + .format(arg, self.ios[-1])) + elif isinstance(arg, Attrs): + self.attrs.update(arg) + else: + raise TypeError("I/O constraint must be one of Pins, DiffPairs, Subsignal, " + "or Attrs, not {!r}" + .format(arg)) def __repr__(self): return "(subsignal {} {} {})".format(self.name, - " ".join(map(repr, self.io)), - " ".join("{}={}".format(k, v) - for k, v in self.extras.items())) + " ".join(map(repr, self.ios)), + repr(self.attrs)) class Resource(Subsignal): - def __init__(self, name, number, *io, extras=None): - super().__init__(name, *io, extras=extras) + def __init__(self, name, number, *args): + super().__init__(name, *args) self.number = number def __repr__(self): return "(resource {} {} {} {})".format(self.name, self.number, - " ".join(map(repr, self.io)), - " ".join("{}={}".format(k, v) - for k, v in self.extras.items())) + " ".join(map(repr, self.ios)), + repr(self.attrs)) class Connector: diff --git a/nmigen/build/plat.py b/nmigen/build/plat.py index 43f0472..7646040 100644 --- a/nmigen/build/plat.py +++ b/nmigen/build/plat.py @@ -17,7 +17,7 @@ from .run import * __all__ = ["Platform", "TemplatedPlatform"] -class Platform(ConstraintManager, metaclass=ABCMeta): +class Platform(ResourceManager, metaclass=ABCMeta): resources = abstractproperty() connectors = abstractproperty() clocks = abstractproperty() @@ -67,25 +67,25 @@ class Platform(ConstraintManager, metaclass=ABCMeta): pin_fragment.flatten = True fragment.add_subfragment(pin_fragment, name="pin_{}".format(pin.name)) - for pin, port, extras in self.iter_single_ended_pins(): + for pin, port, attrs in self.iter_single_ended_pins(): if pin.dir == "i": - add_pin_fragment(pin, self.get_input(pin, port, extras)) + add_pin_fragment(pin, self.get_input(pin, port, attrs)) if pin.dir == "o": - add_pin_fragment(pin, self.get_output(pin, port, extras)) + add_pin_fragment(pin, self.get_output(pin, port, attrs)) if pin.dir == "oe": - add_pin_fragment(pin, self.get_tristate(pin, port, extras)) + add_pin_fragment(pin, self.get_tristate(pin, port, attrs)) if pin.dir == "io": - add_pin_fragment(pin, self.get_input_output(pin, port, extras)) + add_pin_fragment(pin, self.get_input_output(pin, port, attrs)) - for pin, p_port, n_port, extras in self.iter_differential_pins(): + for pin, p_port, n_port, attrs in self.iter_differential_pins(): if pin.dir == "i": - add_pin_fragment(pin, self.get_diff_input(pin, p_port, n_port, extras)) + add_pin_fragment(pin, self.get_diff_input(pin, p_port, n_port, attrs)) if pin.dir == "o": - add_pin_fragment(pin, self.get_diff_output(pin, p_port, n_port, extras)) + add_pin_fragment(pin, self.get_diff_output(pin, p_port, n_port, attrs)) if pin.dir == "oe": - add_pin_fragment(pin, self.get_diff_tristate(pin, p_port, n_port, extras)) + add_pin_fragment(pin, self.get_diff_tristate(pin, p_port, n_port, attrs)) if pin.dir == "io": - add_pin_fragment(pin, self.get_diff_input_output(pin, p_port, n_port, extras)) + add_pin_fragment(pin, self.get_diff_input_output(pin, p_port, n_port, attrs)) return self.toolchain_prepare(fragment, name, **kwargs) @@ -104,7 +104,7 @@ class Platform(ConstraintManager, metaclass=ABCMeta): raise NotImplementedError("Platform {} does not support programming" .format(self.__class__.__name__)) - def _check_feature(self, feature, pin, extras, valid_xdrs, valid_extras): + def _check_feature(self, feature, pin, attrs, valid_xdrs, valid_attrs): if not valid_xdrs: raise NotImplementedError("Platform {} does not support {}" .format(self.__class__.__name__, feature)) @@ -112,29 +112,29 @@ class Platform(ConstraintManager, metaclass=ABCMeta): raise NotImplementedError("Platform {} does not support {} for XDR {}" .format(self.__class__.__name__, feature, pin.xdr)) - if not valid_extras and extras: - raise NotImplementedError("Platform {} does not support extras for {}" + if not valid_attrs and attrs: + raise NotImplementedError("Platform {} does not support attributes for {}" .format(self.__class__.__name__, feature)) - def get_input(self, pin, port, extras): - self._check_feature("single-ended input", pin, extras, - valid_xdrs=(0,), valid_extras=None) + def get_input(self, pin, port, attrs): + self._check_feature("single-ended input", pin, attrs, + valid_xdrs=(0,), valid_attrs=None) m = Module() m.d.comb += pin.i.eq(port) return m - def get_output(self, pin, port, extras): - self._check_feature("single-ended output", pin, extras, - valid_xdrs=(0,), valid_extras=None) + def get_output(self, pin, port, attrs): + self._check_feature("single-ended output", pin, attrs, + valid_xdrs=(0,), valid_attrs=None) m = Module() m.d.comb += port.eq(pin.o) return m - def get_tristate(self, pin, port, extras): - self._check_feature("single-ended tristate", pin, extras, - valid_xdrs=(0,), valid_extras=None) + def get_tristate(self, pin, port, attrs): + self._check_feature("single-ended tristate", pin, attrs, + valid_xdrs=(0,), valid_attrs=None) m = Module() m.submodules += Instance("$tribuf", @@ -145,9 +145,9 @@ class Platform(ConstraintManager, metaclass=ABCMeta): ) return m - def get_input_output(self, pin, port, extras): - self._check_feature("single-ended input/output", pin, extras, - valid_xdrs=(0,), valid_extras=None) + def get_input_output(self, pin, port, attrs): + self._check_feature("single-ended input/output", pin, attrs, + valid_xdrs=(0,), valid_attrs=None) m = Module() m.submodules += Instance("$tribuf", @@ -159,21 +159,21 @@ class Platform(ConstraintManager, metaclass=ABCMeta): m.d.comb += pin.i.eq(port) return m - def get_diff_input(self, pin, p_port, n_port, extras): - self._check_feature("differential input", pin, extras, - valid_xdrs=(), valid_extras=None) + def get_diff_input(self, pin, p_port, n_port, attrs): + self._check_feature("differential input", pin, attrs, + valid_xdrs=(), valid_attrs=None) - def get_diff_output(self, pin, p_port, n_port, extras): - self._check_feature("differential output", pin, extras, - valid_xdrs=(), valid_extras=None) + def get_diff_output(self, pin, p_port, n_port, attrs): + self._check_feature("differential output", pin, attrs, + valid_xdrs=(), valid_attrs=None) - def get_diff_tristate(self, pin, p_port, n_port, extras): - self._check_feature("differential tristate", pin, extras, - valid_xdrs=(), valid_extras=None) + def get_diff_tristate(self, pin, p_port, n_port, attrs): + self._check_feature("differential tristate", pin, attrs, + valid_xdrs=(), valid_attrs=None) - def get_diff_input_output(self, pin, p_port, n_port, extras): - self._check_feature("differential input/output", pin, extras, - valid_xdrs=(), valid_extras=None) + def get_diff_input_output(self, pin, p_port, n_port, attrs): + self._check_feature("differential input/output", pin, attrs, + valid_xdrs=(), valid_attrs=None) class TemplatedPlatform(Platform): diff --git a/nmigen/build/res.py b/nmigen/build/res.py index d1e4a4e..2d3e7e6 100644 --- a/nmigen/build/res.py +++ b/nmigen/build/res.py @@ -7,21 +7,24 @@ from ..lib.io import * from .dsl import * -__all__ = ["ConstraintError", "ConstraintManager"] +__all__ = ["ResourceError", "ResourceManager"] -class ConstraintError(Exception): +class ResourceError(Exception): pass -class ConstraintManager: +class ResourceManager: def __init__(self, resources, connectors, clocks): self.resources = OrderedDict() + self._requested = OrderedDict() + self.connectors = OrderedDict() + self._conn_pins = OrderedDict() + self.clocks = OrderedDict() - self._mapping = OrderedDict() - self._requested = OrderedDict() + # Constraint lists self._ports = [] self.add_resources(resources) @@ -50,36 +53,36 @@ class ConstraintManager: 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 + assert conn_pin not in self._conn_pins + self._conn_pins[conn_pin] = plat_pin def add_clock(self, name, number, frequency): resource = self.lookup(name, number) - if isinstance(resource.io[0], Subsignal): + if isinstance(resource.ios[0], Subsignal): raise TypeError("Cannot constrain frequency of resource {}#{} because it has " "subsignals" .format(resource.name, resource.number, frequency)) if (resource.name, resource.number) in self.clocks: other = self.clocks[resource.name, resource.number] - raise ConstraintError("Resource {}#{} is already constrained to a frequency of " - "{:f} MHz" - .format(resource.name, resource.number, other / 1e6)) + raise ResourceError("Resource {}#{} is already constrained to a frequency of " + "{:f} MHz" + .format(resource.name, resource.number, other / 1e6)) self.clocks[resource.name, resource.number] = frequency def lookup(self, name, number=0): if (name, number) not in self.resources: - raise ConstraintError("Resource {}#{} does not exist" + raise ResourceError("Resource {}#{} does not exist" .format(name, number)) return self.resources[name, number] def request(self, name, number=0, *, dir=None, xdr=None): resource = self.lookup(name, number) if (resource.name, resource.number) in self._requested: - raise ConstraintError("Resource {}#{} has already been requested" - .format(name, number)) + raise ResourceError("Resource {}#{} has already been requested" + .format(name, number)) def merge_options(subsignal, dir, xdr): - if isinstance(subsignal.io[0], Subsignal): + if isinstance(subsignal.ios[0], Subsignal): if dir is None: dir = dict() if xdr is None: @@ -92,52 +95,54 @@ class ConstraintManager: raise TypeError("Data rate must be a dict, not {!r}, because {!r} " "has subsignals" .format(xdr, subsignal)) - for sub in subsignal.io: + for sub in subsignal.ios: sub_dir = dir.get(sub.name, None) sub_xdr = xdr.get(sub.name, None) dir[sub.name], xdr[sub.name] = merge_options(sub, sub_dir, sub_xdr) else: if dir is None: - dir = subsignal.io[0].dir + dir = subsignal.ios[0].dir if xdr is None: xdr = 0 if dir not in ("i", "o", "oe", "io", "-"): raise TypeError("Direction must be one of \"i\", \"o\", \"oe\", \"io\", " "or \"-\", not {!r}" .format(dir)) - if dir != subsignal.io[0].dir and not (subsignal.io[0].dir == "io" or dir == "-"): + if dir != subsignal.ios[0].dir and \ + not (subsignal.ios[0].dir == "io" or dir == "-"): raise ValueError("Direction of {!r} cannot be changed from \"{}\" to \"{}\"; " "direction can be changed from \"io\" to \"i\", \"o\", or " "\"oe\", or from anything to \"-\"" - .format(subsignal.io[0], subsignal.io[0].dir, dir)) + .format(subsignal.ios[0], subsignal.ios[0].dir, dir)) if not isinstance(xdr, int) or xdr < 0: raise ValueError("Data rate of {!r} must be a non-negative integer, not {!r}" - .format(subsignal.io[0], xdr)) + .format(subsignal.ios[0], xdr)) return dir, xdr - def resolve(subsignal, dir, xdr, name): - if isinstance(subsignal.io[0], Subsignal): + def resolve(resource, dir, xdr, name, attrs): + if isinstance(resource.ios[0], Subsignal): fields = OrderedDict() - for sub in subsignal.io: + for sub in resource.ios: fields[sub.name] = resolve(sub, dir[sub.name], xdr[sub.name], - name="{}__{}".format(name, sub.name)) + name="{}__{}".format(name, sub.name), + attrs={**attrs, **sub.attrs}) return Record([ (f_name, f.layout) for (f_name, f) in fields.items() ], fields=fields, name=name) - elif isinstance(subsignal.io[0], (Pins, DiffPairs)): - phys = subsignal.io[0] + elif isinstance(resource.ios[0], (Pins, DiffPairs)): + phys = resource.ios[0] if isinstance(phys, Pins): port = Record([("io", len(phys))], name=name) if isinstance(phys, DiffPairs): port = Record([("p", len(phys)), ("n", len(phys))], name=name) if dir == "-": - self._ports.append((subsignal, None, port)) + self._ports.append((resource, None, port, attrs)) return port else: pin = Pin(len(phys), dir, xdr, name=name) - self._ports.append((subsignal, pin, port)) + self._ports.append((resource, pin, port, attrs)) return pin else: @@ -145,51 +150,61 @@ class ConstraintManager: value = resolve(resource, *merge_options(resource, dir, xdr), - name="{}_{}".format(resource.name, resource.number)) + name="{}_{}".format(resource.name, resource.number), + attrs=resource.attrs) self._requested[resource.name, resource.number] = value return value def iter_single_ended_pins(self): - for res, pin, port in self._ports: + for res, pin, port, attrs in self._ports: if pin is None: continue - if isinstance(res.io[0], Pins): - yield pin, port.io, res.extras + if isinstance(res.ios[0], Pins): + yield pin, port.io, attrs def iter_differential_pins(self): - for res, pin, port in self._ports: + for res, pin, port, attrs in self._ports: if pin is None: continue - if isinstance(res.io[0], DiffPairs): - yield pin, port.p, port.n, res.extras + if isinstance(res.ios[0], DiffPairs): + yield pin, port.p, port.n, attrs + + def should_skip_port_component(self, port, attrs, component): + return False def iter_ports(self): - for res, pin, port in self._ports: - if isinstance(res.io[0], Pins): - yield port.io - elif isinstance(res.io[0], DiffPairs): - yield port.p - yield port.n + for res, pin, port, attrs in self._ports: + if isinstance(res.ios[0], Pins): + if not self.should_skip_port_component(port, attrs, "io"): + yield port.io + elif isinstance(res.ios[0], DiffPairs): + if not self.should_skip_port_component(port, attrs, "p"): + yield port.p + if not self.should_skip_port_component(port, attrs, "n"): + yield port.n else: assert False def iter_port_constraints(self): - 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 + for res, pin, port, attrs in self._ports: + if isinstance(res.ios[0], Pins): + if not self.should_skip_port_component(port, attrs, "io"): + yield port.io.name, res.ios[0].map_names(self._conn_pins, res), attrs + elif isinstance(res.ios[0], DiffPairs): + if not self.should_skip_port_component(port, attrs, "p"): + yield port.p.name, res.ios[0].p.map_names(self._conn_pins, res), attrs + if not self.should_skip_port_component(port, attrs, "n"): + yield port.n.name, res.ios[0].n.map_names(self._conn_pins, res), attrs else: assert False def iter_port_constraints_bits(self): - for port_name, pin_names, extras in self.iter_port_constraints(): + for port_name, pin_names, attrs in self.iter_port_constraints(): if len(pin_names) == 1: - yield port_name, pin_names[0], extras + yield port_name, pin_names[0], attrs else: for bit, pin_name in enumerate(pin_names): - yield "{}[{}]".format(port_name, bit), pin_name, extras + yield "{}[{}]".format(port_name, bit), pin_name, attrs def iter_clock_constraints(self): for name, number in self.clocks.keys() & self._requested.keys(): @@ -197,12 +212,12 @@ class ConstraintManager: 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" - .format(name, number)) - if isinstance(resource.io[0], Pins): + raise ResourceError("Cannot constrain frequency of resource {}#{} because " + "it has been requested as a tristate buffer" + .format(name, number)) + if isinstance(resource.ios[0], Pins): port_name = "{}__io".format(pin.name) - elif isinstance(resource.io[0], DiffPairs): + elif isinstance(resource.ios[0], DiffPairs): port_name = "{}__p".format(pin.name) else: assert False diff --git a/nmigen/test/test_build_dsl.py b/nmigen/test/test_build_dsl.py index 5288736..1ab3807 100644 --- a/nmigen/test/test_build_dsl.py +++ b/nmigen/test/test_build_dsl.py @@ -23,7 +23,7 @@ class PinsTestCase(FHDLTestCase): "pmod_0:1": "A1", "pmod_0:2": "A2", } - self.assertEqual(list(p.map_names(mapping, p)), ["A0", "A1", "A2"]) + self.assertEqual(p.map_names(mapping, p), ["A0", "A1", "A2"]) def test_map_names_recur(self): p = Pins("0", conn=("pmod", 0)) @@ -31,7 +31,7 @@ class PinsTestCase(FHDLTestCase): "pmod_0:0": "ext_0:1", "ext_0:1": "A1", } - self.assertEqual(list(p.map_names(mapping, p)), ["A1"]) + self.assertEqual(p.map_names(mapping, p), ["A1"]) def test_wrong_names(self): with self.assertRaises(TypeError, @@ -51,7 +51,7 @@ class PinsTestCase(FHDLTestCase): with self.assertRaises(NameError, msg="Resource (pins io pmod_0:0 pmod_0:1 pmod_0:2) refers to nonexistent " "connector pin pmod_0:1"): - list(p.map_names(mapping, p)) + p.map_names(mapping, p) class DiffPairsTestCase(FHDLTestCase): @@ -84,81 +84,90 @@ class DiffPairsTestCase(FHDLTestCase): dp = DiffPairs("A0", "B0 B1") +class AttrsTestCase(FHDLTestCase): + def test_basic(self): + a = Attrs(IO_STANDARD="LVCMOS33", PULLUP="1") + self.assertEqual(a["IO_STANDARD"], "LVCMOS33") + self.assertEqual(repr(a), "(attrs IO_STANDARD=LVCMOS33 PULLUP=1)") + + def test_wrong_value(self): + with self.assertRaises(TypeError, + msg="Attribute value must be a string, not 1"): + a = Attrs(FOO=1) + + class SubsignalTestCase(FHDLTestCase): def test_basic_pins(self): - s = Subsignal("a", Pins("A0"), extras={"IOSTANDARD": "LVCMOS33"}) - self.assertEqual(repr(s), "(subsignal a (pins io A0) IOSTANDARD=LVCMOS33)") + s = Subsignal("a", Pins("A0"), Attrs(IOSTANDARD="LVCMOS33")) + self.assertEqual(repr(s), + "(subsignal a (pins io A0) (attrs IOSTANDARD=LVCMOS33))") def test_basic_diffpairs(self): s = Subsignal("a", DiffPairs("A0", "B0")) - self.assertEqual(repr(s), "(subsignal a (diffpairs io (p A0) (n B0)) )") + self.assertEqual(repr(s), + "(subsignal a (diffpairs io (p A0) (n B0)) (attrs ))") def test_basic_subsignals(self): s = Subsignal("a", Subsignal("b", Pins("A0")), Subsignal("c", Pins("A1"))) self.assertEqual(repr(s), - "(subsignal a (subsignal b (pins io A0) ) (subsignal c (pins io A1) ) )") + "(subsignal a (subsignal b (pins io A0) (attrs )) " + "(subsignal c (pins io A1) (attrs )) (attrs ))") - def test_extras(self): + def test_attrs(self): s = Subsignal("a", Subsignal("b", Pins("A0")), - Subsignal("c", Pins("A0"), extras={"SLEW": "FAST"}), - extras={"IOSTANDARD": "LVCMOS33"}) - self.assertEqual(s.extras, {"IOSTANDARD": "LVCMOS33"}) - self.assertEqual(s.io[0].extras, {"IOSTANDARD": "LVCMOS33"}) - self.assertEqual(s.io[1].extras, {"SLEW": "FAST", "IOSTANDARD": "LVCMOS33"}) - - def test_empty_io(self): - with self.assertRaises(TypeError, msg="Missing I/O constraints"): + Subsignal("c", Pins("A0"), Attrs(SLEW="FAST")), + Attrs(IOSTANDARD="LVCMOS33")) + self.assertEqual(s.attrs, {"IOSTANDARD": "LVCMOS33"}) + self.assertEqual(s.ios[0].attrs, {}) + self.assertEqual(s.ios[1].attrs, {"SLEW": "FAST"}) + + def test_attrs_many(self): + s = Subsignal("a", Pins("A0"), Attrs(SLEW="FAST"), Attrs(PULLUP="1")) + self.assertEqual(s.attrs, {"SLEW": "FAST", "PULLUP": "1"}) + + def test_wrong_empty_io(self): + with self.assertRaises(ValueError, msg="Missing I/O constraints"): s = Subsignal("a") def test_wrong_io(self): with self.assertRaises(TypeError, - msg="I/O constraint must be one of Pins, DiffPairs or Subsignal, not 'wrong'"): + msg="I/O constraint must be one of Pins, DiffPairs, Subsignal, or Attrs, " + "not 'wrong'"): s = Subsignal("a", "wrong") def test_wrong_pins(self): with self.assertRaises(TypeError, - msg="Pins and DiffPairs cannot be followed by more I/O constraints, but " - "(pins io A0) is followed by (pins io A1)"): + msg="Pins and DiffPairs are incompatible with other location or subsignal " + "constraints, but (pins io A1) appears after (pins io A0)"): s = Subsignal("a", Pins("A0"), Pins("A1")) def test_wrong_diffpairs(self): with self.assertRaises(TypeError, - msg="Pins and DiffPairs cannot be followed by more I/O constraints, but " - "(diffpairs io (p A0) (n B0)) is followed by " - "(pins io A1)"): + msg="Pins and DiffPairs are incompatible with other location or subsignal " + "constraints, but (pins io A1) appears after (diffpairs io (p A0) (n B0))"): s = Subsignal("a", DiffPairs("A0", "B0"), Pins("A1")) def test_wrong_subsignals(self): with self.assertRaises(TypeError, - msg="A Subsignal can only be followed by more Subsignals, but " - "(subsignal b (pins io A0) ) is followed by (pins io B0)"): + msg="Pins and DiffPairs are incompatible with other location or subsignal " + "constraints, but (pins io B0) appears after (subsignal b (pins io A0) " + "(attrs ))"): s = Subsignal("a", Subsignal("b", Pins("A0")), Pins("B0")) - def test_wrong_extras(self): - with self.assertRaises(TypeError, - msg="Extra constraints must be a dict, not [(pins io B0)]"): - s = Subsignal("a", Pins("A0"), extras=[Pins("B0")]) - with self.assertRaises(TypeError, - msg="Extra constraint key must be a string, not 1"): - s = Subsignal("a", Pins("A0"), extras={1: 2}) - with self.assertRaises(TypeError, - msg="Extra constraint value must be a string, not 2"): - s = Subsignal("a", Pins("A0"), extras={"1": 2}) - class ResourceTestCase(FHDLTestCase): def test_basic(self): r = Resource("serial", 0, Subsignal("tx", Pins("A0", dir="o")), Subsignal("rx", Pins("A1", dir="i")), - extras={"IOSTANDARD": "LVCMOS33"}) + Attrs(IOSTANDARD="LVCMOS33")) self.assertEqual(repr(r), "(resource serial 0" - " (subsignal tx (pins o A0) IOSTANDARD=LVCMOS33)" - " (subsignal rx (pins i A1) IOSTANDARD=LVCMOS33)" - " IOSTANDARD=LVCMOS33)") + " (subsignal tx (pins o A0) (attrs ))" + " (subsignal rx (pins i A1) (attrs ))" + " (attrs IOSTANDARD=LVCMOS33))") class ConnectorTestCase(FHDLTestCase): diff --git a/nmigen/test/test_build_res.py b/nmigen/test/test_build_res.py index 97fa337..6f050ad 100644 --- a/nmigen/test/test_build_res.py +++ b/nmigen/test/test_build_res.py @@ -6,7 +6,7 @@ from ..build.res import * from .tools import * -class ConstraintManagerTestCase(FHDLTestCase): +class ResourceManagerTestCase(FHDLTestCase): def setUp(self): self.resources = [ Resource("clk100", 0, DiffPairs("H1", "H2", dir="i")), @@ -20,14 +20,14 @@ class ConstraintManagerTestCase(FHDLTestCase): self.connectors = [ Connector("pmod", 0, "B0 B1 B2 B3 - -"), ] - self.cm = ConstraintManager(self.resources, self.connectors, []) + self.cm = ResourceManager(self.resources, self.connectors, []) def test_basic(self): self.clocks = [ ("clk100", 100), (("clk50", 0), 50), ] - self.cm = ConstraintManager(self.resources, self.connectors, self.clocks) + self.cm = ResourceManager(self.resources, self.connectors, self.clocks) self.assertEqual(self.cm.resources, { ("clk100", 0): self.resources[0], ("clk50", 0): self.resources[1], @@ -177,8 +177,8 @@ class ConstraintManagerTestCase(FHDLTestCase): def test_wrong_resources_duplicate(self): with self.assertRaises(NameError, - msg="Trying to add (resource user_led 0 (pins o A1) ), but " - "(resource user_led 0 (pins o A0) ) has the same name and number"): + msg="Trying to add (resource user_led 0 (pins o A1) (attrs )), but " + "(resource user_led 0 (pins o A0) (attrs )) has the same name and number"): self.cm.add_resources([Resource("user_led", 0, Pins("A1", dir="o"))]) def test_wrong_connectors(self): @@ -192,7 +192,7 @@ class ConstraintManagerTestCase(FHDLTestCase): self.cm.add_connectors([Connector("pmod", 0, "1 2")]) def test_wrong_lookup(self): - with self.assertRaises(ConstraintError, + with self.assertRaises(ResourceError, msg="Resource user_led#1 does not exist"): r = self.cm.lookup("user_led", 1) @@ -203,7 +203,7 @@ class ConstraintManagerTestCase(FHDLTestCase): self.cm.add_clock("i2c", 0, 10e6) def test_wrong_frequency_tristate(self): - with self.assertRaises(ConstraintError, + with self.assertRaises(ResourceError, msg="Cannot constrain frequency of resource clk50#0 because " "it has been requested as a tristate buffer"): self.cm.add_clock("clk50", 0, 20e6) @@ -211,13 +211,13 @@ class ConstraintManagerTestCase(FHDLTestCase): list(self.cm.iter_clock_constraints()) def test_wrong_frequency_duplicate(self): - with self.assertRaises(ConstraintError, + with self.assertRaises(ResourceError, msg="Resource clk100#0 is already constrained to a frequency of 10.000000 MHz"): self.cm.add_clock("clk100", 0, 10e6) self.cm.add_clock("clk100", 0, 5e6) def test_wrong_request_duplicate(self): - with self.assertRaises(ConstraintError, + with self.assertRaises(ResourceError, msg="Resource user_led#0 has already been requested"): self.cm.request("user_led", 0) self.cm.request("user_led", 0) @@ -238,7 +238,8 @@ class ConstraintManagerTestCase(FHDLTestCase): def test_wrong_request_with_dir_dict(self): with self.assertRaises(TypeError, msg="Directions must be a dict, not 'i', because (resource i2c 0 (subsignal scl " - "(pins o N10) ) (subsignal sda (pins io N11) ) ) has subsignals"): + "(pins o N10) (attrs )) (subsignal sda (pins io N11) (attrs )) (attrs )) " + "has subsignals"): i2c = self.cm.request("i2c", 0, dir="i") def test_wrong_request_with_wrong_xdr(self): @@ -249,5 +250,6 @@ class ConstraintManagerTestCase(FHDLTestCase): def test_wrong_request_with_xdr_dict(self): with self.assertRaises(TypeError, msg="Data rate must be a dict, not 2, because (resource i2c 0 (subsignal scl " - "(pins o N10) ) (subsignal sda (pins io N11) ) ) has subsignals"): + "(pins o N10) (attrs )) (subsignal sda (pins io N11) (attrs )) (attrs )) " + "has subsignals"): i2c = self.cm.request("i2c", 0, xdr=2) diff --git a/nmigen/vendor/lattice_ice40.py b/nmigen/vendor/lattice_ice40.py index bb539ef..bef394c 100644 --- a/nmigen/vendor/lattice_ice40.py +++ b/nmigen/vendor/lattice_ice40.py @@ -70,7 +70,7 @@ class LatticeICE40Platform(TemplatedPlatform): """, "{{name}}.pcf": r""" # {{autogenerated}} - {% for port_name, pin_name, extras in platform.iter_port_constraints_bits() -%} + {% for port_name, pin_name, attrs in platform.iter_port_constraints_bits() -%} set_io {{port_name}} {{pin_name}} {% endfor %} """, @@ -110,33 +110,17 @@ class LatticeICE40Platform(TemplatedPlatform): """ ] - def iter_ports(self): - for res, pin, port in self._ports: - if isinstance(res.io[0], Pins): - yield port.io - elif isinstance(res.io[0], DiffPairs): - if res.extras.get("IO_STANDARD", "SB_LVCMOS") == "SB_LVDS_INPUT": - yield port.p - else: - yield port.p - yield port.n - else: - assert False - - def iter_port_constraints(self): - 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, 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 should_skip_port_component(self, port, attrs, component): + # On iCE40, a differential input is placed by only instantiating an SB_IO primitive for + # the pin with z=0, which is the non-inverting pin. The pinout unfortunately differs + # between LP/HX and UP series: + # * for LP/HX, z=0 is DPxxB (B is non-inverting, A is inverting) + # * for UP, z=0 is IOB_xxA (A is non-inverting, B is inverting) + if attrs.get("IO_STANDARD", "SB_LVCMOS") == "SB_LVDS_INPUT" and component == "n": + return True + return False - def _get_io_buffer(self, m, pin, port, extras, o_invert=None): + def _get_io_buffer(self, m, pin, port, attrs, o_invert=None): def _get_dff(clk, d, q): m.submodules += Instance("$dff", p_CLK_POLARITY=0, @@ -160,9 +144,9 @@ class LatticeICE40Platform(TemplatedPlatform): o_O=y[bit]) return y - if "GLOBAL" in extras: - is_global_input = bool(extras["GLOBAL"]) - del extras["GLOBAL"] + if "GLOBAL" in attrs: + is_global_input = bool(attrs["GLOBAL"]) + del attrs["GLOBAL"] else: is_global_input = False @@ -185,7 +169,7 @@ class LatticeICE40Platform(TemplatedPlatform): for bit in range(len(port)): io_args = [ ("io", "PACKAGE_PIN", port[bit]), - *(("p", key, value) for key, value in extras.items()), + *(("p", key, value) for key, value in attrs.items()), ] if "i" not in pin.dir: @@ -242,56 +226,52 @@ class LatticeICE40Platform(TemplatedPlatform): else: m.submodules += Instance("SB_IO", *io_args) - def get_input(self, pin, port, extras): - self._check_feature("single-ended input", pin, extras, - valid_xdrs=(0, 1, 2), valid_extras=True) + def get_input(self, pin, port, attrs): + self._check_feature("single-ended input", pin, attrs, + valid_xdrs=(0, 1, 2), valid_attrs=True) m = Module() - self._get_io_buffer(m, pin, port, extras) + self._get_io_buffer(m, pin, port, attrs) return m - def get_output(self, pin, port, extras): - self._check_feature("single-ended output", pin, extras, - valid_xdrs=(0, 1, 2), valid_extras=True) + def get_output(self, pin, port, attrs): + self._check_feature("single-ended output", pin, attrs, + valid_xdrs=(0, 1, 2), valid_attrs=True) m = Module() - self._get_io_buffer(m, pin, port, extras) + self._get_io_buffer(m, pin, port, attrs) return m - def get_tristate(self, pin, port, extras): - self._check_feature("single-ended tristate", pin, extras, - valid_xdrs=(0, 1, 2), valid_extras=True) + def get_tristate(self, pin, port, attrs): + self._check_feature("single-ended tristate", pin, attrs, + valid_xdrs=(0, 1, 2), valid_attrs=True) m = Module() - self._get_io_buffer(m, pin, port, extras) + self._get_io_buffer(m, pin, port, attrs) return m - def get_input_output(self, pin, port, extras): - self._check_feature("single-ended input/output", pin, extras, - valid_xdrs=(0, 1, 2), valid_extras=True) + def get_input_output(self, pin, port, attrs): + self._check_feature("single-ended input/output", pin, attrs, + valid_xdrs=(0, 1, 2), valid_attrs=True) m = Module() - self._get_io_buffer(m, pin, port, extras) + self._get_io_buffer(m, pin, port, attrs) return m - def get_diff_input(self, pin, p_port, n_port, extras): - self._check_feature("differential input", pin, extras, - valid_xdrs=(0, 1, 2), valid_extras=True) - # On iCE40, a differential input is placed by only instantiating an SB_IO primitive for - # the pin with z=0, which is the non-inverting pin. The pinout unfortunately differs - # between LP/HX and UP series: - # * for LP/HX, z=0 is DPxxB (B is non-inverting, A is inverting) - # * for UP, z=0 is IOB_xxA (A is non-inverting, B is inverting) + def get_diff_input(self, pin, p_port, n_port, attrs): + self._check_feature("differential input", pin, attrs, + valid_xdrs=(0, 1, 2), valid_attrs=True) m = Module() - self._get_io_buffer(m, pin, p_port, extras) + # See comment in should_skip_port_component above. + self._get_io_buffer(m, pin, p_port, attrs) return m - def get_diff_output(self, pin, p_port, n_port, extras): - self._check_feature("differential output", pin, extras, - valid_xdrs=(0, 1, 2), valid_extras=True) + def get_diff_output(self, pin, p_port, n_port, attrs): + self._check_feature("differential output", pin, attrs, + valid_xdrs=(0, 1, 2), valid_attrs=True) m = Module() # Note that the non-inverting output pin is not driven the same way as a regular # output pin. The inverter introduces a delay, so for a non-inverting output pin, # an identical delay is introduced by instantiating a LUT. This makes the waveform # perfectly symmetric in the xdr=0 case. - self._get_io_buffer(m, pin, p_port, extras, o_invert=False) - self._get_io_buffer(m, pin, n_port, extras, o_invert=True) + self._get_io_buffer(m, pin, p_port, attrs, o_invert=False) + self._get_io_buffer(m, pin, n_port, attrs, o_invert=True) return m # Tristate and bidirectional buffers are not supported on iCE40 because it requires external -- 2.30.2