hdl.ast: implement values with custom lowering.

This commit is contained in:
whitequark 2019-06-11 07:01:44 +00:00
parent 066dd799e8
commit ad1a40c934
4 changed files with 104 additions and 0 deletions

View 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.

View 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):

View 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")

View 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)))
)
)
""")