Skip to content

Commit f2fdc41

Browse files
wanda-phiwhitequark
authored andcommitted
hdl: allow driving individual bits of signals from multiple modules or domains.
Fixes #1454.
1 parent ef38b93 commit f2fdc41

File tree

7 files changed

+360
-163
lines changed

7 files changed

+360
-163
lines changed

amaranth/hdl/_dsl.py

+90-83
Original file line numberDiff line numberDiff line change
@@ -19,82 +19,78 @@
1919
__all__ = ["SyntaxError", "SyntaxWarning", "Module"]
2020

2121

22-
class _Visitor:
23-
def __init__(self):
24-
self.driven_signals = SignalSet()
25-
26-
def visit_stmt(self, stmt):
27-
if isinstance(stmt, _StatementList):
28-
for s in stmt:
29-
self.visit_stmt(s)
30-
elif isinstance(stmt, Assign):
31-
self.visit_lhs(stmt.lhs)
32-
self.visit_rhs(stmt.rhs)
33-
elif isinstance(stmt, Print):
22+
def _check_stmt(stmt):
23+
if isinstance(stmt, _StatementList):
24+
for s in stmt:
25+
_check_stmt(s)
26+
elif isinstance(stmt, Assign):
27+
_check_lhs(stmt.lhs)
28+
_check_rhs(stmt.rhs)
29+
elif isinstance(stmt, Print):
30+
for chunk in stmt.message._chunks:
31+
if not isinstance(chunk, str):
32+
obj, format_spec = chunk
33+
_check_rhs(obj)
34+
elif isinstance(stmt, Property):
35+
_check_rhs(stmt.test)
36+
if stmt.message is not None:
3437
for chunk in stmt.message._chunks:
3538
if not isinstance(chunk, str):
3639
obj, format_spec = chunk
37-
self.visit_rhs(obj)
38-
elif isinstance(stmt, Property):
39-
self.visit_rhs(stmt.test)
40-
if stmt.message is not None:
41-
for chunk in stmt.message._chunks:
42-
if not isinstance(chunk, str):
43-
obj, format_spec = chunk
44-
self.visit_rhs(obj)
45-
elif isinstance(stmt, Switch):
46-
self.visit_rhs(stmt.test)
47-
for _patterns, stmts, _src_loc in stmt.cases:
48-
self.visit_stmt(stmts)
49-
elif isinstance(stmt, _LateBoundStatement):
50-
pass
51-
else:
52-
assert False # :nocov:
53-
54-
def visit_lhs(self, value):
55-
if isinstance(value, Operator) and value.operator in ("u", "s"):
56-
self.visit_lhs(value.operands[0])
57-
elif isinstance(value, (Signal, ClockSignal, ResetSignal)):
58-
self.driven_signals.add(value)
59-
elif isinstance(value, Slice):
60-
self.visit_lhs(value.value)
61-
elif isinstance(value, Part):
62-
self.visit_lhs(value.value)
63-
self.visit_rhs(value.offset)
64-
elif isinstance(value, Concat):
65-
for part in value.parts:
66-
self.visit_lhs(part)
67-
elif isinstance(value, SwitchValue):
68-
self.visit_rhs(value.test)
69-
for _patterns, elem in value.cases:
70-
self.visit_lhs(elem)
71-
elif isinstance(value, MemoryData._Row):
72-
raise ValueError(f"Value {value!r} can only be used in simulator processes")
73-
else:
74-
raise ValueError(f"Value {value!r} cannot be assigned to")
75-
76-
def visit_rhs(self, value):
77-
if isinstance(value, (Const, Signal, ClockSignal, ResetSignal, Initial, AnyValue)):
78-
pass
79-
elif isinstance(value, Operator):
80-
for op in value.operands:
81-
self.visit_rhs(op)
82-
elif isinstance(value, Slice):
83-
self.visit_rhs(value.value)
84-
elif isinstance(value, Part):
85-
self.visit_rhs(value.value)
86-
self.visit_rhs(value.offset)
87-
elif isinstance(value, Concat):
88-
for part in value.parts:
89-
self.visit_rhs(part)
90-
elif isinstance(value, SwitchValue):
91-
self.visit_rhs(value.test)
92-
for _patterns, elem in value.cases:
93-
self.visit_rhs(elem)
94-
elif isinstance(value, MemoryData._Row):
95-
raise ValueError(f"Value {value!r} can only be used in simulator processes")
96-
else:
97-
assert False # :nocov:
40+
_check_rhs(obj)
41+
elif isinstance(stmt, Switch):
42+
_check_rhs(stmt.test)
43+
for _patterns, stmts, _src_loc in stmt.cases:
44+
_check_stmt(stmts)
45+
elif isinstance(stmt, _LateBoundStatement):
46+
pass
47+
else:
48+
assert False # :nocov:
49+
50+
def _check_lhs(value):
51+
if isinstance(value, Operator) and value.operator in ("u", "s"):
52+
_check_lhs(value.operands[0])
53+
elif isinstance(value, (Signal, ClockSignal, ResetSignal)):
54+
pass
55+
elif isinstance(value, Slice):
56+
_check_lhs(value.value)
57+
elif isinstance(value, Part):
58+
_check_lhs(value.value)
59+
_check_rhs(value.offset)
60+
elif isinstance(value, Concat):
61+
for part in value.parts:
62+
_check_lhs(part)
63+
elif isinstance(value, SwitchValue):
64+
_check_rhs(value.test)
65+
for _patterns, elem in value.cases:
66+
_check_lhs(elem)
67+
elif isinstance(value, MemoryData._Row):
68+
raise ValueError(f"Value {value!r} can only be used in simulator processes")
69+
else:
70+
raise ValueError(f"Value {value!r} cannot be assigned to")
71+
72+
def _check_rhs(value):
73+
if isinstance(value, (Const, Signal, ClockSignal, ResetSignal, Initial, AnyValue)):
74+
pass
75+
elif isinstance(value, Operator):
76+
for op in value.operands:
77+
_check_rhs(op)
78+
elif isinstance(value, Slice):
79+
_check_rhs(value.value)
80+
elif isinstance(value, Part):
81+
_check_rhs(value.value)
82+
_check_rhs(value.offset)
83+
elif isinstance(value, Concat):
84+
for part in value.parts:
85+
_check_rhs(part)
86+
elif isinstance(value, SwitchValue):
87+
_check_rhs(value.test)
88+
for _patterns, elem in value.cases:
89+
_check_rhs(elem)
90+
elif isinstance(value, MemoryData._Row):
91+
raise ValueError(f"Value {value!r} can only be used in simulator processes")
92+
else:
93+
assert False # :nocov:
9894

