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

problem_statement: Signals with private names
In some cases it is desirable to create a signal with a name that doesn't show up in the waveform viewer, such as when a signal is created to hold the results of intermediate computations, or to change shape, and it would just add noise. I propose adding a notion of a "private signal name".

I suggest changing naming rules so that `Signal(name="")` creates a signal with a private name. Such a signal is translated to a private wire in RTLIL (with a `$\d+` name), doesn't get displayed in the waveform viewer, and so on.

---

The FSM ongoing signals (not the state signal) should be changed to have private names in the 0.5 milestone.
date: 2024-03-25
patch: diff --git a/amaranth/hdl/_ast.py b/amaranth/hdl/_ast.py
index 1844880..551fcc3 100644
--- a/amaranth/hdl/_ast.py
+++ b/amaranth/hdl/_ast.py
@@ -1884,7 +1884,8 @@ class Signal(Value, DUID, metaclass=_SignalMeta):
         If not specified, ``shape`` defaults to 1-bit and non-signed.
     name : str
         Name hint for this signal. If ``None`` (default) the name is inferred from the variable
-        name this ``Signal`` is assigned to.
+        name this ``Signal`` is assigned to. If the empty string, then this ``Signal`` is treated
+        as private and is generally hidden from view.
     init : int or integral Enum
         Reset (synchronous) or default (combinatorial) value.
         When this ``Signal`` is assigned to in synchronous context and the corresponding clock
@@ -1920,7 +1921,10 @@ class Signal(Value, DUID, metaclass=_SignalMeta):
 
         if name is not None and not isinstance(name, str):
             raise TypeError(f"Name must be a string, not {name!r}")
-        self.name = name or tracer.get_var_name(depth=2 + src_loc_at, default="$signal")
+        if name is None:
+            self.name = tracer.get_var_name(depth=2 + src_loc_at, default="$signal")
+        else:
+            self.name = name
 
         orig_shape = shape
         if shape is None:
@@ -2102,7 +2106,10 @@ class Signal(Value, DUID, metaclass=_SignalMeta):
         return SignalSet((self,))
 
     def __repr__(self):
-        return f"(sig {self.name})"
+        if self.name != "":
+            return f"(sig {self.name})"
+        else:
+            return "(sig)"
 
 
 @final
diff --git a/amaranth/hdl/_dsl.py b/amaranth/hdl/_dsl.py
index a74fd7c..2322a83 100644
--- a/amaranth/hdl/_dsl.py
+++ b/amaranth/hdl/_dsl.py
@@ -167,7 +167,7 @@ class FSM:
         if name not in self.encoding:
             self.encoding[name] = len(self.encoding)
             fsm_name = self._data["name"]
-            self._data["ongoing"][name] = Signal(name=f"{fsm_name}_ongoing_{name}")
+            self._data["ongoing"][name] = Signal(name="")
         return self._data["ongoing"][name]
 
 
@@ -462,7 +462,7 @@ class Module(_ModuleBuilderRoot, Elaboratable):
         if name not in fsm_data["encoding"]:
             fsm_name = fsm_data["name"]
             fsm_data["encoding"][name] = len(fsm_data["encoding"])
-            fsm_data["ongoing"][name] = Signal(name=f"{fsm_name}_ongoing_{name}")
+            fsm_data["ongoing"][name] = Signal(name="")
         try:
             _outer_case, self._statements = self._statements, {}
             self._ctrl_context = None
@@ -486,7 +486,7 @@ class Module(_ModuleBuilderRoot, Elaboratable):
                     if name not in ctrl_data["encoding"]:
                         fsm_name = ctrl_data["name"]
                         ctrl_data["encoding"][name] = len(ctrl_data["encoding"])
