hdl: make ClockSignal and ResetSignal usable on LHS.

Fixes #8.
This commit is contained in:
whitequark 2019-01-14 15:38:16 +00:00
parent 664b4bcb3a
commit 011bf2258e
5 changed files with 73 additions and 4 deletions

19
examples/por.py Normal file
View file

@ -0,0 +1,19 @@
from nmigen import *
from nmigen.cli import main
m = Module()
cd_por = ClockDomain(reset_less=True)
cd_sync = ClockDomain()
m.domains += cd_por, cd_sync
delay = Signal(max=255, reset=255)
with m.If(delay != 0):
m.d.por += delay.eq(delay - 1)
m.d.comb += [
ClockSignal().eq(cd_por.clk),
ResetSignal().eq(delay == 0),
]
if __name__ == "__main__":
main(m.lower(platform=None), ports=[cd_por.clk])

View file

@ -635,6 +635,9 @@ class ClockSignal(Value):
def shape(self): def shape(self):
return 1, False return 1, False
def _lhs_signals(self):
return ValueSet((self,))
def _rhs_signals(self): def _rhs_signals(self):
raise NotImplementedError("ClockSignal must be lowered to a concrete signal") # :nocov: raise NotImplementedError("ClockSignal must be lowered to a concrete signal") # :nocov:
@ -666,6 +669,9 @@ class ResetSignal(Value):
def shape(self): def shape(self):
return 1, False return 1, False
def _lhs_signals(self):
return ValueSet((self,))
def _rhs_signals(self): def _rhs_signals(self):
raise NotImplementedError("ResetSignal must be lowered to a concrete signal") # :nocov: raise NotImplementedError("ResetSignal must be lowered to a concrete signal") # :nocov:
@ -1038,6 +1044,8 @@ class ValueKey:
return hash(self.value.value) return hash(self.value.value)
elif isinstance(self.value, Signal): elif isinstance(self.value, Signal):
return hash(self.value.duid) return hash(self.value.duid)
elif isinstance(self.value, (ClockSignal, ResetSignal)):
return hash(self.value.domain)
elif isinstance(self.value, Operator): elif isinstance(self.value, Operator):
return hash((self.value.op, tuple(ValueKey(o) for o in self.value.operands))) return hash((self.value.op, tuple(ValueKey(o) for o in self.value.operands)))
elif isinstance(self.value, Slice): elif isinstance(self.value, Slice):
@ -1064,6 +1072,8 @@ class ValueKey:
return self.value.value == other.value.value return self.value.value == other.value.value
elif isinstance(self.value, Signal): elif isinstance(self.value, Signal):
return self.value is other.value return self.value is other.value
elif isinstance(self.value, (ClockSignal, ResetSignal)):
return self.value.domain == other.value.domain
elif isinstance(self.value, Operator): elif isinstance(self.value, Operator):
return (self.value.op == other.value.op and return (self.value.op == other.value.op and
len(self.value.operands) == len(other.value.operands) and len(self.value.operands) == len(other.value.operands) and
@ -1123,22 +1133,28 @@ class ValueSet(_MappedKeySet):
class SignalKey: class SignalKey:
def __init__(self, signal): def __init__(self, signal):
if type(signal) is not Signal: if type(signal) is Signal:
self._intern = (0, signal.duid)
elif type(signal) is ClockSignal:
self._intern = (1, signal.domain)
elif type(signal) is ResetSignal:
self._intern = (2, signal.domain)
else:
raise TypeError("Object '{!r}' is not an nMigen signal".format(signal)) raise TypeError("Object '{!r}' is not an nMigen signal".format(signal))
self.signal = signal self.signal = signal
def __hash__(self): def __hash__(self):
return hash(self.signal.duid) return hash(self._intern)
def __eq__(self, other): def __eq__(self, other):
if type(other) is not SignalKey: if type(other) is not SignalKey:
return False return False
return self.signal is other.signal return self._intern == other._intern
def __lt__(self, other): def __lt__(self, other):
if type(other) is not SignalKey: if type(other) is not SignalKey:
raise TypeError("Object '{!r}' cannot be compared to a SignalKey".format(signal)) raise TypeError("Object '{!r}' cannot be compared to a SignalKey".format(signal))
return self.signal.duid < other.signal.duid return self._intern < other._intern
def __repr__(self): def __repr__(self):
return "<{}.SignalKey {!r}>".format(__name__, self.signal) return "<{}.SignalKey {!r}>".format(__name__, self.signal)

View file

@ -293,6 +293,10 @@ class DomainLowerer(FragmentTransformer, ValueTransformer, StatementTransformer)
.format(context, domain)) .format(context, domain))
return self.domains[domain] return self.domains[domain]
def map_drivers(self, fragment, new_fragment):
for domain, signal in fragment.iter_drivers():
new_fragment.add_driver(self.on_value(signal), domain)
def on_ClockSignal(self, value): def on_ClockSignal(self, value):
cd = self._resolve(value.domain, value) cd = self._resolve(value.domain, value)
return cd.clk return cd.clk

View file

@ -95,6 +95,24 @@ class DSLTestCase(FHDLTestCase):
msg="'Module' object has no attribute 'nonexistentattr'"): msg="'Module' object has no attribute 'nonexistentattr'"):
m.nonexistentattr m.nonexistentattr
def test_clock_signal(self):
m = Module()
m.d.comb += ClockSignal("pix").eq(ClockSignal())
self.assertRepr(m._statements, """
(
(eq (clk pix) (clk sync))
)
""")
def test_reset_signal(self):
m = Module()
m.d.comb += ResetSignal("pix").eq(1)
self.assertRepr(m._statements, """
(
(eq (rst pix) (const 1'd1))
)
""")
def test_If(self): def test_If(self):
m = Module() m = Module()
with m.If(self.s1): with m.If(self.s1):

View file

@ -135,6 +135,18 @@ class DomainLowererTestCase(FHDLTestCase):
) )
""") """)
def test_lower_drivers(self):
pix = ClockDomain()
f = Fragment()
f.add_driver(ClockSignal("pix"), None)
f.add_driver(ResetSignal("pix"), "sync")
f = DomainLowerer({"pix": pix})(f)
self.assertEqual(f.drivers, {
None: SignalSet((pix.clk,)),
"sync": SignalSet((pix.rst,))
})
def test_lower_wrong_domain(self): def test_lower_wrong_domain(self):
sync = ClockDomain() sync = ClockDomain()
f = Fragment() f = Fragment()