diff --git a/amaranth/back/rtlil.py b/amaranth/back/rtlil.py index 483ba13..ec7b699 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 @@ -47,89 +48,141 @@ def _const(value): assert False, f"Invalid constant {value!r}" -class _Namer: +def _src(src_loc): + if src_loc is None: + return None + file, line = src_loc + return f"{file}:{line}" + + +class Emitter: def __init__(self): - super().__init__() - self._anon = 0 - self._index = 0 - self._names = set() + self._indent = "" + self._lines = [] + self.port_id = 0 - def anonymous(self): - name = f"U$${self._anon}" - assert name not in self._names - self._anon += 1 - return name + def __call__(self, line=None): + if line is not None: + self._lines.append(f"{self._indent}{line}\n") + else: + self._lines.append("\n") - 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 - - -class _BufferedBuilder: - def __init__(self): - super().__init__() - self._buffer = io.StringIO() + @contextmanager + def indent(self): + orig = self._indent + self._indent += " " + yield + self._indent = orig def __str__(self): - return self._buffer.getvalue() - - def _append(self, fmt, *args, **kwargs): - self._buffer.write(fmt.format(*args, **kwargs)) + return "".join(self._lines) -class _AttrBuilder: - def __init__(self, emit_src, *args, **kwargs): - super().__init__(*args, **kwargs) +class Design: + def __init__(self, emit_src=True): + self.modules = {} self.emit_src = emit_src - def _attribute(self, name, value, *, indent=0): - self._append("{}attribute \\{} {}\n", - " " * indent, name, _const(value)) + 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 _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): + emitter = Emitter() + for module in self.modules.values(): + module.emit(emitter) + return str(emitter) -class _Builder(_BufferedBuilder, _Namer): - def __init__(self, emit_src): - super().__init__() +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) - def module(self, name=None, attrs={}, *, src=None): - name = self._make_name(name, local=False) - return _ModuleBuilder(self, name, attrs, src=src) + def _auto_name(self): + self._auto_index += 1 + return f"${self._auto_index}" + + 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 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 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() -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 _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 - def __enter__(self): - self._attributes(self.attrs, src=self.src) - 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="", signed=False): +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)) + .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) - 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) + 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) + line(f"wire width {self.width} {self.port_kind} {line.port_id} {signed} {self.name}") + line.port_id += 1 + line() -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 +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 __enter__(self): - self._attributes(self.attrs, src=self.src, indent=1) - self._append(" process {}\n", self.name) - return self + def port(self, name, value): + assert name not in self.ports + self.ports[name] = value - def __exit__(self, *args): - self._append(" end\n") - self.rtlil._buffer.write(str(self)) + def parameter(self, name, value): + assert name not in self.parameters + self.parameters[name] = value - def case(self): - return _CaseBuilder(self, indent=2) + 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 _CaseBuilder: - def __init__(self, rtlil, indent): - self.rtlil = rtlil - self.indent = indent +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 _append(self, *args, **kwargs): - self.rtlil._append(*args, **kwargs) + def attribute(self, name, value): + assert name not in self.attributes + self.attributes[name] = value - def __enter__(self): - return self + 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 __exit__(self, *args): - pass + +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 + + +class Process: + def __init__(self, *, name, attrs=None, src_loc=None): + self.name = name + self.contents = [] + self.attributes = _make_attributes(attrs, src_loc) + + 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) + self.contents.append(Assignment(lhs, rhs)) - def switch(self, cond, attrs={}, src=""): - return _SwitchBuilder(self.rtlil, cond, attrs, src, self.indent) + 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 _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 +class Assignment: + def __init__(self, lhs, rhs): + self.lhs = lhs + self.rhs = rhs - def _append(self, *args, **kwargs): - self.rtlil._append(*args, **kwargs) + def emit(self, line): + line(f"assign {self.lhs} {self.rhs}") - 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) +class Switch: + def __init__(self, sel): + self.sel = sel + self.cases = [] - 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 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 = [] + + def assign(self, lhs, rhs): + self.contents.append(Assignment(lhs, rhs)) + + def switch(self, sel): + res = Switch(sel) + self.contents.append(res) + return res + + 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] @@ -392,9 +506,9 @@ class ModuleEmitter: shape = signal.shape() wire = self.builder.wire(width=shape.width, signed=shape.signed, name=name, attrs=attrs, - src=_src(signal.src_loc)) + 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()} @@ -403,9 +517,9 @@ class ModuleEmitter: if name in named_signals: 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 @@ -425,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) @@ -516,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: @@ -538,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: @@ -550,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: @@ -563,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): @@ -573,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 @@ -627,31 +724,25 @@ 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 - # RTLIL cannot support empty pattern sets. - assert 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 emit_operator(self, cell_idx, cell): UNARY_OPERATORS = { @@ -693,12 +784,12 @@ class ModuleEmitter: operand, = cell.inputs self.builder.cell(cell_type, ports={ "A": self.sigspec(operand), - "Y": self.cell_wires[cell_idx] - }, params={ + "Y": self.cell_wires[cell_idx].name + }, parameters={ "A_SIGNED": False, "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 @@ -707,43 +798,43 @@ class ModuleEmitter: 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 @@ -751,10 +842,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: @@ -762,38 +853,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, @@ -805,9 +896,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: @@ -818,11 +909,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] @@ -834,13 +925,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] @@ -850,7 +941,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), @@ -862,13 +953,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)), } @@ -879,7 +970,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, @@ -895,7 +986,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, }) @@ -904,14 +995,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 = [] @@ -964,50 +1055,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: @@ -1071,16 +1163,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/_ir.py b/amaranth/hdl/_ir.py index 9692744..a853733 100644 --- a/amaranth/hdl/_ir.py +++ b/amaranth/hdl/_ir.py @@ -928,9 +928,6 @@ class NetlistEmitter: 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, @@ -1069,9 +1066,6 @@ class NetlistEmitter: 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, @@ -1169,9 +1163,6 @@ class NetlistEmitter: 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, diff --git a/tests/test_back_rtlil.py b/tests/test_back_rtlil.py new file mode 100644 index 0000000..47d7b4b --- /dev/null +++ b/tests/test_back_rtlil.py @@ -0,0 +1,1834 @@ +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 9 + parameter \Y_WIDTH 9 + connect \A { 1'0 \i8u [7:0] } + connect \Y $1 + end + cell $neg $10 + parameter \A_SIGNED 0 + parameter \A_WIDTH 9 + parameter \Y_WIDTH 9 + connect \A { \i8s [7] \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 9 + parameter \B_WIDTH 9 + parameter \Y_WIDTH 9 + connect \A { 1'0 \i8ua [7:0] } + connect \B { 1'0 \i8ub [7:0] } + connect \Y $1 + end + cell $add $6 + parameter \A_SIGNED 0 + parameter \B_SIGNED 0 + parameter \A_WIDTH 10 + parameter \B_WIDTH 10 + parameter \Y_WIDTH 10 + connect \A { 2'00 \i8ua [7:0] } + connect \B { \i8sb [7] \i8sb [7] \i8sb [7:0] } + connect \Y \o2 + end + cell $add $7 + parameter \A_SIGNED 0 + parameter \B_SIGNED 0 + parameter \A_WIDTH 9 + parameter \B_WIDTH 9 + parameter \Y_WIDTH 9 + connect \A { \i8sa [7] \i8sa [7:0] } + connect \B { \i8sb [7] \i8sb [7:0] } + connect \Y $2 + end + cell $sub $8 + 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 { 1'0 \i8ub [7:0] } + connect \Y $3 + end + cell $sub $9 + parameter \A_SIGNED 0 + parameter \B_SIGNED 0 + parameter \A_WIDTH 10 + parameter \B_WIDTH 10 + parameter \Y_WIDTH 10 + connect \A { 2'00 \i8ua [7:0] } + connect \B { \i8sb [7] \i8sb [7] \i8sb [7:0] } + connect \Y \o5 + end + cell $sub $10 + parameter \A_SIGNED 0 + parameter \B_SIGNED 0 + parameter \A_WIDTH 9 + parameter \B_WIDTH 9 + parameter \Y_WIDTH 9 + connect \A { \i8sa [7] \i8sa [7:0] } + connect \B { \i8sb [7] \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_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 8 + parameter \B_WIDTH 8 + parameter \Y_WIDTH 8 + connect \A { 4'0000 \i4ua [3:0] } + connect \B { 4'0000 \i4ub [3:0] } + connect \Y $1 + end + cell $mul $5 + parameter \A_SIGNED 0 + parameter \B_SIGNED 0 + parameter \A_WIDTH 8 + parameter \B_WIDTH 8 + parameter \Y_WIDTH 8 + connect \A { 4'0000 \i4ua [3:0] } + connect \B { \i4sb [3] \i4sb [3] \i4sb [3] \i4sb [3] \i4sb [3:0] } + connect \Y $2 + end + cell $mul $6 + parameter \A_SIGNED 0 + parameter \B_SIGNED 0 + parameter \A_WIDTH 8 + parameter \B_WIDTH 8 + parameter \Y_WIDTH 8 + connect \A { \i4sa [3] \i4sa [3] \i4sa [3] \i4sa [3] \i4sa [3:0] } + connect \B { \i4sb [3] \i4sb [3] \i4sb [3] \i4sb [3] \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 5 + parameter \Y_WIDTH 5 + connect \A { 1'0 \i4ua [3:0] } + connect \B { \i4sb [3] \i4sb [3:0] } + connect \Y $14 + end + wire width 1 $16 + cell $reduce_bool $17 + parameter \A_SIGNED 0 + parameter \A_WIDTH 5 + parameter \Y_WIDTH 1 + connect \A { \i4sb [3] \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 5 + parameter \B_WIDTH 5 + parameter \Y_WIDTH 5 + connect \A { \i4sa [3] \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 5 + parameter \B_WIDTH 5 + parameter \Y_WIDTH 5 + connect \A { \i4sa [3] \i4sa [3:0] } + connect \B { \i4sb [3] \i4sb [3:0] } + connect \Y $24 + end + wire width 1 $26 + cell $reduce_bool $27 + parameter \A_SIGNED 0 + parameter \A_WIDTH 5 + parameter \Y_WIDTH 1 + connect \A { \i4sb [3] \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 5 + parameter \Y_WIDTH 5 + connect \A { 1'0 \i4ua [3:0] } + connect \B { \i4sb [3] \i4sb [3:0] } + connect \Y $34 + end + wire width 1 $36 + cell $reduce_bool $37 + parameter \A_SIGNED 0 + parameter \A_WIDTH 5 + parameter \Y_WIDTH 1 + connect \A { \i4sb [3] \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 5 + parameter \B_WIDTH 5 + parameter \Y_WIDTH 5 + connect \A { \i4sa [3] \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 15 + parameter \B_WIDTH 3 + parameter \Y_WIDTH 15 + connect \A { 7'0000000 \i8ua [7:0] } + connect \B \i3 [2:0] + connect \Y $1 + end + cell $shl $6 + parameter \A_SIGNED 0 + parameter \B_SIGNED 0 + parameter \A_WIDTH 15 + parameter \B_WIDTH 3 + parameter \Y_WIDTH 15 + connect \A { \i8sa [7] \i8sa [7] \i8sa [7] \i8sa [7] \i8sa [7] \i8sa [7] \i8sa [7] \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 0 + parameter \B_SIGNED 0 + parameter \A_WIDTH 9 + parameter \B_WIDTH 9 + parameter \Y_WIDTH 1 + connect \A { 1'0 \i8ua [7:0] } + connect \B { \i8sb [7] \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 9 + parameter \Y_WIDTH 1 + connect \A { 1'0 \i8ua [7:0] } + connect \B { \i8sb [7] \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_ir.py b/tests/test_hdl_ir.py index d2bfabc..516898e 100644 --- a/tests/test_hdl_ir.py +++ b/tests/test_hdl_ir.py @@ -1632,32 +1632,33 @@ class AssignTestCase(FHDLTestCase): (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 's1' 10.0:8) + (output 's2' 11.0:8) (output 's3' 12.0:8) - (output 's4' 11.0:8) + (output 's4' 13.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 's1' 10.0:8) + (output 's2' 11.0:8) (output 's3' 12.0:8) - (output 's4' 11.0:8) + (output 's4' 13.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))) + (cell 3 0 (matches 0.2:6 )) + (cell 4 0 (matches 0.2:6 11--)) + (cell 5 0 (priority_match 1 (cat 1.0 2.0 3.0 4.0))) + (cell 6 0 (matches 0.6:10 0100)) + (cell 7 0 (matches 0.6:10 0101)) + (cell 8 0 (matches 0.6:10 0110)) + (cell 9 0 (priority_match 1 (cat 6.0 7.0 8.0 1'd1))) + (cell 10 0 (assignment_list 8'd0 (5.0 0:8 0.10:18) (9.0 0:8 0.18:26))) + (cell 11 0 (assignment_list 8'd0 (5.1 0:8 0.10:18) (9.1 0:8 0.18:26))) + (cell 12 0 (assignment_list 8'd0 (5.2 0:8 0.10:18) (9.2 0:8 0.18:26))) + (cell 13 0 (assignment_list 8'd0 (5.3 0:8 0.10:18) (9.3 0:8 0.18:26))) ) """) @@ -3042,7 +3043,7 @@ class RhsTestCase(FHDLTestCase): (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)) + (output 'o2' (cat 10.0:8 2'd0)) ) (cell 0 0 (top (input 'i8ua' 2:10) @@ -3051,7 +3052,7 @@ class RhsTestCase(FHDLTestCase): (input 'i8ud' 26:34) (input 'i4' 34:38) (output 'o1' (cat 5.0:8 2'd0)) - (output 'o2' (cat 9.0:8 2'd0)) + (output 'o2' (cat 10.0:8 2'd0)) )) (cell 1 0 (matches 0.34:38 0001)) (cell 2 0 (matches 0.34:38 0010 0011)) @@ -3063,12 +3064,14 @@ class RhsTestCase(FHDLTestCase): (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) + (cell 7 0 (matches 0.34:38 )) + (cell 8 0 (matches 0.34:38 0110 0111)) + (cell 9 0 (priority_match 1 (cat 6.0 7.0 8.0 1'd1))) + (cell 10 0 (assignment_list 8'd0 + (9.0 0:8 0.2:10) + (9.1 0:8 0.10:18) + (9.2 0:8 0.18:26) + (9.3 0:8 0.26:34) )) ) """)