-                        ctrl_data["ongoing"][name] = Signal(name=f"{fsm_name}_ongoing_{name}")
+                        ctrl_data["ongoing"][name] = Signal(name="")
                     self._add_statement(
                         assigns=[FSMNextStatement(ctrl_data, name)],
                         domain=ctrl_data["domain"],
diff --git a/amaranth/hdl/_ir.py b/amaranth/hdl/_ir.py
index e02bae2..f3e9fc9 100644
--- a/amaranth/hdl/_ir.py
+++ b/amaranth/hdl/_ir.py
@@ -552,6 +552,8 @@ class Design:
         assigned_names = {name for name, conn, dir in self.ports if name is not None}
         for name, conn, dir in self.ports:
             if name is None:
+                if conn.name == "": # Nothing to name this port!
+                    raise TypeError("Signals with private names cannot be used in unnamed top-level ports")
                 name = _add_name(assigned_names, conn.name)
                 assigned_names.add(name)
             new_ports.append((name, conn, dir))
@@ -590,7 +592,7 @@ class Design:
                     frag_info.io_port_names[conn] = name
 
         for signal in frag_info.used_signals:
-            if signal not in frag_info.signal_names:
+            if signal not in frag_info.signal_names and signal.name != "": # Private name shouldn't be added.
                 frag_info.signal_names[signal] = _add_name(frag_info.assigned_names, signal.name)
         for port in frag_info.used_io_ports:
             if port not in frag_info.io_port_names:
diff --git a/amaranth/sim/core.py b/amaranth/sim/core.py
index 67c4f10..b54792e 100644
--- a/amaranth/sim/core.py
+++ b/amaranth/sim/core.py
@@ -4,6 +4,7 @@ import warnings
 from .._utils import deprecated
 from ..hdl._cd import *
 from ..hdl._ir import *
+from ..hdl._ast import Value, ValueLike
 from ._base import BaseEngine
 
 
@@ -238,5 +239,15 @@ class Simulator:
                     file.close()
             raise ValueError("Cannot start writing waveforms after advancing simulation time")
 
+        for trace in traces:
+            if isinstance(trace, ValueLike):
+                trace_cast = Value.cast(trace)
+                for trace_signal in trace_cast._rhs_signals():
+                    if trace_signal.name == "":
+                        if trace_signal is trace:
+                            raise TypeError("Cannot trace signal with private name")
+                        else:
+                            raise TypeError(f"Cannot trace signal with private name (within {trace!r})")
+
         return self._engine.write_vcd(vcd_file=vcd_file, gtkw_file=gtkw_file,
                                       traces=traces, fs_per_delta=fs_per_delta)

test_patch: diff --git a/tests/test_back_rtlil.py b/tests/test_back_rtlil.py
new file mode 100644
index 0000000..480a3f2
--- /dev/null
+++ b/tests/test_back_rtlil.py
@@ -0,0 +1,1910 @@
+import operator
+import re
+
+from amaranth.back import rtlil
+from amaranth.hdl import *
+from amaranth.hdl._ast import *
+from amaranth.lib import memory, wiring
+
+from .utils import *
+
+class RTLILTestCase(FHDLTestCase):
+    maxDiff = 10000
+
+    def assertRTLIL(self, fragment, ports, rtlil_gold):
+        rtlil_test = rtlil.convert(fragment, ports=ports, emit_src=False)
+        def normalize(s):
+            s = s.strip()
+            s = re.sub(r" +", " ", s)
+            s = re.sub(r"\n ", "\n", s)
+            s = re.sub(r"\n+", "\n", s)
+            return s + "\n"
+        self.assertEqual(normalize(rtlil_test), normalize(rtlil_gold))
+
+class TreeTestCase(RTLILTestCase):
+    def test_tree(self):
+        a = Signal()
+        b = Signal()
+        c = Signal()
+        m = Module()
+        m.submodules.m1 = m1 = Module()
+        m.submodules.m2 = m2 = Module()
+        m.submodules.m3 = m3 = Module()
+        m3.submodules.m4 = m4 = Module()
+        m1.d.comb += a.eq(~b)
+        m2.d.comb += b.eq(~c)
+        self.assertRTLIL(m, [a, c], R"""
+        attribute \generator "Amaranth"
+        attribute \top 1
+        module \top
+            wire width 1 \b
+            wire width 1 input 0 \c
+            wire width 1 output 1 \a
+            cell \top.m1 \m1
+                connect \a \a [0]
+                connect \b \b [0]
+            end
+            cell \top.m2 \m2
+                connect \c \c [0]
+                connect \b \b [0]
+            end
+        end
+        attribute \generator "Amaranth"
+        module \top.m1
+            wire width 1 output 0 \a
+            wire width 1 input 1 \b
+            cell $not $1
+                parameter \A_SIGNED 0
+                parameter \A_WIDTH 1
+                parameter \Y_WIDTH 1
+                connect \A \b [0]
+                connect \Y \a
+            end
+        end
+        attribute \generator "Amaranth"
+        module \top.m2
+            wire width 1 input 0 \c
+            wire width 1 output 1 \b
+            cell $not $1
+                parameter \A_SIGNED 0
+                parameter \A_WIDTH 1
+                parameter \Y_WIDTH 1
+                connect \A \c [0]
+                connect \Y \b
+            end
+        end
+        """)
+
+
+class RHSTestCase(RTLILTestCase):
+    def test_operator_unary(self):
+        i8u = Signal(8)
+        i8s = Signal(signed(8))
+        o1 = Signal(10)
+        o2 = Signal(10)
+        o3 = Signal(10)
+        o4 = Signal(10)
+        o5 = Signal(10)
+        o6 = Signal(10)
+        o7 = Signal(10)
+        o8 = Signal(10)
+        m = Module()
+        m.d.comb += [
+            o1.eq(-i8u),
+            o2.eq(-i8s),
+            o3.eq(~i8u),
+            o4.eq(~i8s),
+            o5.eq(i8u.bool()),
+            o6.eq(i8u.all()),
+            o7.eq(i8u.any()),
+            o8.eq(i8u.xor()),
+        ]
+        self.assertRTLIL(m, [i8u, i8s, o1, o2, o3, o4, o5, o6, o7, o8], R"""
+        attribute \generator "Amaranth"
+        attribute \top 1
+        module \top
+            wire width 8 input 0 \i8u
+            wire width 8 input 1 signed \i8s
+            wire width 10 output 2 \o1
+            wire width 10 output 3 \o2
+            wire width 10 output 4 \o3
+            wire width 10 output 5 \o4
+            wire width 10 output 6 \o5
+            wire width 10 output 7 \o6
+            wire width 10 output 8 \o7
+            wire width 10 output 9 \o8
+            wire width 9 $1
+            wire width 9 $2
+            wire width 8 $3
+            wire width 8 $4
+            wire width 1 $5
+            wire width 1 $6
+            wire width 1 $7
+            wire width 1 $8
+            cell $neg $9
+                parameter \A_SIGNED 0
+                parameter \A_WIDTH 8
+                parameter \Y_WIDTH 9
+                connect \A \i8u [7:0]
+                connect \Y $1
+            end
+            cell $neg $10
+                parameter \A_SIGNED 1
+                parameter \A_WIDTH 8
+                parameter \Y_WIDTH 9
+                connect \A \i8s [7:0]
+                connect \Y $2
+            end
+            cell $not $11
+                parameter \A_SIGNED 0
+                parameter \A_WIDTH 8
+                parameter \Y_WIDTH 8
+                connect \A \i8u [7:0]
+                connect \Y $3
+            end
+            cell $not $12
+                parameter \A_SIGNED 0
+                parameter \A_WIDTH 8
+                parameter \Y_WIDTH 8
+                connect \A \i8s [7:0]
+                connect \Y $4
+            end
+            cell $reduce_bool $13
+                parameter \A_SIGNED 0
+                parameter \A_WIDTH 8
+                parameter \Y_WIDTH 1
+                connect \A \i8u [7:0]
+                connect \Y $5
+            end
+            cell $reduce_and $14
+                parameter \A_SIGNED 0
+                parameter \A_WIDTH 8
+                parameter \Y_WIDTH 1
+                connect \A \i8u [7:0]
+                connect \Y $6
+            end
+            cell $reduce_or $15
+                parameter \A_SIGNED 0
+                parameter \A_WIDTH 8
+                parameter \Y_WIDTH 1
+                connect \A \i8u [7:0]
+                connect \Y $7
+            end
+            cell $reduce_xor $16
+                parameter \A_SIGNED 0
+                parameter \A_WIDTH 8
+                parameter \Y_WIDTH 1
+                connect \A \i8u [7:0]
+                connect \Y $8
+            end
+            connect \o1 { $1 [8] $1 [8:0] }
+            connect \o2 { $2 [8] $2 [8:0] }
+            connect \o3 { 2'00 $3 [7:0] }
+            connect \o4 { $4 [7] $4 [7] $4 [7:0] }
+            connect \o5 { 9'000000000 $5 [0] }
+            connect \o6 { 9'000000000 $6 [0] }
+            connect \o7 { 9'000000000 $7 [0] }
+            connect \o8 { 9'000000000 $8 [0] }
+        end
+        """)
+
+    def test_operator_addsub(self):
+        i8ua = Signal(8)
+        i8ub = Signal(8)
+        i8sa = Signal(signed(8))
+        i8sb = Signal(signed(8))
+        o1 = Signal(10)
+        o2 = Signal(10)
+        o3 = Signal(10)
+        o4 = Signal(10)
+        o5 = Signal(10)
+        o6 = Signal(10)
+        m = Module()
+        m.d.comb += [
+            o1.eq(i8ua + i8ub),
+            o2.eq(i8ua + i8sb),
+            o3.eq(i8sa + i8sb),
+            o4.eq(i8ua - i8ub),
+            o5.eq(i8ua - i8sb),
+            o6.eq(i8sa - i8sb),
+        ]
+        self.assertRTLIL(m, [i8ua, i8ub, i8sa, i8sb, o1, o2, o3, o4, o5, o6], R"""
+        attribute \generator "Amaranth"
+        attribute \top 1
+        module \top
+            wire width 8 input 0 \i8ua
+            wire width 8 input 1 \i8ub
+            wire width 8 input 2 signed \i8sa
+            wire width 8 input 3 signed \i8sb
+            wire width 10 output 4 \o1
+            wire width 10 output 5 \o2
+            wire width 10 output 6 \o3
+            wire width 10 output 7 \o4
+            wire width 10 output 8 \o5
+            wire width 10 output 9 \o6
+            wire width 9 $1
+            wire width 9 $2
+            wire width 9 $3
+            wire width 9 $4
+            cell $add $5
+                parameter \A_SIGNED 0
+                parameter \B_SIGNED 0
+                parameter \A_WIDTH 8
+                parameter \B_WIDTH 8
+                parameter \Y_WIDTH 9
+                connect \A \i8ua [7:0]
+                connect \B \i8ub [7:0]
+                connect \Y $1
+            end
+            cell $add $6
+                parameter \A_SIGNED 1
+                parameter \B_SIGNED 1
+                parameter \A_WIDTH 9
+                parameter \B_WIDTH 8
+                parameter \Y_WIDTH 10
+                connect \A { 1'0 \i8ua [7:0] }
+                connect \B \i8sb [7:0]
+                connect \Y \o2
+            end
+            cell $add $7
+                parameter \A_SIGNED 1
+                parameter \B_SIGNED 1
+                parameter \A_WIDTH 8
+                parameter \B_WIDTH 8
+                parameter \Y_WIDTH 9
+                connect \A \i8sa [7:0]
+                connect \B \i8sb [7:0]
+                connect \Y $2
+            end
+            cell $sub $8
+                parameter \A_SIGNED 0
+                parameter \B_SIGNED 0
+                parameter \A_WIDTH 8
+                parameter \B_WIDTH 8
+                parameter \Y_WIDTH 9
+                connect \A \i8ua [7:0]
+                connect \B \i8ub [7:0]
+                connect \Y $3
+            end
+            cell $sub $9
+                parameter \A_SIGNED 1
+                parameter \B_SIGNED 1
+                parameter \A_WIDTH 9
+                parameter \B_WIDTH 8
+                parameter \Y_WIDTH 10
+                connect \A { 1'0 \i8ua [7:0] }
+                connect \B \i8sb [7:0]
+                connect \Y \o5
+            end
+            cell $sub $10
+                parameter \A_SIGNED 1
+                parameter \B_SIGNED 1
+                parameter \A_WIDTH 8
+                parameter \B_WIDTH 8
+                parameter \Y_WIDTH 9
+                connect \A \i8sa [7:0]
+                connect \B \i8sb [7:0]
+                connect \Y $4
+            end
+            connect \o1 { 1'0 $1 [8:0] }
+            connect \o3 { $2 [8] $2 [8:0] }
+            connect \o4 { $3 [8] $3 [8:0] }
+            connect \o6 { $4 [8] $4 [8:0] }
+        end
+        """)
+
+    def test_operator_add_imm(self):
+        i8u = Signal(8)
+        i8s = Signal(signed(8))
+        o1 = Signal(10)
+        o2 = Signal(10)
+        o3 = Signal(10)
+        o4 = Signal(10)
+        m = Module()
+        m.d.comb += [
+            o1.eq(i8u + 3),
+            o2.eq(i8s + 3),
+            o3.eq(3 + i8u),
+            o4.eq(3 + i8s),
+        ]
+        self.assertRTLIL(m, [i8u, i8s, o1, o2, o3, o4], R"""
+        attribute \generator "Amaranth"
+        attribute \top 1
+        module \top
+            wire width 8 input 0 \i8u
+            wire width 8 input 1 signed \i8s
+            wire width 10 output 2 \o1
+            wire width 10 output 3 \o2
+            wire width 10 output 4 \o3
+            wire width 10 output 5 \o4
+            wire width 9 $1
+            wire width 9 $2
+            wire width 9 $3
+            wire width 9 $4
+            cell $add $5
+                parameter \A_SIGNED 0
+                parameter \B_SIGNED 0
+                parameter \A_WIDTH 8
+                parameter \B_WIDTH 2
+                parameter \Y_WIDTH 9
+                connect \A \i8u [7:0]
+                connect \B 2'11
+                connect \Y $1
+            end
+            cell $add $6
+                parameter \A_SIGNED 1
+                parameter \B_SIGNED 1
+                parameter \A_WIDTH 8
+                parameter \B_WIDTH 3
+                parameter \Y_WIDTH 9
+                connect \A \i8s [7:0]
+                connect \B 3'011
+                connect \Y $2
+            end
+            cell $add $7
+                parameter \A_SIGNED 0
+                parameter \B_SIGNED 0
+                parameter \A_WIDTH 2
+                parameter \B_WIDTH 8
+                parameter \Y_WIDTH 9
+                connect \A 2'11
+                connect \B \i8u [7:0]
+                connect \Y $3
+            end
+            cell $add $8
+                parameter \A_SIGNED 1
+                parameter \B_SIGNED 1
+                parameter \A_WIDTH 3
+                parameter \B_WIDTH 8
+                parameter \Y_WIDTH 9
+                connect \A 3'011
+                connect \B \i8s [7:0]
+                connect \Y $4
+            end
+            connect \o1 { 1'0 $1 [8:0] }
+            connect \o2 { $2 [8] $2 [8:0] }
+            connect \o3 { 1'0 $3 [8:0] }
+            connect \o4 { $4 [8] $4 [8:0] }
+        end
+        """)
+
+
+    def test_operator_mul(self):
+        i4ua = Signal(4)
+        i4ub = Signal(4)
+        i4sa = Signal(signed(4))
+        i4sb = Signal(signed(4))
+        o1 = Signal(9)
+        o2 = Signal(9)
+        o3 = Signal(9)
+        m = Module()
+        m.d.comb += [
+            o1.eq(i4ua * i4ub),
+            o2.eq(i4ua * i4sb),
+            o3.eq(i4sa * i4sb),
+        ]
+        self.assertRTLIL(m, [i4ua, i4ub, i4sa, i4sb, o1, o2, o3], R"""
+        attribute \generator "Amaranth"
+        attribute \top 1
+        module \top
+            wire width 4 input 0 \i4ua
+            wire width 4 input 1 \i4ub
+            wire width 4 input 2 signed \i4sa
+            wire width 4 input 3 signed \i4sb
+            wire width 9 output 4 \o1
+            wire width 9 output 5 \o2
+            wire width 9 output 6 \o3
+            wire width 8 $1
+            wire width 8 $2
+            wire width 8 $3
+            cell $mul $4
+                parameter \A_SIGNED 0
+                parameter \B_SIGNED 0
+                parameter \A_WIDTH 4
+                parameter \B_WIDTH 4
+                parameter \Y_WIDTH 8
+                connect \A \i4ua [3:0]
+                connect \B \i4ub [3:0]
+                connect \Y $1
+            end
+            cell $mul $5
+                parameter \A_SIGNED 1
+                parameter \B_SIGNED 1
+                parameter \A_WIDTH 5
+                parameter \B_WIDTH 4
+                parameter \Y_WIDTH 8
+                connect \A { 1'0 \i4ua [3:0] }
+                connect \B \i4sb [3:0]
+                connect \Y $2
+            end
+            cell $mul $6
+                parameter \A_SIGNED 1
+                parameter \B_SIGNED 1
+                parameter \A_WIDTH 4
+                parameter \B_WIDTH 4
+                parameter \Y_WIDTH 8
+                connect \A \i4sa [3:0]
+                connect \B \i4sb [3:0]
+                connect \Y $3
+            end
+            connect \o1 { 1'0 $1 [7:0] }
+            connect \o2 { $2 [7] $2 [7:0] }
+            connect \o3 { $3 [7] $3 [7:0] }
+        end
+        """)
+
+    def test_operator_divmod(self):
+        i4ua = Signal(4)
+        i4ub = Signal(4)
+        i4sa = Signal(signed(4))
+        i4sb = Signal(signed(4))
+        o1 = Signal(6)
+        o2 = Signal(6)
+        o3 = Signal(6)
+        o4 = Signal(6)
+        o5 = Signal(6)
+        o6 = Signal(6)
+        o7 = Signal(6)
+        o8 = Signal(6)
+        m = Module()
+        m.d.comb += [
+            o1.eq(i4ua // i4ub),
+            o2.eq(i4ua // i4sb),
+            o3.eq(i4sa // i4ub),
+            o4.eq(i4sa // i4sb),
+            o5.eq(i4ua % i4ub),
+            o6.eq(i4ua % i4sb),
+            o7.eq(i4sa % i4ub),
+            o8.eq(i4sa % i4sb),
+        ]
+        self.assertRTLIL(m, [i4ua, i4ub, i4sa, i4sb, o1, o2, o3, o4, o5, o6, o7, o8], R"""
+        attribute \generator "Amaranth"
+        attribute \top 1
+        module \top
+            wire width 4 input 0 \i4ua
+            wire width 4 input 1 \i4ub
+            wire width 4 input 2 signed \i4sa
+            wire width 4 input 3 signed \i4sb
+            wire width 6 output 4 \o1
+            wire width 6 output 5 \o2
+            wire width 6 output 6 \o3
+            wire width 6 output 7 \o4
+            wire width 6 output 8 \o5
+            wire width 6 output 9 \o6
+            wire width 6 output 10 \o7
+            wire width 6 output 11 \o8
+            wire width 4 $1
+            wire width 5 $2
+            wire width 5 $3
+            wire width 5 $4
+            wire width 4 $5
+            wire width 5 $6
+            wire width 5 $7
+            wire width 4 $8
+
+            wire width 4 $9
+            cell $divfloor $10
+                parameter \A_SIGNED 0
+                parameter \B_SIGNED 0
+                parameter \A_WIDTH 4
+                parameter \B_WIDTH 4
+                parameter \Y_WIDTH 4
+                connect \A \i4ua [3:0]
+                connect \B \i4ub [3:0]
+                connect \Y $9
+            end
+            wire width 1 $11
+            cell $reduce_bool $12
+                parameter \A_SIGNED 0
+                parameter \A_WIDTH 4
+                parameter \Y_WIDTH 1
+                connect \A \i4ub [3:0]
+                connect \Y $11
+            end
+            cell $mux $13
+                parameter \WIDTH 4
+                connect \S $11
+                connect \A 4'0000
+                connect \B $9
+                connect \Y $1
+            end
+
+            wire width 5 $14
+            cell $divfloor $15
+                parameter \A_SIGNED 1
+                parameter \B_SIGNED 1
+                parameter \A_WIDTH 5
+                parameter \B_WIDTH 4
+                parameter \Y_WIDTH 5
+                connect \A { 1'0 \i4ua [3:0] }
+                connect \B \i4sb [3:0]
+                connect \Y $14
+            end
+            wire width 1 $16
+            cell $reduce_bool $17
+                parameter \A_SIGNED 0
+                parameter \A_WIDTH 4
+                parameter \Y_WIDTH 1
+                connect \A \i4sb [3:0]
+                connect \Y $16
+            end
+            cell $mux $18
+                parameter \WIDTH 5
+                connect \S $16
+                connect \A 5'00000
+                connect \B $14
+                connect \Y $2
+            end
+
+            wire width 5 $19
+            cell $divfloor $20
+                parameter \A_SIGNED 1
+                parameter \B_SIGNED 1
+                parameter \A_WIDTH 4
+                parameter \B_WIDTH 5
+                parameter \Y_WIDTH 5
+                connect \A \i4sa [3:0]
+                connect \B { 1'0 \i4ub [3:0] }
+                connect \Y $19
+            end
+            wire width 1 $21
+            cell $reduce_bool $22
+                parameter \A_SIGNED 0
+                parameter \A_WIDTH 5
+                parameter \Y_WIDTH 1
+                connect \A { 1'0 \i4ub [3:0] }
+                connect \Y $21
+            end
+            cell $mux $23
+                parameter \WIDTH 5
+                connect \S $21
+                connect \A 5'00000
+                connect \B $19
+                connect \Y $3
+            end
+
+            wire width 5 $24
+            cell $divfloor $25
+                parameter \A_SIGNED 1
+                parameter \B_SIGNED 1
+                parameter \A_WIDTH 4
+                parameter \B_WIDTH 4
+                parameter \Y_WIDTH 5
+                connect \A \i4sa [3:0]
+                connect \B \i4sb [3:0]
+                connect \Y $24
+            end
+            wire width 1 $26
+            cell $reduce_bool $27
+                parameter \A_SIGNED 0
+                parameter \A_WIDTH 4
+                parameter \Y_WIDTH 1
+                connect \A \i4sb [3:0]
+                connect \Y $26
+            end
+            cell $mux $28
+                parameter \WIDTH 5
+                connect \S $26
+                connect \A 5'00000
+                connect \B $24
+                connect \Y $4
+            end
+
+            wire width 4 $29
+            cell $modfloor $30
+                parameter \A_SIGNED 0
+                parameter \B_SIGNED 0
+                parameter \A_WIDTH 4
+                parameter \B_WIDTH 4
+                parameter \Y_WIDTH 4
+                connect \A \i4ua [3:0]
+                connect \B \i4ub [3:0]
+                connect \Y $29
+            end
+            wire width 1 $31
+            cell $reduce_bool $32
+                parameter \A_SIGNED 0
+                parameter \A_WIDTH 4
+                parameter \Y_WIDTH 1
+                connect \A \i4ub [3:0]
+                connect \Y $31
+            end
+            cell $mux $33
+                parameter \WIDTH 4
+                connect \S $31
+                connect \A 4'0000
+                connect \B $29
+                connect \Y $5
+            end
+
+            wire width 5 $34
+            cell $modfloor $35
+                parameter \A_SIGNED 1
+                parameter \B_SIGNED 1
+                parameter \A_WIDTH 5
+                parameter \B_WIDTH 4
+                parameter \Y_WIDTH 5
+                connect \A { 1'0 \i4ua [3:0] }
+                connect \B \i4sb [3:0]
+                connect \Y $34
+            end
+            wire width 1 $36
+            cell $reduce_bool $37
+                parameter \A_SIGNED 0
+                parameter \A_WIDTH 4
+                parameter \Y_WIDTH 1
+                connect \A \i4sb [3:0]
+                connect \Y $36
+            end
+            cell $mux $38
+                parameter \WIDTH 5
+                connect \S $36
+                connect \A 5'00000
+                connect \B $34
+                connect \Y $6
+            end
+
+            wire width 5 $39
+            cell $modfloor $40
+                parameter \A_SIGNED 1
+                parameter \B_SIGNED 1
+                parameter \A_WIDTH 4
+                parameter \B_WIDTH 5
+                parameter \Y_WIDTH 5
+                connect \A \i4sa [3:0]
+                connect \B { 1'0 \i4ub [3:0] }
+                connect \Y $39
+            end
+            wire width 1 $41
+            cell $reduce_bool $42
+                parameter \A_SIGNED 0
+                parameter \A_WIDTH 5
+                parameter \Y_WIDTH 1
+                connect \A { 1'0 \i4ub [3:0] }
+                connect \Y $41
+            end
+            cell $mux $43
+                parameter \WIDTH 5
+                connect \S $41
+                connect \A 5'00000
+                connect \B $39
+                connect \Y $7
+            end
+
+            wire width 4 $44
+            cell $modfloor $45
+                parameter \A_SIGNED 1
+                parameter \B_SIGNED 1
+                parameter \A_WIDTH 4
+                parameter \B_WIDTH 4
+                parameter \Y_WIDTH 4
+                connect \A \i4sa [3:0]
+                connect \B \i4sb [3:0]
+                connect \Y $44
+            end
+            wire width 1 $46
+            cell $reduce_bool $47
+                parameter \A_SIGNED 0
+                parameter \A_WIDTH 4
+                parameter \Y_WIDTH 1
+                connect \A \i4sb [3:0]
+                connect \Y $46
+            end
+            cell $mux $48
+                parameter \WIDTH 4
+                connect \S $46
+                connect \A 4'0000
+                connect \B $44
+                connect \Y $8
+            end
+
+            connect \o1 { 2'00 $1 [3:0] }
+            connect \o2 { $2 [4] $2 [4:0] }
+            connect \o3 { $3 [3] $3 [3] $3 [3:0] }
+            connect \o4 { $4 [4] $4 [4:0] }
+            connect \o5 { 2'00 $5 [3:0] }
+            connect \o6 { $6 [3] $6 [3] $6 [3:0] }
+            connect \o7 { 2'00 $7 [3:0] }
+            connect \o8 { $8 [3] $8 [3] $8 [3:0] }
+        end
+        """)
+
+    def test_operator_shift(self):
+        i8ua = Signal(8)
+        i8sa = Signal(signed(8))
+        i3 = Signal(3)
+        o1 = Signal(16)
+        o2 = Signal(16)
+        o3 = Signal(16)
+        o4 = Signal(16)
+        m = Module()
+        m.d.comb += [
+            o1.eq(i8ua << i3),
+            o2.eq(i8sa << i3),
+            o3.eq(i8ua >> i3),
+            o4.eq(i8sa >> i3),
+        ]
+        self.assertRTLIL(m, [i8ua, i8sa, i3, o1, o2, o3, o4], R"""
+        attribute \generator "Amaranth"
+        attribute \top 1
+        module \top
+            wire width 8 input 0 \i8ua
+            wire width 8 input 1 signed \i8sa
+            wire width 3 input 2 \i3
+            wire width 16 output 3 \o1
+            wire width 16 output 4 \o2
+            wire width 16 output 5 \o3
+            wire width 16 output 6 \o4
+            wire width 15 $1
+            wire width 15 $2
+            wire width 8 $3
+            wire width 8 $4
+            cell $shl $5
+                parameter \A_SIGNED 0
+                parameter \B_SIGNED 0
+                parameter \A_WIDTH 8
+                parameter \B_WIDTH 3
+                parameter \Y_WIDTH 15
+                connect \A \i8ua [7:0]
+                connect \B \i3 [2:0]
+                connect \Y $1
+            end
+            cell $shl $6
+                parameter \A_SIGNED 1
+                parameter \B_SIGNED 0
+                parameter \A_WIDTH 8
+                parameter \B_WIDTH 3
+                parameter \Y_WIDTH 15
+                connect \A \i8sa [7:0]
+                connect \B \i3 [2:0]
+                connect \Y $2
+            end
+            cell $shr $7
+                parameter \A_SIGNED 0
+                parameter \B_SIGNED 0
+                parameter \A_WIDTH 8
+                parameter \B_WIDTH 3
+                parameter \Y_WIDTH 8
+                connect \A \i8ua [7:0]
+                connect \B \i3 [2:0]
+                connect \Y $3
+            end
+            cell $sshr $8
+                parameter \A_SIGNED 1
+                parameter \B_SIGNED 0
+                parameter \A_WIDTH 8
+                parameter \B_WIDTH 3
+                parameter \Y_WIDTH 8
+                connect \A \i8sa [7:0]
+                connect \B \i3 [2:0]
+                connect \Y $4
+            end
+            connect \o1 { 1'0 $1 [14:0] }
+            connect \o2 { $2 [14] $2 [14:0] }
+            connect \o3 { 8'00000000 $3 [7:0] }
+            connect \o4 { $4 [7] $4 [7] $4 [7] $4 [7] $4 [7] $4 [7] $4 [7] $4 [7] $4 [7:0] }
+        end
+        """)
+
+    def test_operator_bitwise(self):
+        for (name, op) in [
+            ("and", operator.__and__),
+            ("or", operator.__or__),
+            ("xor", operator.__xor__),
+        ]:
+            i8ua = Signal(8)
+            i8ub = Signal(8)
+            i8sa = Signal(signed(8))
+            i8sb = Signal(signed(8))
+            o1 = Signal(10)
+            o2 = Signal(10)
+            o3 = Signal(10)
+            m = Module()
+            m.d.comb += [
+                o1.eq(op(i8ua, i8ub)),
+                o2.eq(op(i8ua, i8sb)),
+                o3.eq(op(i8sa, i8sb)),
+            ]
+            self.assertRTLIL(m, [i8ua, i8ub, i8sa, i8sb, o1, o2, o3], R"""
+            attribute \generator "Amaranth"
+            attribute \top 1
+            module \top
+                wire width 8 input 0 \i8ua
+                wire width 8 input 1 \i8ub
+                wire width 8 input 2 signed \i8sa
+                wire width 8 input 3 signed \i8sb
+                wire width 10 output 4 \o1
+                wire width 10 output 5 \o2
+                wire width 10 output 6 \o3
+                wire width 8 $1
+                wire width 9 $2
+                wire width 8 $3
+                cell $bitop $4
+                    parameter \A_SIGNED 0
+                    parameter \B_SIGNED 0
+                    parameter \A_WIDTH 8
+                    parameter \B_WIDTH 8
+                    parameter \Y_WIDTH 8
+                    connect \A \i8ua [7:0]
+                    connect \B \i8ub [7:0]
+                    connect \Y $1
+                end
+                cell $bitop $5
+                    parameter \A_SIGNED 0
+                    parameter \B_SIGNED 0
+                    parameter \A_WIDTH 9
+                    parameter \B_WIDTH 9
+                    parameter \Y_WIDTH 9
+                    connect \A { 1'0 \i8ua [7:0] }
+                    connect \B { \i8sb [7] \i8sb [7:0] }
+                    connect \Y $2
+                end
+                cell $bitop $6
+                    parameter \A_SIGNED 0
+                    parameter \B_SIGNED 0
+                    parameter \A_WIDTH 8
+                    parameter \B_WIDTH 8
+                    parameter \Y_WIDTH 8
+                    connect \A \i8sa [7:0]
+                    connect \B \i8sb [7:0]
+                    connect \Y $3
+                end
+                connect \o1 { 2'00 $1 [7:0] }
+                connect \o2 { $2 [8] $2 [8:0] }
+                connect \o3 { $3 [7] $3 [7] $3 [7:0] }
+            end
+            """.replace("bitop", name))
+
+    def test_operator_eq(self):
+        for (name, op) in [
+            ("eq", operator.__eq__),
+            ("ne", operator.__ne__),
+        ]:
+            i8ua = Signal(8)
+            i8ub = Signal(8)
+            i8sa = Signal(signed(8))
+            i8sb = Signal(signed(8))
+            o1 = Signal(2)
+            o2 = Signal(2)
+            o3 = Signal(2)
+            m = Module()
+            m.d.comb += [
+                o1.eq(op(i8ua, i8ub)),
+                o2.eq(op(i8ua, i8sb)),
+                o3.eq(op(i8sa, i8sb)),
+            ]
+            self.assertRTLIL(m, [i8ua, i8ub, i8sa, i8sb, o1, o2, o3], R"""
+            attribute \generator "Amaranth"
+            attribute \top 1
+            module \top
+                wire width 8 input 0 \i8ua
+                wire width 8 input 1 \i8ub
+                wire width 8 input 2 signed \i8sa
+                wire width 8 input 3 signed \i8sb
+                wire width 2 output 4 \o1
+                wire width 2 output 5 \o2
+                wire width 2 output 6 \o3
+                wire width 1 $1
+                wire width 1 $2
+                wire width 1 $3
+                cell $eqop $4
+                    parameter \A_SIGNED 0
+                    parameter \B_SIGNED 0
+                    parameter \A_WIDTH 8
+                    parameter \B_WIDTH 8
+                    parameter \Y_WIDTH 1
+                    connect \A \i8ua [7:0]
+                    connect \B \i8ub [7:0]
+                    connect \Y $1
+                end
+                cell $eqop $5
+                    parameter \A_SIGNED 1
+                    parameter \B_SIGNED 1
+                    parameter \A_WIDTH 9
+                    parameter \B_WIDTH 8
+                    parameter \Y_WIDTH 1
+                    connect \A { 1'0 \i8ua [7:0] }
+                    connect \B \i8sb [7:0]
+                    connect \Y $2
+                end
+                cell $eqop $6
+                    parameter \A_SIGNED 0
+                    parameter \B_SIGNED 0
+                    parameter \A_WIDTH 8
+                    parameter \B_WIDTH 8
+                    parameter \Y_WIDTH 1
+                    connect \A \i8sa [7:0]
+                    connect \B \i8sb [7:0]
+                    connect \Y $3
+                end
+                connect \o1 { 1'0 $1 [0] }
+                connect \o2 { 1'0 $2 [0] }
+                connect \o3 { 1'0 $3 [0] }
+            end
+            """.replace("eqop", name))
+
+    def test_operator_cmp(self):
+        for (name, op) in [
+            ("lt", operator.__lt__),
+            ("le", operator.__le__),
+            ("gt", operator.__gt__),
+            ("ge", operator.__ge__),
+        ]:
+            i8ua = Signal(8)
+            i8ub = Signal(8)
+            i8sa = Signal(signed(8))
+            i8sb = Signal(signed(8))
+            o1 = Signal(2)
+            o2 = Signal(2)
+            o3 = Signal(2)
+            m = Module()
+            m.d.comb += [
+                o1.eq(op(i8ua, i8ub)),
+                o2.eq(op(i8ua, i8sb)),
+                o3.eq(op(i8sa, i8sb)),
+            ]
+            self.assertRTLIL(m, [i8ua, i8ub, i8sa, i8sb, o1, o2, o3], R"""
+            attribute \generator "Amaranth"
+            attribute \top 1
+            module \top
+                wire width 8 input 0 \i8ua
+                wire width 8 input 1 \i8ub
+                wire width 8 input 2 signed \i8sa
+                wire width 8 input 3 signed \i8sb
+                wire width 2 output 4 \o1
+                wire width 2 output 5 \o2
+                wire width 2 output 6 \o3
+                wire width 1 $1
+                wire width 1 $2
+                wire width 1 $3
+                cell $cmpop $4
+                    parameter \A_SIGNED 0
+                    parameter \B_SIGNED 0
+                    parameter \A_WIDTH 8
+                    parameter \B_WIDTH 8
+                    parameter \Y_WIDTH 1
+                    connect \A \i8ua [7:0]
+                    connect \B \i8ub [7:0]
+                    connect \Y $1
+                end
+                cell $cmpop $5
+                    parameter \A_SIGNED 1
+                    parameter \B_SIGNED 1
+                    parameter \A_WIDTH 9
+                    parameter \B_WIDTH 8
+                    parameter \Y_WIDTH 1
+                    connect \A { 1'0 \i8ua [7:0] }
+                    connect \B \i8sb [7:0]
+                    connect \Y $2
+                end
+                cell $cmpop $6
+                    parameter \A_SIGNED 1
+                    parameter \B_SIGNED 1
+                    parameter \A_WIDTH 8
+                    parameter \B_WIDTH 8
+                    parameter \Y_WIDTH 1
+                    connect \A \i8sa [7:0]
+                    connect \B \i8sb [7:0]
+                    connect \Y $3
+                end
+                connect \o1 { 1'0 $1 [0] }
+                connect \o2 { 1'0 $2 [0] }
+                connect \o3 { 1'0 $3 [0] }
+            end
+            """.replace("cmpop", name))
+
+    def test_operator_mux(self):
+        i8ua = Signal(8)
+        i8ub = Signal(8)
+        i8sa = Signal(signed(8))
+        i8sb = Signal(signed(8))
+        i1 = Signal()
+        o1 = Signal(10)
+        o2 = Signal(10)
+        o3 = Signal(10)
+        m = Module()
+        m.d.comb += [
+            o1.eq(Mux(i1, i8ua, i8ub)),
+            o2.eq(Mux(i1, i8ua, i8sb)),
+            o3.eq(Mux(i1, i8sa, i8sb)),
+        ]
+        self.assertRTLIL(m, [i8ua, i8ub, i8sa, i8sb, i1, o1, o2, o3], R"""
+        attribute \generator "Amaranth"
+        attribute \top 1
+        module \top
+            wire width 8 input 0 \i8ua
+            wire width 8 input 1 \i8ub
+            wire width 8 input 2 signed \i8sa
+            wire width 8 input 3 signed \i8sb
+            wire width 1 input 4 \i1
+            wire width 10 output 5 \o1
+            wire width 10 output 6 \o2
+            wire width 10 output 7 \o3
+            wire width 8 $1
+            wire width 9 $2
+            wire width 8 $3
+            cell $mux $4
+                parameter \WIDTH 8
+                connect \S \i1 [0]
+                connect \A \i8ub [7:0]
+                connect \B \i8ua [7:0]
+                connect \Y $1
+            end
+            cell $mux $5
+                parameter \WIDTH 9
+                connect \S \i1 [0]
+                connect \A { \i8sb [7] \i8sb [7:0] }
+                connect \B { 1'0 \i8ua [7:0] }
+                connect \Y $2
+            end
+            cell $mux $6
+                parameter \WIDTH 8
+                connect \S \i1 [0]
+                connect \A \i8sb [7:0]
+                connect \B \i8sa [7:0]
+                connect \Y $3
+            end
+            connect \o1 { 2'00 $1 [7:0] }
+            connect \o2 { $2 [8] $2 [8:0] }
+            connect \o3 { $3 [7] $3 [7] $3 [7:0] }
+        end
+        """)
+
+    def test_part(self):
+        i8ua = Signal(8)
+        i8sa = Signal(signed(8))
+        i3 = Signal(3)
+        o1 = Signal(4)
+        o2 = Signal(4)
+        o3 = Signal(4)
+        o4 = Signal(4)
+        m = Module()
+        m.d.comb += [
+            o1.eq(i8ua.bit_select(i3, width=3)),
+            o2.eq(i8sa.bit_select(i3, width=3)),
+            o3.eq(i8ua.word_select(i3, width=3)),
+            o4.eq(i8sa.word_select(i3, width=3)),
+        ]
+        self.assertRTLIL(m, [i8ua, i8sa, i3, o1, o2, o3, o4], R"""
+        attribute \generator "Amaranth"
+        attribute \top 1
+        module \top
+            wire width 8 input 0 \i8ua
+            wire width 8 input 1 signed \i8sa
+            wire width 3 input 2 \i3
+            wire width 4 output 3 \o1
+            wire width 4 output 4 \o2
+            wire width 4 output 5 \o3
+            wire width 4 output 6 \o4
+            wire width 3 $1
+            wire width 3 $2
+            wire width 3 $3
+            wire width 3 $4
+            cell $shift $5
+                parameter \A_SIGNED 0
+                parameter \B_SIGNED 0
+                parameter \A_WIDTH 8
+                parameter \B_WIDTH 3
+                parameter \Y_WIDTH 3
+                connect \A \i8ua [7:0]
+                connect \B \i3 [2:0]
+                connect \Y $1
+            end
+            cell $shift $6
+                parameter \A_SIGNED 1
+                parameter \B_SIGNED 0
+                parameter \A_WIDTH 8
+                parameter \B_WIDTH 3
+                parameter \Y_WIDTH 3
+                connect \A \i8sa [7:0]
+                connect \B \i3 [2:0]
+                connect \Y $2
+            end
+            wire width 5 $7
+            cell $mul $8
+                parameter \A_SIGNED 0
+                parameter \B_SIGNED 0
+                parameter \A_WIDTH 3
+                parameter \B_WIDTH 2
+                parameter \Y_WIDTH 5
+                connect \A \i3 [2:0]
+                connect \B 2'11
+                connect \Y $7
+            end
+            cell $shift $9
+                parameter \A_SIGNED 0
+                parameter \B_SIGNED 0
+                parameter \A_WIDTH 8
+                parameter \B_WIDTH 5
+                parameter \Y_WIDTH 3
+                connect \A \i8ua [7:0]
+                connect \B $7
+                connect \Y $3
+            end
+            wire width 5 $10
+            cell $mul $11
+                parameter \A_SIGNED 0
+                parameter \B_SIGNED 0
+                parameter \A_WIDTH 3
+                parameter \B_WIDTH 2
+                parameter \Y_WIDTH 5
+                connect \A \i3 [2:0]
+                connect \B 2'11
+                connect \Y $10
+            end
+            cell $shift $12
+                parameter \A_SIGNED 1
+                parameter \B_SIGNED 0
+                parameter \A_WIDTH 8
+                parameter \B_WIDTH 5
+                parameter \Y_WIDTH 3
+                connect \A \i8sa [7:0]
+                connect \B $10
+                connect \Y $4
+            end
+            connect \o1 { 1'0 $1 [2:0] }
+            connect \o2 { 1'0 $2 [2:0] }
+            connect \o3 { 1'0 $3 [2:0] }
+            connect \o4 { 1'0 $4 [2:0] }
+        end
+        """)
+
+    def test_initial(self):
+        o = Signal()
+        m = Module()
+        m.d.comb += [
+            o.eq(Initial())
+        ]
+        self.assertRTLIL(m, [o], R"""
+        attribute \generator "Amaranth"
+        attribute \top 1
+        module \top
+            wire width 1 output 0 \o
+            cell $initstate $1
+                connect \Y \o
+            end
+        end
+        """)
+
+    def test_anyvalue(self):
+        o1 = Signal(8)
+        o2 = Signal(8)
+        m = Module()
+        m.d.comb += [
+            o1.eq(AnyConst(unsigned(4))),
+            o2.eq(AnySeq(signed(4))),
+        ]
+        self.assertRTLIL(m, [o1, o2], R"""
+        attribute \generator "Amaranth"
+        attribute \top 1
+        module \top
+            wire width 8 output 0 \o1
+            wire width 8 output 1 \o2
+            wire width 4 $1
+            wire width 4 $2
+            cell $anyconst $3
+                parameter \WIDTH 4
+                connect \Y $1
+            end
+            cell $anyseq $4
+                parameter \WIDTH 4
+                connect \Y $2
+            end
+            connect \o1 { 4'0000 $1 [3:0] }
+            connect \o2 { $2 [3] $2 [3] $2 [3] $2 [3] $2 [3:0] }
+        end
+        """)
+
+
+class FlopTestCase(RTLILTestCase):
+    def test_sync(self):
+        o = Signal(8, init=0x55)
+        i = Signal(8)
+        m = Module()
+        m.domains.sync = sync = ClockDomain()
+        m.d.sync += o.eq(i)
+        self.assertRTLIL(m, [i, ClockSignal(), ResetSignal(), o], R"""
+        attribute \generator "Amaranth"
+        attribute \top 1
+        module \top
+            wire width 8 input 0 \i
+            wire width 1 input 1 \clk
+            wire width 1 input 2 \rst
+            attribute \init 8'01010101
+            wire width 8 output 3 \o
+            wire width 8 $1
+            process $2
+                assign $1 [7:0] \o [7:0]
+                assign $1 [7:0] \i [7:0]
+                switch \rst [0]
+                    case 1'1
+                        assign $1 [7:0] 8'01010101
+                end
+            end
+            cell $dff $3
+                parameter \WIDTH 8
+                parameter \CLK_POLARITY 1
+                connect \D $1 [7:0]
+                connect \CLK \clk [0]
+                connect \Q \o
+            end
+        end
+        """)
+
+    def test_async(self):
+        o = Signal(8, init=0x55)
+        i = Signal(8)
+        m = Module()
+        m.domains.sync = sync = ClockDomain(clk_edge="neg", async_reset=True)
+        m.d.sync += o.eq(i)
+        self.assertRTLIL(m, [i, ClockSignal(), ResetSignal(), o], R"""
+        attribute \generator "Amaranth"
+        attribute \top 1
+        module \top
+            wire width 8 input 0 \i
+            wire width 1 input 1 \clk
+            wire width 1 input 2 \rst
+            attribute \init 8'01010101
+            wire width 8 output 3 \o
+            cell $adff $1
+                parameter \WIDTH 8
+                parameter \CLK_POLARITY 0
+                parameter \ARST_POLARITY 1
+                parameter \ARST_VALUE 8'01010101
+                connect \D \i [7:0]
+                connect \CLK \clk [0]
+                connect \Q \o
+                connect \ARST \rst [0]
+            end
+        end
+        """)
+
+
+class SwitchTestCase(RTLILTestCase):
+    def test_simple(self):
+        sel = Signal(4)
+        out = Signal(4, init=12)
+        m = Module()
+        with m.Switch(sel):
+            with m.Case(0):
+                m.d.comb += out.eq(1)
+            with m.Case(1, 2):
+                m.d.comb += out.eq(2)
+            with m.Case("11--"):
+                m.d.comb += out.eq(3)
+            with m.Case():
+                m.d.comb += out.eq(4)
+            with m.Default():
+                m.d.comb += out.eq(5)
+        self.assertRTLIL(m, [sel, out], R"""
+        attribute \generator "Amaranth"
+        attribute \top 1
+        module \top
+            wire width 4 input 0 \sel
+            wire width 4 output 1 \out
+            process $1
+                assign \out [3:0] 4'1100
+                switch \sel [3:0]
+                    case 4'0000
+                        assign \out [3:0] 4'0001
+                    case 4'0001, 4'0010
+                        assign \out [3:0] 4'0010
+                    case 4'11--
+                        assign \out [3:0] 4'0011
+                    case
+                        assign \out [3:0] 4'0101
+                end
+            end
+        end
+        """)
+
+    def test_aba(self):
+        a = Signal(2)
+        sel = Signal(4)
+        out = Signal(4, init=12)
+        m = Module()
+        with m.Switch(sel):
+            with m.Case(0):
+                m.d.comb += out.eq(1)
+            with m.Case(1, 2):
+                m.d.comb += out.eq(2)
+            with m.Case("11--"):
+                m.d.comb += out.eq(3)
+            with m.Case():
+                m.d.comb += out.eq(4)
+            with m.Default():
+                m.d.comb += out.eq(5)
+        m.d.comb += out[1:3].eq(a)
+        self.assertRTLIL(m, [sel, a, out], R"""
+        attribute \generator "Amaranth"
+        attribute \top 1
+        module \top
+            wire width 4 input 0 \sel
+            wire width 2 input 1 \a
+            wire width 4 output 2 \out
+            process $1
+                assign \out [3:0] 4'1100
+                switch \sel [3:0]
+                    case 4'0000
+                        assign \out [3:0] 4'0001
+                    case 4'0001, 4'0010
+                        assign \out [3:0] 4'0010
+                    case 4'11--
+                        assign \out [3:0] 4'0011
+                    case
+                        assign \out [3:0] 4'0101
+                end
+                switch {}
+                    case
+                        assign \out [2:1] \a [1:0]
+                end
+            end
+        end
+        """)
+
+    def test_nested(self):
+        sel1 = Signal(4)
+        sel2 = Signal(4)
+        sel3 = Signal(4)
+        out = Signal(4, init=12)
+        m = Module()
+        with m.Switch(sel1):
+            with m.Case(0):
+                m.d.comb += out.eq(1)
+            with m.Case(1):
+                with m.Switch(sel2):
+                    with m.Case(0):
+                        m.d.comb += out.eq(2)
+                    with m.Case(1):
+                        with m.Switch(sel3):
+                            with m.Case(0):
+                                m.d.comb += out.eq(3)
+                            with m.Default():
+                                m.d.comb += out.eq(4)
+            with m.Case(2):
+                m.d.comb += out.eq(5)
+            with m.Default():
+                m.d.comb += out.eq(6)
+        self.assertRTLIL(m, [sel1, sel2, sel3, out], R"""
+        attribute \generator "Amaranth"
+        attribute \top 1
+        module \top
+            wire width 4 input 0 \sel1
+            wire width 4 input 1 \sel2
+            wire width 4 input 2 \sel3
+            wire width 4 output 3 \out
+            process $1
+                assign \out [3:0] 4'1100
+                switch \sel1 [3:0]
+                    case 4'0000
+                        assign \out [3:0] 4'0001
+                    case 4'0001
+                         switch \sel2 [3:0]
+                            case 4'0000
+                                assign \out [3:0] 4'0010
+                            case 4'0001
+                                switch \sel3 [3:0]
+                                    case 4'0000
+                                        assign \out [3:0] 4'0011
+                                    case
+                                        assign \out [3:0] 4'0100
+                                end
+                        end
+                    case 4'0010
+                        assign \out [3:0] 4'0101
+                    case
+                        assign \out [3:0] 4'0110
+                end
+            end
+        end
+        """)
+
+    def test_trivial(self):
+        sel = Signal(4)
+        out = Signal(4)
+        m = Module()
+        with m.Switch(sel):
+            with m.Default():
+                m.d.comb += out.eq(1)
+        self.assertRTLIL(m, [sel, out], R"""
+        attribute \generator "Amaranth"
+        attribute \top 1
+        module \top
+            wire width 4 input 0 \sel
+            wire width 4 output 1 \out
+            process $1
+                assign \out [3:0] 4'0000
+                switch { }
+                    case
+                        assign \out [3:0] 4'0001
+                end
+            end
+        end
+        """)
+
+
+class IOBTestCase(RTLILTestCase):
+    def test_iob(self):
+        io_i = IOPort(1)
+        io_o = IOPort(1)
+        io_oe = IOPort(1)
+        io_io = IOPort(1)
+        i = Signal()
+        o = Signal()
+        oe = Signal()
+        m = Module()
+        m.submodules += IOBufferInstance(io_i, o=i)
+        m.submodules += IOBufferInstance(io_o, i=o)
+        m.submodules += IOBufferInstance(io_oe, i=oe)
+        m.submodules += IOBufferInstance(io_io, i=i, o=o, oe=oe)
+        self.assertRTLIL(m, [], R"""
+        attribute \generator "Amaranth"
+        attribute \top 1
+        module \top
+            wire width 1 \i
+            wire width 1 \o
+            wire width 1 \oe
+            wire width 1 output 0 \io_i
+            wire width 1 input 1 \io_o
+            wire width 1 input 2 \io_oe
+            wire width 1 inout 3 \io_io
+            cell $tribuf $1
+                parameter \WIDTH 1
+                connect \Y \io_io [0]
+                connect \A \o [0]
+                connect \EN \oe [0]
+            end
+            connect \io_i [0] \i [0]
+            connect \o \io_o [0]
+            connect \oe \io_oe [0]
+            connect \i \io_io [0]
+        end
+        """)
+
+
+class InstanceTestCase(RTLILTestCase):
+    def test_instance(self):
+        m = Module()
+        i = Signal(2)
+        o = Signal(3)
+        m.submodules.inst = Instance("t", i_i=i, o_o=o)
+        self.assertRTLIL(m, [i, o], R"""
+        attribute \generator "Amaranth"
+        attribute \top 1
+        module \top
+            wire width 2 input 0 \i
+            wire width 3 output 1 \o
+            cell \t \inst
+                connect \i \i [1:0]
+                connect \o \o
+            end
+        end
+        """)
+
+    def test_attr(self):
+        m = Module()
+        i = Signal(2)
+        o = Signal(3)
+        m.submodules.inst = Instance("t", i_i=i, o_o=o,
+                                     a_str="abc", a_int=123,
+                                     a_const=Const(0x55, 8),
+                                     a_sint=-3, a_sconst=Const(-2, 3))
+        self.assertRTLIL(m, [i, o], R"""
+        attribute \generator "Amaranth"
+        attribute \top 1
+        module \top
+            wire width 2 input 0 \i
+            wire width 3 output 1 \o
+            attribute \str "abc"
+            attribute \int 123
+            attribute \const 8'01010101
+            attribute \sint 32'11111111111111111111111111111101
+            attribute \sconst 3'110
+            cell \t \inst
+                connect \i \i [1:0]
+                connect \o \o
+            end
+        end
+        """)
+
+    def test_param(self):
+        m = Module()
+        i = Signal(2)
+        o = Signal(3)
+        m.submodules.inst = Instance("t", i_i=i, o_o=o,
+                                     p_str="abc", p_int=123, p_float=3.0,
+                                     p_const=Const(0x55, 8),
+                                     p_sint=-3, p_sconst=Const(2, signed(3)))
+        self.assertRTLIL(m, [i, o], R"""
+        attribute \generator "Amaranth"
+        attribute \top 1
+        module \top
+            wire width 2 input 0 \i
+            wire width 3 output 1 \o
+            cell \t \inst
+                parameter \str "abc"
+                parameter \int 123
+                parameter real \float "3.0"
+                parameter \const 8'01010101
+                parameter signed \sint 32'11111111111111111111111111111101
+                parameter signed \sconst 3'010
+                connect \i \i [1:0]
+                connect \o \o
+            end
+        end
+        """)
+
+    def test_ioport(self):
+        io_i = IOPort(2)
+        io_o = IOPort(3)
+        io_io = IOPort(4)
+        m = Module()
+        m.submodules.sm = sm = Module()
+        sm.submodules.inst = Instance("t", i_i=io_i, o_o=io_o, io_io=io_io)
+        self.assertRTLIL(m, [], R"""
+        attribute \generator "Amaranth"
+        attribute \top 1
+        module \top
+            wire width 2 input 0 \io_i
+            wire width 3 output 1 \io_o
+            wire width 4 inout 2 \io_io
+            cell \top.sm \sm
+                connect \io_i \io_i [1:0]
+                connect \io_o \io_o [2:0]
+                connect \io_io \io_io [3:0]
+            end
+        end
+        attribute \generator "Amaranth"
+        module \top.sm
+            wire width 2 input 0 \io_i
+            wire width 3 output 1 \io_o
+            wire width 4 inout 2 \io_io
+            cell \t \inst
+                connect \i \io_i [1:0]
+                connect \o \io_o [2:0]
+                connect \io \io_io [3:0]
+            end
+        end
+        """)
+
+    def test_concat(self):
+        io_a = IOPort(2)
+        io_b = IOPort(2)
+        m = Module()
+        m.submodules.inst = Instance("t", io_io=Cat(io_a, io_b))
+        self.assertRTLIL(m, [], R"""
+        attribute \generator "Amaranth"
+        attribute \top 1
+        module \top
+            wire width 2 inout 0 \io_a
+            wire width 2 inout 1 \io_b
+            cell \t \inst
+                connect \io { \io_b [1:0] \io_a [1:0] }
+            end
+        end
+        """)
+
+
+class MemoryTestCase(RTLILTestCase):
+    def test_async_read(self):
+        m = Module()
+        m.submodules.mem = mem = memory.Memory(shape=8, depth=4, init=[1, 2, 3, 4])
+        wp = mem.write_port()
+        rp = mem.read_port(domain="comb")
+        self.assertRTLIL(m, [
+            ("rd", rp.data, None),
+            ("ra", rp.addr, None),
+            ("wd", wp.data, None),
+            ("wa", wp.addr, None),
+            ("we", wp.en, None),
+        ], R"""
+        attribute \generator "Amaranth"
+        attribute \top 1
+        module \top
+            memory width 8 size 4 \mem
+            wire width 8 \rp__data
+            wire width 2 \rp__addr
+            wire width 8 \wp__data
+            wire width 2 \wp__addr
+            wire width 1 \wp__en
+            wire width 2 input 0 \ra
+            wire width 8 input 1 \wd
+            wire width 2 input 2 \wa
+            wire width 1 input 3 \we
+            wire width 1 input 4 \clk
+            wire width 1 input 5 \rst
+            wire width 8 output 6 \rd
+            cell $meminit_v2 $1
+                parameter \MEMID "\\mem"
+                parameter \ABITS 0
+                parameter \WIDTH 8
+                parameter \WORDS 4
+                parameter \PRIORITY 0
+                connect \ADDR { }
+                connect \DATA 32'00000100000000110000001000000001
+                connect \EN 8'11111111
+            end
+            cell $memwr_v2 $2
+                parameter \MEMID "\\mem"
+                parameter \ABITS 2
+                parameter \WIDTH 8
+                parameter \CLK_ENABLE 1
+                parameter \CLK_POLARITY 1
+                parameter \PORTID 0
+                parameter \PRIORITY_MASK 0
+                connect \ADDR \wa [1:0]
+                connect \DATA \wd [7:0]
+                connect \EN { \we [0] \we [0] \we [0] \we [0] \we [0] \we [0] \we [0] \we [0] }
+                connect \CLK \clk [0]
+            end
+            cell $memrd_v2 $3
+                parameter \MEMID "\\mem"
+                parameter \ABITS 2
+                parameter \WIDTH 8
+                parameter \TRANSPARENCY_MASK 1'0
+                parameter \COLLISION_X_MASK 1'0
+                parameter \ARST_VALUE 8'00000000
+                parameter \SRST_VALUE 8'00000000
+                parameter \INIT_VALUE 8'00000000
+                parameter \CE_OVER_SRST 0
+                parameter \CLK_ENABLE 0
+                parameter \CLK_POLARITY 1
+                connect \ADDR \ra [1:0]
+                connect \DATA \rp__data
+                connect \ARST 1'0
+                connect \SRST 1'0
+                connect \EN 1'1
+                connect \CLK 1'0
+            end
+            connect \rp__addr \ra [1:0]
+            connect \wp__data \wd [7:0]
+            connect \wp__addr \wa [1:0]
+            connect \wp__en \we [0]
+            connect \rd \rp__data [7:0]
+        end
+        """)
+
+    def test_sync_read(self):
+        m = Module()
+        m.submodules.mem = mem = memory.Memory(shape=8, depth=4, init=[1, 2, 3, 4], attrs={"ram_style": "block"})
+        wp = mem.write_port()
+        rp = mem.read_port(transparent_for=(wp,))
+        self.assertRTLIL(m, [
+            ("rd", rp.data, None),
+            ("ra", rp.addr, None),
+            ("re", rp.en, None),
+            ("wd", wp.data, None),
+            ("wa", wp.addr, None),
+            ("we", wp.en, None),
+        ], R"""
+        attribute \generator "Amaranth"
+        attribute \top 1
+        module \top
+            attribute \ram_style "block"
+            memory width 8 size 4 \mem
+            wire width 8 \rp__data
+            wire width 2 \rp__addr
+            wire width 1 \rp__en
+            wire width 8 \wp__data
+            wire width 2 \wp__addr
+            wire width 1 \wp__en
+            wire width 2 input 0 \ra
+            wire width 1 input 1 \re
+            wire width 8 input 2 \wd
+            wire width 2 input 3 \wa
+            wire width 1 input 4 \we
+            wire width 1 input 5 \clk
+            wire width 1 input 6 \rst
+            wire width 8 output 7 \rd
+            cell $meminit_v2 $1
+                parameter \MEMID "\\mem"
+                parameter \ABITS 0
+                parameter \WIDTH 8
+                parameter \WORDS 4
+                parameter \PRIORITY 0
+                connect \ADDR { }
+                connect \DATA 32'00000100000000110000001000000001
+                connect \EN 8'11111111
+            end
+            cell $memwr_v2 $2
+                parameter \MEMID "\\mem"
+                parameter \ABITS 2
+                parameter \WIDTH 8
+                parameter \CLK_ENABLE 1
+                parameter \CLK_POLARITY 1
+                parameter \PORTID 0
+                parameter \PRIORITY_MASK 0
+                connect \ADDR \wa [1:0]
+                connect \DATA \wd [7:0]
+                connect \EN { \we [0] \we [0] \we [0] \we [0] \we [0] \we [0] \we [0] \we [0] }
+                connect \CLK \clk [0]
+            end
+            cell $memrd_v2 $3
+                parameter \MEMID "\\mem"
+                parameter \ABITS 2
+                parameter \WIDTH 8
+                parameter \TRANSPARENCY_MASK 1'1
+                parameter \COLLISION_X_MASK 1'0
+                parameter \ARST_VALUE 8'00000000
+                parameter \SRST_VALUE 8'00000000
+                parameter \INIT_VALUE 8'00000000
+                parameter \CE_OVER_SRST 0
+                parameter \CLK_ENABLE 1
+                parameter \CLK_POLARITY 1
+                connect \ADDR \ra [1:0]
+                connect \DATA \rp__data
+                connect \ARST 1'0
+                connect \SRST 1'0
+                connect \EN \re [0]
+                connect \CLK \clk [0]
+            end
+            connect \rp__addr \ra [1:0]
+            connect \rp__en \re [0]
+            connect \wp__data \wd [7:0]
+            connect \wp__addr \wa [1:0]
+            connect \wp__en \we [0]
+            connect \rd \rp__data [7:0]
+        end
+        """)
+
+
+class PrintTestCase(RTLILTestCase):
+    def test_print_simple(self):
+        i8u = Signal(8)
+        i8s = Signal(signed(8))
+        m = Module()
+        m.d.comb += [
+            Print(i8u, i8s),
+        ]
+        self.assertRTLIL(m, [i8u, i8s], R"""
+        attribute \generator "Amaranth"
+        attribute \top 1
+        module \top
+            wire width 8 input 0 \i8u
+            wire width 8 input 1 signed \i8s
+            wire width 1 $1
+            process $2
+                assign $1 [0] 1'0
+                assign $1 [0] 1'1
+            end
+            cell $print $3
+                parameter \FORMAT "{8:> du} {8:> ds}\n"
+                parameter \ARGS_WIDTH 16
+                parameter signed \PRIORITY 32'11111111111111111111111111111110
+                parameter \TRG_ENABLE 0
+                parameter \TRG_WIDTH 0
+                parameter \TRG_POLARITY 0
+                connect \EN $1 [0]
+                connect \ARGS { \i8s [7:0] \i8u [7:0] }
+                connect \TRG { }
+            end
+        end
+        """)
+
+    def test_print_sync(self):
+        i8u = Signal(8)
+        i8s = Signal(signed(8))
+        m = Module()
+        m.domains.sync = ClockDomain()
+        m.d.sync += [
+            Print(i8u, i8s),
+        ]
+        self.assertRTLIL(m, [i8u, i8s, ClockSignal(), ResetSignal()], R"""
+        attribute \generator "Amaranth"
+        attribute \top 1
+        module \top
+            wire width 8 input 0 \i8u
+            wire width 8 input 1 signed \i8s
+            wire width 1 input 2 \clk
+            wire width 1 input 3 \rst
+            wire width 1 $1
+            process $2
+                assign $1 [0] 1'0
+                assign $1 [0] 1'1
+            end
+            cell $print $3
+                parameter \FORMAT "{8:> du} {8:> ds}\n"
+                parameter \ARGS_WIDTH 16
+                parameter signed \PRIORITY 32'11111111111111111111111111111110
+                parameter \TRG_ENABLE 1
+                parameter \TRG_WIDTH 1
+                parameter \TRG_POLARITY 1
+                connect \EN $1 [0]
+                connect \ARGS { \i8s [7:0] \i8u [7:0] }
+                connect \TRG \clk [0]
+            end
+        end
+        """)
+
+    def test_assert_simple(self):
+        test = Signal()
+        en = Signal()
+        m = Module()
+        with m.If(en):
+            m.d.comb += Assert(test)
+        self.assertRTLIL(m, [test, en], R"""
+        attribute \generator "Amaranth"
+        attribute \top 1
+        module \top
+            wire width 1 input 0 \test
+            wire width 1 input 1 \en
+            wire width 1 $1
+            process $2
+                assign $1 [0] 1'0
+                switch \en [0]
+                    case 1'1
+                        assign $1 [0] 1'1
+                end
+            end
+            cell $check $3
+                parameter \FORMAT ""
+                parameter \ARGS_WIDTH 0
+                parameter signed \PRIORITY 32'11111111111111111111111111111100
+                parameter \TRG_ENABLE 0
+                parameter \TRG_WIDTH 0
+                parameter \TRG_POLARITY 0
+                parameter \FLAVOR "assert"
+                connect \EN $1 [0]
+                connect \ARGS { }
+                connect \TRG { }
+                connect \A \test [0]
+            end
+        end
+        """)
+
+    def test_assume_msg(self):
+        msg = Signal(32)
+        test = Signal()
+        m = Module()
+        m.domains.sync = ClockDomain()
+        m.d.sync += [
+            Assume(test, Format("{:s}", msg)),
+        ]
+        self.assertRTLIL(m, [msg, test, ClockSignal(), ResetSignal()], R"""
+        attribute \generator "Amaranth"
+        attribute \top 1
+        module \top
+            wire width 32 input 0 \msg
+            wire width 1 input 1 \test
+            wire width 1 input 2 \clk
+            wire width 1 input 3 \rst
+            wire width 1 $1
+            process $2
+                assign $1 [0] 1'0
+                assign $1 [0] 1'1
+            end
+            cell $check $3
+                parameter \FORMAT "{32:< c}"
+                parameter \ARGS_WIDTH 32
+                parameter signed \PRIORITY 32'11111111111111111111111111111110
+                parameter \TRG_ENABLE 1
+                parameter \TRG_WIDTH 1
+                parameter \TRG_POLARITY 1
+                parameter \FLAVOR "assume"
+                connect \EN $1 [0]
+                connect \ARGS { \msg [7:0] \msg [15:8] \msg [23:16] \msg [31:24] }
+                connect \TRG \clk [0]
+                connect \A \test [0]
+            end
+        end
+        """)
+
+class ComponentTestCase(RTLILTestCase):
+    def test_component(self):
+        class MyComponent(wiring.Component):
+            i: wiring.In(unsigned(8))
+            o: wiring.Out(unsigned(8))
+
+            def elaborate(self, platform):
+                return Module()
+
+        self.assertRTLIL(MyComponent(), None, R"""
+        attribute \generator "Amaranth"
+        attribute \top 1
+        module \top
+        wire width 8 input 0 \i
+        wire width 8 output 1 \o
+        connect \o 8'00000000
+        end
+        """)
+
diff --git a/tests/test_hdl_ast.py b/tests/test_hdl_ast.py
index d233b23..e17268a 100644
--- a/tests/test_hdl_ast.py
+++ b/tests/test_hdl_ast.py
@@ -522,10 +522,16 @@ class ConstTestCase(FHDLTestCase):
             hash(Const(0))
 
     def test_shape_castable(self):
-        class MockConstValue:
+        class MockConstValue(ValueCastable):
             def __init__(self, value):
                 self.value = value
 
+            def shape(self):
+                return MockConstShape()
+            
+            def as_value(self):
+                return Const(self.value, 8)
+
         class MockConstShape(ShapeCastable):
             def as_shape(self):
                 return unsigned(8)
@@ -789,7 +795,7 @@ class OperatorTestCase(FHDLTestCase):
     def test_matches_enum(self):
         s = Signal(SignedEnum)
         self.assertRepr(s.matches(SignedEnum.FOO), """
-        (== (sig s) (const 2'sd-1))
+        (== (sig s) (const 1'sd-1))
         """)
 
     def test_matches_const_castable(self):
@@ -801,28 +807,28 @@ class OperatorTestCase(FHDLTestCase):
     def test_matches_width_wrong(self):
         s = Signal(4)
         with self.assertRaisesRegex(SyntaxError,
-                r"^Match pattern '--' must have the same width as match value \(which is 4\)$"):
+                r"^Pattern '--' must have the same width as match value \(which is 4\)$"):
             s.matches("--")
         with self.assertWarnsRegex(SyntaxWarning,
-                r"^Match pattern '22' \(5'10110\) is wider than match value \(which has "
-                r"width 4\); comparison will never be true$"):
+                r"^Pattern '22' \(5'10110\) is not representable in match value shape "
+                r"\(unsigned\(4\)\); comparison will never be true$"):
             s.matches(0b10110)
         with self.assertWarnsRegex(SyntaxWarning,
-                r"^Match pattern '\(cat \(const 1'd0\) \(const 4'd11\)\)' \(5'10110\) is wider "
-                r"than match value \(which has width 4\); comparison will never be true$"):
+                r"^Pattern '\(cat \(const 1'd0\) \(const 4'd11\)\)' \(5'10110\) is not "
+                r"representable in match value shape \(unsigned\(4\)\); comparison will never be true$"):
             s.matches(Cat(0, C(0b1011, 4)))
 
     def test_matches_bits_wrong(self):
         s = Signal(4)
         with self.assertRaisesRegex(SyntaxError,
-                r"^Match pattern 'abc' must consist of 0, 1, and - \(don't care\) bits, "
+                r"^Pattern 'abc' must consist of 0, 1, and - \(don't care\) bits, "
                 r"and may include whitespace$"):
             s.matches("abc")
 
     def test_matches_pattern_wrong(self):
         s = Signal(4)
         with self.assertRaisesRegex(SyntaxError,
-                r"^Match pattern must be a string or a constant-castable expression, not 1\.0$"):
+                r"^Pattern must be a string or a constant-castable expression, not 1\.0$"):
             s.matches(1.0)
 
     def test_hash(self):
@@ -897,18 +903,16 @@ class SliceTestCase(FHDLTestCase):
     def test_const(self):
         a = Const.cast(Const(0x1234, 16)[4:12])
         self.assertEqual(a.value, 0x23)
-        self.assertEqual(a.width, 8)
-        self.assertEqual(a.signed, False)
+        self.assertEqual(a.shape(), unsigned(8))
         a = Const.cast(Const(-4, signed(8))[1:6])
         self.assertEqual(a.value, 0x1e)
-        self.assertEqual(a.width, 5)
-        self.assertEqual(a.signed, False)
+        self.assertEqual(a.shape(), unsigned(5))
 
 
 class BitSelectTestCase(FHDLTestCase):
     def setUp(self):
         self.c = Const(0, 8)
-        self.s = Signal(range(self.c.width))
+        self.s = Signal(range(len(self.c)))
 
     def test_shape(self):
         s1 = self.c.bit_select(self.s, 2)
@@ -946,7 +950,7 @@ class BitSelectTestCase(FHDLTestCase):
 class WordSelectTestCase(FHDLTestCase):
     def setUp(self):
         self.c = Const(0, 8)
-        self.s = Signal(range(self.c.width))
+        self.s = Signal(range(len(self.c)))
 
     def test_shape(self):
         s1 = self.c.word_select(self.s, 2)
@@ -1043,12 +1047,10 @@ class CatTestCase(FHDLTestCase):
     def test_const(self):
         a = Const.cast(Cat(Const(1, 1), Const(0, 1), Const(3, 2), Const(2, 2)))
         self.assertEqual(a.value, 0x2d)
-        self.assertEqual(a.width, 6)
-        self.assertEqual(a.signed, False)
+        self.assertEqual(a.shape(), unsigned(6))
         a = Const.cast(Cat(Const(-4, 8), Const(-3, 8)))
         self.assertEqual(a.value, 0xfdfc)
-        self.assertEqual(a.width, 16)
-        self.assertEqual(a.signed, False)
+        self.assertEqual(a.shape(), unsigned(16))
 
 
 class ArrayTestCase(FHDLTestCase):
@@ -1142,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):
@@ -1180,6 +1183,8 @@ class SignalTestCase(FHDLTestCase):
         self.assertEqual(s1.name, "s1")
         s2 = Signal(name="sig")
         self.assertEqual(s2.name, "sig")
