From ad1a40c934cc33374109069296386d66023671f1 Mon Sep 17 00:00:00 2001 From: whitequark Date: Tue, 11 Jun 2019 07:01:44 +0000 Subject: [PATCH] hdl.ast: implement values with custom lowering. --- nmigen/hdl/ast.py | 45 ++++++++++++++++++++++++++++++++++++ nmigen/hdl/xfrm.py | 3 +++ nmigen/test/test_hdl_ast.py | 20 ++++++++++++++++ nmigen/test/test_hdl_xfrm.py | 36 +++++++++++++++++++++++++++++ 4 files changed, 104 insertions(+) diff --git a/nmigen/hdl/ast.py b/nmigen/hdl/ast.py index c246af1..809ff20 100644 --- a/nmigen/hdl/ast.py +++ b/nmigen/hdl/ast.py @@ -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. +=``. + """ + 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. diff --git a/nmigen/hdl/xfrm.py b/nmigen/hdl/xfrm.py index 9e00cc6..b27de27 100644 --- a/nmigen/hdl/xfrm.py +++ b/nmigen/hdl/xfrm.py @@ -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): diff --git a/nmigen/test/test_hdl_ast.py b/nmigen/test/test_hdl_ast.py index a3fadb2..3983239 100644 --- a/nmigen/test/test_hdl_ast.py +++ b/nmigen/test/test_hdl_ast.py @@ -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") diff --git a/nmigen/test/test_hdl_xfrm.py b/nmigen/test/test_hdl_xfrm.py index 8a281d1..3892299 100644 --- a/nmigen/test/test_hdl_xfrm.py +++ b/nmigen/test/test_hdl_xfrm.py @@ -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))) + ) + ) + """) -- 2.30.2