hdl.ast: implement values with custom lowering.
authorwhitequark <cz@m-labs.hk>
Tue, 11 Jun 2019 07:01:44 +0000 (07:01 +0000)
committerwhitequark <cz@m-labs.hk>
Tue, 11 Jun 2019 07:01:44 +0000 (07:01 +0000)
nmigen/hdl/ast.py
nmigen/hdl/xfrm.py
nmigen/test/test_hdl_ast.py
nmigen/test/test_hdl_xfrm.py

index c246af13f5f6bfa4df3c96af03f8dcd772b7ffa2..809ff20240f7fb976d828b9c7577be728efff103 100644 (file)
@@ -13,6 +13,7 @@ __all__ = [
     "Array", "ArrayProxy",
     "Sample", "Past", "Stable", "Rose", "Fell",
     "Signal", "ClockSignal", "ResetSignal",
+    "UserValue",
     "Statement", "Assign", "Assert", "Assume", "Switch", "Delay", "Tick",
     "Passive", "ValueKey", "ValueDict", "ValueSet", "SignalKey", "SignalDict",
     "SignalSet",
@@ -848,6 +849,50 @@ class ArrayProxy(Value):
         return "(proxy (array [{}]) {!r})".format(", ".join(map(repr, self.elems)), self.index)
 
 
+class UserValue(Value):
+    """Value with custom lowering.
+
+    A ``UserValue`` is a value whose precise representation does not have to be immediately known,
+    which is useful in certain metaprogramming scenarios. Instead of providing fixed semantics
+    upfront, it is kept abstract for as long as possible, only being lowered to a concrete nMigen
+    value when required.
+
+    Note that the ``lower`` method will only be called once; this is necessary to ensure that
+    nMigen's view of representation of all values stays internally consistent. If the class
+    deriving from  ``UserValue`` is mutable, then it must ensure that after ``lower`` is called,
+    it is not mutated in a way that changes its representation.
+
+    The following is an incomplete list of actions that, when applied to an ``UserValue`` directly
+    or indirectly, will cause it to be lowered, provided as an illustrative reference:
+        * Querying the shape using ``.shape()`` or ``len()``;
+        * Creating a similarly shaped signal using ``Signal.like``;
+        * Indexing or iterating through individual bits;
+        * Adding an assignment to the value to a ``Module`` using ``m.d.<domain> +=``.
+    """
+    def __init__(self, src_loc_at=1):
+        super().__init__(src_loc_at=1 + src_loc_at)
+        self.__lowered = None
+
+    @abstractmethod
+    def lower(self):
+        """Conversion to a concrete representation."""
+        pass # :nocov:
+
+    def _lazy_lower(self):
+        if self.__lowered is None:
+            self.__lowered = Value.wrap(self.lower())
+        return self.__lowered
+
+    def shape(self):
+        return self._lazy_lower().shape()
+
+    def _lhs_signals(self):
+        return self._lazy_lower()._lhs_signals()
+
+    def _rhs_signals(self):
+        return self._lazy_lower()._rhs_signals()
+
+
 @final
 class Sample(Value):
     """Value from the past.
index 9e00cc6d0e21aff0297d2b64a9b8e205b8e00747..b27de27fbf6748db516ccbd839854de1adcfefbc 100644 (file)
@@ -111,6 +111,9 @@ class ValueVisitor(metaclass=ABCMeta):
             new_value = self.on_ArrayProxy(value)
         elif type(value) is Sample:
             new_value = self.on_Sample(value)
+        elif isinstance(value, UserValue):
+            # Uses `isinstance()` and not `type() is` to allow inheriting.
+            new_value = self.on_value(value._lazy_lower())
         else:
             new_value = self.on_unknown_value(value)
         if isinstance(new_value, Value):
index a3fadb27ec3fb4c18142c968fa87bdae9612538c..3983239a1da06de0a82164b08e66b35817e3650d 100644 (file)
@@ -523,6 +523,26 @@ class ResetSignalTestCase(FHDLTestCase):
         self.assertEqual(repr(s1), "(rst sync)")
 
 
+class MockUserValue(UserValue):
+    def __init__(self, lowered):
+        super().__init__()
+        self.lower_count = 0
+        self.lowered     = lowered
+
+    def lower(self):
+        self.lower_count += 1
+        return self.lowered
+
+
+class UserValueTestCase(FHDLTestCase):
+    def test_shape(self):
+        uv = MockUserValue(1)
+        self.assertEqual(uv.shape(), (1, False))
+        uv.lowered = 2
+        self.assertEqual(uv.shape(), (1, False))
+        self.assertEqual(uv.lower_count, 1)
+
+
 class SampleTestCase(FHDLTestCase):
     def test_const(self):
         s = Sample(1, 1, "sync")
index 8a281d16b7bd7e2f570c2ec960577f2febd4770d..389229923e5b1a9533c43046a4256cb31adc3e0e 100644 (file)
@@ -548,3 +548,39 @@ class TransformedElaboratableTestCase(FHDLTestCase):
             )
         )
         """)
+
+
+class MockUserValue(UserValue):
+    def __init__(self, lowered):
+        super().__init__()
+        self.lowered = lowered
+
+    def lower(self):
+        return self.lowered
+
+
+class UserValueTestCase(FHDLTestCase):
+    def setUp(self):
+        self.s  = Signal()
+        self.c  = Signal()
+        self.uv = MockUserValue(self.s)
+
+    def test_lower(self):
+        sync = ClockDomain()
+        f = Fragment()
+        f.add_statements(
+            self.uv.eq(1)
+        )
+        for signal in self.uv._lhs_signals():
+            f.add_driver(signal, "sync")
+
+        f = ResetInserter(self.c)(f)
+        f = DomainLowerer({"sync": sync})(f)
+        self.assertRepr(f.statements, """
+        (
+            (eq (sig s) (const 1'd1))
+            (switch (sig c)
+                (case 1 (eq (sig s) (const 1'd0)))
+            )
+        )
+        """)