+        s3 = Signal(name="")
+        self.assertEqual(s3.name, "")
 
     def test_init(self):
         s1 = Signal(4, init=0b111, reset_less=True)
@@ -1294,6 +1299,8 @@ class SignalTestCase(FHDLTestCase):
     def test_repr(self):
         s1 = Signal()
         self.assertEqual(repr(s1), "(sig s1)")
+        s2 = Signal(name="")
+        self.assertEqual(repr(s2), "(sig)")
 
     def test_like(self):
         s1 = Signal.like(Signal(4))
@@ -1681,38 +1688,38 @@ class AssertTestCase(FHDLTestCase):
 
 class SwitchTestCase(FHDLTestCase):
     def test_default_case(self):
-        s = Switch(Const(0), {None: []})
-        self.assertEqual(s.cases, {(): []})
+        s = Switch(Const(0), [(None, [], None)])
+        self.assertEqual(s.cases, ((None, [], None),))
 
     def test_int_case(self):
-        s = Switch(Const(0, 8), {10: []})
-        self.assertEqual(s.cases, {("00001010",): []})
+        s = Switch(Const(0, 8), [(10, [], None)])
+        self.assertEqual(s.cases, ((("00001010",), [], None),))
 
     def test_int_neg_case(self):
-        s = Switch(Const(0, 8), {-10: []})
-        self.assertEqual(s.cases, {("11110110",): []})
+        s = Switch(Const(0, signed(8)), [(-10, [], None)])
+        self.assertEqual(s.cases, ((("11110110",), [], None),))
 
     def test_int_zero_width(self):
