Repository: amaranth-lang/amaranth (2024-04-02 ~ 2024-04-10)

problem_statement: Detect and warn about elaboratables added more than once into the hierarchy
date: 2024-04-02
patch: diff --git a/amaranth/hdl/_ast.py b/amaranth/hdl/_ast.py
index 0b7f15e..57f102e 100644
--- a/amaranth/hdl/_ast.py
+++ b/amaranth/hdl/_ast.py
@@ -18,7 +18,7 @@ from .._unused import *
 __all__ = [
     "SyntaxError", "SyntaxWarning",
     "Shape", "signed", "unsigned", "ShapeCastable", "ShapeLike",
-    "Value", "Const", "C", "AnyConst", "AnySeq", "Operator", "Mux", "Part", "Slice", "Cat", "Concat",
+    "Value", "Const", "C", "AnyConst", "AnySeq", "Operator", "Mux", "Part", "Slice", "Cat", "Concat", "SwitchValue",
     "Array", "ArrayProxy",
     "Signal", "ClockSignal", "ResetSignal",
     "ValueCastable", "ValueLike",
@@ -1892,6 +1892,60 @@ class Concat(Value):
         return "(cat {})".format(" ".join(map(repr, self.parts)))
 
 
+@final
+class SwitchValue(Value):
+    def __init__(self, test, cases, *, src_loc=None, src_loc_at=0):
+        if src_loc is None:
+            super().__init__(src_loc_at=src_loc_at)
+        else:
+            self.src_loc = src_loc
+        self._test = Value.cast(test)
+        new_cases = []
+        for patterns, value in cases:
+            if patterns is not None:
+                if not isinstance(patterns, tuple):
+                    patterns = (patterns,)
+                new_patterns = ()
+                key_mask = (1 << len(self.test)) - 1
+                for key in _normalize_patterns(patterns, self._test.shape()):
+                    if isinstance(key, int):
+                        key = to_binary(key & key_mask, len(self.test))
+                    new_patterns = (*new_patterns, key)
+            else:
+                new_patterns = None
+            new_cases.append((new_patterns, Value.cast(value)))
+        self._cases = tuple(new_cases)
+
+    @property
+    def test(self):
+        return self._test
+
+    @property
+    def cases(self):
+        return self._cases
+
+    def shape(self):
+        return Shape._unify(value.shape() for _patterns, value in self._cases)
+
+    def _lhs_signals(self):
+        return union((value._lhs_signals() for _patterns, value in self.cases), start=SignalSet())
+
+    def _rhs_signals(self):
+        signals = union((value._rhs_signals() for _patterns, value in self.cases), start=SignalSet())
+        return self.test._rhs_signals() | signals
+
+    def __repr__(self):
+        def case_repr(patterns, value):
+            if patterns is None:
+                return f"(default {value!r})"
+            elif len(patterns) == 1:
+                return f"(case {patterns[0]} {value!r})"
+            else:
+                return "(case ({}) {!r})".format(" ".join(patterns), value)
+        case_reprs = (case_repr(patterns, value) for patterns, value in self.cases)
+        return "(switch-value {!r} {})".format(self.test, " ".join(case_reprs))
+
+
 class _SignalMeta(ABCMeta):
     def __call__(cls, shape=None, src_loc_at=0, **kwargs):
         signal = super().__call__(shape, **kwargs, src_loc_at=src_loc_at + 1)
@@ -2356,10 +2410,17 @@ class Array(MutableSequence):
                                        ", ".join(map(repr, self._inner)))
 
 
+def _proxy_value(name):
+    @functools.wraps(getattr(Value, name))
+    def inner(self, *args, **kwargs):
+        return getattr(Value.cast(self), name)(*args, **kwargs)
+    return inner
+
+
 @final
-class ArrayProxy(Value):
+class ArrayProxy(ValueCastable):
     def __init__(self, elems, index, *, src_loc_at=0):
-        super().__init__(src_loc_at=1 + src_loc_at)
+        self.src_loc = tracer.get_src_loc(1 + src_loc_at)
         self._elems = elems
         self._index = Value.cast(index)
 
@@ -2385,19 +2446,73 @@ class ArrayProxy(Value):
         # elements. I.e., shape-wise, an array proxy must be identical to an equivalent mux tree.
         return Shape._unify(elem.shape() for elem in self._iter_as_values())
 
-    def _lhs_signals(self):
-        signals = union((elem._lhs_signals() for elem in self._iter_as_values()),
-                        start=SignalSet())
-        return signals
+    def as_value(self):
+        return SwitchValue(
+            self._index,
+            (
+                (index, value)
+                for index, value in enumerate(self._elems)
+                if index in range(1 << len(self._index))
+            ),
+            src_loc=self.src_loc,
+        )
 
-    def _rhs_signals(self):
-        signals = union((elem._rhs_signals() for elem in self._iter_as_values()),
-                        start=SignalSet())
-        return self.index._rhs_signals() | signals
+    def eq(self, value, *, src_loc_at=0):
+        return self.as_value().eq(value, src_loc_at=1 + src_loc_at)
 
     def __repr__(self):
         return "(proxy (array [{}]) {!r})".format(", ".join(map(repr, self.elems)), self.index)
 