9995

10096
class _ModuleBuilderProxy:
@@ -632,16 +628,27 @@ def _add_statement(self, assigns, domain, depth):
632628

633629
stmt._MustUse__used = True
634630

635-
visitor = _Visitor()
636-
visitor.visit_stmt(stmt)
637-
for signal in visitor.driven_signals:
638-
if signal not in self._driving:
639-
self._driving[signal] = domain
640-
elif self._driving[signal] != domain:
641-
cd_curr = self._driving[signal]
642-
raise SyntaxError(
643-
f"Driver-driver conflict: trying to drive {signal!r} from d.{domain}, but it is "
644-
f"already driven from d.{cd_curr}")
631+
_check_stmt(stmt)
632+
633+
lhs_masks = LHSMaskCollector()
634+
# This is an opportunistic early check — not much harm skipping it, since
635+
# the whole-design check will be later done in NIR emitter.
636+
if not isinstance(stmt, _LateBoundStatement):
637+
lhs_masks.visit_stmt(stmt)
638+
639+
for sig, mask in lhs_masks.masks():
640+
if sig not in self._driving:
641+
self._driving[sig] = [None] * len(sig)
642+
sig_domain = self._driving[sig]
643+
for bit in range(len(sig)):
644+
if not (mask & (1 << bit)):
645+
continue
646+
if sig_domain[bit] is None:
647+
sig_domain[bit] = domain
648+
if sig_domain[bit] != domain:
649+
raise SyntaxError(
650+
f"Driver-driver conflict: trying to drive {sig!r} bit {bit} from d.{domain}, but it is "
651+
f"already driven from d.{sig_domain[bit]}")
645652

646653
self._statements.setdefault(domain, []).append(stmt)
647654

0 commit comments

Comments
 (0)