-        s = Switch(Const(0, 0), {0: []})
-        self.assertEqual(s.cases, {("",): []})
+        s = Switch(Const(0, 0), [(0, [], None)])
+        self.assertEqual(s.cases, ((("",), [], None),))
 
     def test_int_zero_width_enum(self):
         class ZeroEnum(Enum):
             A = 0
-        s = Switch(Const(0, 0), {ZeroEnum.A: []})
-        self.assertEqual(s.cases, {("",): []})
+        s = Switch(Const(0, 0), [(ZeroEnum.A, [], None)])
+        self.assertEqual(s.cases, ((("",), [], None),))
 
     def test_enum_case(self):
-        s = Switch(Const(0, UnsignedEnum), {UnsignedEnum.FOO: []})
-        self.assertEqual(s.cases, {("01",): []})
+        s = Switch(Const(0, UnsignedEnum), [(UnsignedEnum.FOO, [], None)])
+        self.assertEqual(s.cases, ((("01",), [], None),))
 
     def test_str_case(self):
-        s = Switch(Const(0, 8), {"0000 11\t01": []})
-        self.assertEqual(s.cases, {("00001101",): []})
+        s = Switch(Const(0, 8), [("0000 11\t01", [], None)])
+        self.assertEqual(s.cases, ((("00001101",), [], None),))
 
     def test_two_cases(self):
-        s = Switch(Const(0, 8), {("00001111", 123): []})
-        self.assertEqual(s.cases, {("00001111", "01111011"): []})
+        s = Switch(Const(0, 8), [(("00001111", 123), [], None)])
+        self.assertEqual(s.cases, ((("00001111", "01111011"), [], None),))
 
 
 class IOValueTestCase(FHDLTestCase):
diff --git a/tests/test_hdl_dsl.py b/tests/test_hdl_dsl.py
index c3e8c02..8048e54 100644
--- a/tests/test_hdl_dsl.py
+++ b/tests/test_hdl_dsl.py
@@ -603,8 +603,8 @@ class DSLTestCase(FHDLTestCase):
                 )
                 (case 1 )
             )
