hdl.ast: implement values with custom lowering.
This commit is contained in:
parent
066dd799e8
commit
ad1a40c934
|
@ -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.
|
||||
|
|
|
@ -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):
|
||||
|
|
|
@ -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")
|
||||
|
|
|
@ -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)))
|
||||
)
|
||||
)
|
||||
""")
|
||||
|
|
Loading…
Reference in a new issue