+    as_signed = _proxy_value("as_signed")
+    as_unsigned = _proxy_value("as_unsigned")
+    __len__ = _proxy_value("__len__")
+    __bool__ = _proxy_value("__bool__")
+    bool = _proxy_value("bool")
+    __pos__ = _proxy_value("__pos__")
+    __neg__ = _proxy_value("__neg__")
+    __add__ = _proxy_value("__add__")
+    __radd__ = _proxy_value("__radd__")
+    __sub__ = _proxy_value("__sub__")
+    __rsub__ = _proxy_value("__rsub__")
+    __mul__ = _proxy_value("__mul__")
+    __rmul__ = _proxy_value("__rmul__")
+    __floordiv__ = _proxy_value("__floordiv__")
+    __rfloordiv__ = _proxy_value("__rfloordiv__")
+    __mod__ = _proxy_value("__mod__")
+    __rmod__ = _proxy_value("__rmod__")
+    __eq__ = _proxy_value("__eq__")
+    __ne__ = _proxy_value("__ne__")
+    __lt__ = _proxy_value("__lt__")
+    __le__ = _proxy_value("__le__")
+    __gt__ = _proxy_value("__gt__")
+    __ge__ = _proxy_value("__ge__")
+    __abs__ = _proxy_value("__abs__")
+    __invert__ = _proxy_value("__invert__")
+    __and__ = _proxy_value("__and__")
+    __rand__ = _proxy_value("__rand__")
+    __or__ = _proxy_value("__or__")
+    __ror__ = _proxy_value("__ror__")
+    __xor__ = _proxy_value("__xor__")
+    __rxor__ = _proxy_value("__rxor__")
+    any = _proxy_value("any")
+    all = _proxy_value("all")
+    xor = _proxy_value("xor")
+    implies = _proxy_value("implies")
+    __lshift__ = _proxy_value("__lshift__")
+    __rlshift__ = _proxy_value("__rlshift__")
+    __rshift__ = _proxy_value("__rshift__")
+    __rrshift__ = _proxy_value("__rrshift__")
+    shift_left = _proxy_value("shift_left")
+    shift_right = _proxy_value("shift_right")
+    rotate_left = _proxy_value("rotate_left")
+    rotate_right = _proxy_value("rotate_right")
+    __contains__ = _proxy_value("__contains__")
+    bit_select = _proxy_value("bit_select")
+    word_select = _proxy_value("word_select")
+    replicate = _proxy_value("replicate")
+    matches = _proxy_value("matches")
+    __format__ = _proxy_value("__format__")
+
 
 @final
 class Initial(Value):
@@ -2772,7 +2887,7 @@ class Switch(Statement):
             self.src_loc = src_loc
 
         self._test  = Value.cast(test)
-        self._cases = []
+        new_cases = []
         for patterns, stmts, case_src_loc in cases:
             if patterns is not None:
                 # Map: key -> (key,); (key...) -> (key...)
@@ -2787,10 +2902,8 @@ class Switch(Statement):
                     new_patterns = (*new_patterns, key)
             else:
                 new_patterns = None
-            if not isinstance(stmts, Iterable):
-                stmts = [stmts]
-            self._cases.append((new_patterns, Statement.cast(stmts), case_src_loc))
-        self._cases = tuple(self._cases)
+            new_cases.append((new_patterns, Statement.cast(stmts), case_src_loc))
+        self._cases = tuple(new_cases)
 
     @property
     def test(self):
@@ -2816,7 +2929,7 @@ class Switch(Statement):
                 return f"(case {patterns[0]} {stmts_repr})"
             else:
                 return "(case ({}) {})".format(" ".join(patterns), stmts_repr)
-        case_reprs = [case_repr(patterns, stmts) for patterns, stmts, _src_loc in self.cases]
+        case_reprs = (case_repr(patterns, stmts) for patterns, stmts, _src_loc in self.cases)
         return "(switch {!r} {})".format(self.test, " ".join(case_reprs))
 
 
diff --git a/amaranth/hdl/_ir.py b/amaranth/hdl/_ir.py
index 717db2e..b4c3801 100644
--- a/amaranth/hdl/_ir.py
+++ b/amaranth/hdl/_ir.py
@@ -9,8 +9,8 @@ from . import _ast, _cd, _ir, _nir
 
 
 __all__ = [
-    "UnusedElaboratable", "Elaboratable", "DriverConflict", "Fragment", "Instance",
-    "IOBufferInstance", "PortDirection", "Design", "build_netlist",
+    "UnusedElaboratable", "Elaboratable", "DuplicateElaboratable", "DriverConflict", "Fragment",
+    "Instance", "IOBufferInstance", "PortDirection", "Design", "build_netlist",
 ]
 
 
@@ -30,6 +30,10 @@ class DriverConflict(UserWarning):
     pass
 
 
+class DuplicateElaboratable(Exception):
+    pass
+
+
 class Fragment:
     @staticmethod
     def get(obj, platform):
@@ -430,6 +434,7 @@ class Design:
         self.hierarchy = hierarchy
         self.fragments: dict[Fragment, DesignFragmentInfo] = {}
         self.signal_lca = _ast.SignalDict()
+        self.elaboratables: dict[Elaboratable, Fragment] = {}
         self._compute_fragment_depth_parent(fragment, None, 0)
         self._collect_used_signals(fragment)
         self._add_io_ports()
@@ -598,6 +603,15 @@ class Design:
         frag_info = self.fragments[fragment]
         frag_info.name = hierarchy
 
+        if fragment.origins is not None:
+            for origin in fragment.origins:
+                if origin in self.elaboratables:
+                    other_hierarchy = self.fragments[self.elaboratables[origin]].name
+                    raise DuplicateElaboratable(f"Elaboratable {origin!r} is included twice "
+                                                f"in the hierarchy, as {'.'.join(other_hierarchy)} "
+                                                f"and {'.'.join(hierarchy)}")
+                self.elaboratables[origin] = fragment
+
         if fragment is self.fragment:
             # Reserve names for top-level ports. If equal to the signal name, let the signal share it.
             for name, conn, _dir in self.ports:
@@ -885,30 +899,39 @@ class NetlistEmitter:
                              stride=value.stride, offset=offset, src_loc=value.src_loc)
             result = self.netlist.add_value_cell(value.width, cell)
             signed = False
-        elif isinstance(value, _ast.ArrayProxy):
-            elems = [self.emit_rhs(module_idx, elem) for elem in value.elems]
+        elif isinstance(value, _ast.SwitchValue):
+            test, _signed = self.emit_rhs(module_idx, value.test)
+            conds = []
+            elems = []
+            for patterns, elem, in value.cases:
+                if patterns is not None:
+                    if not patterns:
+                        # Hack: empty pattern set cannot be supported by RTLIL.
+                        continue
+                    for pattern in patterns:
+                        assert len(pattern) == len(test)
+                    cell = _nir.Matches(module_idx, value=test, patterns=patterns,
+                                        src_loc=value.src_loc)
+                    net, = self.netlist.add_value_cell(1, cell)
+                    conds.append(net)
+                else:
+                    conds.append(_nir.Net.from_const(1))
+                elems.append(self.emit_rhs(module_idx, elem))
+            cell = _nir.PriorityMatch(module_idx, en=_nir.Net.from_const(1),
+                                      inputs=_nir.Value(conds),
+                                      src_loc=value.src_loc)
+            conds = self.netlist.add_value_cell(len(conds), cell)
             shape = _ast.Shape._unify(
                 _ast.Shape(len(value), signed)
                 for value, signed in elems
             )
             elems = tuple(self.extend(elem, elem_signed, shape.width) for elem, elem_signed in elems)
