back.rtlil: fix legalization of Part() with stride.
[nmigen.git] / nmigen / back / rtlil.py
index 6fff6d25114f39df6629a5889402fa0ed55b8502..c074a97f5042fd8c9a04c393192d7efc7077d2ef 100644 (file)
@@ -3,16 +3,26 @@ import textwrap
 from collections import defaultdict, OrderedDict
 from contextlib import contextmanager
 
-from ..tools import bits_for
-from ..hdl import ast, ir, mem, xfrm
+from .._utils import bits_for, flatten
+from ..hdl import ast, rec, ir, mem, xfrm
+
+
+__all__ = ["convert", "convert_fragment"]
 
 
 class _Namer:
     def __init__(self):
         super().__init__()
+        self._anon  = 0
         self._index = 0
         self._names = set()
 
+    def anonymous(self):
+        name = "U$${}".format(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
@@ -26,14 +36,7 @@ class _Namer:
         return name
 
 
-class _Bufferer:
-    _escape_map = str.maketrans({
-        "\"": "\\\"",
-        "\\": "\\\\",
-        "\t": "\\t",
-        "\r": "\\r",
-        "\n": "\\n",
-    })
+class _BufferedBuilder:
     def __init__(self):
         super().__init__()
         self._buffer = io.StringIO()
@@ -44,7 +47,22 @@ class _Bufferer:
     def _append(self, fmt, *args, **kwargs):
         self._buffer.write(fmt.format(*args, **kwargs))
 
-    def attribute(self, name, value, indent=0):
+
+class _ProxiedBuilder:
+    def _append(self, *args, **kwargs):
+        self.rtlil._append(*args, **kwargs)
+
+
+class _AttrBuilder:
+    _escape_map = str.maketrans({
+        "\"": "\\\"",
+        "\\": "\\\\",
+        "\t": "\\t",
+        "\r": "\\r",
+        "\n": "\\n",
+    })
+
+    def _attribute(self, name, value, *, indent=0):
         if isinstance(value, str):
             self._append("{}attribute \\{} \"{}\"\n",
                          "  " * indent, name, value.translate(self._escape_map))
@@ -52,18 +70,20 @@ class _Bufferer:
             self._append("{}attribute \\{} {}\n",
                          "  " * indent, name, int(value))
 
-    def _src(self, src):
+    def _attributes(self, attrs, *, src=None, **kwargs):
+        for name, value in attrs.items():
+            self._attribute(name, value, **kwargs)
         if src:
-            self.attribute("src", src)
+            self._attribute("src", src, **kwargs)
 
 
-class _Builder(_Namer, _Bufferer):
+class _Builder(_Namer, _BufferedBuilder):
     def module(self, name=None, attrs={}):
         name = self._make_name(name, local=False)
         return _ModuleBuilder(self, name, attrs)
 
 
-class _ModuleBuilder(_Namer, _Bufferer):
+class _ModuleBuilder(_Namer, _BufferedBuilder, _AttrBuilder):
     def __init__(self, rtlil, name, attrs):
         super().__init__()
         self.rtlil = rtlil
@@ -72,8 +92,7 @@ class _ModuleBuilder(_Namer, _Bufferer):
         self.attrs.update(attrs)
 
     def __enter__(self):
-        for name, value in self.attrs.items():
-            self.attribute(name, value, indent=0)
+        self._attributes(self.attrs)
         self._append("module {}\n", self.name)
         return self
 
@@ -81,11 +100,8 @@ class _ModuleBuilder(_Namer, _Bufferer):
         self._append("end\n")
         self.rtlil._buffer.write(str(self))
 
-    def attribute(self, name, value, indent=1):
-        super().attribute(name, value, indent)
-
-    def wire(self, width, port_id=None, port_kind=None, name=None, src=""):
-        self._src(src)
+    def wire(self, width, port_id=None, port_kind=None, name=None, attrs={}, src=""):
+        self._attributes(attrs, src=src, indent=1)
         name = self._make_name(name, local=False)
         if port_id is None:
             self._append("  wire width {} {}\n", width, name)
@@ -97,42 +113,51 @@ class _ModuleBuilder(_Namer, _Bufferer):
     def connect(self, lhs, rhs):
         self._append("  connect {} {}\n", lhs, rhs)
 
-    def memory(self, width, size, name=None, src=""):
-        self._src(src)
+    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={}, src=""):
-        self._src(src)
+    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, str):
                 self._append("    parameter \\{} \"{}\"\n",
                              param, value.translate(self._escape_map))
