hdl.xfrm: allow using FragmentTransformer on any elaboratable.
authorwhitequark <whitequark@whitequark.org>
Wed, 10 Apr 2019 00:23:11 +0000 (00:23 +0000)
committerwhitequark <whitequark@whitequark.org>
Wed, 10 Apr 2019 00:23:11 +0000 (00:23 +0000)
Fixes #29.

nmigen/hdl/ir.py
nmigen/hdl/xfrm.py
nmigen/test/test_hdl_xfrm.py

index 50b23d0167ee751884470e5907d98c84b59961ad..079bba264d463747896592da485252426f24be4b 100644 (file)
@@ -18,7 +18,7 @@ class Fragment:
     def get(obj, platform):
         if isinstance(obj, Fragment):
             return obj
-        if hasattr(obj, "elaborate"):
+        elif hasattr(obj, "elaborate"):
             frag = obj.elaborate(platform)
         else:
             raise AttributeError("Object '{!r}' cannot be elaborated".format(obj))
index e5f415e9e710e7adb2b7ba758891f560147bae9c..7c51a6194c3c4f10d05272492debdcb7c8e7041f 100644 (file)
@@ -13,6 +13,7 @@ from .rec import *
 __all__ = ["ValueVisitor", "ValueTransformer",
            "StatementVisitor", "StatementTransformer",
            "FragmentTransformer",
+           "TransformedElaboratable",
            "DomainRenamer", "DomainLowerer",
            "SampleDomainInjector", "SampleLowerer",
            "SwitchCleaner", "LHSGroupAnalyzer", "LHSGroupFilter",
@@ -277,7 +278,36 @@ class FragmentTransformer:
         return new_fragment
 
     def __call__(self, value):
-        return self.on_fragment(value)
+        if isinstance(value, Fragment):
+            return self.on_fragment(value)
+        elif isinstance(value, TransformedElaboratable):
+            value._transforms_.append(self)
+            return value
+        elif hasattr(value, "elaborate"):
+            value = TransformedElaboratable(value)
+            value._transforms_.append(self)
+            return value
+        else:
+            raise AttributeError("Object '{!r}' cannot be elaborated".format(value))
+
+
+class TransformedElaboratable:
+    def __init__(self, elaboratable):
+        assert hasattr(elaboratable, "elaborate")
+
+        # Fields prefixed and suffixed with underscore to avoid as many conflicts with the inner
+        # object as possible, since we're forwarding attribute requests to it.
+        self._elaboratable_ = elaboratable
+        self._transforms_   = []
+
+    def __getattr__(self, attr):
+        return getattr(self._elaboratable_, attr)
+
+    def elaborate(self, platform):
+        fragment = Fragment.get(self._elaboratable_, platform)
+        for transform in self._transforms_:
+            fragment = transform(fragment)
+        return fragment
 
 
 class DomainRenamer(FragmentTransformer, ValueTransformer, StatementTransformer):
index f983ab74a84ba4b2bd1c7a57a23290ded3d25102..94af1f8a89d8b65f2d68c24b3c8d0fbc71fdedb2 100644 (file)
@@ -486,3 +486,49 @@ class CEInserterTestCase(FHDLTestCase):
             )
         )
         """)
+
+
+class _MockElaboratable:
+    def __init__(self):
+        self.s1 = Signal()
+
+    def elaborate(self, platform):
+        f = Fragment()
+        f.add_statements(
+            self.s1.eq(1)
+        )
+        f.add_driver(self.s1, "sync")
+        return f
+
+
+class TransformedElaboratableTestCase(FHDLTestCase):
+    def setUp(self):
+        self.c1 = Signal()
+        self.c2 = Signal()
+
+    def test_getattr(self):
+        e = _MockElaboratable()
+        te = CEInserter(self.c1)(e)
+
+        self.assertIs(te.s1, e.s1)
+
+    def test_composition(self):
+        e = _MockElaboratable()
+        te1 = CEInserter(self.c1)(e)
+        te2 = ResetInserter(self.c2)(te1)
+
+        self.assertIsInstance(te1, TransformedElaboratable)
+        self.assertIs(te1, te2)
+
+        f = Fragment.get(te2, None)
+        self.assertRepr(f.statements, """
+        (
+            (eq (sig s1) (const 1'd1))
+            (switch (sig c1)
+                (case 0 (eq (sig s1) (sig s1)))
+            )
+            (switch (sig c2)
+                (case 1 (eq (sig s1) (const 1'd0)))
+            )
+        )
+        """)