hdl.ast: implement values with custom lowering.
This commit is contained in:
parent
066dd799e8
commit
ad1a40c934
|
@ -13,6 +13,7 @@ __all__ = [
|
||||||
"Array", "ArrayProxy",
|
"Array", "ArrayProxy",
|
||||||
"Sample", "Past", "Stable", "Rose", "Fell",
|
"Sample", "Past", "Stable", "Rose", "Fell",
|
||||||
"Signal", "ClockSignal", "ResetSignal",
|
"Signal", "ClockSignal", "ResetSignal",
|
||||||
|
"UserValue",
|
||||||
"Statement", "Assign", "Assert", "Assume", "Switch", "Delay", "Tick",
|
"Statement", "Assign", "Assert", "Assume", "Switch", "Delay", "Tick",
|
||||||
"Passive", "ValueKey", "ValueDict", "ValueSet", "SignalKey", "SignalDict",
|
"Passive", "ValueKey", "ValueDict", "ValueSet", "SignalKey", "SignalDict",
|
||||||
"SignalSet",
|
"SignalSet",
|
||||||
|
@ -848,6 +849,50 @@ class ArrayProxy(Value):
|
||||||
return "(proxy (array [{}]) {!r})".format(", ".join(map(repr, self.elems)), self.index)
|
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
|
@final
|
||||||
class Sample(Value):
|
class Sample(Value):
|
||||||
"""Value from the past.
|
"""Value from the past.
|
||||||
|
|
|
@ -111,6 +111,9 @@ class ValueVisitor(metaclass=ABCMeta):
|
||||||
new_value = self.on_ArrayProxy(value)
|
new_value = self.on_ArrayProxy(value)
|
||||||
elif type(value) is Sample:
|
elif type(value) is Sample:
|
||||||
new_value = self.on_Sample(value)
|
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:
|
else:
|
||||||
new_value = self.on_unknown_value(value)
|
new_value = self.on_unknown_value(value)
|
||||||
if isinstance(new_value, Value):
|
if isinstance(new_value, Value):
|
||||||
|
|
|
@ -523,6 +523,26 @@ class ResetSignalTestCase(FHDLTestCase):
|
||||||
self.assertEqual(repr(s1), "(rst sync)")
|
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):
|
class SampleTestCase(FHDLTestCase):
|
||||||
def test_const(self):
|
def test_const(self):
|
||||||
s = Sample(1, 1, "sync")
|
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