-            (eq (sig fsm_ongoing_FIRST) (== (sig fsm_state) (const 1'd0)))
-            (eq (sig fsm_ongoing_SECOND) (== (sig fsm_state) (const 1'd1)))
+            (eq (sig) (== (sig fsm_state) (const 1'd0)))
+            (eq (sig) (== (sig fsm_state) (const 1'd1)))
         )
         """)
         self.assertRepr(frag.statements["sync"], """
@@ -627,8 +627,7 @@ class DSLTestCase(FHDLTestCase):
             "(sig a)": "comb",
             "(sig fsm_state)": "sync",
             "(sig b)": "sync",
-            "(sig fsm_ongoing_FIRST)": "comb",
-            "(sig fsm_ongoing_SECOND)": "comb",
+            "(sig)": "comb",
         })
         fsm  = frag.find_generated("fsm")
         self.assertIsInstance(fsm.state, Signal)
@@ -659,8 +658,8 @@ class DSLTestCase(FHDLTestCase):
                 )
                 (case 1 )
             )
-            (eq (sig fsm_ongoing_FIRST) (== (sig fsm_state) (const 1'd0)))
-            (eq (sig fsm_ongoing_SECOND) (== (sig fsm_state) (const 1'd1)))
+            (eq (sig) (== (sig fsm_state) (const 1'd0)))
+            (eq (sig) (== (sig fsm_state) (const 1'd1)))
         )
         """)
         self.assertRepr(frag.statements["sync"], """
@@ -697,8 +696,8 @@ class DSLTestCase(FHDLTestCase):
                 )
                 (case 1 )
             )
-            (eq (sig fsm_ongoing_FIRST) (== (sig fsm_state) (const 1'd0)))
-            (eq (sig fsm_ongoing_SECOND) (== (sig fsm_state) (const 1'd1)))
+            (eq (sig) (== (sig fsm_state) (const 1'd0)))
+            (eq (sig) (== (sig fsm_state) (const 1'd1)))
         )
         """)
         self.assertRepr(frag.statements["sync"], """
@@ -743,10 +742,10 @@ class DSLTestCase(FHDLTestCase):
         self.maxDiff = 10000
         self.assertRepr(frag.statements["comb"], """
         (
-            (eq (sig b) (sig fsm_ongoing_SECOND))
-            (eq (sig a) (sig fsm_ongoing_FIRST))
-            (eq (sig fsm_ongoing_SECOND) (== (sig fsm_state) (const 1'd0)))
-            (eq (sig fsm_ongoing_FIRST) (== (sig fsm_state) (const 1'd1)))
+            (eq (sig b) (sig))
+            (eq (sig a) (sig))
+            (eq (sig) (== (sig fsm_state) (const 1'd0)))
+            (eq (sig) (== (sig fsm_state) (const 1'd1)))
         )
         """)
 
diff --git a/tests/test_hdl_ir.py b/tests/test_hdl_ir.py
index f968379..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()
@@ -668,6 +680,20 @@ class FragmentDomainsTestCase(FHDLTestCase):
                     r"domain 'sync' \(defines 'foo'\)\.$")):
             f1._propagate_domains(missing_domain=lambda name: f2)
 
+    def test_propagate_up_use(self):
+        a = Signal()
+        m = Module()
+        m1 = Module()
+        m2 = Module()
+        m.submodules.m1 = m1
+        m.submodules.m2 = m2
+        m1.d.test += a.eq(1)
+        m2.domains.test = ClockDomain()
+        with self.assertWarnsRegex(DeprecationWarning,
+                r"^Domain 'test' is used in 'top.m1', but defined in 'top.m2', which will not be "
+                r"supported in Amaranth 0.6; define the domain in 'top' or one of its parents$"):
+            Fragment.get(m, None).prepare()
+
 
 class FragmentHierarchyConflictTestCase(FHDLTestCase):
     def test_no_conflict_local_domains(self):
@@ -721,7 +747,7 @@ class InstanceTestCase(FHDLTestCase):
             ("PARAM1", 0x1234),
             ("PARAM2", 0x5678),
         ]))
-        self.assertEqual(inst.named_ports, OrderedDict([
+        self.assertEqual(inst.ports, OrderedDict([
             ("s1", (s1, "i")),
             ("s2", (s2, "o")),
             ("io1", (io1, "i")),
@@ -741,10 +767,10 @@ class InstanceTestCase(FHDLTestCase):
             i_s3=3,
             io_s4=Cat(),
         )
-        self.assertRepr(inst.named_ports["s1"][0], "(const 1'd1)")
-        self.assertRepr(inst.named_ports["s2"][0], "(io-cat )")
-        self.assertRepr(inst.named_ports["s3"][0], "(const 2'd3)")
-        self.assertRepr(inst.named_ports["s4"][0], "(io-cat )")
+        self.assertRepr(inst.ports["s1"][0], "(const 1'd1)")
+        self.assertRepr(inst.ports["s2"][0], "(io-cat )")
+        self.assertRepr(inst.ports["s3"][0], "(const 2'd3)")
+        self.assertRepr(inst.ports["s4"][0], "(io-cat )")
 
     def test_wrong_construct_arg(self):
         s = Signal()
@@ -782,7 +808,7 @@ class InstanceTestCase(FHDLTestCase):
         f = self.inst
         self.assertEqual(f.type, "cpu")
         self.assertEqual(f.parameters, OrderedDict([("RESET", 0x1234)]))
-        self.assertEqual(list(f.named_ports.keys()), ["clk", "rst", "stb", "data", "pins"])
+        self.assertEqual(list(f.ports.keys()), ["clk", "rst", "stb", "data", "pins"])
 
     def test_prepare_attrs(self):
         self.setUp_cpu()
@@ -962,6 +988,7 @@ class NamesTestCase(FHDLTestCase):
         o1 = Signal()
         o2 = Signal()
         o3 = Signal()
+        o4 = Signal(name="")
         i1 = Signal(name="i")
 
         f = Fragment()
@@ -980,6 +1007,7 @@ class NamesTestCase(FHDLTestCase):
             "o1": (o1, PortDirection.Output),
             "o2": (o2, PortDirection.Output),
             "o3": (o3, PortDirection.Output),
+            "o4": (o4, PortDirection.Output),
         }
         design = f.prepare(ports)
         self.assertEqual(design.fragments[design.fragment].signal_names, SignalDict([
@@ -988,12 +1016,20 @@ class NamesTestCase(FHDLTestCase):
             (o1, "o1"),
             (o2, "o2"),
             (o3, "o3"),
+            # (o4, "o4"), # Signal has a private name.
             (cd_sync.clk, "clk"),
-            (cd_sync.rst, "rst$6"),
+            (cd_sync.rst, "rst$7"),
             (cd_sync_norst.clk, "sync_norst_clk"),
-            (i1, "i$7"),
+            (i1, "i$8"),
         ]))
 
+    def test_wrong_private_unnamed_toplevel_ports(self):
+        s = Signal(name="")
+        f = Fragment()
+        with self.assertRaisesRegex(TypeError,
+                r"^Signals with private names cannot be used in unnamed top-level ports$"):
+            Design(f, ports=((None, s, None),), hierarchy=("top",))
+
     def test_assign_names_to_fragments(self):
         f = Fragment()
         f.add_subfragment(a := Fragment())
@@ -1599,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)
@@ -2929,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)
@@ -2938,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))
@@ -2947,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)
@@ -2956,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))
@@ -2964,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_sim.py b/tests/test_sim.py
index 978f8c0..bb5d495 100644
--- a/tests/test_sim.py
+++ b/tests/test_sim.py
@@ -14,6 +14,7 @@ from amaranth.hdl._dsl import  *
 from amaranth.hdl._ir import *
 from amaranth.sim import *
 from amaranth.lib.memory import Memory
+from amaranth.lib.data import View, StructLayout
 
 from .utils import *
 from amaranth._utils import _ignore_deprecated
@@ -313,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)
@@ -332,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))
@@ -512,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():
@@ -1042,6 +1045,21 @@ class SimulatorIntegrationTestCase(FHDLTestCase):
                 with sim.write_vcd(f):
                     pass
 
+    def test_vcd_private_signal(self):
+        sim = Simulator(Module())
+        with self.assertRaisesRegex(TypeError,
+                r"^Cannot trace signal with private name$"):
+            with open(os.path.devnull, "w") as f:
+                with sim.write_vcd(f, traces=(Signal(name=""),)):
+                    pass
+
+        sim = Simulator(Module())
+        with self.assertRaisesRegex(TypeError,
+                r"^Cannot trace signal with private name \(within \(cat \(sig x\) \(sig\)\)\)$"):
+            with open(os.path.devnull, "w") as f:
+                with sim.write_vcd(f, traces=(Cat(Signal(name="x"), Signal(name="")),)):
+                    pass
+
     def test_no_negated_boolean_warning(self):
         m = Module()
         a = Signal()


----

problem_statement: New IR causes DSP inference issues with Quartus
The following design (within the Details) infers 2 DSP blocks: `Info (21062): Implemented 2 DSP elements`.

<details>

```python
from amaranth import *
from amaranth.lib import wiring
from amaranth.lib.wiring import In, Out

from amaranth_boards.de10_nano import DE10NanoPlatform

class DSPMACBlock(wiring.Component):
    # using signed 18x18->36 mode
    # https://www.intel.com/content/www/us/en/docs/programmable/683375/current/independent-multiplier-mode.html

    mul_a: In(signed(18)) # A input is 18 bits
    mul_b: In(signed(18)) # B input is 18 bits
    result: Out(signed(36)) # result is len(A)+len(B)

    def elaborate(self, platform):
        m = Module()

        m.d.sync += self.result.eq(self.mul_a * self.mul_b)

        return m

class Top(Elaboratable):
    def elaborate(self, platform):
        m = Module()

        m.submodules.mac = mac = DSPMACBlock()
        m.d.comb += [
            mac.mul_a.eq(Mux(platform.request("switch", 0).i, -1, 0)), # use all bits
            mac.mul_b.eq(Mux(platform.request("switch", 1).i, -1, 0)),
            platform.request("led", 0).o.eq(mac.result[-1])
        ]

        return m

if __name__ == "__main__":
    DE10NanoPlatform().build(Top())
```

</details>

Given that this is a supported DSP mode, I would expect this to infer only 1 block. This worked properly with the old IR. If I make both operands of the `*` signed by modifying line 107 of the generated Verilog (`assign \$1 = ...`) then correctly 1 block is inferred.

Not sure if there's any difference in functionality between the two scenarios.

Versions:
* amaranth 24a392887af19a9d013252759ec209d5a91a378a
* amaranth-boards: https://github.com/amaranth-lang/amaranth-boards/commit/170675812b71ee722bcf8ccdb88409a9ad97ffe2
* Yosys 0.38 (git sha1 543faed9c8c, gcc 13.2.0 -fPIC -Os)
* Quartus x86_64 Linux 20.1.1.720
date: 2024-03-27
patch: diff --git a/amaranth/back/rtlil.py b/amaranth/back/rtlil.py
index edc1dbf..a6367bd 100644
--- a/amaranth/back/rtlil.py
+++ b/amaranth/back/rtlil.py
@@ -1,4 +1,5 @@
 from typing import Iterable
+from contextlib import contextmanager
 import io
 
 from ..utils import bits_for
@@ -24,7 +25,7 @@ def _signed(value):
     elif isinstance(value, int):
         return value < 0
     elif isinstance(value, _ast.Const):
-        return value.signed
+        return value.shape().signed
     else:
         assert False, f"Invalid constant {value!r}"
 
@@ -41,95 +42,147 @@ def _const(value):
             width = max(32, bits_for(value))
             return _const(_ast.Const(value, width))
     elif isinstance(value, _ast.Const):
-        value_twos_compl = value.value & ((1 << value.width) - 1)
-        return "{}'{:0{}b}".format(value.width, value_twos_compl, value.width)
+        value_twos_compl = value.value & ((1 << len(value)) - 1)
+        return "{}'{:0{}b}".format(len(value), value_twos_compl, len(value))
     else:
         assert False, f"Invalid constant {value!r}"
 
 
-class _Namer:
-    def __init__(self):
-        super().__init__()
-        self._anon  = 0
-        self._index = 0
-        self._names = set()
-
-    def anonymous(self):
-        name = f"U$${self._anon}"
-        assert name not in self._names
-        self._anon += 1
-        return name
-
-    def _make_name(self, name, local):
-        if name is None:
-            self._index += 1
-            name = f"${self._index}"
-        elif not local and name[0] not in "\\$":
-            name = f"\\{name}"
-        while name in self._names:
-            self._index += 1
-            name = f"{name}${self._index}"
-        self._names.add(name)
-        return name
+def _src(src_loc):
+    if src_loc is None:
+        return None
+    file, line = src_loc
+    return f"{file}:{line}"
 
 
-class _BufferedBuilder:
+class Emitter:
     def __init__(self):
-        super().__init__()
-        self._buffer = io.StringIO()
-
-    def __str__(self):
-        return self._buffer.getvalue()
+        self._indent = ""
+        self._lines = []
+        self.port_id = 0
 
-    def _append(self, fmt, *args, **kwargs):
-        self._buffer.write(fmt.format(*args, **kwargs))
-
-
-class _AttrBuilder:
-    def __init__(self, emit_src, *args, **kwargs):
-        super().__init__(*args, **kwargs)
-        self.emit_src = emit_src
+    def __call__(self, line=None):
+        if line is not None:
+            self._lines.append(f"{self._indent}{line}\n")
+        else:
+            self._lines.append("\n")
 
-    def _attribute(self, name, value, *, indent=0):
-        self._append("{}attribute \\{} {}\n",
-                     "  " * indent, name, _const(value))
+    @contextmanager
+    def indent(self):
+        orig = self._indent
+        self._indent += "  "
+        yield
+        self._indent = orig
 
-    def _attributes(self, attrs, *, src=None, **kwargs):
-        for name, value in attrs.items():
-            self._attribute(name, value, **kwargs)
-        if src and self.emit_src:
-            self._attribute("src", src, **kwargs)
+    def __str__(self):
+        return "".join(self._lines)
 
 
-class _Builder(_BufferedBuilder, _Namer):
-    def __init__(self, emit_src):
-        super().__init__()
+class Design:
+    def __init__(self, emit_src=True):
+        self.modules = {}
         self.emit_src = emit_src
 
-    def module(self, name=None, attrs={}, *, src=None):
-        name = self._make_name(name, local=False)
-        return _ModuleBuilder(self, name, attrs, src=src)
+    def module(self, name, **kwargs):
+        assert name not in self.modules
+        self.modules[name] = res = Module(name, emit_src=self.emit_src, **kwargs)
+        return res
 
+    def __str__(self):
+        emitter = Emitter()
+        for module in self.modules.values():
+            module.emit(emitter)
+        return str(emitter)
+
+
+class Module:
+    def __init__(self, name, src_loc=None, attrs=None, emit_src=True):
+        self.name = name
+        self._auto_index = 0
+        self.contents = {}
+        self.connections = []
+        self.attributes = {"generator": "Amaranth"}
+        self.emit_src = emit_src
+        if src_loc is not None and emit_src:
+            self.attributes["src"] = _src(src_loc)
+        if attrs is not None:
+            self.attributes.update(attrs)
 
-class _ModuleBuilder(_AttrBuilder, _BufferedBuilder, _Namer):
-    def __init__(self, rtlil, name, attrs, *, src=None):
-        super().__init__(emit_src=rtlil.emit_src)
-        self.rtlil = rtlil
-        self.name  = name
-        self.src   = src
-        self.attrs = {"generator": "Amaranth"}
-        self.attrs.update(attrs)
+    def _auto_name(self):
+        self._auto_index += 1
+        return f"${self._auto_index}"
 
-    def __enter__(self):
-        self._attributes(self.attrs, src=self.src)
-        self._append("module {}\n", self.name)
-        return self
+    def _name(self, name):
+        if name is None:
+            name = self._auto_name()
+        else:
+            name = f"\\{name}"
+        assert name not in self.contents
+        return name
 
-    def __exit__(self, *args):
-        self._append("end\n")
-        self.rtlil._buffer.write(str(self))
+    def wire(self, width, *, name=None, **kwargs):
+        name = self._name(name)
+        if not self.emit_src and "src_loc" in kwargs:
+            del kwargs["src_loc"]
+        self.contents[name] = res = Wire(width, name=name, **kwargs)
+        return res
+
+    def cell(self, kind, name=None, **kwargs):
+        name = self._name(name)
+        if not self.emit_src and "src_loc" in kwargs:
+            del kwargs["src_loc"]
+        self.contents[name] = res = Cell(kind, name=name, **kwargs)
+        return res
+
+    def memory(self, width, depth, name=None, **kwargs):
+        name = self._name(name)
+        if not self.emit_src and "src_loc" in kwargs:
+            del kwargs["src_loc"]
+        self.contents[name] = res = Memory(width, depth, name=name, **kwargs)
+        return res
+
+    def process(self, *, name=None, **kwargs):
+        name = self._name(name)
+        if not self.emit_src and "src_loc" in kwargs:
+            del kwargs["src_loc"]
+        self.contents[name] = res = Process(name=name, **kwargs)
+        return res
 
-    def wire(self, width, port_id=None, port_kind=None, name=None, attrs={}, src="", signed=False):
+    def connect(self, lhs, rhs):
+        self.connections.append((lhs, rhs))
+
+    def attribute(self, name, value):
+        assert name not in self.attributes
+        self.attributes[name] = value
+
+    def emit(self, line):
+        line.port_id = 0
+        for name, value in self.attributes.items():
+            line(f"attribute \\{name} {_const(value)}")
+        line(f"module \\{self.name}")
+        line()
+        with line.indent():
+            for item in self.contents.values():
+                item.emit(line)
+        for (lhs, rhs) in self.connections:
+            line(f"connect {lhs} {rhs}")
+        if self.connections:
+            line()
+        line("end")
+        line()
+
+
+def _make_attributes(attrs, src_loc):
+    res = {}
+    if src_loc is not None:
+        res["src"] = _src(src_loc)
+    if attrs is not None:
+        res.update(attrs)
+    return res
+
+
+class Wire:
+    def __init__(self, width, *, name, src_loc=None, attrs=None, signed=False, port_kind=None):
         # Very large wires are unlikely to work. Verilog 1364-2005 requires the limit on vectors
         # to be at least 2**16 bits, and Yosys 0.9 cannot read RTLIL with wires larger than 2**32
         # bits. In practice, wires larger than 2**16 bits, although accepted, cause performance
@@ -137,131 +190,192 @@ class _ModuleBuilder(_AttrBuilder, _BufferedBuilder, _Namer):
         if width > 2 ** 16:
             raise OverflowError("Wire created at {} is {} bits wide, which is unlikely to "
                                 "synthesize correctly"
-                                .format(src or "unknown location", width))
-
-        self._attributes(attrs, src=src, indent=1)
-        name = self._make_name(name, local=False)
-        signed = " signed" if signed else ""
-        if port_id is None:
-            self._append("  wire width {}{} {}\n", width, signed, name)
+                                .format(_src(src_loc) or "unknown location", width))
+        self.name = name
+        self.width = width
+        self.signed = signed
+        self.port_kind = port_kind
+        self.attributes = _make_attributes(attrs, src_loc)
+
+    def attribute(self, name, value):
+        assert name not in self.attributes
+        self.attributes[name] = value
+
+    def emit(self, line):
+        for name, value in self.attributes.items():
+            line(f"attribute \\{name} {_const(value)}")
+        signed = " signed" if self.signed else ""
+        if self.port_kind is None:
+            line(f"wire width {self.width}{signed} {self.name}")
         else:
-            assert port_kind in ("input", "output", "inout")
-            # By convention, Yosys ports named $\d+ are positional, so there is no way to use
-            # a port with such a name. See amaranth-lang/amaranth#733.
-            assert port_id is not None
-            self._append("  wire width {} {} {}{} {}\n", width, port_kind, port_id, signed, name)
-        return name
-
-    def connect(self, lhs, rhs):
-        self._append("  connect {} {}\n", lhs, rhs)
-
-    def memory(self, width, size, name=None, attrs={}, src=""):
-        self._attributes(attrs, src=src, indent=1)
-        name = self._make_name(name, local=False)
-        self._append("  memory width {} size {} {}\n", width, size, name)
-        return name
-
-    def cell(self, kind, name=None, params={}, ports={}, attrs={}, src=""):
-        self._attributes(attrs, src=src, indent=1)
-        name = self._make_name(name, local=False)
-        self._append("  cell {} {}\n", kind, name)
-        for param, value in params.items():
-            if isinstance(value, float):
-                self._append("    parameter real \\{} \"{!r}\"\n",
-                             param, value)
-            elif _signed(value):
-                self._append("    parameter signed \\{} {}\n",
-                             param, _const(value))
-            else:
-                self._append("    parameter \\{} {}\n",
-                             param, _const(value))
-        for port, wire in ports.items():
-            self._append("    connect \\{} {}\n", port, wire)
-        self._append("  end\n")
-        return name
-
-    def process(self, name=None, attrs={}, src=""):
-        name = self._make_name(name, local=True)
-        return _ProcessBuilder(self, name, attrs, src)
-
-
-class _ProcessBuilder(_AttrBuilder, _BufferedBuilder):
-    def __init__(self, rtlil, name, attrs, src):
-        super().__init__(emit_src=rtlil.emit_src)
-        self.rtlil = rtlil
-        self.name  = name
-        self.attrs = {}
-        self.src   = src
-
-    def __enter__(self):
-        self._attributes(self.attrs, src=self.src, indent=1)
-        self._append("  process {}\n", self.name)
-        return self
-
-    def __exit__(self, *args):
-        self._append("  end\n")
-        self.rtlil._buffer.write(str(self))
-
-    def case(self):
-        return _CaseBuilder(self, indent=2)
-
-
-class _CaseBuilder:
-    def __init__(self, rtlil, indent):
-        self.rtlil  = rtlil
-        self.indent = indent
+            line(f"wire width {self.width} {self.port_kind} {line.port_id} {signed} {self.name}")
+            line.port_id += 1
+        line()
+
+
+class Cell:
+    def __init__(self, kind, *, name, ports=None, parameters=None, attrs=None, src_loc=None):
+        self.kind = kind
+        self.name = name
+        self.parameters = parameters or {}
+        self.ports = ports or {}
+        self.attributes = _make_attributes(attrs, src_loc)
+
+    def port(self, name, value):
+        assert name not in self.ports
+        self.ports[name] = value
+
+    def parameter(self, name, value):
+        assert name not in self.parameters
+        self.parameters[name] = value
+
+    def attribute(self, name, value):
+        assert name not in self.attributes
+        self.attributes[name] = value
+
+    def emit(self, line):
+        for name, value in self.attributes.items():
+            line(f"attribute \\{name} {_const(value)}")
+        line(f"cell {self.kind} {self.name}")
+        with line.indent():
+            for name, value in self.parameters.items():
+                if isinstance(value, float):
+                    line(f"parameter real \\{name} \"{value!r}\"")
+                elif _signed(value):
+                    line(f"parameter signed \\{name} {_const(value)}")
+                else:
+                    line(f"parameter \\{name} {_const(value)}")
+            for name, value in self.ports.items():
+                line(f"connect \\{name} {value}")
+        line(f"end")
+        line()
+
+
+class Memory:
+    def __init__(self, width, depth, *, name, attrs=None, src_loc=None):
+        self.width = width
+        self.depth = depth
+        self.name = name
+        self.attributes = _make_attributes(attrs, src_loc)
+
+    def attribute(self, name, value):
+        assert name not in self.attributes
+        self.attributes[name] = value
+
+    def emit(self, line):
+        for name, value in self.attributes.items():
+            line(f"attribute \\{name} {_const(value)}")
+        line(f"memory width {self.width} size {self.depth} {self.name}")
+        line()
+
+
+def _emit_process_contents(contents, emit):
+    index = 0
+    while index < len(contents) and isinstance(contents[index], Assignment):
+        contents[index].emit(emit)
+        index += 1
+    while index < len(contents):
+        if isinstance(contents[index], Assignment):
+            emit(f"switch {{}}")
+            with emit.indent():
+                emit(f"case")
+                with emit.indent():
+                    while index < len(contents) and isinstance(contents[index], Assignment):
+                        contents[index].emit(emit)
+                        index += 1
+            emit(f"end")
+        else:
+            contents[index].emit(emit)
+            index += 1
 
-    def _append(self, *args, **kwargs):
-        self.rtlil._append(*args, **kwargs)
 
-    def __enter__(self):
-        return self
+class Process:
+    def __init__(self, *, name, attrs=None, src_loc=None):
+        self.name = name
+        self.contents = []
+        self.attributes = _make_attributes(attrs, src_loc)
 
-    def __exit__(self, *args):
-        pass
+    def attribute(self, name, value):
+        assert name not in self.attributes
+        self.attributes[name] = value
 
     def assign(self, lhs, rhs):
-        self._append("{}assign {} {}\n", "  " * self.indent, lhs, rhs)
-
-    def switch(self, cond, attrs={}, src=""):
-        return _SwitchBuilder(self.rtlil, cond, attrs, src, self.indent)
-
+        self.contents.append(Assignment(lhs, rhs))
+
+    def switch(self, sel):
+        res = Switch(sel)
+        self.contents.append(res)
+        return res
+
+    def emit(self, line):
+        for name, value in self.attributes.items():
+            line(f"attribute \\{name} {_const(value)}")
+        line(f"process {self.name}")
+        with line.indent():
+            _emit_process_contents(self.contents, line)
+        line(f"end")
+        line()
+
+
+class Assignment:
+    def __init__(self, lhs, rhs):
+        self.lhs = lhs
+        self.rhs = rhs
+
+    def emit(self, line):
+        line(f"assign {self.lhs} {self.rhs}")
+
+
+class Switch:
+    def __init__(self, sel):
+        self.sel = sel
+        self.cases = []
+
+    def case(self, patterns):
+        res = Case(patterns)
+        if patterns:
+            # RTLIL doesn't support cases with empty pattern list (they get interpreted
+            # as a default case instead, which is batshit and the exact opposite of
+            # what we want). When such a case is requested, return a case so that
+            # the caller can emit stuff into it, but don't actually include it in
+            # the switch.
+            self.cases.append(res)
+        return res
+
+    def default(self):
+        res = Case(())
+        self.cases.append(res)
+        return res
+
+    def emit(self, line):
+        line(f"switch {self.sel}")
+        with line.indent():
+            for case in self.cases:
+                case.emit(line)
+        line("end")
+
+
+class Case:
+    def __init__(self, patterns):
+        self.patterns = patterns
+        self.contents = []
 
-class _SwitchBuilder(_AttrBuilder):
-    def __init__(self, rtlil, cond, attrs, src, indent):
-        super().__init__(emit_src=rtlil.emit_src)
-        self.rtlil  = rtlil
-        self.cond   = cond
-        self.attrs  = attrs
-        self.src    = src
-        self.indent = indent
-
-    def _append(self, *args, **kwargs):
-        self.rtlil._append(*args, **kwargs)
-
-    def __enter__(self):
-        self._attributes(self.attrs, src=self.src, indent=self.indent)
-        self._append("{}switch {}\n", "  " * self.indent, self.cond)
-        return self
+    def assign(self, lhs, rhs):
+        self.contents.append(Assignment(lhs, rhs))
 
-    def __exit__(self, *args):
-        self._append("{}end\n", "  " * self.indent)
+    def switch(self, sel):
+        res = Switch(sel)
+        self.contents.append(res)
+        return res
 
-    def case(self, *values, attrs={}, src=""):
-        self._attributes(attrs, src=src, indent=self.indent + 1)
-        if values == ():
-            self._append("{}case\n", "  " * (self.indent + 1))
+    def emit(self, line):
+        if self.patterns:
+            patterns = ", ".join(f"{len(pattern)}'{pattern}" for pattern in self.patterns)
+            line(f"case {patterns}")
         else:
-            self._append("{}case {}\n", "  " * (self.indent + 1),
-                         ", ".join(f"{len(value)}'{value}" for value in values))
-        return _CaseBuilder(self.rtlil, self.indent + 2)
-
-
-def _src(src_loc):
-    if src_loc is None:
-        return None
-    file, line = src_loc
-    return f"{file}:{line}"
+            line(f"case")
+        with line.indent():
+            _emit_process_contents(self.contents, line)
 
 
 class MemoryInfo:
@@ -316,7 +430,7 @@ class ModuleEmitter:
             if isinstance(cell, _nir.Memory):
                 self.memories[cell_idx] = MemoryInfo(
                     self.builder.memory(cell.width, cell.depth, name=cell.name,
-                                       attrs=cell.attributes, src=_src(cell.src_loc)))
+                                        attrs=cell.attributes, src_loc=cell.src_loc).name)
 
         for cell_idx in self.module.cells:
             cell = self.netlist.cells[cell_idx]
@@ -389,22 +503,23 @@ class ModuleEmitter:
                 assert value == port_value
                 self.name_map[signal] = (*self.module.name, name)
             else:
-                wire = self.builder.wire(width=signal.width, signed=signal.signed,
-                                         name=name, attrs=attrs,
-                                         src=_src(signal.src_loc))
+                shape = signal.shape()
+                wire  = self.builder.wire(width=shape.width, signed=shape.signed,
+                                          name=name, attrs=attrs,
+                                          src_loc=signal.src_loc)
                 self.sigport_wires[name] = (wire, value)
-                self.name_map[signal] = (*self.module.name, wire[1:])
+                self.name_map[signal] = (*self.module.name, wire.name[1:])
 
     def emit_port_wires(self):
         named_signals = {name: signal for signal, name in self.module.signal_names.items()}
         for port_id, (name, (value, flow)) in enumerate(self.module.ports.items()):
             signed = False
             if name in named_signals:
-                signed = named_signals[name].signed
+                signed = named_signals[name].shape().signed
             wire = self.builder.wire(width=len(value), signed=signed,
-                                     port_id=port_id, port_kind=flow.value,
+                                     port_kind=flow.value,
                                      name=name, attrs=self.value_attrs.get(value, {}),
-                                     src=_src(self.value_src_loc.get(value)))
+                                     src_loc=self.value_src_loc.get(value))
             self.sigport_wires[name] = (wire, value)
             if flow == _nir.ModuleNetFlow.Output:
                 continue
@@ -424,9 +539,9 @@ class ModuleEmitter:
                 attrs = {}
                 src_loc = None
             wire = self.builder.wire(width=len(value),
-                                     port_id=port_id, port_kind=dir.value,
+                                     port_kind=dir.value,
                                      name=name, attrs=attrs,
-                                     src=_src(src_loc))
+                                     src_loc=src_loc)
             for bit, net in enumerate(value):
                 self.ionets[net] = (wire, bit)
 
@@ -515,9 +630,9 @@ class ModuleEmitter:
                     bit += 1
                 width = end_pos - begin_pos
                 if width == 1:
-                    chunks.append(f"{wire} [{start_bit}]")
+                    chunks.append(f"{wire.name} [{start_bit}]")
                 else:
-                    chunks.append(f"{wire} [{start_bit + width - 1}:{start_bit}]")
+                    chunks.append(f"{wire.name} [{start_bit + width - 1}:{start_bit}]")
             begin_pos = end_pos
 
         if len(chunks) == 1:
@@ -537,9 +652,9 @@ class ModuleEmitter:
                 bit += 1
             width = end_pos - begin_pos
             if width == 1:
-                chunks.append(f"{wire} [{start_bit}]")
+                chunks.append(f"{wire.name} [{start_bit}]")
             else:
-                chunks.append(f"{wire} [{start_bit + width - 1}:{start_bit}]")
+                chunks.append(f"{wire.name} [{start_bit + width - 1}:{start_bit}]")
             begin_pos = end_pos
 
         if len(chunks) == 1:
@@ -549,7 +664,7 @@ class ModuleEmitter:
     def emit_connects(self):
         for name, (wire, value) in self.sigport_wires.items():
             if name not in self.driven_sigports:
-                self.builder.connect(wire, self.sigspec(value))
+                self.builder.connect(wire.name, self.sigspec(value))
 
     def emit_submodules(self):
         for submodule_idx in self.module.submodules:
@@ -562,7 +677,7 @@ class ModuleEmitter:
                 for name, (value, _dir) in submodule.io_ports.items():
                     ports[name] = self.io_sigspec(value)
                 self.builder.cell(f"\\{dotted_name}", submodule.name[-1], ports=ports,
-                                  src=_src(submodule.cell_src_loc))
+                                  src_loc=submodule.cell_src_loc)
 
     def emit_assignment_list(self, cell_idx, cell):
         def emit_assignments(case, cond):
@@ -572,30 +687,13 @@ class ModuleEmitter:
             # letting parent invocation take care of the remaining assignments.
             nonlocal pos
 
-            emitted_switch = False
             while pos < len(cell.assignments):
                 assign = cell.assignments[pos]
-                if assign.cond == cond and not emitted_switch:
-                    # Not nested, and we didn't emit a switch yet, so emit the assignment.
+                if assign.cond == cond:
+                    # Not nested, so emit the assignment.
                     case.assign(self.sigspec(lhs[assign.start:assign.start + len(assign.value)]),
                                 self.sigspec(assign.value))
                     pos += 1
-                elif assign.cond == cond:
-                    # Not nested, but we emitted a subswitch. Wrap the assignments in a dummy
-                    # switch. This is necessary because Yosys executes all assignments before all
-                    # subswitches (but allows you to mix asssignments and switches in RTLIL, for
-                    # maximum confusion).
-                    with case.switch("{ }") as switch:
-                        with switch.case("") as subcase:
-                            while pos < len(cell.assignments):
-                                assign = cell.assignments[pos]
-                                if assign.cond == cond:
-                                    subcase.assign(self.sigspec(lhs[assign.start:assign.start +
-                                                                    len(assign.value)]),
-                                                   self.sigspec(assign.value))
-                                    pos += 1
-                                else:
-                                    break
                 else:
                     # Condition doesn't match this case's condition — either we encountered
                     # a nested condition, or we should break out.  Try to find out exactly
@@ -626,29 +724,35 @@ class ModuleEmitter:
                             break
                     # Now emit cases for all PriorityMatch inputs, in sequence. Consume as many
                     # assignments as possible along the way.
-                    with case.switch(self.sigspec(test)) as switch:
-                        for bit, net in enumerate(priority_cell.inputs):
-                            subcond = _nir.Net.from_cell(priority_cell_idx, bit)
-                            if net == _nir.Net.from_const(1):
-                                patterns = ()
-                            else:
-                                # Validate the above assumptions.
-                                matches_cell = self.netlist.cells[net.cell]
-                                assert isinstance(matches_cell, _nir.Matches)
-                                assert test == matches_cell.value
-                                patterns = matches_cell.patterns
-                            with switch.case(*patterns) as subcase:
-                                emit_assignments(subcase, subcond)
-                    emitted_switch = True
+                    switch = case.switch(self.sigspec(test))
+                    for bit, net in enumerate(priority_cell.inputs):
+                        subcond = _nir.Net.from_cell(priority_cell_idx, bit)
+                        if net == _nir.Net.from_const(1):
+                            emit_assignments(switch.default(), subcond)
+                        else:
+                            # Validate the above assumptions.
+                            matches_cell = self.netlist.cells[net.cell]
+                            assert isinstance(matches_cell, _nir.Matches)
+                            assert test == matches_cell.value
+                            patterns = matches_cell.patterns
+                            emit_assignments(switch.case(patterns), subcond)
 
         lhs = _nir.Value(_nir.Net.from_cell(cell_idx, bit) for bit in range(len(cell.default)))
-        with self.builder.process(src=_src(cell.src_loc)) as proc:
-            with proc.case() as root_case:
-                root_case.assign(self.sigspec(lhs), self.sigspec(cell.default))
-
-                pos = 0 # nonlocally used in `emit_assignments`
-                emit_assignments(root_case, _nir.Net.from_const(1))
-                assert pos == len(cell.assignments)
+        proc = self.builder.process(src_loc=cell.src_loc)
+        proc.assign(self.sigspec(lhs), self.sigspec(cell.default))
+        pos = 0 # nonlocally used in `emit_assignments`
+        emit_assignments(proc, _nir.Net.from_const(1))
+        assert pos == len(cell.assignments)
+
+    def shorten_operand(self, value, *, signed):
+        value = list(value)
+        if signed:
+            while len(value) > 1 and value[-1] == value[-2]:
+                value.pop()
+        else:
+            while len(value) > 0 and value[-1] == _nir.Net.from_const(0):
+                value.pop()
+        return _nir.Value(value)
 
     def emit_operator(self, cell_idx, cell):
         UNARY_OPERATORS = {
@@ -688,59 +792,117 @@ class ModuleEmitter:
         if len(cell.inputs) == 1:
             cell_type = UNARY_OPERATORS[cell.operator]
             operand, = cell.inputs
+            signed = False
+            if cell.operator == "-":
+                # For arithmetic operands, we trim the extra sign or zero extension on the operands
+                # to make the output prettier, and to fix inference problems in some not very smart
+                # synthesis tools.
+                operand_u = self.shorten_operand(operand, signed=False)
+                operand_s = self.shorten_operand(operand, signed=True)
+                # The operator will work when lowered with either signedness.  Pick whichever
+                # is prettier.
+                if len(operand_s) < len(operand_u):
+                    signed = True
+                    operand = operand_s
+                else:
+                    signed = False
+                    operand = operand_u
             self.builder.cell(cell_type, ports={
                 "A": self.sigspec(operand),
-                "Y": self.cell_wires[cell_idx]
-            }, params={
-                "A_SIGNED": False,
+                "Y": self.cell_wires[cell_idx].name
+            }, parameters={
+                "A_SIGNED": signed,
                 "A_WIDTH": len(operand),
                 "Y_WIDTH": cell.width,
-            }, src=_src(cell.src_loc))
+            }, src_loc=cell.src_loc)
         elif len(cell.inputs) == 2:
             cell_type, a_signed, b_signed = BINARY_OPERATORS[cell.operator]
             operand_a, operand_b = cell.inputs
+            if cell.operator in ("+", "-", "*", "==", "!="):
+                # Arithmetic operators that will work with any signedness, but we have to choose
+                # a common one for both operands. Prefer signed in case of mixed signedness.
+                operand_a_u = self.shorten_operand(operand_a, signed=False)
+                operand_b_u = self.shorten_operand(operand_b, signed=False)
+                operand_a_s = self.shorten_operand(operand_a, signed=True)
+                operand_b_s = self.shorten_operand(operand_b, signed=True)
+                if operand_a.is_const:
+                    # In case of constant operand, choose whichever shortens the other one better.
+                    signed = len(operand_b_s) < len(operand_b_u)
+                elif operand_b.is_const:
+                    signed = len(operand_a_s) < len(operand_a_u)
+                elif (len(operand_a_s) < len(operand_a) and len(operand_a_u) == len(operand_a)):
+                    # Operand A can only be shortened by signed. Pick it.
+                    signed = True
+                elif (len(operand_b_s) < len(operand_b) and len(operand_b_u) == len(operand_b)):
+                    # Operand B can only be shortened by signed. Pick it.
+                    signed = True
+                else:
+                    # Otherwise, use unsigned shortening.
+                    signed = False
+                if signed:
+                    operand_a = operand_a_s
+                    operand_b = operand_b_s
+                else:
+                    operand_a = operand_a_u
+                    operand_b = operand_b_u
+                a_signed = b_signed = signed
+            if cell.operator[0] in "us":
+                # Signedness forced, just shorten.
+                operand_a = self.shorten_operand(operand_a, signed=a_signed)
+                operand_b = self.shorten_operand(operand_b, signed=b_signed)
+            if cell.operator == "<<":
+                # We can pick the signedness for left operand, but right is fixed.
+                operand_a_u = self.shorten_operand(operand_a, signed=False)
+                operand_a_s = self.shorten_operand(operand_a, signed=True)
+                if len(operand_a_s) < len(operand_a_u):
+                    a_signed = True
+                    operand_a = operand_a_s
+                else:
+                    a_signed = False
+                    operand_a = operand_a_u
+                operand_b = self.shorten_operand(operand_b, signed=b_signed)
             if cell.operator in ("u//", "s//", "u%", "s%"):
                 result = self.builder.wire(cell.width)
                 self.builder.cell(cell_type, ports={
                     "A": self.sigspec(operand_a),
                     "B": self.sigspec(operand_b),
-                    "Y": result,
-                }, params={
+                    "Y": result.name,
+                }, parameters={
                     "A_SIGNED": a_signed,
                     "B_SIGNED": b_signed,
                     "A_WIDTH": len(operand_a),
                     "B_WIDTH": len(operand_b),
                     "Y_WIDTH": cell.width,
-                }, src=_src(cell.src_loc))
+                }, src_loc=cell.src_loc)
                 nonzero = self.builder.wire(1)
                 self.builder.cell("$reduce_bool", ports={
                     "A": self.sigspec(operand_b),
-                    "Y": nonzero,
-                }, params={
+                    "Y": nonzero.name,
+                }, parameters={
                     "A_SIGNED": False,
                     "A_WIDTH": len(operand_b),
                     "Y_WIDTH": 1,
-                }, src=_src(cell.src_loc))
+                }, src_loc=cell.src_loc)
                 self.builder.cell("$mux", ports={
-                    "S": nonzero,
+                    "S": nonzero.name,
                     "A": self.sigspec(_nir.Value.zeros(cell.width)),
-                    "B": result,
-                    "Y": self.cell_wires[cell_idx]
-                }, params={
+                    "B": result.name,
+                    "Y": self.cell_wires[cell_idx].name
+                }, parameters={
                     "WIDTH": cell.width,
-                }, src=_src(cell.src_loc))
+                }, src_loc=cell.src_loc)
             else:
                 self.builder.cell(cell_type, ports={
                     "A": self.sigspec(operand_a),
                     "B": self.sigspec(operand_b),
-                    "Y": self.cell_wires[cell_idx],
-                }, params={
+                    "Y": self.cell_wires[cell_idx].name,
+                }, parameters={
                     "A_SIGNED": a_signed,
                     "B_SIGNED": b_signed,
                     "A_WIDTH": len(operand_a),
                     "B_WIDTH": len(operand_b),
                     "Y_WIDTH": cell.width,
-                }, src=_src(cell.src_loc))
+                }, src_loc=cell.src_loc)
         else:
             assert cell.operator == "m"
             condition, if_true, if_false = cell.inputs
@@ -748,10 +910,10 @@ class ModuleEmitter:
                 "S": self.sigspec(condition),
                 "A": self.sigspec(if_false),
                 "B": self.sigspec(if_true),
-                "Y": self.cell_wires[cell_idx]
-            }, params={
+                "Y": self.cell_wires[cell_idx].name
+            }, parameters={
                 "WIDTH": cell.width,
-            }, src=_src(cell.src_loc))
+            }, src_loc=cell.src_loc)
 
     def emit_part(self, cell_idx, cell):
         if cell.stride == 1:
@@ -759,38 +921,38 @@ class ModuleEmitter:
             offset_width = len(cell.offset)
         else:
             stride = _ast.Const(cell.stride)
-            offset_width = len(cell.offset) + stride.width
-            offset = self.builder.wire(offset_width)
+            offset_width = len(cell.offset) + len(stride)
+            offset = self.builder.wire(offset_width).name
             self.builder.cell("$mul", ports={
                 "A": self.sigspec(cell.offset),
                 "B": _const(stride),
                 "Y": offset,
-            }, params={
+            }, parameters={
                 "A_SIGNED": False,
                 "B_SIGNED": False,
                 "A_WIDTH": len(cell.offset),
-                "B_WIDTH": stride.width,
+                "B_WIDTH": len(stride),
                 "Y_WIDTH": offset_width,
-            }, src=_src(cell.src_loc))
+            }, src_loc=cell.src_loc)
         self.builder.cell("$shift", ports={
             "A": self.sigspec(cell.value),
             "B": offset,
-            "Y": self.cell_wires[cell_idx],
-        }, params={
+            "Y": self.cell_wires[cell_idx].name,
+        }, parameters={
             "A_SIGNED": cell.value_signed,
             "B_SIGNED": False,
             "A_WIDTH": len(cell.value),
             "B_WIDTH": offset_width,
             "Y_WIDTH": cell.width,
-        }, src=_src(cell.src_loc))
+        }, src_loc=cell.src_loc)
 
     def emit_flip_flop(self, cell_idx, cell):
         ports = {
             "D": self.sigspec(cell.data),
             "CLK": self.sigspec(cell.clk),
-            "Q": self.cell_wires[cell_idx]
+            "Q": self.cell_wires[cell_idx].name
         }
-        params = {
+        parameters = {
             "WIDTH": len(cell.data),
             "CLK_POLARITY": {
                 "pos": True,
@@ -802,9 +964,9 @@ class ModuleEmitter:
         else:
             cell_type = "$adff"
             ports["ARST"] = self.sigspec(cell.arst)
-            params["ARST_POLARITY"] = True
-            params["ARST_VALUE"] = _ast.Const(cell.init, len(cell.data))
-        self.builder.cell(cell_type, ports=ports, params=params, src=_src(cell.src_loc))
+            parameters["ARST_POLARITY"] = True
+            parameters["ARST_VALUE"] = _ast.Const(cell.init, len(cell.data))
+        self.builder.cell(cell_type, ports=ports, parameters=parameters, src_loc=cell.src_loc)
 
     def emit_io_buffer(self, cell_idx, cell):
         if cell.dir is not _nir.IODirection.Input:
@@ -815,11 +977,11 @@ class ModuleEmitter:
                     "Y": self.io_sigspec(cell.port),
                     "A": self.sigspec(cell.o),
                     "EN": self.sigspec(cell.oe),
-                }, params={
+                }, parameters={
                     "WIDTH": len(cell.port),
-                }, src=_src(cell.src_loc))
+                }, src_loc=cell.src_loc)
         if cell.dir is not _nir.IODirection.Output:
-            self.builder.connect(self.cell_wires[cell_idx], self.io_sigspec(cell.port))
+            self.builder.connect(self.cell_wires[cell_idx].name, self.io_sigspec(cell.port))
 
     def emit_memory(self, cell_idx, cell):
         memory_info = self.memories[cell_idx]
@@ -831,13 +993,13 @@ class ModuleEmitter:
                 for bit in range(cell.width)
             ),
             "EN": self.sigspec(_nir.Value.ones(cell.width)),
-        }, params={
+        }, parameters={
             "MEMID": memory_info.memid,
             "ABITS": 0,
             "WIDTH": cell.width,
             "WORDS": cell.depth,
             "PRIORITY": 0,
-        }, src=_src(cell.src_loc))
+        }, src_loc=cell.src_loc)
 
     def emit_write_port(self, cell_idx, cell):
         memory_info = self.memories[cell.memory]
@@ -847,7 +1009,7 @@ class ModuleEmitter:
             "EN": self.sigspec(cell.en),
             "CLK": self.sigspec(cell.clk),
         }
-        params = {
+        parameters = {
             "MEMID": memory_info.memid,
             "ABITS": len(cell.addr),
             "WIDTH": len(cell.data),
@@ -859,13 +1021,13 @@ class ModuleEmitter:
             "PORTID": memory_info.write_port_ids[cell_idx],
             "PRIORITY_MASK": 0,
         }
-        self.builder.cell(f"$memwr_v2", ports=ports, params=params, src=_src(cell.src_loc))
+        self.builder.cell(f"$memwr_v2", ports=ports, parameters=parameters, src_loc=cell.src_loc)
 
     def emit_read_port(self, cell_idx, cell):
         memory_info = self.memories[cell.memory]
         ports = {
             "ADDR": self.sigspec(cell.addr),
-            "DATA": self.cell_wires[cell_idx],
+            "DATA": self.cell_wires[cell_idx].name,
             "ARST": self.sigspec(_nir.Net.from_const(0)),
             "SRST": self.sigspec(_nir.Net.from_const(0)),
         }
@@ -876,7 +1038,7 @@ class ModuleEmitter:
                 1 << memory_info.write_port_ids[write_port_cell_index]
                 for write_port_cell_index in cell.transparent_for
             )
-        params = {
+        parameters = {
             "MEMID": memory_info.memid,
             "ABITS": len(cell.addr),
             "WIDTH": cell.width,
@@ -892,7 +1054,7 @@ class ModuleEmitter:
                 "EN": self.sigspec(_nir.Net.from_const(1)),
                 "CLK": self.sigspec(_nir.Net.from_const(0)),
             })
-            params.update({
+            parameters.update({
                 "CLK_ENABLE": False,
                 "CLK_POLARITY": True,
             })
@@ -901,14 +1063,14 @@ class ModuleEmitter:
                 "EN": self.sigspec(cell.en),
                 "CLK": self.sigspec(cell.clk),
             })
-            params.update({
+            parameters.update({
                 "CLK_ENABLE": True,
                 "CLK_POLARITY": {
                     "pos": True,
                     "neg": False,
                 }[cell.clk_edge],
             })
-        self.builder.cell(f"$memrd_v2", ports=ports, params=params, src=_src(cell.src_loc))
+        self.builder.cell(f"$memrd_v2", ports=ports, parameters=parameters, src_loc=cell.src_loc)
 
     def emit_print(self, cell_idx, cell):
         args = []
@@ -961,50 +1123,51 @@ class ModuleEmitter:
             "EN": self.sigspec(cell.en),
             "ARGS": self.sigspec(_nir.Value(args)),
         }
-        params = {
+        parameters = {
             "FORMAT": "".join(format),
             "ARGS_WIDTH": len(args),
             "PRIORITY": -cell_idx,
         }
         if isinstance(cell, (_nir.AsyncPrint, _nir.AsyncProperty)):
             ports["TRG"] = self.sigspec(_nir.Value())
-            params["TRG_ENABLE"] = False
-            params["TRG_WIDTH"] = 0
-            params["TRG_POLARITY"] = 0
+            parameters["TRG_ENABLE"] = False
+            parameters["TRG_WIDTH"] = 0
+            parameters["TRG_POLARITY"] = 0
         if isinstance(cell, (_nir.SyncPrint, _nir.SyncProperty)):
             ports["TRG"] = self.sigspec(cell.clk)
-            params["TRG_ENABLE"] = True
-            params["TRG_WIDTH"] = 1
-            params["TRG_POLARITY"] = cell.clk_edge == "pos"
+            parameters["TRG_ENABLE"] = True
+            parameters["TRG_WIDTH"] = 1
+            parameters["TRG_POLARITY"] = cell.clk_edge == "pos"
         if isinstance(cell, (_nir.AsyncPrint, _nir.SyncPrint)):
-            self.builder.cell(f"$print", params=params, ports=ports, src=_src(cell.src_loc))
+            self.builder.cell(f"$print", parameters=parameters, ports=ports, src_loc=cell.src_loc)
         if isinstance(cell, (_nir.AsyncProperty, _nir.SyncProperty)):
-            params["FLAVOR"] = cell.kind
+            parameters["FLAVOR"] = cell.kind
             ports["A"] = self.sigspec(cell.test)
-            self.builder.cell(f"$check", params=params, ports=ports, src=_src(cell.src_loc))
+            self.builder.cell(f"$check", parameters=parameters, ports=ports, src_loc=cell.src_loc)
 
     def emit_any_value(self, cell_idx, cell):
         self.builder.cell(f"${cell.kind}", ports={
-            "Y": self.cell_wires[cell_idx],
-        }, params={
+            "Y": self.cell_wires[cell_idx].name,
+        }, parameters={
             "WIDTH": cell.width,
-        }, src=_src(cell.src_loc))
+        }, src_loc=cell.src_loc)
 
     def emit_initial(self, cell_idx, cell):
         self.builder.cell("$initstate", ports={
-            "Y": self.cell_wires[cell_idx],
-        }, src=_src(cell.src_loc))
+            "Y": self.cell_wires[cell_idx].name,
+        }, src_loc=cell.src_loc)
 
     def emit_instance(self, cell_idx, cell):
         ports = {}
         for name, nets in cell.ports_i.items():
             ports[name] = self.sigspec(nets)
         for name in cell.ports_o:
-            ports[name] = self.instance_wires[cell_idx, name]
+            ports[name] = self.instance_wires[cell_idx, name].name
         for name, (ionets, _dir) in cell.ports_io.items():
             ports[name] = self.io_sigspec(ionets)
-        self.builder.cell(f"\\{cell.type}", cell.name, ports=ports, params=cell.parameters,
-                          attrs=cell.attributes, src=_src(cell.src_loc))
+        self.builder.cell(f"\\{cell.type}", cell.name, ports=ports,
+                          parameters=cell.parameters, attrs=cell.attributes,
+                          src_loc=cell.src_loc)
 
     def emit_cells(self):
         for cell_idx in self.module.cells:
@@ -1068,16 +1231,15 @@ def convert_fragment(fragment, ports=(), name="top", *, emit_src=True, **kwargs)
     name_map = _ast.SignalDict()
     netlist = _ir.build_netlist(fragment, ports=ports, name=name, **kwargs)
     empty_checker = EmptyModuleChecker(netlist)
-    builder = _Builder(emit_src=emit_src)
+    builder = Design(emit_src=emit_src)
     for module_idx, module in enumerate(netlist.modules):
         if empty_checker.is_empty(module_idx):
             continue
-        attrs = {}
+        module_builder = builder.module(".".join(module.name), src_loc=module.src_loc)
         if module_idx == 0:
-            attrs["top"] = 1
-        with builder.module(".".join(module.name), attrs=attrs, src=_src(module.src_loc)) as module_builder:
-            ModuleEmitter(module_builder, netlist, module, name_map,
-                          empty_checker=empty_checker).emit()
+            module_builder.attribute("top", 1)
+        ModuleEmitter(module_builder, netlist, module, name_map,
+                      empty_checker=empty_checker).emit()
     return str(builder), name_map
 
 
diff --git a/amaranth/hdl/_nir.py b/amaranth/hdl/_nir.py
index 045c1b8..828e858 100644
--- a/amaranth/hdl/_nir.py
+++ b/amaranth/hdl/_nir.py
@@ -161,6 +161,10 @@ class Value(tuple):
         else:
             return f"(cat {' '.join(chunks)})"
 
+    @property
+    def is_const(self):
+        return all(net.is_const for net in self)
+
     __str__ = __repr__
 
 
@@ -559,8 +563,8 @@ class Operator(Cell):
 
     The ternary operators are:
 
-    - 'm': like AST, first input needs to have width of 1, second and third operand need to have the same
-      width as output
+    - 'm': multiplexer, first input needs to have width of 1, second and third operand need to have 
+      the same width as output; implements arg0 ? arg1 : arg2
 
     Attributes
     ----------


----

problem_statement: Detect and warn about elaboratables added more than once into the hierarchy
date: 2024-04-02
patch: diff --git a/amaranth/_utils.py b/amaranth/_utils.py
index db2ade1..f1d8ed4 100644
--- a/amaranth/_utils.py
+++ b/amaranth/_utils.py
@@ -7,8 +7,8 @@ from collections import OrderedDict
 from collections.abc import Iterable
 
 
-__all__ = ["flatten", "union", "final", "deprecated", "get_linter_options",
-           "get_linter_option"]
+__all__ = ["flatten", "union", "to_binary", "final", "deprecated",
+           "get_linter_options", "get_linter_option"]
 
 
 def flatten(i):
@@ -27,6 +27,10 @@ def union(i, start=None):
     return r
 
 
+def to_binary(value, width):
+    return format(value, "0{}b".format(width))
+
+
 def final(cls):
     def init_subclass():
         raise TypeError("Subclassing {}.{} is not supported"
diff --git a/amaranth/hdl/_ast.py b/amaranth/hdl/_ast.py
index 551fcc3..57f102e 100644
--- a/amaranth/hdl/_ast.py
+++ b/amaranth/hdl/_ast.py
@@ -16,8 +16,9 @@ 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",
@@ -30,6 +31,14 @@ __all__ = [
 ]
 
 
+class SyntaxError(Exception):
+    pass
+
+
+class SyntaxWarning(Warning):
+    pass
+
+
 class DUID:
     """Deterministic Unique IDentifier."""
     __next_uid = 0
@@ -426,6 +435,37 @@ class ShapeLike(metaclass=_ShapeLikeMeta):
         raise TypeError("ShapeLike is an abstract class and cannot be instantiated")
 
 
+def _normalize_patterns(patterns, shape, *, src_loc_at=1):
+    new_patterns = []
+    for pattern in patterns:
+        orig_pattern = pattern
+        if isinstance(pattern, str):
+            if any(bit not in "01- \t" for bit in pattern):
+                raise SyntaxError(f"Pattern '{pattern}' must consist of 0, 1, and - (don't "
+                                  f"care) bits, and may include whitespace")
+            pattern = "".join(pattern.split()) # remove whitespace
+            if len(pattern) != shape.width:
+                raise SyntaxError(f"Pattern '{orig_pattern}' must have the same width as "
+                                  f"match value (which is {shape.width})")
+        else:
+            try:
+                pattern = Const.cast(pattern)
+            except TypeError as e:
+                raise SyntaxError(f"Pattern must be a string or a constant-castable "
+                                  f"expression, not {pattern!r}") from e
+            cast_pattern = Const(pattern.value, shape)
+            if cast_pattern.value != pattern.value:
+                warnings.warn(f"Pattern '{orig_pattern!r}' "
+                              f"({pattern.shape().width}'{pattern.value:b}) is not "
+                              f"representable in match value shape "
+                              f"({shape!r}); comparison will never be true",
+                              SyntaxWarning, stacklevel=2 + src_loc_at)
+                continue
+            pattern = pattern.value
+        new_patterns.append(pattern)
+    return tuple(new_patterns)
+
+
 def _overridable_by_reflected(method_name):
     """Allow overriding the decorated method.
 
@@ -1248,36 +1288,12 @@ class Value(metaclass=ABCMeta):
             If a pattern has invalid syntax.
         """
         matches = []
-        # This code should accept exactly the same patterns as `with m.Case(...):`.
-        for pattern in patterns:
-            if isinstance(pattern, str) and any(bit not in "01- \t" for bit in pattern):
-                raise SyntaxError("Match pattern '{}' must consist of 0, 1, and - (don't care) "
-                                  "bits, and may include whitespace"
-                                  .format(pattern))
-            if (isinstance(pattern, str) and
-                    len("".join(pattern.split())) != len(self)):
-                raise SyntaxError("Match pattern '{}' must have the same width as match value "
-                                  "(which is {})"
-                                  .format(pattern, len(self)))
+        for pattern in _normalize_patterns(patterns, self.shape()):
             if isinstance(pattern, str):
-                pattern = "".join(pattern.split()) # remove whitespace
-                mask    = int(pattern.replace("0", "1").replace("-", "0"), 2)
-                pattern = int(pattern.replace("-", "0"), 2)
+                mask    = int("0" + pattern.replace("0", "1").replace("-", "0"), 2)
+                pattern = int("0" + pattern.replace("-", "0"), 2)
                 matches.append((self & mask) == pattern)
             else:
-                try:
-                    orig_pattern, pattern = pattern, Const.cast(pattern)
-                except TypeError as e:
-                    raise SyntaxError("Match pattern must be a string or a constant-castable "
-                                      "expression, not {!r}"
-                                      .format(pattern)) from e
-                pattern_len = bits_for(pattern.value)
-                if pattern_len > len(self):
-                    warnings.warn("Match pattern '{!r}' ({}'{:b}) is wider than match value "
-                                  "(which has width {}); comparison will never be true"
-                                  .format(orig_pattern, pattern_len, pattern.value, len(self)),
-                                  SyntaxWarning, stacklevel=2)
-                    continue
                 matches.append(self == pattern)
         if not matches:
             return Const(0)
@@ -1503,7 +1519,13 @@ class ValueLike(metaclass=_ValueLikeMeta):
 class _ConstMeta(ABCMeta):
     def __call__(cls, value, shape=None, src_loc_at=0, **kwargs):
         if isinstance(shape, ShapeCastable):
-            return shape.const(value)
+            value = shape.const(value)
+            cast_shape = Shape.cast(shape)
+            cast_value = Const.cast(value)
+            if cast_value.shape() != cast_shape:
+                raise ValueError(f"Constant returned by {shape!r}.const() must have the shape that "
+                                 f"it casts to, {cast_shape!r}, and not {cast_value.shape()!r}")
+            return value
         return super().__call__(value, shape, **kwargs, src_loc_at=src_loc_at + 1)
 
 
@@ -1542,7 +1564,7 @@ class Const(Value, metaclass=_ConstMeta):
             width = 0
             for part in obj.parts:
                 const  = Const.cast(part)
-                part_value = Const(const.value, unsigned(const.width)).value
+                part_value = Const(const.value, unsigned(len(const))).value
                 value |= part_value << width
                 width += len(const)
             return Const(value, width)
@@ -1567,34 +1589,40 @@ class Const(Value, metaclass=_ConstMeta):
                     category=SyntaxWarning,
                     stacklevel=3)
             shape = Shape.cast(shape, src_loc_at=1 + src_loc_at)
-        self._width  = shape.width
-        self._signed = shape.signed
         if shape.signed and value >> (shape.width - 1) & 1:
             value |= -(1 << shape.width)
         else:
             value &= (1 << shape.width) - 1
+        self._shape = shape
         self._value = value
 
+    def shape(self):
+        return self._shape
+
     @property
     def value(self):
         return self._value
 
+    # TODO(amaranth-0.6): remove
     @property
+    @deprecated("`const.width` is deprecated and will be removed in Amaranth 0.6; use `len(const)` instead")
     def width(self):
-        return self._width
+        return self.shape().width
 
+    # TODO(amaranth-0.6): remove
     @property
+    @deprecated("`const.signed` is deprecated and will be removed in Amaranth 0.6; use `const.shape().signed` instead")
     def signed(self):
-        return self._signed
-
-    def shape(self):
-        return Shape(self.width, self.signed)
+        return self.shape().signed
 
     def _rhs_signals(self):
         return SignalSet()
 
     def __repr__(self):
-        return "(const {}'{}d{})".format(self.width, "s" if self.signed else "", self.value)
+        if self._shape.signed:
+            return f"(const {self._shape.width}'sd{self._value})"
+        else:
+            return f"(const {self._shape.width}'d{self._value})"
 
 
 C = Const  # shorthand
@@ -1864,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)
@@ -1964,15 +2046,15 @@ class Signal(Value, DUID, metaclass=_SignalMeta):
                                 .format(orig_init))
         # Avoid false positives for all-zeroes and all-ones
         if orig_init is not None and not (isinstance(orig_init, int) and orig_init in (0, -1)):
-            if init.shape().signed and not self.signed:
+            if init.shape().signed and not self._signed:
                 warnings.warn(
                     message="Initial value {!r} is signed, but the signal shape is {!r}"
                             .format(orig_init, shape),
                     category=SyntaxWarning,
                     stacklevel=2)
-            elif (init.shape().width > self.width or
-                  init.shape().width == self.width and
-                    self.signed and not init.shape().signed):
+            elif (init.shape().width > self._width or
+                  init.shape().width == self._width and
+                    self._signed and not init.shape().signed):
                 warnings.warn(
                     message="Initial value {!r} will be truncated to the signal shape {!r}"
                             .format(orig_init, shape),
@@ -2030,13 +2112,20 @@ class Signal(Value, DUID, metaclass=_SignalMeta):
         else:
             self._decoder = decoder
 
+    def shape(self):
+        return Shape(self._width, self._signed)
+
+    # TODO(amaranth-0.6): remove
     @property
+    @deprecated("`signal.width` is deprecated and will be removed in Amaranth 0.6; use `len(signal)` instead")
     def width(self):
-        return self._width
+        return self.shape().width
 
+    # TODO(amaranth-0.6): remove
     @property
+    @deprecated("`signal.signed` is deprecated and will be removed in Amaranth 0.6; use `signal.shape().signed` instead")
     def signed(self):
-        return self._signed
+        return self.shape().signed
 
     @property
     def init(self):
@@ -2096,9 +2185,6 @@ class Signal(Value, DUID, metaclass=_SignalMeta):
             kw["init"] = init
         return cls(**kw, src_loc_at=1 + src_loc_at)
 
-    def shape(self):
-        return Shape(self.width, self.signed)
-
     def _lhs_signals(self):
         return SignalSet((self,))
 
@@ -2324,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)
 
@@ -2353,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):
@@ -2731,52 +2878,32 @@ class _LateBoundStatement(Statement):
 
 @final
 class Switch(Statement):
-    def __init__(self, test, cases, *, src_loc=None, src_loc_at=0, case_src_locs={}):
+    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:
             # Switch is a bit special in terms of location tracking because it is usually created
             # long after the control has left the statement that directly caused its creation.
             self.src_loc = src_loc
-        # Switch is also a bit special in that its parts also have location information. It can't
-        # be automatically traced, so whatever constructs a Switch may optionally provide it.
-        self.case_src_locs = {}
 
         self._test  = Value.cast(test)
-        self._cases = OrderedDict()
-        for orig_keys, stmts in cases.items():
-            # Map: None -> (); key -> (key,); (key...) -> (key...)
-            keys = orig_keys
-            if keys is None:
-                keys = ()
-            if not isinstance(keys, tuple):
-                keys = (keys,)
-            # Map: 2 -> "0010"; "0010" -> "0010"
-            new_keys = ()
-            key_mask = (1 << len(self.test)) - 1
-            for key in keys:
-                if isinstance(key, str):
-                    key = "".join(key.split()) # remove whitespace
-                elif isinstance(key, int):
-                    key = format(key & key_mask, "b").rjust(len(self.test), "0")
-                    # fixup for 0-width test
-                    if key_mask == 0:
-                        key = ""
-                elif isinstance(key, Enum):
-                    key = format(key.value & key_mask, "b").rjust(len(self.test), "0")
-                    # fixup for 0-width test
-                    if key_mask == 0:
-                        key = ""
-                else:
-                    raise TypeError("Object {!r} cannot be used as a switch key"
-                                    .format(key))
-                assert len(key) == len(self.test)
-                new_keys = (*new_keys, key)
-            if not isinstance(stmts, Iterable):
-                stmts = [stmts]
-            self._cases[new_keys] = Statement.cast(stmts)
-            if orig_keys in case_src_locs:
-                self.case_src_locs[new_keys] = case_src_locs[orig_keys]
+        new_cases = []
+        for patterns, stmts, case_src_loc in cases:
+            if patterns is not None:
+                # Map: key -> (key,); (key...) -> (key...)
+                if not isinstance(patterns, tuple):
+                    patterns = (patterns,)
+                # Map: 2 -> "0010"; "0010" -> "0010"
+                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, Statement.cast(stmts), case_src_loc))
+        self._cases = tuple(new_cases)
 
     @property
     def test(self):