-            index, _signed = self.emit_rhs(module_idx, value.index)
-            conds = []
-            for case_index in range(len(elems)):
-                cell = _nir.Matches(module_idx, value=index,
-                                       patterns=(to_binary(case_index, len(index)),),
-                                       src_loc=value.src_loc)
-                subcond, = self.netlist.add_value_cell(1, cell)
-                conds.append(subcond)
-            conds = _nir.Value(conds)
-            cell = _nir.PriorityMatch(module_idx, en=_nir.Net.from_const(1), inputs=conds, src_loc=value.src_loc)
-            conds = self.netlist.add_value_cell(len(conds), cell)
             assignments = [
-                _nir.Assignment(cond=cond, start=0, value=elem, src_loc=value.src_loc)
-                for cond, elem in zip(conds, elems)
+                _nir.Assignment(cond=subcond, start=0, value=elem, src_loc=value.src_loc)
+                for subcond, elem in zip(conds, elems)
             ]
-            cell = _nir.AssignmentList(module_idx, default=elems[0], assignments=assignments,
-                                 src_loc=value.src_loc)
+            cell = _nir.AssignmentList(module_idx, default=_nir.Value.from_const(0, shape.width),
+                                       assignments=assignments, src_loc=value.src_loc)
             result = self.netlist.add_value_cell(shape.width, cell)
             signed = shape.signed
         elif isinstance(value, _ast.Concat):
@@ -1017,19 +1040,29 @@ class NetlistEmitter:
                 else:
                     subrhs = rhs
                 self.emit_assign(module_idx, cd, lhs.value, start, subrhs, subcond, src_loc=src_loc)
-        elif isinstance(lhs, _ast.ArrayProxy):
-            index, _signed = self.emit_rhs(module_idx, lhs.index)
+        elif isinstance(lhs, _ast.SwitchValue):
+            test, _signed = self.emit_rhs(module_idx, lhs.test)
             conds = []
-            for case_index in range(len(lhs.elems)):
-                cell = _nir.Matches(module_idx, value=index,
-                                       patterns=(to_binary(case_index, len(index)),),
-                                       src_loc=lhs.src_loc)
-                subcond, = self.netlist.add_value_cell(1, cell)
-                conds.append(subcond)
+            elems = []
+            for patterns, elem in lhs.cases:
+                if patterns is not None:
+                    if not patterns:
+                        # Hack: empty pattern set cannot be supported by RTLIL.
+                        continue
+                    for pattern in patterns:
+                        assert len(pattern) == len(test)
+                    cell = _nir.Matches(module_idx, value=test, patterns=patterns,
+                                        src_loc=lhs.src_loc)
+                    net, = self.netlist.add_value_cell(1, cell)
+                    conds.append(net)
+                else:
+                    conds.append(_nir.Net.from_const(1))
+                elems.append(elem)
             conds = _nir.Value(conds)
-            cell = _nir.PriorityMatch(module_idx, en=cond, inputs=conds, src_loc=lhs.src_loc)
+            cell = _nir.PriorityMatch(module_idx, en=_nir.Net.from_const(1),
+                                      inputs=conds, src_loc=lhs.src_loc)
             conds = self.netlist.add_value_cell(len(conds), cell)
-            for subcond, val in zip(conds, lhs.elems):
+            for subcond, val in zip(conds, elems):
                 self.emit_assign(module_idx, cd, val, lhs_start, rhs[:len(val)], subcond, src_loc=src_loc)
         elif isinstance(lhs, _ast.Operator):
             assert lhs.operator in ('u', 's')
diff --git a/amaranth/hdl/_xfrm.py b/amaranth/hdl/_xfrm.py
index 01b4fa1..ccc91ae 100644
--- a/amaranth/hdl/_xfrm.py
+++ b/amaranth/hdl/_xfrm.py
@@ -58,7 +58,7 @@ class ValueVisitor(metaclass=ABCMeta):
         pass # :nocov:
 
     @abstractmethod
-    def on_ArrayProxy(self, value):
+    def on_SwitchValue(self, value):
         pass # :nocov:
 
     @abstractmethod
@@ -90,8 +90,8 @@ class ValueVisitor(metaclass=ABCMeta):
             new_value = self.on_Part(value)
         elif type(value) is Concat:
             new_value = self.on_Concat(value)
-        elif type(value) is ArrayProxy:
-            new_value = self.on_ArrayProxy(value)
+        elif type(value) is SwitchValue:
+            new_value = self.on_SwitchValue(value)
         elif type(value) is Initial:
             new_value = self.on_Initial(value)
         else:
@@ -133,9 +133,8 @@ class ValueTransformer(ValueVisitor):
     def on_Concat(self, value):
         return Concat(self.on_value(o) for o in value.parts)
 
-    def on_ArrayProxy(self, value):
-        return ArrayProxy([self.on_value(elem) for elem in value._iter_as_values()],
-                          self.on_value(value.index))
+    def on_SwitchValue(self, value):
+        return SwitchValue(self.on_value(value.test), [(patterns, self.on_value(val)) for patterns, val in value.cases])
 
     def on_Initial(self, value):
         return value
@@ -319,6 +318,7 @@ class FragmentTransformer:
         else:
             new_fragment = Fragment(src_loc=fragment.src_loc)
         new_fragment.attrs = OrderedDict(fragment.attrs)
+        new_fragment.origins = fragment.origins
         self.map_subfragments(fragment, new_fragment)
         self.map_domains(fragment, new_fragment)
         self.map_statements(fragment, new_fragment)
@@ -399,10 +399,10 @@ class DomainCollector(ValueVisitor, StatementVisitor):
         for o in value.parts:
             self.on_value(o)
 
-    def on_ArrayProxy(self, value):
-        for elem in value._iter_as_values():
-            self.on_value(elem)
-        self.on_value(value.index)
+    def on_SwitchValue(self, value):
+        self.on_value(value.test)
+        for patterns, val in value.cases:
+            self.on_value(val)
 
     def on_Initial(self, value):
         pass
diff --git a/amaranth/sim/_pyrtl.py b/amaranth/sim/_pyrtl.py
index 3f41f34..14e8b01 100644
--- a/amaranth/sim/_pyrtl.py
+++ b/amaranth/sim/_pyrtl.py
@@ -67,6 +67,53 @@ class _Compiler:
         self.state = state
         self.emitter = emitter
 
