"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",
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.
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):
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")
)
)
""")
+
+
+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)))
+ )
+ )
+ """)