@@ -2787,22 +2914,22 @@ class Switch(Statement):
         return self._cases
 
     def _lhs_signals(self):
-        return union((s._lhs_signals() for s in self.cases.values()), start=SignalSet())
+        return union((stmts._lhs_signals() for _patterns, stmts, _src_loc in self.cases), start=SignalSet())
 
     def _rhs_signals(self):
-        signals = union((s._rhs_signals() for s in self.cases.values()), start=SignalSet())
+        signals = union((stmts._rhs_signals() for _patterns, stmts, _src_loc in self.cases), start=SignalSet())
         return self.test._rhs_signals() | signals
 
     def __repr__(self):
-        def case_repr(keys, stmts):
+        def case_repr(patterns, stmts):
             stmts_repr = " ".join(map(repr, stmts))
-            if keys == ():
+            if patterns is None:
                 return f"(default {stmts_repr})"
-            elif len(keys) == 1:
-                return f"(case {keys[0]} {stmts_repr})"
+            elif len(patterns) == 1:
+                return f"(case {patterns[0]} {stmts_repr})"
             else:
-                return "(case ({}) {})".format(" ".join(keys), stmts_repr)
-        case_reprs = [case_repr(keys, stmts) for keys, stmts in self.cases.items()]
+                return "(case ({}) {})".format(" ".join(patterns), stmts_repr)
+        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 f3e9fc9..b4c3801 100644
--- a/amaranth/hdl/_ir.py
+++ b/amaranth/hdl/_ir.py
@@ -3,14 +3,14 @@ from collections import defaultdict, OrderedDict
 import enum
 import warnings
 
-from .._utils import flatten
+from .._utils import flatten, to_binary
 from .. import tracer, _unused
 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):
@@ -67,6 +71,7 @@ class Fragment:
         self.generated = OrderedDict()
         self.src_loc = src_loc
         self.origins = None
+        self.domains_propagated_up = {}
 
     def add_driver(self, signal, domain="comb"):
         assert isinstance(domain, str)
@@ -179,22 +184,37 @@ class Fragment:
                 self.subfragments[i] = (DomainRenamer(domain_name_map)(subfrag), name, src_loc)
 
         # Finally, collect the (now unique) subfragment domains, and merge them into our domains.
-        for subfrag, name, src_loc in self.subfragments:
+        for i, (subfrag, name, src_loc) in enumerate(self.subfragments):
+            hier_name = name
+            if hier_name is None:
+                hier_name = f"<unnamed #{i}>"
             for domain_name, domain in subfrag.domains.items():
                 if domain.local:
                     continue
                 self.add_domains(domain)
+                if domain_name in subfrag.domains_propagated_up:
+                    _used_in, defined_in = subfrag.domains_propagated_up[domain_name]
+                else:
+                    defined_in = (*hierarchy, hier_name)
+                self.domains_propagated_up[domain_name] = (hierarchy, defined_in)
 
-    def _propagate_domains_down(self):
+    def _propagate_domains_down(self, hierarchy=("top",)):
         # For each domain defined in this fragment, ensure it also exists in all subfragments.
-        for subfrag, name, src_loc in self.subfragments:
+        for i, (subfrag, name, src_loc) in enumerate(self.subfragments):
+            hier_name = name
+            if hier_name is None:
+                hier_name = f"<unnamed #{i}>"
+
             for domain in self.iter_domains():
                 if domain in subfrag.domains:
                     assert self.domains[domain] is subfrag.domains[domain]
                 else:
                     subfrag.add_domains(self.domains[domain])
+                    if domain in self.domains_propagated_up:
+                        _used_in, defined_in = self.domains_propagated_up[domain]
+                        subfrag.domains_propagated_up[domain] = (hierarchy + (hier_name,)), defined_in
 
-            subfrag._propagate_domains_down()
+            subfrag._propagate_domains_down(hierarchy + (hier_name,))
 
     def _create_missing_domains(self, missing_domain, *, platform=None):
         from ._xfrm import DomainCollector
@@ -309,9 +329,9 @@ class Instance(Fragment):
     def __init__(self, type, *args, src_loc=None, src_loc_at=0, **kwargs):
         super().__init__(src_loc=src_loc or tracer.get_src_loc(src_loc_at))
 
-        self.type        = type
-        self.parameters  = OrderedDict()
-        self.named_ports = OrderedDict()
+        self.type       = type
+        self.parameters = OrderedDict()
+        self.ports      = OrderedDict()
 
         for (kind, name, value) in args:
             if kind == "a":
@@ -324,7 +344,7 @@ class Instance(Fragment):
                 else:
                     if not isinstance(value, _ast.IOValue):
                         value = _ast.Value.cast(value)
-                self.named_ports[name] = (value, kind)
+                self.ports[name] = (value, kind)
             else:
                 raise NameError("Instance argument {!r} should be a tuple (kind, name, value) "
                                 "where kind is one of \"a\", \"p\", \"i\", \"o\", or \"io\""
@@ -338,13 +358,13 @@ class Instance(Fragment):
             elif kw.startswith("i_"):
                 if not isinstance(arg, _ast.IOValue):
                     arg = _ast.Value.cast(arg)
-                self.named_ports[kw[2:]] = (arg, "i")
+                self.ports[kw[2:]] = (arg, "i")
             elif kw.startswith("o_"):
                 if not isinstance(arg, _ast.IOValue):
                     arg = _ast.Value.cast(arg)
-                self.named_ports[kw[2:]] = (arg, "o")
+                self.ports[kw[2:]] = (arg, "o")
             elif kw.startswith("io_"):
-                self.named_ports[kw[3:]] = (_ast.IOValue.cast(arg), "io")
+                self.ports[kw[3:]] = (_ast.IOValue.cast(arg), "io")
             else:
                 raise NameError("Instance keyword argument {}={!r} does not start with one of "
                                 "\"a_\", \"p_\", \"i_\", \"o_\", or \"io_\""
@@ -414,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()
@@ -481,7 +502,7 @@ class Design:
         """Collects used signals and IO ports for a fragment and all its subfragments."""
         from . import _mem
         if isinstance(fragment, _ir.Instance):
-            for conn, kind in fragment.named_ports.values():
+            for conn, kind in fragment.ports.values():
                 if isinstance(conn, _ast.IOValue):
                     for port in conn._ioports():
                         self._use_io_port(fragment, port)
@@ -582,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:
@@ -625,7 +655,7 @@ class NetlistDriver:
 
     def emit_value(self, builder):
         if self.domain is None:
-            init = _ast.Const(self.signal.init, self.signal.width)
+            init = _ast.Const(self.signal.init, len(self.signal))
             default, _signed = builder.emit_rhs(self.module_idx, init)
         else:
             default = builder.emit_signal(self.signal)
@@ -740,11 +770,13 @@ class NetlistEmitter:
         except KeyError:
             pass
         if isinstance(value, _ast.Const):
-            result = _nir.Value.from_const(value.value, value.width)
-            signed = value.signed
+            shape  = value.shape()
+            result = _nir.Value.from_const(value.value, shape.width)
+            signed = shape.signed
         elif isinstance(value, _ast.Signal):
+            shape  = value.shape()
             result = self.emit_signal(value)
-            signed = value.signed
+            signed = shape.signed
         elif isinstance(value, _ast.Operator):
             if len(value.operands) == 1:
                 operand_a, signed_a = self.emit_rhs(module_idx, value.operands[0])
@@ -867,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=(f"{case_index:0{len(index)}b}",),
-                                       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):
@@ -983,7 +1024,7 @@ class NetlistEmitter:
             conds = []
             for case_index in range(num_cases):
                 cell = _nir.Matches(module_idx, value=offset,
-                                    patterns=(f"{case_index:0{len(offset)}b}",),
+                                    patterns=(to_binary(case_index, len(offset)),),
                                     src_loc=lhs.src_loc)
                 subcond, = self.netlist.add_value_cell(1, cell)
                 conds.append(subcond)
@@ -999,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=(f"{case_index:0{len(index)}b}",),
-                                       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')
@@ -1092,20 +1143,25 @@ class NetlistEmitter:
         elif isinstance(stmt, _ast.Switch):
             test, _signed = self.emit_rhs(module_idx, stmt.test)
             conds = []
-            for patterns in stmt.cases:
-                if patterns:
+            case_stmts = []
+            for patterns, stmts, case_src_loc in stmt.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=stmt.case_src_locs.get(patterns))
+                                        src_loc=case_src_loc)
                     net, = self.netlist.add_value_cell(1, cell)
                     conds.append(net)
                 else:
                     conds.append(_nir.Net.from_const(1))
+                case_stmts.append(stmts)
             cell = _nir.PriorityMatch(module_idx, en=cond, inputs=_nir.Value(conds),
                                       src_loc=stmt.src_loc)
             conds = self.netlist.add_value_cell(len(conds), cell)
-            for subcond, substmts in zip(conds, stmt.cases.values()):
+            for subcond, substmts in zip(conds, case_stmts):
                 for substmt in substmts:
                     self.emit_stmt(module_idx, fragment, domain, substmt, subcond)
         else:
@@ -1132,9 +1188,9 @@ class NetlistEmitter:
 
     def emit_memory(self, module_idx: int, fragment: '_mem.MemoryInstance', name: str):
         cell = _nir.Memory(module_idx,
-            width=fragment._width,
-            depth=fragment._depth,
-            init=fragment._init,
+            width=_ast.Shape.cast(fragment._data._shape).width,
+            depth=fragment._data._depth,
+            init=fragment._data._init._raw,
             name=name,
             attributes=fragment._attrs,
             src_loc=fragment.src_loc,
@@ -1194,7 +1250,7 @@ class NetlistEmitter:
         ports_io = {}
         outputs = []
         next_output_bit = 0
-        for port_name, (port_conn, dir) in instance.named_ports.items():
+        for port_name, (port_conn, dir) in instance.ports.items():
             if isinstance(port_conn, _ast.IOValue):
                 if dir == 'i':
                     xlat_dir = _nir.IODirection.Input
@@ -1247,12 +1303,12 @@ class NetlistEmitter:
                 else:
                     dir = PortDirection.Input
             if dir == PortDirection.Input:
-                top.ports_i[name] = (next_input_bit, signal.width)
+                top.ports_i[name] = (next_input_bit, len(signal))
                 value = _nir.Value(
                     _nir.Net.from_cell(0, bit)
-                    for bit in range(next_input_bit, next_input_bit + signal.width)
+                    for bit in range(next_input_bit, next_input_bit + len(signal))
                 )
-                next_input_bit += signal.width
+                next_input_bit += len(signal)
                 self.connect(signal_value, value, src_loc=signal.src_loc)
             elif dir == PortDirection.Output:
                 top.ports_o[name] = signal_value
@@ -1276,7 +1332,7 @@ class NetlistEmitter:
                                           inputs=_nir.Value(cond),
                                           src_loc=driver.domain.rst.src_loc)
                 cond, = self.netlist.add_value_cell(1, cell)
-                init = _nir.Value.from_const(driver.signal.init, driver.signal.width)
+                init = _nir.Value.from_const(driver.signal.init, len(driver.signal))
                 driver.assignments.append(_nir.Assignment(cond=cond, start=0,
                                                          value=init, src_loc=driver.signal.src_loc))
             value = driver.emit_value(self)
diff --git a/amaranth/hdl/_xfrm.py b/amaranth/hdl/_xfrm.py
index 85ff344..ccc91ae 100644
--- a/amaranth/hdl/_xfrm.py
+++ b/amaranth/hdl/_xfrm.py
@@ -1,3 +1,4 @@
+import warnings
 from abc import ABCMeta, abstractmethod
 from collections import OrderedDict
 from collections.abc import Iterable
@@ -57,7 +58,7 @@ class ValueVisitor(metaclass=ABCMeta):
         pass # :nocov:
 
     @abstractmethod
-    def on_ArrayProxy(self, value):
+    def on_SwitchValue(self, value):
         pass # :nocov:
 
     @abstractmethod
@@ -89,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:
@@ -132,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
@@ -182,8 +182,6 @@ class StatementVisitor(metaclass=ABCMeta):
             new_stmt = self.on_unknown_statement(stmt)
         if isinstance(new_stmt, Statement) and self.replace_statement_src_loc(stmt, new_stmt):
             new_stmt.src_loc = stmt.src_loc
-            if isinstance(new_stmt, Switch) and isinstance(stmt, Switch):
-                new_stmt.case_src_locs = stmt.case_src_locs
         if isinstance(new_stmt, (Print, Property)):
             new_stmt._MustUse__used = True
         return new_stmt
@@ -220,7 +218,7 @@ class StatementTransformer(StatementVisitor):
         return Property(stmt.kind, self.on_value(stmt.test), message)
 
     def on_Switch(self, stmt):
-        cases = OrderedDict((k, self.on_statement(s)) for k, s in stmt.cases.items())
+        cases = [(k, self.on_statement(s), l) for k, s, l in stmt.cases]
         return Switch(self.on_value(stmt.test), cases)
 
     def on_statements(self, stmts):
@@ -232,15 +230,15 @@ class FragmentTransformer:
         for subfragment, name, src_loc in fragment.subfragments:
             new_fragment.add_subfragment(self(subfragment), name, src_loc=src_loc)
 
-    def map_named_ports(self, fragment, new_fragment):
+    def map_ports(self, fragment, new_fragment):
         if hasattr(self, "on_value"):
-            for name, (value, dir) in fragment.named_ports.items():
+            for name, (value, dir) in fragment.ports.items():
                 if isinstance(value, Value):
-                    new_fragment.named_ports[name] = self.on_value(value), dir
+                    new_fragment.ports[name] = self.on_value(value), dir
                 else:
-                    new_fragment.named_ports[name] = value, dir
+                    new_fragment.ports[name] = value, dir
         else:
-            new_fragment.named_ports = OrderedDict(fragment.named_ports.items())
+            new_fragment.ports = OrderedDict(fragment.ports.items())
 
     def map_domains(self, fragment, new_fragment):
         for domain in fragment.iter_domains():
@@ -272,10 +270,7 @@ class FragmentTransformer:
     def on_fragment(self, fragment):
         if isinstance(fragment, MemoryInstance):
             new_fragment = MemoryInstance(
-                identity=fragment._identity,
-                width=fragment._width,
-                depth=fragment._depth,
-                init=fragment._init,
+                data=fragment._data,
                 attrs=fragment._attrs,
                 src_loc=fragment.src_loc
             )
@@ -302,7 +297,7 @@ class FragmentTransformer:
         elif isinstance(fragment, Instance):
             new_fragment = Instance(fragment.type, src_loc=fragment.src_loc)
             new_fragment.parameters = OrderedDict(fragment.parameters)
-            self.map_named_ports(fragment, new_fragment)
+            self.map_ports(fragment, new_fragment)
         elif isinstance(fragment, IOBufferInstance):
             if hasattr(self, "on_value"):
                 new_fragment = IOBufferInstance(
@@ -323,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)
@@ -403,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
@@ -431,7 +427,7 @@ class DomainCollector(ValueVisitor, StatementVisitor):
 
     def on_Switch(self, stmt):
         self.on_value(stmt.test)
-        for stmts in stmt.cases.values():
+        for _patterns, stmts, _src_loc in stmt.cases:
             self.on_statement(stmts)
 
     def on_statements(self, stmts):
@@ -452,7 +448,7 @@ class DomainCollector(ValueVisitor, StatementVisitor):
                 self._add_used_domain(port._domain)
 
         if isinstance(fragment, Instance):
-            for name, (value, dir) in fragment.named_ports.items():
+            for name, (value, dir) in fragment.ports.items():
                 if not isinstance(value, IOValue):
                     self.on_value(value)
 
@@ -542,11 +538,26 @@ class DomainRenamer(FragmentTransformer, ValueTransformer, StatementTransformer)
 class DomainLowerer(FragmentTransformer, ValueTransformer, StatementTransformer):
     def __init__(self, domains=None):
         self.domains = domains
+        self.domains_propagated_up = {}
+
+    def _warn_on_propagation_up(self, domain, src_loc):
+        if domain in self.domains_propagated_up:
+            used_in, defined_in = self.domains_propagated_up[domain]
+            common_prefix = []
+            for u, d in zip(used_in, defined_in):
+                if u == d:
+                    common_prefix.append(u)
+            warnings.warn_explicit(f"Domain '{domain}' is used in '{'.'.join(used_in)}', but "
+                                   f"defined in '{'.'.join(defined_in)}', which will not be "
+                                   f"supported in Amaranth 0.6; define the domain in "
+                                   f"'{'.'.join(common_prefix)}' or one of its parents",
+                                   DeprecationWarning, filename=src_loc[0], lineno=src_loc[1])
 
     def _resolve(self, domain, context):
         if domain not in self.domains:
             raise DomainError("Signal {!r} refers to nonexistent domain '{}'"
                               .format(context, domain))
+        self._warn_on_propagation_up(domain, context.src_loc)
         return self.domains[domain]
 
     def map_drivers(self, fragment, new_fragment):
@@ -572,6 +583,14 @@ class DomainLowerer(FragmentTransformer, ValueTransformer, StatementTransformer)
 
     def on_fragment(self, fragment):
         self.domains = fragment.domains
+        self.domains_propagated_up = fragment.domains_propagated_up
+        for domain, statements in fragment.statements.items():
+            self._warn_on_propagation_up(domain, statements[0].src_loc)
+        if isinstance(fragment, MemoryInstance):
+            for port in fragment._read_ports:
+                self._warn_on_propagation_up(port._domain, fragment.src_loc)
+            for port in fragment._write_ports:
+                self._warn_on_propagation_up(port._domain, fragment.src_loc)
         return super().on_fragment(fragment)
 
 
@@ -602,8 +621,8 @@ class _ControlInserter(FragmentTransformer):
 
 class ResetInserter(_ControlInserter):
     def _insert_control(self, fragment, domain, signals):
-        stmts = [s.eq(Const(s.init, s.width)) for s in signals if not s.reset_less]
-        fragment.add_statements(domain, Switch(self.controls[domain], {1: stmts}, src_loc=self.src_loc))
+        stmts = [s.eq(Const(s.init, s.shape())) for s in signals if not s.reset_less]
+        fragment.add_statements(domain, Switch(self.controls[domain], [(1, stmts, None)], src_loc=self.src_loc))
 
 
 class EnableInserter(_ControlInserter):
@@ -611,7 +630,7 @@ class EnableInserter(_ControlInserter):
         if domain in fragment.statements:
             fragment.statements[domain] = _StatementList([Switch(
                 self.controls[domain],
-                {1: fragment.statements[domain]},
+                [(1, fragment.statements[domain], None)],
                 src_loc=self.src_loc,
             )])
 
diff --git a/amaranth/sim/_pyrtl.py b/amaranth/sim/_pyrtl.py
index 8edee13..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,25 +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) in enumerate(stmt.cases.items()):
-            gen_checks = []
-            if not patterns:
-                gen_checks.append(f"True")
-            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 = []
@@ -481,7 +467,6 @@ class _FragmentCompiler:
         domains = set(fragment.statements)
 
         if isinstance(fragment, MemoryInstance):
-            self.state.add_memory(fragment)
             for port in fragment._read_ports:
                 domains.add(port._domain)
             for port in fragment._write_ports:
@@ -510,8 +495,8 @@ class _FragmentCompiler:
                 _StatementCompiler(self.state, emitter, inputs=inputs)(domain_stmts)
 
                 if isinstance(fragment, MemoryInstance):
-                    self.state.add_memory_trigger(domain_process, fragment._identity)
-                    memory_index = self.state.memories[fragment._identity]
+                    self.state.add_memory_trigger(domain_process, fragment._data)
+                    memory_index = self.state.get_memory(fragment._data)
                     rhs = _RHSValueCompiler(self.state, emitter, mode="curr", inputs=inputs)
                     lhs = _LHSValueCompiler(self.state, emitter, rhs=rhs)
 
@@ -554,7 +539,7 @@ class _FragmentCompiler:
                                 emitter.append(f"next_{signal_index} = {signal.init}")
 
                 if isinstance(fragment, MemoryInstance):
-                    memory_index = self.state.memories[fragment._identity]
+                    memory_index = self.state.get_memory(fragment._data)
                     rhs = _RHSValueCompiler(self.state, emitter, mode="curr")
                     lhs = _LHSValueCompiler(self.state, emitter, rhs=rhs)