-            else:
-                self._append("    parameter \\{} {:d}\n",
+            elif isinstance(value, int):
+                self._append("    parameter \\{} {}'{:b}\n",
+                             param, bits_for(value), value)
+            elif isinstance(value, float):
+                self._append("    parameter real \\{} \"{!r}\"\n",
                              param, value)
+            elif isinstance(value, ast.Const):
+                self._append("    parameter \\{} {}'{:b}\n",
+                             param, len(value), value.value)
+            else:
+                assert False, "Bad parameter {!r}".format(value)
         for port, wire in ports.items():
             self._append("    connect {} {}\n", port, wire)
         self._append("  end\n")
         return name
 
-    def process(self, name=None, src=""):
+    def process(self, name=None, attrs={}, src=""):
         name = self._make_name(name, local=True)
-        return _ProcessBuilder(self, name, src)
+        return _ProcessBuilder(self, name, attrs, src)
 
 
-class _ProcessBuilder(_Bufferer):
-    def __init__(self, rtlil, name, src):
+class _ProcessBuilder(_BufferedBuilder, _AttrBuilder):
+    def __init__(self, rtlil, name, attrs, src):
         super().__init__()
         self.rtlil = rtlil
         self.name  = name
+        self.attrs = {}
         self.src   = src
 
     def __enter__(self):
-        self._src(self.src)
+        self._attributes(self.attrs, src=self.src, indent=1)
         self._append("  process {}\n", self.name)
         return self
 
@@ -147,7 +172,7 @@ class _ProcessBuilder(_Bufferer):
         return _SyncBuilder(self, kind, cond)
 
 
-class _CaseBuilder:
+class _CaseBuilder(_ProxiedBuilder):
     def __init__(self, rtlil, indent):
         self.rtlil  = rtlil
         self.indent = indent
@@ -159,35 +184,39 @@ class _CaseBuilder:
         pass
 
     def assign(self, lhs, rhs):
-        self.rtlil._append("{}assign {} {}\n", "  " * self.indent, lhs, rhs)
+        self._append("{}assign {} {}\n", "  " * self.indent, lhs, rhs)
 
-    def switch(self, cond):
-        return _SwitchBuilder(self.rtlil, cond, self.indent)
+    def switch(self, cond, attrs={}, src=""):
+        return _SwitchBuilder(self.rtlil, cond, attrs, src, self.indent)
 
 
-class _SwitchBuilder:
-    def __init__(self, rtlil, cond, indent):
+class _SwitchBuilder(_ProxiedBuilder, _AttrBuilder):
+    def __init__(self, rtlil, cond, attrs, src, indent):
         self.rtlil  = rtlil
         self.cond   = cond
+        self.attrs  = attrs
+        self.src    = src
         self.indent = indent
 
     def __enter__(self):
-        self.rtlil._append("{}switch {}\n", "  " * self.indent, self.cond)
+        self._attributes(self.attrs, src=self.src, indent=self.indent)
+        self._append("{}switch {}\n", "  " * self.indent, self.cond)
         return self
 
     def __exit__(self, *args):
-        self.rtlil._append("{}end\n", "  " * self.indent)
+        self._append("{}end\n", "  " * self.indent)
 
-    def case(self, value=None):
-        if value is None:
-            self.rtlil._append("{}case\n", "  " * (self.indent + 1))
+    def case(self, *values, attrs={}, src=""):
+        self._attributes(attrs, src=src, indent=self.indent + 1)
+        if values == ():
+            self._append("{}case\n", "  " * (self.indent + 1))
         else:
-            self.rtlil._append("{}case {}'{}\n", "  " * (self.indent + 1),
-                               len(value), value)
+            self._append("{}case {}\n", "  " * (self.indent + 1),
+                         ", ".join("{}'{}".format(len(value), value) for value in values))
         return _CaseBuilder(self.rtlil, self.indent + 2)
 
 
-class _SyncBuilder:
+class _SyncBuilder(_ProxiedBuilder):
     def __init__(self, rtlil, kind, cond):
         self.rtlil = rtlil
         self.kind  = kind
@@ -195,27 +224,34 @@ class _SyncBuilder:
 
     def __enter__(self):
         if self.cond is None:
-            self.rtlil._append("    sync {}\n", self.kind)
+            self._append("    sync {}\n", self.kind)
         else:
-            self.rtlil._append("    sync {} {}\n", self.kind, self.cond)
+            self._append("    sync {} {}\n", self.kind, self.cond)
         return self
 
     def __exit__(self, *args):
         pass
 
     def update(self, lhs, rhs):
-        self.rtlil._append("      update {} {}\n", lhs, rhs)
+        self._append("      update {} {}\n", lhs, rhs)
 
 
 def src(src_loc):
+    if src_loc is None:
+        return None
     file, line = src_loc
     return "{}:{}".format(file, line)
 
 
+def srcs(src_locs):
+    return "|".join(sorted(filter(lambda x: x, map(src, src_locs))))
+
+
 class LegalizeValue(Exception):
-    def __init__(self, value, branches):
+    def __init__(self, value, branches, src_loc):
         self.value    = value
         self.branches = list(branches)
+        self.src_loc  = src_loc
 
 
 class _ValueCompilerState:
@@ -224,6 +260,7 @@ class _ValueCompilerState:
         self.wires  = ast.SignalDict()
         self.driven = ast.SignalDict()
         self.ports  = ast.SignalDict()
+        self.anys   = ast.ValueDict()
 
         self.expansions = ast.ValueDict()
 
@@ -241,6 +278,9 @@ class _ValueCompilerState:
         self.ports[signal] = (len(self.ports), kind)
 
     def resolve(self, signal, prefix=None):
+        if len(signal) == 0:
+            return "{ }", "{ }"
+
         if signal in self.wires:
             return self.wires[signal]
 
@@ -253,13 +293,12 @@ class _ValueCompilerState:
         else:
             wire_name = signal.name
 
-        for attr_name, attr_signal in signal.attrs.items():
-            self.rtlil.attribute(attr_name, attr_signal)
-        wire_curr = self.rtlil.wire(width=signal.nbits, name=wire_name,
+        wire_curr = self.rtlil.wire(width=signal.width, name=wire_name,
                                     port_id=port_id, port_kind=port_kind,
+                                    attrs=signal.attrs,
                                     src=src(signal.src_loc))
-        if signal in self.driven:
-            wire_next = self.rtlil.wire(width=signal.nbits, name=wire_curr + "$next",
+        if signal in self.driven and self.driven[signal]:
+            wire_next = self.rtlil.wire(width=signal.width, name=wire_curr + "$next",
                                         src=src(signal.src_loc))
         else:
             wire_next = None
@@ -286,13 +325,10 @@ class _ValueCompilerState:
             del self.expansions[value]
 
 
-class _ValueCompiler(xfrm.AbstractValueTransformer):
+class _ValueCompiler(xfrm.ValueVisitor):
     def __init__(self, state):
         self.s = state
 
-    def on_value(self, value):
-        return super().on_value(self.s.expand(value))
-
     def on_unknown(self, value):
         if value is None:
             return None
@@ -305,6 +341,15 @@ class _ValueCompiler(xfrm.AbstractValueTransformer):
     def on_ResetSignal(self, value):
         raise NotImplementedError # :nocov:
 
+    def on_Sample(self, value):
+        raise NotImplementedError # :nocov:
+
+    def on_Initial(self, value):
+        raise NotImplementedError # :nocov:
+
+    def on_Record(self, value):
+        return self(ast.Cat(value.fields.values()))
+
     def on_Cat(self, value):
         return "{{ {} }}".format(" ".join(reversed([self(o) for o in value.parts])))
 
@@ -312,24 +357,29 @@ class _ValueCompiler(xfrm.AbstractValueTransformer):
         raise NotImplementedError # :nocov:
 
     def on_Slice(self, value):
-        if value.start == 0 and value.end == len(value.value):
+        if value.start == 0 and value.stop == len(value.value):
             return self(value.value)
 
         sigspec = self._prepare_value_for_Slice(value.value)
-        if value.start + 1 == value.end:
+        if value.start == value.stop:
+            return "{}"
+        elif value.start + 1 == value.stop:
             return "{} [{}]".format(sigspec, value.start)
         else:
-            return "{} [{}:{}]".format(sigspec, value.end - 1, value.start)
+            return "{} [{}:{}]".format(sigspec, value.stop - 1, value.start)
 
     def on_ArrayProxy(self, value):
         index = self.s.expand(value.index)
         if isinstance(index, ast.Const):
             if index.value < len(value.elems):
-                return self(value.elems[index.value])
+                elem = value.elems[index.value]
             else:
-                return self(value.elems[-1])
+                elem = value.elems[-1]
+            return self.match_shape(elem, *value.shape())
         else:
-            raise LegalizeValue(value.index, range(len(value.elems)))
+            max_index = 1 << len(value.index)
+            max_elem  = len(value.elems)
+            raise LegalizeValue(value.index, range(min(max_index, max_elem)), value.src_loc)
 
 
 class _RHSValueCompiler(_ValueCompiler):
@@ -337,10 +387,13 @@ class _RHSValueCompiler(_ValueCompiler):
         (1, "~"):    "$not",
         (1, "-"):    "$neg",
         (1, "b"):    "$reduce_bool",
+        (1, "r|"):   "$reduce_or",
+        (1, "r&"):   "$reduce_and",
+        (1, "r^"):   "$reduce_xor",
         (2, "+"):    "$add",
         (2, "-"):    "$sub",
         (2, "*"):    "$mul",
-        (2, "/"):    "$div",
+        (2, "//"):   "$div",
         (2, "%"):    "$mod",
         (2, "**"):   "$pow",
         (2, "<<"):   "$sshl",
@@ -357,11 +410,43 @@ class _RHSValueCompiler(_ValueCompiler):
         (3, "m"):    "$mux",
     }
 
+    def on_value(self, value):
+        return super().on_value(self.s.expand(value))
+
     def on_Const(self, value):
         if isinstance(value.value, str):
-            return "{}'{}".format(value.nbits, value.value)
+            return "{}'{}".format(value.width, value.value)
         else:
-            return "{}'{:0{}b}".format(value.nbits, value.value, value.nbits)
+            value_twos_compl = value.value & ((1 << value.width) - 1)
+            return "{}'{:0{}b}".format(value.width, value_twos_compl, value.width)
+
+    def on_AnyConst(self, value):
+        if value in self.s.anys:
+            return self.s.anys[value]
+
+        res_bits, res_sign = value.shape()
+        res = self.s.rtlil.wire(width=res_bits, src=src(value.src_loc))
+        self.s.rtlil.cell("$anyconst", ports={
+            "\\Y": res,
+        }, params={
+            "WIDTH": res_bits,
+        }, src=src(value.src_loc))
+        self.s.anys[value] = res
+        return res
+
+    def on_AnySeq(self, value):
+        if value in self.s.anys:
+            return self.s.anys[value]
+
+        res_bits, res_sign = value.shape()
+        res = self.s.rtlil.wire(width=res_bits, src=src(value.src_loc))
+        self.s.rtlil.cell("$anyseq", ports={
+            "\\Y": res,
+        }, params={
+            "WIDTH": res_bits,
+        }, src=src(value.src_loc))
+        self.s.anys[value] = res
+        return res
 
     def on_Signal(self, value):
         wire_curr, wire_next = self.s.resolve(value)
@@ -369,10 +454,14 @@ class _RHSValueCompiler(_ValueCompiler):
 
     def on_Operator_unary(self, value):
         arg, = value.operands
+        if value.operator in ("u", "s"):
+            # These operators don't change the bit pattern, only its interpretation.
+            return self(arg)
+
         arg_bits, arg_sign = arg.shape()
         res_bits, res_sign = value.shape()
-        res = self.s.rtlil.wire(width=res_bits)
-        self.s.rtlil.cell(self.operator_map[(1, value.op)], ports={
+        res = self.s.rtlil.wire(width=res_bits, src=src(value.src_loc))
+        self.s.rtlil.cell(self.operator_map[(1, value.operator)], ports={
             "\\A": self(arg),
             "\\Y": res,
         }, params={
@@ -384,13 +473,13 @@ class _RHSValueCompiler(_ValueCompiler):
 
     def match_shape(self, value, new_bits, new_sign):
         if isinstance(value, ast.Const):
-            return self(ast.Const(value.value, (new_bits, new_sign)))
+            return self(ast.Const(value.value, ast.Shape(new_bits, new_sign)))
 
         value_bits, value_sign = value.shape()
         if new_bits <= value_bits:
             return self(ast.Slice(value, 0, new_bits))
 
-        res = self.s.rtlil.wire(width=new_bits)
+        res = self.s.rtlil.wire(width=new_bits, src=src(value.src_loc))
         self.s.rtlil.cell("$pos", ports={
             "\\A": self(value),
             "\\Y": res,
@@ -405,7 +494,7 @@ class _RHSValueCompiler(_ValueCompiler):
         lhs, rhs = value.operands
         lhs_bits, lhs_sign = lhs.shape()
         rhs_bits, rhs_sign = rhs.shape()
-        if lhs_sign == rhs_sign:
+        if lhs_sign == rhs_sign or value.operator in ("<<", ">>", "**"):
             lhs_wire = self(lhs)
             rhs_wire = self(rhs)
         else:
@@ -414,8 +503,8 @@ class _RHSValueCompiler(_ValueCompiler):
             lhs_wire = self.match_shape(lhs, lhs_bits, lhs_sign)
             rhs_wire = self.match_shape(rhs, rhs_bits, rhs_sign)
         res_bits, res_sign = value.shape()
-        res = self.s.rtlil.wire(width=res_bits)
-        self.s.rtlil.cell(self.operator_map[(2, value.op)], ports={
+        res = self.s.rtlil.wire(width=res_bits, src=src(value.src_loc))
+        self.s.rtlil.cell(self.operator_map[(2, value.operator)], ports={
             "\\A": lhs_wire,
             "\\B": rhs_wire,
             "\\Y": res,
@@ -426,20 +515,32 @@ class _RHSValueCompiler(_ValueCompiler):
             "B_WIDTH": rhs_bits,
             "Y_WIDTH": res_bits,
         }, src=src(value.src_loc))
+        if value.operator in ("//", "%"):
+            # RTLIL leaves division by zero undefined, but we require it to return zero.
+            divmod_res = res
+            res = self.s.rtlil.wire(width=res_bits, src=src(value.src_loc))
+            self.s.rtlil.cell("$mux", ports={
+                "\\A": divmod_res,
+                "\\B": self(ast.Const(0, ast.Shape(res_bits, res_sign))),
+                "\\S": self(lhs == 0),
+                "\\Y": res,
+            }, params={
+                "WIDTH": res_bits
+            }, src=src(value.src_loc))
         return res
 
     def on_Operator_mux(self, value):
-        sel, lhs, rhs = value.operands
-        lhs_bits, lhs_sign = lhs.shape()
-        rhs_bits, rhs_sign = rhs.shape()
+        sel, val1, val0 = value.operands
+        val1_bits, val1_sign = val1.shape()
+        val0_bits, val0_sign = val0.shape()
         res_bits, res_sign = value.shape()
-        lhs_bits = rhs_bits = res_bits = max(lhs_bits, rhs_bits, res_bits)
-        lhs_wire = self.match_shape(lhs, lhs_bits, lhs_sign)
-        rhs_wire = self.match_shape(rhs, rhs_bits, rhs_sign)
-        res = self.s.rtlil.wire(width=res_bits)
+        val1_bits = val0_bits = res_bits = max(val1_bits, val0_bits, res_bits)
+        val1_wire = self.match_shape(val1, val1_bits, val1_sign)
+        val0_wire = self.match_shape(val0, val0_bits, val0_sign)
+        res = self.s.rtlil.wire(width=res_bits, src=src(value.src_loc))
         self.s.rtlil.cell("$mux", ports={
-            "\\A": lhs_wire,
-            "\\B": rhs_wire,
+            "\\A": val0_wire,
+            "\\B": val1_wire,
             "\\S": self(sel),
             "\\Y": res,
         }, params={
@@ -453,7 +554,7 @@ class _RHSValueCompiler(_ValueCompiler):
         elif len(value.operands) == 2:
             return self.on_Operator_binary(value)
         elif len(value.operands) == 3:
-            assert value.op == "m"
+            assert value.operator == "m"
             return self.on_Operator_mux(value)
         else:
             raise TypeError # :nocov:
@@ -462,18 +563,20 @@ class _RHSValueCompiler(_ValueCompiler):
         if isinstance(value, (ast.Signal, ast.Slice, ast.Cat)):
             sigspec = self(value)
         else:
-            sigspec = self.s.rtlil.wire(len(value))
+            sigspec = self.s.rtlil.wire(len(value), src=src(value.src_loc))
             self.s.rtlil.connect(sigspec, self(value))
         return sigspec
 
     def on_Part(self, value):
         lhs, rhs = value.value, value.offset
+        if value.stride != 1:
+            rhs *= value.stride
         lhs_bits, lhs_sign = lhs.shape()
         rhs_bits, rhs_sign = rhs.shape()
         res_bits, res_sign = value.shape()
-        res = self.s.rtlil.wire(width=res_bits)
+        res = self.s.rtlil.wire(width=res_bits, src=src(value.src_loc))
         # Note: Verilog's x[o+:w] construct produces a $shiftx cell, not a $shift cell.
-        # However, Migen's semantics defines the out-of-range bits to be zero, so it is correct
+        # However, nMigen's semantics defines the out-of-range bits to be zero, so it is correct
         # to use a $shift cell here instead, even though it produces less idiomatic Verilog.
         self.s.rtlil.cell("$shift", ports={
             "\\A": self(lhs),
@@ -496,88 +599,178 @@ class _LHSValueCompiler(_ValueCompiler):
     def on_Const(self, value):
         raise TypeError # :nocov:
 
+    def on_AnyConst(self, value):
+        raise TypeError # :nocov:
+
+    def on_AnySeq(self, value):
+        raise TypeError # :nocov:
+
     def on_Operator(self, value):
         raise TypeError # :nocov:
 
+    def match_shape(self, value, new_bits, new_sign):
+        value_bits, value_sign = value.shape()
+        if new_bits == value_bits:
+            return self(value)
+        elif new_bits < value_bits:
+            return self(ast.Slice(value, 0, new_bits))
+        else: # new_bits > value_bits
+            dummy_bits = new_bits - value_bits
+            dummy_wire = self.s.rtlil.wire(dummy_bits)
+            return "{{ {} {} }}".format(dummy_wire, self(value))
+
     def on_Signal(self, value):
-        wire_curr, wire_next = self.s.resolve(value)
-        if wire_next is None:
+        if value not in self.s.driven:
             raise ValueError("No LHS wire for non-driven signal {}".format(repr(value)))
-        return wire_next
+        wire_curr, wire_next = self.s.resolve(value)
+        return wire_next or wire_curr
 
     def _prepare_value_for_Slice(self, value):
-        assert isinstance(value, (ast.Signal, ast.Slice, ast.Cat))
+        assert isinstance(value, (ast.Signal, ast.Slice, ast.Cat, rec.Record))
         return self(value)
 
     def on_Part(self, value):
         offset = self.s.expand(value.offset)
         if isinstance(offset, ast.Const):
-            return self(ast.Slice(value.value, offset.value, offset.value + value.width))
+            if offset.value == len(value.value):
+                dummy_wire = self.s.rtlil.wire(value.width)
+                return dummy_wire
+            return self(ast.Slice(value.value,
+                                  offset.value * value.stride,
+                                  offset.value * value.stride + value.width))
         else:
-            raise LegalizeValue(value.offset, range((1 << len(value.offset)) - 1))
+            # Only so many possible parts. The amount of branches is exponential; if value.offset
+            # is large (e.g. 32-bit wide), trying to naively legalize it is likely to exhaust
+            # system resources.
+            max_branches = len(value.value) // value.stride + 1
+            raise LegalizeValue(value.offset,
+                                range(1 << len(value.offset))[:max_branches],
+                                value.src_loc)
 
     def on_Repl(self, value):
         raise TypeError # :nocov:
 
 
-class _StatementCompiler(xfrm.AbstractStatementTransformer):
+class _StatementCompiler(xfrm.StatementVisitor):
     def __init__(self, state, rhs_compiler, lhs_compiler):
         self.state        = state
         self.rhs_compiler = rhs_compiler
         self.lhs_compiler = lhs_compiler
 
-        self._case = None
+        self._case        = None
+        self._test_cache  = {}
+        self._has_rhs     = False
+        self._wrap_assign = False
 
     @contextmanager
-    def case(self, switch, value):
+    def case(self, switch, values, attrs={}, src=""):
         try:
             old_case = self._case
-            with switch.case(value) as self._case:
+            with switch.case(*values, attrs=attrs, src=src) as self._case:
                 yield
         finally:
             self._case = old_case
 
+    def _check_rhs(self, value):
+        if self._has_rhs or next(iter(value._rhs_signals()), None) is not None:
+            self._has_rhs = True
+
     def on_Assign(self, stmt):
-        if isinstance(stmt, ast.Assign):
-            lhs_bits, lhs_sign = stmt.lhs.shape()
-            rhs_bits, rhs_sign = stmt.rhs.shape()
-            if lhs_bits == rhs_bits:
-                rhs_sigspec = self.rhs_compiler(stmt.rhs)
-            else:
-                # In RTLIL, LHS and RHS of assignment must have exactly same width.
-                rhs_sigspec = self.rhs_compiler.match_shape(
-                    stmt.rhs, lhs_bits, rhs_sign)
+        self._check_rhs(stmt.rhs)
+
+        lhs_bits, lhs_sign = stmt.lhs.shape()
+        rhs_bits, rhs_sign = stmt.rhs.shape()
+        if lhs_bits == rhs_bits:
+            rhs_sigspec = self.rhs_compiler(stmt.rhs)
+        else:
+            # In RTLIL, LHS and RHS of assignment must have exactly same width.
+            rhs_sigspec = self.rhs_compiler.match_shape(
+                stmt.rhs, lhs_bits, lhs_sign)
+        if self._wrap_assign:
+            # In RTLIL, all assigns are logically sequenced before all switches, even if they are
+            # interleaved in the source. In nMigen, the source ordering is used. To handle this
+            # mismatch, we wrap all assigns following a switch in a dummy switch.
+            with self._case.switch("{ }") as wrap_switch:
+                with wrap_switch.case() as wrap_case:
+                    wrap_case.assign(self.lhs_compiler(stmt.lhs), rhs_sigspec)
+        else:
             self._case.assign(self.lhs_compiler(stmt.lhs), rhs_sigspec)
 
+    def on_property(self, stmt):
+        self(stmt._check.eq(stmt.test))
+        self(stmt._en.eq(1))
+
+        en_wire = self.rhs_compiler(stmt._en)
+        check_wire = self.rhs_compiler(stmt._check)
+        self.state.rtlil.cell("$" + stmt._kind, ports={
+            "\\A": check_wire,
+            "\\EN": en_wire,
+        }, src=src(stmt.src_loc))
+
+    on_Assert = on_property
+    on_Assume = on_property
+    on_Cover  = on_property
+
     def on_Switch(self, stmt):
-        with self._case.switch(self.rhs_compiler(stmt.test)) as switch:
-            for value, stmts in stmt.cases.items():
-                with self.case(switch, value):
+        self._check_rhs(stmt.test)
+
+        if not self.state.expansions:
+            # We repeatedly translate the same switches over and over (see the LHSGroupAnalyzer
+            # related code below), and translating the switch test only once helps readability.
+            if stmt not in self._test_cache:
+                self._test_cache[stmt] = self.rhs_compiler(stmt.test)
+            test_sigspec = self._test_cache[stmt]
+        else:
+            # However, if the switch test contains an illegal value, then it may not be cached
+            # (since the illegal value will be repeatedly replaced with different constants), so
+            # don't cache anything in that case.
+            test_sigspec = self.rhs_compiler(stmt.test)
+
+        with self._case.switch(test_sigspec, src=src(stmt.src_loc)) as switch:
+            for values, stmts in stmt.cases.items():
+                case_attrs = {}
+                if values in stmt.case_src_locs:
+                    case_attrs["src"] = src(stmt.case_src_locs[values])
+                if isinstance(stmt.test, ast.Signal) and stmt.test.decoder:
+                    decoded_values = []
+                    for value in values:
+                        if "-" in value:
+                            decoded_values.append("<multiple>")
+                        else:
+                            decoded_values.append(stmt.test.decoder(int(value, 2)))
+                    case_attrs["nmigen.decoding"] = "|".join(decoded_values)
+                with self.case(switch, values, attrs=case_attrs):
+                    self._wrap_assign = False
                     self.on_statements(stmts)
+        self._wrap_assign = True
 
     def on_statement(self, stmt):
         try:
             super().on_statement(stmt)
         except LegalizeValue as legalize:
-            with self._case.switch(self.rhs_compiler(legalize.value)) as switch:
-                bits, sign = legalize.value.shape()
-                tests = ["{:0{}b}".format(v, bits) for v in legalize.branches]
-                tests[-1] = "-" * bits
+            with self._case.switch(self.rhs_compiler(legalize.value),
+                                   src=src(legalize.src_loc)) as switch:
+                shape = legalize.value.shape()
+                tests = ["{:0{}b}".format(v, shape.width) for v in legalize.branches]
+                if tests:
+                    tests[-1] = "-" * shape.width
                 for branch, test in zip(legalize.branches, tests):
-                    with self.case(switch, test):
-                        branch_value = ast.Const(branch, (bits, sign))
+                    with self.case(switch, (test,)):
+                        self._wrap_assign = False
+                        branch_value = ast.Const(branch, shape)
                         with self.state.expand_to(legalize.value, branch_value):
-                            super().on_statement(stmt)
+                            self.on_statement(stmt)
+            self._wrap_assign = True
 
     def on_statements(self, stmts):
         for stmt in stmts:
             self.on_statement(stmt)
 
 
-def convert_fragment(builder, fragment, name, top):
+def _convert_fragment(builder, fragment, name_map, hierarchy):
     if isinstance(fragment, ir.Instance):
         port_map = OrderedDict()
-        for port_name, value in fragment.named_ports.items():
+        for port_name, (value, dir) in fragment.named_ports.items():
             port_map["\\{}".format(port_name)] = value
 
         if fragment.type[0] == "$":
@@ -585,14 +778,23 @@ def convert_fragment(builder, fragment, name, top):
         else:
             return "\\{}".format(fragment.type), port_map
 
-    with builder.module(name or "anonymous", attrs={"top": 1} if top else {}) as module:
+    module_name  = hierarchy[-1] or "anonymous"
+    module_attrs = OrderedDict()
+    if len(hierarchy) == 1:
+        module_attrs["top"] = 1
+    module_attrs["nmigen.hierarchy"] = ".".join(name or "anonymous" for name in hierarchy)
+
+    with builder.module(module_name, attrs=module_attrs) as module:
         compiler_state = _ValueCompilerState(module)
         rhs_compiler   = _RHSValueCompiler(compiler_state)
         lhs_compiler   = _LHSValueCompiler(compiler_state)
         stmt_compiler  = _StatementCompiler(compiler_state, rhs_compiler, lhs_compiler)
 
+        verilog_trigger = None
+        verilog_trigger_sync_emitted = False
+
         # Register all signals driven in the current fragment. This must be done first, as it
-        # affects further codegen; e.g. whether sig$next signals will be generated and used.
+        # affects further codegen; e.g. whether \sig$next signals will be generated and used.
         for domain, signal in fragment.iter_drivers():
             compiler_state.add_driven(signal, sync=domain is not None)
 
@@ -615,6 +817,12 @@ def convert_fragment(builder, fragment, name, top):
         # name) names.
         memories = OrderedDict()
         for subfragment, sub_name in fragment.subfragments:
+            if not subfragment.ports:
+                continue
+
+            if sub_name is None:
+                sub_name = module.anonymous()
+
             sub_params = OrderedDict()
             if hasattr(subfragment, "parameters"):
                 for param_name, param_value in subfragment.parameters.items():
@@ -622,93 +830,190 @@ def convert_fragment(builder, fragment, name, top):
                         memory = param_value
                         if memory not in memories:
                             memories[memory] = module.memory(width=memory.width, size=memory.depth,
-                                                             name=memory.name)
+                                                             name=memory.name, attrs=memory.attrs)
                             addr_bits = bits_for(memory.depth)
+                            data_parts = []
+                            data_mask = (1 << memory.width) - 1
                             for addr in range(memory.depth):
                                 if addr < len(memory.init):
-                                    data = memory.init[addr]
+                                    data = memory.init[addr] & data_mask
                                 else:
                                     data = 0
-                                module.cell("$meminit", ports={
-                                    "\\ADDR": rhs_compiler(ast.Const(addr, addr_bits)),
-                                    "\\DATA": rhs_compiler(ast.Const(data, memory.width)),
-                                }, params={
-                                    "MEMID": memories[memory],
-                                    "ABITS": addr_bits,
-                                    "WIDTH": memory.width,
-                                    "WORDS": 1,
-                                    "PRIORITY": 0,
-                                })
+                                data_parts.append("{:0{}b}".format(data, memory.width))
+                            module.cell("$meminit", ports={
+                                "\\ADDR": rhs_compiler(ast.Const(0, addr_bits)),
+                                "\\DATA": "{}'".format(memory.width * memory.depth) +
+                                          "".join(reversed(data_parts)),
+                            }, params={
+                                "MEMID": memories[memory],
+                                "ABITS": addr_bits,
+                                "WIDTH": memory.width,
+                                "WORDS": memory.depth,
+                                "PRIORITY": 0,
+                            })
 
                         param_value = memories[memory]
 
                     sub_params[param_name] = param_value
 
             sub_type, sub_port_map = \
-                convert_fragment(builder, subfragment, top=False, name=sub_name)
+                _convert_fragment(builder, subfragment, name_map,
+                                  hierarchy=hierarchy + (sub_name,))
 
             sub_ports = OrderedDict()
             for port, value in sub_port_map.items():
-                for signal in value._rhs_signals():
-                    compiler_state.resolve_curr(signal, prefix=sub_name)
+                if not isinstance(subfragment, ir.Instance):
+                    for signal in value._rhs_signals():
+                        compiler_state.resolve_curr(signal, prefix=sub_name)
                 sub_ports[port] = rhs_compiler(value)
 
-            module.cell(sub_type, name=sub_name, ports=sub_ports, params=sub_params)
-
-        with module.process() as process:
-            with process.case() as case:
-                # For every signal in comb domain, assign \sig$next to the reset value.
-                # For every signal in sync domains, assign \sig$next to the current value (\sig).
-                for domain, signal in fragment.iter_drivers():
+            module.cell(sub_type, name=sub_name, ports=sub_ports, params=sub_params,
+                        attrs=subfragment.attrs)
+
+        # If we emit all of our combinatorial logic into a single RTLIL process, Verilog
+        # simulators will break horribly, because Yosys write_verilog transforms RTLIL processes
+        # into always @* blocks with blocking assignment, and that does not create delta cycles.
+        #
+        # Therefore, we translate the fragment as many times as there are independent groups
+        # of signals (a group is a transitive closure of signals that appear together on LHS),
+        # splitting them into many RTLIL (and thus Verilog) processes.
+        lhs_grouper = xfrm.LHSGroupAnalyzer()
+        lhs_grouper.on_statements(fragment.statements)
+
+        for group, group_signals in lhs_grouper.groups().items():
+            lhs_group_filter = xfrm.LHSGroupFilter(group_signals)
+            group_stmts = lhs_group_filter(fragment.statements)
+
+            with module.process(name="$group_{}".format(group)) as process:
+                with process.case() as case:
+                    # For every signal in comb domain, assign \sig$next to the reset value.
+                    # For every signal in sync domains, assign \sig$next to the current
+                    # value (\sig).
+                    for domain, signal in fragment.iter_drivers():
+                        if signal not in group_signals:
+                            continue
+                        if domain is None:
+                            prev_value = ast.Const(signal.reset, signal.width)
+                        else:
+                            prev_value = signal
+                        case.assign(lhs_compiler(signal), rhs_compiler(prev_value))
+
+                    # Convert statements into decision trees.
+                    stmt_compiler._case = case
+                    stmt_compiler._has_rhs = False
+                    stmt_compiler._wrap_assign = False
+                    stmt_compiler(group_stmts)
+
+                    # Verilog `always @*` blocks will not run if `*` does not match anything, i.e.
+                    # if the implicit sensitivity list is empty. We check this while translating,
+                    # by looking for any signals on RHS. If there aren't any, we add some logic
+                    # whose only purpose is to trigger Verilog simulators when it converts
+                    # through RTLIL and to Verilog, by populating the sensitivity list.
+                    #
+                    # Unfortunately, while this workaround allows true (event-driven) Verilog
+                    # simulators to work properly, and is universally ignored by synthesizers,
+                    # Verilator rejects it.
+                    #
+                    # Running the Yosys proc_prune pass converts such pathological `always @*`
+                    # blocks to `assign` statements, so this workaround can be removed completely
+                    # once support for Yosys 0.9 is dropped.
+                    if not stmt_compiler._has_rhs:
+                        if verilog_trigger is None:
+                            verilog_trigger = \
+                                module.wire(1, name="$verilog_initial_trigger")
+                        case.assign(verilog_trigger, verilog_trigger)
+
+                # For every signal in the sync domain, assign \sig's initial value (which will
+                # end up as the \init reg attribute) to the reset value.
+                with process.sync("init") as sync:
+                    for domain, signal in fragment.iter_sync():
+                        if signal not in group_signals:
+                            continue
+                        wire_curr, wire_next = compiler_state.resolve(signal)
+                        sync.update(wire_curr, rhs_compiler(ast.Const(signal.reset, signal.width)))
+
+                    # The Verilog simulator trigger needs to change at time 0, so if we haven't
+                    # yet done that in some process, do it.
+                    if verilog_trigger and not verilog_trigger_sync_emitted:
+                        sync.update(verilog_trigger, "1'0")
+                        verilog_trigger_sync_emitted = True
+
+                # For every signal in every sync domain, assign \sig to \sig$next. The sensitivity
+                # list, however, differs between domains: for domains with sync reset, it is
+                # `[pos|neg]edge clk`, for sync domains with async reset it is `[pos|neg]edge clk
+                # or posedge rst`.
+                for domain, signals in fragment.drivers.items():
                     if domain is None:
-                        prev_value = ast.Const(signal.reset, signal.nbits)
-                    else:
-                        prev_value = signal
-                    case.assign(lhs_compiler(signal), rhs_compiler(prev_value))
-
-                # Convert statements into decision trees.
-                stmt_compiler._case = case
-                stmt_compiler(fragment.statements)
-
-            # For every signal in the sync domain, assign \sig's initial value (which will end up
-            # as the \init reg attribute) to the reset value.
-            with process.sync("init") as sync:
-                for domain, signal in fragment.iter_sync():
-                    wire_curr, wire_next = compiler_state.resolve(signal)
-                    sync.update(wire_curr, rhs_compiler(ast.Const(signal.reset, signal.nbits)))
-
-            # For every signal in every domain, assign \sig to \sig$next. The sensitivity list,
-            # however, differs between domains: for comb domains, it is `always`, for sync domains
-            # with sync reset, it is `posedge clk`, for sync domains with async rest it is
-            # `posedge clk or posedge rst`.
-            for domain, signals in fragment.drivers.items():
-                triggers = []
-                if domain is None:
-                    triggers.append(("always",))
-                else:
+                        continue
+
+                    signals = signals & group_signals
+                    if not signals:
+                        continue
+
                     cd = fragment.domains[domain]
-                    triggers.append(("posedge", compiler_state.resolve_curr(cd.clk)))
+
+                    triggers = []
+                    triggers.append((cd.clk_edge + "edge", compiler_state.resolve_curr(cd.clk)))
                     if cd.async_reset:
                         triggers.append(("posedge", compiler_state.resolve_curr(cd.rst)))
 
-                for trigger in triggers:
-                    with process.sync(*trigger) as sync:
-                        for signal in signals:
-                            wire_curr, wire_next = compiler_state.resolve(signal)
-                            sync.update(wire_curr, wire_next)
-
-    # Finally, collect the names we've given to our ports in RTLIL, and correlate these with
-    # the signals represented by these ports. If we are a submodule, this will be necessary
-    # to create a cell for us in the parent module.
+                    for trigger in triggers:
+                        with process.sync(*trigger) as sync:
+                            for signal in signals:
+                                wire_curr, wire_next = compiler_state.resolve(signal)
+                                sync.update(wire_curr, wire_next)
+
+        # Any signals that are used but neither driven nor connected to an input port always
+        # assume their reset values. We need to assign the reset value explicitly, since only
+        # driven sync signals are handled by the logic above.
+        #
+        # Because this assignment is done at a late stage, a single Signal object can get assigned
+        # many times, once in each module it is used. This is a deliberate decision; the possible
+        # alternatives are to add ports for undriven signals (which requires choosing one module
+        # to drive it to reset value arbitrarily) or to replace them with their reset value (which
+        # removes valuable source location information).
+        driven = ast.SignalSet()
+        for domain, signals in fragment.iter_drivers():
+            driven.update(flatten(signal._lhs_signals() for signal in signals))
+        driven.update(fragment.iter_ports(dir="i"))
+        driven.update(fragment.iter_ports(dir="io"))
+        for subfragment, sub_name in fragment.subfragments:
+            driven.update(subfragment.iter_ports(dir="o"))
+            driven.update(subfragment.iter_ports(dir="io"))
+
+        for wire in compiler_state.wires:
+            if wire in driven:
+                continue
+            wire_curr, _ = compiler_state.wires[wire]
+            module.connect(wire_curr, rhs_compiler(ast.Const(wire.reset, wire.width)))
+
+    # Collect the names we've given to our ports in RTLIL, and correlate these with the signals
+    # represented by these ports. If we are a submodule, this will be necessary to create a cell
+    # for us in the parent module.
     port_map = OrderedDict()
     for signal in fragment.ports:
         port_map[compiler_state.resolve_curr(signal)] = signal
 
+    # Finally, collect tha names we've given to each wire in RTLIL, and provide these to
+    # the caller, to allow manipulating them in the toolchain.
+    for signal in compiler_state.wires:
+        wire_name = compiler_state.resolve_curr(signal)
+        if wire_name.startswith("\\"):
+            wire_name = wire_name[1:]
+        name_map[signal] = hierarchy + (wire_name,)
+
     return module.name, port_map
 
 
-def convert(fragment, name="top", **kwargs):
-    fragment = fragment.prepare(**kwargs)
+def convert_fragment(fragment, name="top"):
+    assert isinstance(fragment, ir.Fragment)
     builder = _Builder()
-    convert_fragment(builder, fragment, name=name, top=True)
-    return str(builder)
+    name_map = ast.SignalDict()
+    _convert_fragment(builder, fragment, name_map, hierarchy=(name,))
+    return str(builder), name_map
+
+
+def convert(elaboratable, name="top", platform=None, **kwargs):
+    fragment = ir.Fragment.get(elaboratable, platform).prepare(**kwargs)
+    il_text, name_map = convert_fragment(fragment, name)
+    return il_text