+    def _emit_switch(self, test, cases, case_handler):
+        if not cases:
+            return
+        use_match = _USE_PATTERN_MATCHING
+        for patterns, *_ in cases:
+            if patterns is None:
+                continue
+            for pattern in patterns:
+                if "-" in pattern:
+                    use_match = False
+        if use_match:
+            self.emitter.append(f"match {test}:")
+            with self.emitter.indent():
+                for case in cases:
+                    patterns = case[0]
+                    if patterns is None:
+                        self.emitter.append(f"case _:")
+                    elif not patterns:
+                        self.emitter.append(f"case _ if False:")
+                    else:
+                        self.emitter.append(f"case {' | '.join(f'0b0{pattern}' for pattern in patterns)}:")
+                    with self.emitter.indent():
+                        case_handler(*case)
+        else:
+            for index, case in enumerate(cases):
+                patterns = case[0]
+                gen_checks = []
+                if patterns is None:
+                    gen_checks.append(f"True")
+                elif not patterns:
+                    gen_checks.append(f"False")
+                else:
+                    for pattern in patterns:
+                        if "-" in pattern:
+                            mask  = int("".join("0" if b == "-" else "1" for b in pattern), 2)
+                            value = int("".join("0" if b == "-" else  b  for b in pattern), 2)
+                            gen_checks.append(f"{value} == ({mask} & {test})")
+                        else:
+                            value = int(pattern or "0", 2)
+                            gen_checks.append(f"{value} == {test}")
+                if index == 0:
+                    self.emitter.append(f"if {' or '.join(gen_checks)}:")
+                else:
+                    self.emitter.append(f"elif {' or '.join(gen_checks)}:")
+                with self.emitter.indent():
+                    case_handler(*case)
+
 
 class _ValueCompiler(ValueVisitor, _Compiler):
     helpers = {
@@ -223,36 +270,13 @@ class _RHSValueCompiler(_ValueCompiler):
             return f"({' | '.join(gen_parts)})"
         return f"0"
 
-    def on_ArrayProxy(self, value):
-        index_mask = (1 << len(value.index)) - 1
-        gen_index = self.emitter.def_var("rhs_index", f"{index_mask:#x} & {self(value.index)}")
-        gen_value = self.emitter.gen_var("rhs_proxy")
-        if value.elems:
-            if _USE_PATTERN_MATCHING:
-                self.emitter.append(f"match {gen_index}:")
-                with self.emitter.indent():
-                    for index, elem in enumerate(value.elems):
-                        self.emitter.append(f"case {index}:")
-                        with self.emitter.indent():
-                            self.emitter.append(f"{gen_value} = {self(elem)}")
-                    self.emitter.append("case _:")
-                    with self.emitter.indent():
-                            self.emitter.append(f"{gen_value} = {self(value.elems[-1])}")
-            else:
-                for index, elem in enumerate(value.elems):
-                    if index == 0:
-                        self.emitter.append(f"if {index} == {gen_index}:")
-                    else:
-                        self.emitter.append(f"elif {index} == {gen_index}:")
-                    with self.emitter.indent():
-                        self.emitter.append(f"{gen_value} = {self(elem)}")
-                self.emitter.append(f"else:")
-                with self.emitter.indent():
-                    self.emitter.append(f"{gen_value} = {self(value.elems[-1])}")
-
-            return gen_value
-        else:
-            return f"0"
+    def on_SwitchValue(self, value):
+        gen_test = self.emitter.def_var("test", f"{(1 << len(value.test)) - 1:#x} & {self(value.test)}")
+        gen_value = self.emitter.def_var("rhs_switch", "0")
+        def case_handler(patterns, elem):
+            self.emitter.append(f"{gen_value} = {self(elem)}")
+        self._emit_switch(gen_test, value.cases, case_handler)
+        return gen_value
 
     @classmethod
     def compile(cls, state, value, *, mode):
@@ -323,34 +347,12 @@ class _LHSValueCompiler(_ValueCompiler):
                 offset += len(part)
         return gen
 
-    def on_ArrayProxy(self, value):
+    def on_SwitchValue(self, value):
         def gen(arg):
-            index_mask = (1 << len(value.index)) - 1
-            gen_index = self.emitter.def_var("index", f"{self.rrhs(value.index)} & {index_mask:#x}")
-            if value.elems:
-                if _USE_PATTERN_MATCHING:
-                    self.emitter.append(f"match {gen_index}:")
-                    with self.emitter.indent():
-                        for index, elem in enumerate(value.elems):
-                            self.emitter.append(f"case {index}:")
-                            with self.emitter.indent():
-                                self(elem)(arg)
-                        self.emitter.append("case _:")
-                        with self.emitter.indent():
-                            self(value.elems[-1])(arg)
-                else:
-                    for index, elem in enumerate(value.elems):
-                        if index == 0:
-                            self.emitter.append(f"if {index} == {gen_index}:")
-                        else:
-                            self.emitter.append(f"elif {index} == {gen_index}:")
-                        with self.emitter.indent():
-                            self(elem)(arg)
-                    self.emitter.append(f"else:")
-                    with self.emitter.indent():
-                        self(value.elems[-1])(arg)
-            else:
-                self.emitter.append(f"pass")
+            gen_test = self.emitter.def_var("test", f"{(1 << len(value.test)) - 1:#x} & {self.rrhs(value.test)}")
+            def case_handler(patterns, elem):
+                self(elem)(arg)
+            self._emit_switch(gen_test, value.cases, case_handler)
         return gen
 
 
@@ -396,27 +398,9 @@ class _StatementCompiler(StatementVisitor, _Compiler):
     def on_Switch(self, stmt):
         gen_test_value = self.rhs(stmt.test) # check for oversized value before generating mask
         gen_test = self.emitter.def_var("test", f"{(1 << len(stmt.test)) - 1:#x} & {gen_test_value}")
-        for index, (patterns, stmts, _src_loc) in enumerate(stmt.cases):
-            gen_checks = []
-            if patterns is None:
-                gen_checks.append(f"True")
-            elif not patterns:
-                gen_checks.append(f"False")
-            else:
-                for pattern in patterns:
-                    if "-" in pattern:
-                        mask  = int("".join("0" if b == "-" else "1" for b in pattern), 2)
-                        value = int("".join("0" if b == "-" else  b  for b in pattern), 2)
-                        gen_checks.append(f"{value} == ({mask} & {gen_test})")
-                    else:
-                        value = int(pattern or "0", 2)
-                        gen_checks.append(f"{value} == {gen_test}")
-            if index == 0:
-                self.emitter.append(f"if {' or '.join(gen_checks)}:")
-            else:
-                self.emitter.append(f"elif {' or '.join(gen_checks)}:")
-            with self.emitter.indent():
-                self(stmts)
+        def case_handler(pattern, stmt, src_loc):
+            self(stmt)
+        self._emit_switch(gen_test, stmt.cases, case_handler)
 
     def emit_format(self, format):
         format_string = []

test_patch: diff --git a/tests/test_hdl_ast.py b/tests/test_hdl_ast.py
index 23e8e76..e17268a 100644
--- a/tests/test_hdl_ast.py
+++ b/tests/test_hdl_ast.py
@@ -1144,6 +1144,7 @@ class ArrayProxyTestCase(FHDLTestCase):
         s = Signal(range(3))
         v = a[s]
         self.assertEqual(repr(v), "(proxy (array [1, 2, 3]) (sig s))")
+        self.assertEqual(repr(v.as_value()), "(switch-value (sig s) (case 00 (const 1'd1)) (case 01 (const 2'd2)) (case 10 (const 2'd3)))")
 
 
 class SignalTestCase(FHDLTestCase):
diff --git a/tests/test_hdl_dsl.py b/tests/test_hdl_dsl.py
index ad373ee..68a4522 100644
--- a/tests/test_hdl_dsl.py
+++ b/tests/test_hdl_dsl.py
@@ -631,12 +631,6 @@ class DSLTestCase(FHDLTestCase):
             )
         )
         """)
-        self.assertEqual({repr(sig): k for k, v in frag.drivers.items() for sig in v}, {
-            "(sig a)": "comb",
-            "(sig fsm_state)": "sync",
-            "(sig b)": "sync",
-            "(sig)": "comb",
-        })
         fsm  = frag.find_generated("fsm")
         self.assertIsInstance(fsm.state, Signal)
         self.assertEqual(fsm.encoding, OrderedDict({
@@ -936,7 +930,7 @@ class DSLTestCase(FHDLTestCase):
     def test_domain_add_wrong_name(self):
         m = Module()
         with self.assertRaisesRegex(NameError,
-                r"^Clock domain name 'bar' must match name in `m\.domains\.foo \+= \.\.\.` syntax$"):
+                r"^Clock domain name 'bar' must match name in `m\.domains\.foo = \.\.\.` syntax$"):
             m.domains.foo = ClockDomain("bar")
 
     def test_domain_add_wrong_duplicate(self):
@@ -960,9 +954,6 @@ class DSLTestCase(FHDLTestCase):
             (eq (sig c1) (sig s1))
         )
         """)
