build.{dsl,res}: allow removing attributes from subsignals.
authorwhitequark <cz@m-labs.hk>
Mon, 8 Jul 2019 10:41:45 +0000 (10:41 +0000)
committerwhitequark <cz@m-labs.hk>
Mon, 8 Jul 2019 10:42:10 +0000 (10:42 +0000)
This is useful when most attributes in a large composite resource
are the same, but a few signals are different, and also when building
abstractions around resources.

Fixes #128.

nmigen/build/dsl.py
nmigen/build/res.py
nmigen/test/test_build_dsl.py

index 90e09f8baa3f22bc2eb0d31cc3b66870fd01d1f0..0c936bb7a88c8a293a09b2a054d910afdfd6699c 100644 (file)
@@ -92,15 +92,20 @@ def DiffPairsN(*args, **kwargs):
 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}"
+            if not (attr_value is None or isinstance(attr_value, str)):
+                raise TypeError("Attribute value must be None or str, not {!r}"
                                 .format(attr_value))
 
         super().__init__(**attrs)
 
     def __repr__(self):
-        return "(attrs {})".format(" ".join("{}={}".format(k, v)
-                                    for k, v in self.items()))
+        items = []
+        for key, value in self.items():
+            if value is None:
+                items.append("!" + key)
+            else:
+                items.append(key + "=" + value)
+        return "(attrs {})".format(" ".join(items))
 
 
 class Clock:
index 0c9e8518aa938deef62af6ef21c1d97a44a683e6..9977dd54abc0bb5a1beb93a26f2dcce26afa8399 100644 (file)
@@ -106,9 +106,10 @@ class ResourceManager:
             if isinstance(resource.ios[0], Subsignal):
                 fields = OrderedDict()
                 for sub in resource.ios:
+                    sub_attrs = {k: v for k, v in {**attrs, **sub.attrs}.items() if v is not None}
                     fields[sub.name] = resolve(sub, dir[sub.name], xdr[sub.name],
                                                name="{}__{}".format(name, sub.name),
-                                               attrs={**attrs, **sub.attrs})
+                                               attrs=sub_attrs)
                 return Record([
                     (f_name, f.layout) for (f_name, f) in fields.items()
                 ], fields=fields, name=name)
index d0843406e101c5d32e0aee61e12a7b7386160ebe..e7839c02ccc18351e06fbe91d680ae5dd8ff3f12 100644 (file)
@@ -113,9 +113,14 @@ class AttrsTestCase(FHDLTestCase):
         self.assertEqual(a["IO_STANDARD"], "LVCMOS33")
         self.assertEqual(repr(a), "(attrs IO_STANDARD=LVCMOS33 PULLUP=1)")
 
+    def test_remove(self):
+        a = Attrs(FOO=None)
+        self.assertEqual(a["FOO"], None)
+        self.assertEqual(repr(a), "(attrs !FOO)")
+
     def test_wrong_value(self):
         with self.assertRaises(TypeError,
-                msg="Attribute value must be a string, not 1"):
+                msg="Attribute value must be None or str, not 1"):
             a = Attrs(FOO=1)