Skip to content
Permalink

Comparing changes

Choose two branches to see what’s changed or to start a new pull request. If you need to, you can also or learn more about diff comparisons.

Open a pull request

Create a new pull request by comparing changes across two branches. If you need to, you can also . Learn more about diff comparisons here.
base repository: m-labs/nmigen
Failed to load repositories. Confirm that selected base ref is valid, then try again.
Loading
base: 066dd799e8b4
Choose a base ref
...
head repository: m-labs/nmigen
Failed to load repositories. Confirm that selected head ref is valid, then try again.
Loading
compare: ad1a40c934cc
Choose a head ref
  • 1 commit
  • 4 files changed
  • 1 contributor

Commits on Jun 11, 2019

  1. hdl.ast: implement values with custom lowering.

    whitequark committed Jun 11, 2019
    Copy the full SHA
    ad1a40c View commit details
Showing with 104 additions and 0 deletions.
  1. +45 −0 nmigen/hdl/ast.py
  2. +3 −0 nmigen/hdl/xfrm.py
  3. +20 −0 nmigen/test/test_hdl_ast.py
  4. +36 −0 nmigen/test/test_hdl_xfrm.py
45 changes: 45 additions & 0 deletions nmigen/hdl/ast.py
Original file line number Diff line number Diff line change
@@ -13,6 +13,7 @@
"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 @@ def __repr__(self):
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.
3 changes: 3 additions & 0 deletions nmigen/hdl/xfrm.py
Original file line number Diff line number Diff line change
@@ -111,6 +111,9 @@ def on_value(self, value):
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):
20 changes: 20 additions & 0 deletions nmigen/test/test_hdl_ast.py
Original file line number Diff line number Diff line change
@@ -523,6 +523,26 @@ def test_repr(self):
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")
36 changes: 36 additions & 0 deletions nmigen/test/test_hdl_xfrm.py
Original file line number Diff line number Diff line change
@@ -548,3 +548,39 @@ def test_composition(self):
)
)
""")


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