build.dsl: replace extras= with Attrs().
authorwhitequark <cz@m-labs.hk>
Wed, 5 Jun 2019 07:02:08 +0000 (07:02 +0000)
committerwhitequark <cz@m-labs.hk>
Wed, 5 Jun 2019 07:02:08 +0000 (07:02 +0000)
This change proved more tricky than expected due to downstream
dependencies, so it also includes some secondary refactoring.

nmigen/build/__init__.py
nmigen/build/dsl.py
nmigen/build/plat.py
nmigen/build/res.py
nmigen/test/test_build_dsl.py
nmigen/test/test_build_res.py
nmigen/vendor/lattice_ice40.py

index d13f3cd615b4a94aa01eeef1aa73cb3d8d73c49b..26a34ade3592175d2e9381e7c4918284feefde25 100644 (file)
@@ -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
index 0a0fb2b9e988946243afc6ed6c524414f938d79a..14cfce3668e6ae7ec8fe5aa315284c55c3c11b7a 100644 (file)
@@ -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:
index 43f0472441fa0a5b3edd750f2ddd3b087d9a35ff..7646040ac39e5b30e39d8555a36dcc502161cb69 100644 (file)
@@ -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):
index d1e4a4e18ed85ab5332ad7ab71304c53d31f7c73..2d3e7e6ba6891ad84be79df9008d0f63fd1637d5 100644 (file)
@@ -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
index 52887360936f12bebcaa5db95a98940b1ed05869..1ab380778e5af476ea0d4937ed53ff67490f7b59 100644 (file)
@@ -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):
index 97fa337870cac7e5299382471cade636fb7c3787..6f050ad6d8169f5b5a123b7cf04c9fdc8d28d9e1 100644 (file)
@@ -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)
index bb539ef2ee67377d116ae3607044f9a8e1efc063..bef394cea98594ebf2e7121549be7de4a8ada929 100644 (file)
@@ -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