-        self.assertEqual(f1.drivers, {
-            "comb": SignalSet((self.c1,))
-        })
         self.assertEqual(len(f1.subfragments), 1)
         (f2, f2_name, _), = f1.subfragments
         self.assertEqual(f2_name, "foo")
@@ -976,8 +967,11 @@ class DSLTestCase(FHDLTestCase):
             (eq (sig c3) (sig s3))
         )
         """)
-        self.assertEqual(f2.drivers, {
-            "comb": SignalSet((self.c2,)),
-            "sync": SignalSet((self.c3,))
-        })
         self.assertEqual(len(f2.subfragments), 0)
+
+    def test_bug_1331(self):
+        m = Module()
+        with self.assertRaisesRegex(NameError,
+                r"^Domain name should not be prefixed with 'cd_' in `m.domains`, "
+                r"use `m.domains.rx = ...` instead$"):
+            m.domains.cd_rx = ClockDomain()
diff --git a/tests/test_hdl_ir.py b/tests/test_hdl_ir.py
index 07464e3..f54bc08 100644
--- a/tests/test_hdl_ir.py
+++ b/tests/test_hdl_ir.py
@@ -77,6 +77,18 @@ class FragmentDriversTestCase(FHDLTestCase):
         self.assertEqual(list(f.iter_sync()), [])
 
 
+class DuplicateElaboratableTestCase(FHDLTestCase):
+    def test_duplicate(self):
+        sub = Module()
+        m = Module()
+        m.submodules.a = sub
+        m.submodules.b = sub
+        with self.assertRaisesRegex(DuplicateElaboratable,
+                r"^Elaboratable .* is included twice in the hierarchy, as "
+                r"top\.a and top\.b$"):
+            Fragment.get(m, None).prepare()
+
+
 class FragmentPortsTestCase(FHDLTestCase):
     def setUp(self):
         self.s1 = Signal()
@@ -1623,6 +1635,69 @@ class AssignTestCase(FHDLTestCase):
         )
         """)
 
+    def test_switchvalue(self):
+        s1 = Signal(8)
+        s2 = Signal(8)
+        s3 = Signal(8)
+        s4 = Signal(8)
+        s5 = Signal(8)
+        s6 = Signal(8)
+        s7 = Signal(8)
+        f = Fragment()
+        f.add_statements("comb", [
+            SwitchValue(s5[:4], [
+                (1, s1),
+                ((2, 3), s2),
+                ((), s3),
+                ('11--', s4),
+            ]).eq(s6),
+            SwitchValue(s5[4:], [
+                (4, s1),
+                (5, s2),
+                (6, s3),
+                (None, s4),
+            ]).eq(s7),
+        ])
+        f.add_driver(s1, "comb")
+        f.add_driver(s2, "comb")
+        f.add_driver(s3, "comb")
+        f.add_driver(s4, "comb")
+        nl = build_netlist(f, ports=[s1, s2, s3, s4, s5, s6, s7])
+        self.assertRepr(nl, """
+        (
+            (module 0 None ('top')
+                (input 's5' 0.2:10)
+                (input 's6' 0.10:18)
+                (input 's7' 0.18:26)
+                (output 's1' 9.0:8)
+                (output 's2' 10.0:8)
+                (output 's3' 12.0:8)
+                (output 's4' 11.0:8)
+            )
+            (cell 0 0 (top
+                (input 's5' 2:10)
+                (input 's6' 10:18)
+                (input 's7' 18:26)
+                (output 's1' 9.0:8)
+                (output 's2' 10.0:8)
+                (output 's3' 12.0:8)
+                (output 's4' 11.0:8)
+            ))
+            (cell 1 0 (matches 0.2:6 0001))
+            (cell 2 0 (matches 0.2:6 0010 0011))
+            (cell 3 0 (matches 0.2:6 11--))
+            (cell 4 0 (priority_match 1 (cat 1.0 2.0 3.0)))
+            (cell 5 0 (matches 0.6:10 0100))
+            (cell 6 0 (matches 0.6:10 0101))
+            (cell 7 0 (matches 0.6:10 0110))
+            (cell 8 0 (priority_match 1 (cat 5.0 6.0 7.0 1'd1)))
+            (cell 9 0 (assignment_list 8'd0 (4.0 0:8 0.10:18) (8.0 0:8 0.18:26)))
+            (cell 10 0 (assignment_list 8'd0 (4.1 0:8 0.10:18) (8.1 0:8 0.18:26)))
+            (cell 11 0 (assignment_list 8'd0 (4.2 0:8 0.10:18) (8.3 0:8 0.18:26)))
+            (cell 12 0 (assignment_list 8'd0 (8.2 0:8 0.18:26)))
+        )
+        """)
+
     def test_sliced_slice(self):
         s1 = Signal(12)
         s2 = Signal(4)
