import io import textwrap from collections import defaultdict, OrderedDict from contextlib import contextmanager 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 name = "${}".format(self._index) elif not local and name[0] not in "\\$": name = "\\{}".format(name) while name in self._names: self._index += 1 name = "{}${}".format(name, self._index) self._names.add(name) return name class _BufferedBuilder: def __init__(self): super().__init__() self._buffer = io.StringIO() def __str__(self): return self._buffer.getvalue() def _append(self, fmt, *args, **kwargs): self._buffer.write(fmt.format(*args, **kwargs)) 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)) else: self._append("{}attribute \\{} {}\n", " " * indent, name, int(value)) def _attributes(self, attrs, *, src=None, **kwargs): for name, value in attrs.items(): self._attribute(name, value, **kwargs) if src: self._attribute("src", src, **kwargs) 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, _BufferedBuilder, _AttrBuilder): def __init__(self, rtlil, name, attrs): super().__init__() self.rtlil = rtlil self.name = name self.attrs = {"generator": "nMigen"} self.attrs.update(attrs) def __enter__(self): self._attributes(self.attrs) self._append("module {}\n", self.name) return self def __exit__(self, *args): self._append("end\n") self.rtlil._buffer.write(str(self)) 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) else: assert port_kind in ("input", "output", "inout") self._append(" wire width {} {} {} {}\n", width, port_kind, port_id, 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, str): self._append(" parameter \\{} \"{}\"\n", param, value.translate(self._escape_map)) 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, attrs={}, src=""): name = self._make_name(name, local=True) return _ProcessBuilder(self, name, attrs, 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._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) def sync(self, kind, cond=None): return _SyncBuilder(self, kind, cond) class _CaseBuilder(_ProxiedBuilder): def __init__(self, rtlil, indent): self.rtlil = rtlil self.indent = indent def __enter__(self): return self def __exit__(self, *args): pass 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) 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._attributes(self.attrs, src=self.src, indent=self.indent) self._append("{}switch {}\n", " " * self.indent, self.cond) return self def __exit__(self, *args): self._append("{}end\n", " " * self.indent) 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._append("{}case {}\n", " " * (self.indent + 1), ", ".join("{}'{}".format(len(value), value) for value in values)) return _CaseBuilder(self.rtlil, self.indent + 2) class _SyncBuilder(_ProxiedBuilder): def __init__(self, rtlil, kind, cond): self.rtlil = rtlil self.kind = kind self.cond = cond def __enter__(self): if self.cond is None: self._append(" sync {}\n", self.kind) else: self._append(" sync {} {}\n", self.kind, self.cond) return self def __exit__(self, *args): pass def update(self, 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, src_loc): self.value = value self.branches = list(branches) self.src_loc = src_loc class _ValueCompilerState: def __init__(self, rtlil): self.rtlil = rtlil self.wires = ast.SignalDict() self.driven = ast.SignalDict() self.ports = ast.SignalDict() self.anys = ast.ValueDict() self.expansions = ast.ValueDict() def add_driven(self, signal, sync): self.driven[signal] = sync def add_port(self, signal, kind): assert kind in ("i", "o", "io") if kind == "i": kind = "input" elif kind == "o": kind = "output" elif kind == "io": kind = "inout" 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] if signal in self.ports: port_id, port_kind = self.ports[signal] else: port_id = port_kind = None if prefix is not None: wire_name = "{}_{}".format(prefix, signal.name) else: wire_name = signal.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 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 self.wires[signal] = (wire_curr, wire_next) return wire_curr, wire_next def resolve_curr(self, signal, prefix=None): wire_curr, wire_next = self.resolve(signal, prefix) return wire_curr def expand(self, value): if not self.expansions: return value return self.expansions.get(value, value) @contextmanager def expand_to(self, value, expansion): try: assert value not in self.expansions self.expansions[value] = expansion yield finally: del self.expansions[value] class _ValueCompiler(xfrm.ValueVisitor): def __init__(self, state): self.s = state def on_unknown(self, value): if value is None: return None else: super().on_unknown(value) def on_ClockSignal(self, value): raise NotImplementedError # :nocov: 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]))) def _prepare_value_for_Slice(self, value): raise NotImplementedError # :nocov: def on_Slice(self, 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 == value.stop: return "{}" elif value.start + 1 == value.stop: return "{} [{}]".format(sigspec, value.start) else: 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): elem = value.elems[index.value] else: elem = value.elems[-1] return self.match_shape(elem, *value.shape()) else: 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): operator_map = { (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, "%"): "$mod", (2, "**"): "$pow", (2, "<<"): "$sshl", (2, ">>"): "$sshr", (2, "&"): "$and", (2, "^"): "$xor", (2, "|"): "$or", (2, "=="): "$eq", (2, "!="): "$ne", (2, "<"): "$lt", (2, "<="): "$le", (2, ">"): "$gt", (2, ">="): "$ge", (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.width, value.value) else: 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) return wire_curr 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, src=src(value.src_loc)) self.s.rtlil.cell(self.operator_map[(1, value.operator)], ports={ "\\A": self(arg), "\\Y": res, }, params={ "A_SIGNED": arg_sign, "A_WIDTH": arg_bits, "Y_WIDTH": res_bits, }, src=src(value.src_loc)) return res def match_shape(self, value, new_bits, new_sign): if isinstance(value, ast.Const): 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, src=src(value.src_loc)) self.s.rtlil.cell("$pos", ports={ "\\A": self(value), "\\Y": res, }, params={ "A_SIGNED": value_sign, "A_WIDTH": value_bits, "Y_WIDTH": new_bits, }, src=src(value.src_loc)) return res def on_Operator_binary(self, value): lhs, rhs = value.operands lhs_bits, lhs_sign = lhs.shape() rhs_bits, rhs_sign = rhs.shape() if lhs_sign == rhs_sign or value.operator in ("<<", ">>", "**"): lhs_wire = self(lhs) rhs_wire = self(rhs) else: lhs_sign = rhs_sign = True lhs_bits = rhs_bits = max(lhs_bits, rhs_bits) 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, src=src(value.src_loc)) self.s.rtlil.cell(self.operator_map[(2, value.operator)], ports={ "\\A": lhs_wire, "\\B": rhs_wire, "\\Y": res, }, params={ "A_SIGNED": lhs_sign, "A_WIDTH": lhs_bits, "B_SIGNED": rhs_sign, "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, val1, val0 = value.operands val1_bits, val1_sign = val1.shape() val0_bits, val0_sign = val0.shape() res_bits, res_sign = value.shape() 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": val0_wire, "\\B": val1_wire, "\\S": self(sel), "\\Y": res, }, params={ "WIDTH": res_bits }, src=src(value.src_loc)) return res def on_Operator(self, value): if len(value.operands) == 1: return self.on_Operator_unary(value) elif len(value.operands) == 2: return self.on_Operator_binary(value) elif len(value.operands) == 3: assert value.operator == "m" return self.on_Operator_mux(value) else: raise TypeError # :nocov: def _prepare_value_for_Slice(self, value): if isinstance(value, (ast.Signal, ast.Slice, ast.Cat)): sigspec = self(value) else: 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, 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 # to use a $shift cell here instead, even though it produces less idiomatic Verilog. self.s.rtlil.cell("$shift", ports={ "\\A": self(lhs), "\\B": self(rhs), "\\Y": res, }, params={ "A_SIGNED": lhs_sign, "A_WIDTH": lhs_bits, "B_SIGNED": rhs_sign, "B_WIDTH": rhs_bits, "Y_WIDTH": res_bits, }, src=src(value.src_loc)) return res def on_Repl(self, value): return "{{ {} }}".format(" ".join(self(value.value) for _ in range(value.count))) 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): if value not in self.s.driven: raise ValueError("No LHS wire for non-driven signal {}".format(repr(value))) 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, rec.Record)) return self(value) def on_Part(self, value): offset = self.s.expand(value.offset) if isinstance(offset, ast.Const): 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: # 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)) // value.stride)[:max_branches], value.src_loc) def on_Repl(self, value): raise TypeError # :nocov: 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._test_cache = {} self._has_rhs = False self._wrap_assign = False @contextmanager def case(self, switch, values, attrs={}, src=""): try: old_case = 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): 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): 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("") 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), 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,)): self._wrap_assign = False branch_value = ast.Const(branch, shape) with self.state.expand_to(legalize.value, branch_value): 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_map, hierarchy): if isinstance(fragment, ir.Instance): port_map = OrderedDict() for port_name, (value, dir) in fragment.named_ports.items(): port_map["\\{}".format(port_name)] = value if fragment.type[0] == "$": return fragment.type, port_map else: return "\\{}".format(fragment.type), port_map 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. for domain, signal in fragment.iter_drivers(): compiler_state.add_driven(signal, sync=domain is not None) # Transform all signals used as ports in the current fragment eagerly and outside of # any hierarchy, to make sure they get sensible (non-prefixed) names. for signal in fragment.ports: compiler_state.add_port(signal, fragment.ports[signal]) compiler_state.resolve_curr(signal) # Transform all clocks clocks and resets eagerly and outside of any hierarchy, to make # sure they get sensible (non-prefixed) names. This does not affect semantics. for domain, _ in fragment.iter_sync(): cd = fragment.domains[domain] compiler_state.resolve_curr(cd.clk) if cd.rst is not None: compiler_state.resolve_curr(cd.rst) # Transform all subfragments to their respective cells. Transforming signals connected # to their ports into wires eagerly makes sure they get sensible (prefixed with submodule # 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(): if isinstance(param_value, mem.Memory): memory = param_value if memory not in memories: memories[memory] = module.memory(width=memory.width, size=memory.depth, 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_mask else: data = 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, name_map, hierarchy=hierarchy + (sub_name,)) sub_ports = OrderedDict() for port, value in sub_port_map.items(): 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, 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: continue signals = signals & group_signals if not signals: continue cd = fragment.domains[domain] 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) # 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(fragment, name="top"): assert isinstance(fragment, ir.Fragment) builder = _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