@@ -2953,7 +3028,7 @@ class RhsTestCase(FHDLTestCase):
             (cell 2 0 (matches 0.50:54 0001))
             (cell 3 0 (matches 0.50:54 0010))
             (cell 4 0 (priority_match 1 (cat 1.0 2.0 3.0)))
-            (cell 5 0 (assignment_list 0.2:10
+            (cell 5 0 (assignment_list 8'd0
                 (4.0 0:8 0.2:10)
                 (4.1 0:8 0.10:18)
                 (4.2 0:8 0.18:26)
@@ -2962,7 +3037,7 @@ class RhsTestCase(FHDLTestCase):
             (cell 7 0 (matches 0.50:54 0001))
             (cell 8 0 (matches 0.50:54 0010))
             (cell 9 0 (priority_match 1 (cat 6.0 7.0 8.0)))
-            (cell 10 0 (assignment_list (cat 0.2:10 1'd0)
+            (cell 10 0 (assignment_list 9'd0
                 (9.0 0:9 (cat 0.2:10 1'd0))
                 (9.1 0:9 (cat 0.10:18 1'd0))
                 (9.2 0:9 (cat 0.42:50 0.49))
@@ -2971,7 +3046,7 @@ class RhsTestCase(FHDLTestCase):
             (cell 12 0 (matches 0.50:54 0001))
             (cell 13 0 (matches 0.50:54 0010))
             (cell 14 0 (priority_match 1 (cat 11.0 12.0 13.0)))
-            (cell 15 0 (assignment_list 0.26:34
+            (cell 15 0 (assignment_list 8'd0
                 (14.0 0:8 0.26:34)
                 (14.1 0:8 0.34:42)
                 (14.2 0:8 0.42:50)
@@ -2980,7 +3055,7 @@ class RhsTestCase(FHDLTestCase):
             (cell 17 0 (matches 0.50:54 0001))
             (cell 18 0 (matches 0.50:54 0010))
             (cell 19 0 (priority_match 1 (cat 16.0 17.0 18.0)))
-            (cell 20 0 (assignment_list 0.26:34
+            (cell 20 0 (assignment_list 8'd0
                 (19.0 0:8 0.26:34)
                 (19.1 0:8 0.34:42)
                 (19.2 0:8 (cat 0.50:54 4'd0))
@@ -2988,6 +3063,67 @@ class RhsTestCase(FHDLTestCase):
         )
         """)
 
+    def test_switchvalue(self):
+        i8ua = Signal(8)
+        i8ub = Signal(8)
+        i8uc = Signal(8)
+        i8ud = Signal(8)
+        i4 = Signal(4)
+        o1 = Signal(10)
+        o2 = Signal(10)
+        m = Module()
+        m.d.comb += o1.eq(SwitchValue(i4, [
+            (1, i8ua),
+            ((2, 3), i8ub),
+            ('11--', i8uc),
+        ]))
+        m.d.comb += o2.eq(SwitchValue(i4, [
+            ((4, 5), i8ua),
+            ((), i8ub),
+            ((6, 7), i8uc),
+            (None, i8ud),
+        ]))
+        nl = build_netlist(Fragment.get(m, None), [i8ua, i8ub, i8uc, i8ud, i4, o1, o2])
+        self.assertRepr(nl, """
+        (
+            (module 0 None ('top')
+                (input 'i8ua' 0.2:10)
+                (input 'i8ub' 0.10:18)
+                (input 'i8uc' 0.18:26)
+                (input 'i8ud' 0.26:34)
+                (input 'i4' 0.34:38)
+                (output 'o1' (cat 5.0:8 2'd0))
+                (output 'o2' (cat 9.0:8 2'd0))
+            )
+            (cell 0 0 (top
+                (input 'i8ua' 2:10)
+                (input 'i8ub' 10:18)
+                (input 'i8uc' 18:26)
+                (input 'i8ud' 26:34)
+                (input 'i4' 34:38)
+                (output 'o1' (cat 5.0:8 2'd0))
+                (output 'o2' (cat 9.0:8 2'd0))
+            ))
+            (cell 1 0 (matches 0.34:38 0001))
+            (cell 2 0 (matches 0.34:38 0010 0011))
+            (cell 3 0 (matches 0.34:38 11--))
+            (cell 4 0 (priority_match 1 (cat 1.0 2.0 3.0)))
+            (cell 5 0 (assignment_list 8'd0
+                (4.0 0:8 0.2:10)
+                (4.1 0:8 0.10:18)
+                (4.2 0:8 0.18:26)
+            ))
+            (cell 6 0 (matches 0.34:38 0100 0101))
+            (cell 7 0 (matches 0.34:38 0110 0111))
+            (cell 8 0 (priority_match 1 (cat 6.0 7.0 1'd1)))
+            (cell 9 0 (assignment_list 8'd0
+                (8.0 0:8 0.2:10)
+                (8.1 0:8 0.18:26)
+                (8.2 0:8 0.26:34)
+            ))
+        )
+        """)
+
     def test_anyvalue(self):
         o1 = Signal(12)
         o2 = Signal(12)
diff --git a/tests/test_lib_wiring.py b/tests/test_lib_wiring.py
index b1471c0..4602f27 100644
--- a/tests/test_lib_wiring.py
+++ b/tests/test_lib_wiring.py
@@ -453,10 +453,10 @@ class SignatureTestCase(unittest.TestCase):
             r"^'obj\.a' is expected to have the initial value 1, but it has the initial value 0$",
             sig=Signature({"a": In(1, init=1)}),
             obj=NS(a=Signal(1)))
-        self.assertNotCompliant(
-            r"^'obj\.a' is expected to not be reset-less$",
-            sig=Signature({"a": In(1)}),
-            obj=NS(a=Signal(1, reset_less=True)))
+        self.assertTrue(
+            Signature({"a": In(1)}).is_compliant(
+                NS(signature=Signature({"a": In(1)}),
+                   a=Signal(1, reset_less=True))))
         self.assertNotCompliant(
             r"^'obj\.a' does not have an attribute 'b'$",
             sig=Signature({"a": Out(Signature({"b": In(1)}))}),
diff --git a/tests/test_sim.py b/tests/test_sim.py
index 9be4299..bb5d495 100644
--- a/tests/test_sim.py
+++ b/tests/test_sim.py
@@ -314,8 +314,8 @@ class SimulatorUnitTestCase(FHDLTestCase):
     def test_array_oob(self):
         array = Array([1, 4, 10])
         stmt = lambda y, a: y.eq(array[a])
-        self.assertStatement(stmt, [C(3)], C(10))
-        self.assertStatement(stmt, [C(4)], C(10))
+        self.assertStatement(stmt, [C(3)], C(0))
+        self.assertStatement(stmt, [C(4)], C(0))
 
     def test_array_lhs(self):
         l = Signal(3, init=1)
@@ -333,8 +333,8 @@ class SimulatorUnitTestCase(FHDLTestCase):
         n = Signal(3)
         array = Array([l, m, n])
         stmt = lambda y, a, b: [array[a].eq(b), y.eq(Cat(*array))]
-        self.assertStatement(stmt, [C(3), C(0b001)], C(0b001000000))
-        self.assertStatement(stmt, [C(4), C(0b010)], C(0b010000000))
+        self.assertStatement(stmt, [C(3), C(0b001)], C(0))
+        self.assertStatement(stmt, [C(4), C(0b010)], C(0))
 
     def test_array_index(self):
         array = Array(Array(x * y for y in range(10)) for x in range(10))
@@ -513,6 +513,8 @@ class SimulatorIntegrationTestCase(FHDLTestCase):
         with self.m.Switch(self.s):
             with self.m.Case(0):
                 self.m.d.sync += self.o.eq(self.a + self.b)
+            with self.m.Case():
+                self.m.d.sync += self.o.eq(self.a * self.b)
             with self.m.Case(1):
                 self.m.d.sync += self.o.eq(self.a - self.b)
             with self.m.Default():


----

problem_statement: Question: reset-less signals in interfaces
This is more of a question (or perhaps a feature request) than an issue.

I was reading the [documentation of the wiring module](https://amaranth-lang.org/docs/amaranth/latest/stdlib/wiring.html#) in the context of https://github.com/amaranth-lang/rfcs/pull/61. I've seen that there isn't a straightforward way to specify if a signal is reset-less or not in a `Component` that uses a `Signature`. Also, the documentation about `is_compliant()` mentions the following:

> for port members, is a [value-like](https://amaranth-lang.org/docs/amaranth/latest/guide.html#lang-valuelike) object casting to a Signal or a Const whose width and signedness is the same as that of the member, and (in case of a Signal) **which is not reset-less** and whose initial value is that of the member;

Usually, when implementing an AXI-Stream producer, I will make TDATA reset-less. This makes sense because the consumer isn't allowed to look at the value of TDATA after the producer has asserted TVALID some time after reset is deasserted (and when asserting TVALID, a good value has been put into TDATA). Doing this helps reduce the load on the reset net, which can be important in large designs.

For example, consider the following AXI-S producer (the example can be pasted in the playground). Here `self.tdata` is declared as reset-less on purpose, reducing the load on the reset net and not affecting the functionality. I wonder if/how this example can be re-written with a `Component` that uses a `Signature` for the AXI-S port. A workaround is to have an "internal" `tdata = Signal(64, reset_less=True)` created in `elaborate()` and then doing `m.d.comb += self.tdata.eq(tdata)`, but being able to do this directly would save some boilerplate code.

```python
from amaranth import *
from amaranth.sim import Simulator, Tick
from amaranth.back import rtlil, verilog
import amaranth_playground
import random

class AXISProducer(Elaboratable):
    def __init__(self):
        self.tdata = Signal(64, reset_less=True)
        self.tvalid = Signal()
        self.tready = Signal()

    def elaborate(self, platform):
        m = Module()
        counter_a = Signal(64)
        counter_b = Signal(64)
        with m.If(~self.tvalid):
            m.d.sync += [
                counter_a.eq(counter_a + 3),
                counter_b.eq(counter_b + 5),
                self.tdata.eq(counter_a ^ counter_b),
                self.tvalid.eq(1),
            ]
        with m.If(self.tvalid & self.tready):
            m.d.sync += self.tvalid.eq(0)
        return m

m = Module()
m.submodules.producer = producer = AXISProducer()

def testbench():
    for _ in range(100):
        yield Tick("sync")
        yield producer.tready.eq(random.randrange(2))

sim = Simulator(m)
sim.add_clock(1e-6)
sim.add_process(testbench)
with amaranth_playground.show_waveforms(sim):
    sim.run()

amaranth_playground.show_verilog(verilog.convert(m, ports=[producer.tdata, producer.tvalid, producer.tready]))
# amaranth_playground.show_rtlil(rtlil.convert(m, ports=[producer.tdata, producer.tvalid, producer.tready]))
```

date: 2024-04-03
patch: diff --git a/amaranth/lib/wiring.py b/amaranth/lib/wiring.py
index 2fcad85..03f8de1 100644
--- a/amaranth/lib/wiring.py
+++ b/amaranth/lib/wiring.py
@@ -893,10 +893,6 @@ class Signature(metaclass=SignatureMeta):
                                            f"the initial value {member.init!r}, but it has "
                                            f"the initial value {attr_value_cast.init!r}")
                         return False
-                    if attr_value_cast.reset_less:
-                        if reasons is not None:
-                            reasons.append(f"{_format_path(path)} is expected to not be reset-less")
-                        return False
                 return True
             if member.is_signature:
                 return member.signature.is_compliant(attr_value, reasons=reasons, path=path)


----

problem_statement: Unclear error message on `m.domains.cd_tx = ClockDomain()`
One could reasonably write the following code:

```py
m.domains.cd_tx = ClockDomain()
```

This fails with:

```
NameError: Clock domain name 'rx' must match name in `m.domains.cd_rx += ...` syntax
```

It fails because the `cd_` prefix is stripped by `ClockDomain()` but not `m.domains.x = `. The correct syntax is is:

```py
m.domains.cd_tx = ClockDomain()
```

This is a rather confusing diagnostic and should be improved.
date: 2024-04-10
patch: diff --git a/amaranth/hdl/_dsl.py b/amaranth/hdl/_dsl.py
index d92255f..5b24275 100644
--- a/amaranth/hdl/_dsl.py
+++ b/amaranth/hdl/_dsl.py
@@ -13,11 +13,91 @@ from ._ast import _StatementList, _LateBoundStatement, _normalize_patterns
+from ._ast import AnyValue
 from ._ir import *
 from ._cd import *
 from ._xfrm import *
+from ._mem import MemoryData
 
 
 __all__ = ["SyntaxError", "SyntaxWarning", "Module"]
 
 
+class _Visitor:
+    def __init__(self):
+        self.driven_signals = SignalSet()
+
+    def visit_stmt(self, stmt):
+        if isinstance(stmt, _StatementList):
+            for s in stmt:
+                self.visit_stmt(s)
+        elif isinstance(stmt, Assign):
+            self.visit_lhs(stmt.lhs)
+            self.visit_rhs(stmt.rhs)
+        elif isinstance(stmt, Print):
+            for chunk in stmt.message._chunks:
+                if not isinstance(chunk, str):
+                    obj, format_spec = chunk
+                    self.visit_rhs(obj)
+        elif isinstance(stmt, Property):
+            self.visit_rhs(stmt.test)
+            if stmt.message is not None:
+                for chunk in stmt.message._chunks:
+                    if not isinstance(chunk, str):
+                        obj, format_spec = chunk
+                        self.visit_rhs(obj)
+        elif isinstance(stmt, Switch):
+            self.visit_rhs(stmt.test)
+            for _patterns, stmts, _src_loc in stmt.cases:
+                self.visit_stmt(stmts)
+        elif isinstance(stmt, _LateBoundStatement):
+            pass
+        else:
+            assert False # :nocov:
+
+    def visit_lhs(self, value):
+        if isinstance(value, Operator) and value.operator in ("u", "s"):
+            self.visit_lhs(value.operands[0])
+        elif isinstance(value, (Signal, ClockSignal, ResetSignal)):
+            self.driven_signals.add(value)
+        elif isinstance(value, Slice):
+            self.visit_lhs(value.value)
+        elif isinstance(value, Part):
+            self.visit_lhs(value.value)
+            self.visit_rhs(value.offset)
+        elif isinstance(value, Concat):
+            for part in value.parts:
+                self.visit_lhs(part)
+        elif isinstance(value, SwitchValue):
+            self.visit_rhs(value.test)
+            for _patterns, elem in value.cases:
+                self.visit_lhs(elem)
+        elif isinstance(value, MemoryData._Row):
+            raise ValueError(f"Value {value!r} can only be used in simulator processes")
+        else:
+            raise ValueError(f"Value {value!r} cannot be assigned to")
+
+    def visit_rhs(self, value):
+        if isinstance(value, (Const, Signal, ClockSignal, ResetSignal, Initial, AnyValue)):
+            pass
+        elif isinstance(value, Operator):
+            for op in value.operands:
+                self.visit_rhs(op)
+        elif isinstance(value, Slice):
+            self.visit_rhs(value.value)
+        elif isinstance(value, Part):
+            self.visit_rhs(value.value)
+            self.visit_rhs(value.offset)
+        elif isinstance(value, Concat):
+            for part in value.parts:
+                self.visit_rhs(part)
+        elif isinstance(value, SwitchValue):
+            self.visit_rhs(value.test)
+            for _patterns, elem in value.cases:
+                self.visit_rhs(elem)
+        elif isinstance(value, MemoryData._Row):
+            raise ValueError(f"Value {value!r} can only be used in simulator processes")
+        else:
+            assert False # :nocov:
+
+
 class _ModuleBuilderProxy:
     def __init__(self, builder, depth):
         object.__setattr__(self, "_builder", builder)
@@ -112,9 +191,11 @@ class _ModuleBuilderDomainSet:
             raise TypeError("Only clock domains may be added to `m.domains`, not {!r}"
                             .format(domain))
         if domain.name != name:
-            raise NameError("Clock domain name {!r} must match name in `m.domains.{} += ...` "
-                            "syntax"
-                            .format(domain.name, name))
+            if name == "cd_" + domain.name:
+                raise NameError(f"Domain name should not be prefixed with 'cd_' in `m.domains`, "
+                                f"use `m.domains.{domain.name} = ...` instead")
+            raise NameError(f"Clock domain name {domain.name!r} must match name in "
+                            f"`m.domains.{name} = ...` syntax")
         self._builder._add_domain(domain)
 
 
@@ -545,15 +626,16 @@ class Module(_ModuleBuilderRoot, Elaboratable):
 
             stmt._MustUse__used = True
 
-            if isinstance(stmt, Assign):
-                for signal in stmt._lhs_signals():
-                    if signal not in self._driving:
-                        self._driving[signal] = domain
-                    elif self._driving[signal] != domain:
-                        cd_curr = self._driving[signal]
-                        raise SyntaxError(
-                            f"Driver-driver conflict: trying to drive {signal!r} from d.{domain}, but it is "
-                            f"already driven from d.{cd_curr}")
+            visitor = _Visitor()
+            visitor.visit_stmt(stmt)
+            for signal in visitor.driven_signals:
+                if signal not in self._driving:
+                    self._driving[signal] = domain
+                elif self._driving[signal] != domain:
+                    cd_curr = self._driving[signal]
+                    raise SyntaxError(
+                        f"Driver-driver conflict: trying to drive {signal!r} from d.{domain}, but it is "
+                        f"already driven from d.{cd_curr}")
 
             self._statements.setdefault(domain, []).append(stmt)
 
@@ -595,11 +677,7 @@ class Module(_ModuleBuilderRoot, Elaboratable):
         for domain, statements in self._statements.items():
             statements = resolve_statements(statements)
             fragment.add_statements(domain, statements)
-            for signal in statements._lhs_signals():
-                fragment.add_driver(signal, domain)
         fragment.add_statements("comb", self._top_comb_statements)
-        for signal in self._top_comb_statements._lhs_signals():
-            fragment.add_driver(signal, "comb")
         fragment.add_domains(self._domains.values())
         fragment.generated.update(self._generated)
         return fragment