diff --git a/amaranth/back/rtlil.py b/amaranth/back/rtlil.py index b70fd39..a5e5a8b 100644 --- a/amaranth/back/rtlil.py +++ b/amaranth/back/rtlil.py @@ -3,6 +3,7 @@ from contextlib import contextmanager import io from ..utils import bits_for +from .._utils import to_binary from ..lib import wiring from ..hdl import _repr, _ast, _ir, _nir @@ -421,6 +422,7 @@ class ModuleEmitter: self.emit_cell_wires() self.emit_submodule_wires() self.emit_connects() + self.emit_signal_fields() self.emit_submodules() self.emit_cells() @@ -491,12 +493,12 @@ class ModuleEmitter: attrs.update(signal.attrs) self.value_src_loc[value] = signal.src_loc - for repr in signal._value_repr: - if repr.path == () and isinstance(repr.format, _repr.FormatEnum): - enum = repr.format.enum - attrs["enum_base_type"] = enum.__name__ - for enum_value in enum: - attrs["enum_value_{:0{}b}".format(enum_value.value, len(signal))] = enum_value.name + field = self.netlist.signal_fields[signal][()] + if field.enum_name is not None: + attrs["enum_base_type"] = field.enum_name + if field.enum_variants is not None: + for var_val, var_name in field.enum_variants.items(): + attrs["enum_value_" + to_binary(var_val, len(signal))] = var_name if name in self.module.ports: port_value, _flow = self.module.ports[name] @@ -666,6 +668,30 @@ class ModuleEmitter: if name not in self.driven_sigports: self.builder.connect(wire.name, self.sigspec(value)) + def emit_signal_fields(self): + for signal, name in self.module.signal_names.items(): + fields = self.netlist.signal_fields[signal] + for path, field in fields.items(): + if path == (): + continue + name_parts = [name] + for component in path: + if isinstance(component, str): + name_parts.append(f".{component}") + elif isinstance(component, int): + name_parts.append(f"[{component}]") + else: + assert False # :nocov: + attrs = {} + if field.enum_name is not None: + attrs["enum_base_type"] = field.enum_name + if field.enum_variants is not None: + for var_val, var_name in field.enum_variants.items(): + attrs["enum_value_" + to_binary(var_val, len(field.value))] = var_name + wire = self.builder.wire(width=len(field.value), signed=field.signed, attrs=attrs, + name="".join(name_parts), src_loc=signal.src_loc) + self.builder.connect(wire.name, self.sigspec(field.value)) + def emit_submodules(self): for submodule_idx in self.module.submodules: submodule = self.netlist.modules[submodule_idx] diff --git a/amaranth/hdl/_ast.py b/amaranth/hdl/_ast.py index 86abc42..81bce00 100644 --- a/amaranth/hdl/_ast.py +++ b/amaranth/hdl/_ast.py @@ -2095,6 +2095,11 @@ class Signal(Value, DUID, metaclass=_SignalMeta): self._attrs = OrderedDict(() if attrs is None else attrs) + if isinstance(orig_shape, ShapeCastable): + self._format = orig_shape.format(orig_shape(self), "") + else: + self._format = Format("{}", self) + if decoder is not None: # The value representation is specified explicitly. Since we do not expose `hdl._repr`, # this is the only way to add a custom filter to the signal right now. diff --git a/amaranth/hdl/_ir.py b/amaranth/hdl/_ir.py index e1696b1..cc77e4a 100644 --- a/amaranth/hdl/_ir.py +++ b/amaranth/hdl/_ir.py @@ -690,7 +690,7 @@ class NetlistDriver: class NetlistEmitter: - def __init__(self, netlist: _nir.Netlist, design, *, all_undef_to_ff=False): + def __init__(self, netlist: _nir.Netlist, design: Design, *, all_undef_to_ff=False): self.netlist = netlist self.design = design self.all_undef_to_ff = all_undef_to_ff @@ -776,7 +776,7 @@ class NetlistEmitter: def emit_operator(self, module_idx: int, operator: str, *inputs: _nir.Value, src_loc): op = _nir.Operator(module_idx, operator=operator, inputs=inputs, src_loc=src_loc) return self.netlist.add_value_cell(op.width, op) - + def emit_matches(self, module_idx: int, value: _nir.Value, patterns, *, src_loc): key = module_idx, value, patterns, src_loc try: @@ -1334,6 +1334,42 @@ class NetlistEmitter: else: raise ValueError(f"Invalid port direction {dir!r}") + def emit_signal_fields(self): + for signal, fragment in self.design.signal_lca.items(): + module_idx = self.fragment_module_idx[fragment] + fields = {} + def emit_format(path, fmt): + if isinstance(fmt, _ast.Format): + specs = [ + chunk[0] + for chunk in fmt._chunks + if not isinstance(chunk, str) + ] + if len(specs) != 1: + return + val, signed = self.emit_rhs(module_idx, specs[0]) + fields[path] = _nir.SignalField(val, signed=signed) + elif isinstance(fmt, _ast.Format.Enum): + val, signed = self.emit_rhs(module_idx, fmt._value) + fields[path] = _nir.SignalField(val, signed=signed, + enum_name=fmt._name, + enum_variants=fmt._variants) + elif isinstance(fmt, _ast.Format.Struct): + val, signed = self.emit_rhs(module_idx, fmt._value) + fields[path] = _nir.SignalField(val, signed=signed) + for name, subfmt in fmt._fields.items(): + emit_format(path + (name,), subfmt) + elif isinstance(fmt, _ast.Format.Array): + val, signed = self.emit_rhs(module_idx, fmt._value) + fields[path] = _nir.SignalField(val, signed=signed) + for idx, subfmt in enumerate(fmt._fields): + emit_format(path + (idx,), subfmt) + emit_format((), signal._format) + val, signed = self.emit_rhs(module_idx, signal) + if () not in fields or fields[()].value != val: + fields[()] = _nir.SignalField(val, signed=signed) + self.netlist.signal_fields[signal] = fields + def emit_drivers(self): for driver in self.drivers.values(): if (driver.domain is not None and @@ -1452,6 +1488,7 @@ class NetlistEmitter: for subfragment, _name, sub_src_loc in fragment.subfragments: self.emit_fragment(subfragment, module_idx, cell_src_loc=sub_src_loc) if parent_module_idx is None: + self.emit_signal_fields() self.emit_drivers() self.emit_top_ports(fragment) if self.all_undef_to_ff: diff --git a/amaranth/hdl/_nir.py b/amaranth/hdl/_nir.py index 0ee5b1d..595e31f 100644 --- a/amaranth/hdl/_nir.py +++ b/amaranth/hdl/_nir.py @@ -289,6 +289,22 @@ class Format: chunk.value = netlist.resolve_value(chunk.value) +class SignalField: + """Describes a single field of a signal.""" + def __init__(self, value, *, signed, enum_name=None, enum_variants=None): + self.value = Value(value) + self.signed = bool(signed) + self.enum_name = enum_name + self.enum_variants = enum_variants + + def __eq__(self, other): + return (type(self) is type(other) and + self.value == other.value and + self.signed == other.signed and + self.enum_name == other.enum_name and + self.enum_variants == other.enum_variants) + + class Netlist: """A fine netlist. Consists of: @@ -321,6 +337,7 @@ class Netlist: connections : dict of (negative) int to int io_ports : list of ``IOPort`` signals : dict of Signal to ``Value`` + signal_fields: dict of Signal to dict of tuple[str | int] to SignalField last_late_net: int """ def __init__(self): @@ -329,6 +346,7 @@ class Netlist: self.connections: dict[Net, Net] = {} self.io_ports: list[_ast.IOPort] = [] self.signals = SignalDict() + self.signal_fields = SignalDict() self.last_late_net = 0 def resolve_net(self, net: Net): @@ -345,6 +363,9 @@ class Netlist: cell.resolve_nets(self) for sig in self.signals: self.signals[sig] = self.resolve_value(self.signals[sig]) + for fields in self.signal_fields.values(): + for field in fields.values(): + field.value = self.resolve_value(field.value) def __repr__(self): result = ["("] diff --git a/tests/test_back_rtlil.py b/tests/test_back_rtlil.py index f168b7e..96ac2ee 100644 --- a/tests/test_back_rtlil.py +++ b/tests/test_back_rtlil.py @@ -4,7 +4,7 @@ import re from amaranth.back import rtlil from amaranth.hdl import * from amaranth.hdl._ast import * -from amaranth.lib import memory, wiring +from amaranth.lib import memory, wiring, data, enum from .utils import * @@ -2010,6 +2010,67 @@ class PrintTestCase(RTLILTestCase): """) +class DetailTestCase(RTLILTestCase): + def test_enum(self): + class MyEnum(enum.Enum, shape=unsigned(2)): + A = 0 + B = 1 + C = 2 + + sig = Signal(MyEnum) + m = Module() + m.d.comb += sig.eq(MyEnum.A) + self.assertRTLIL(m, [sig.as_value()], R""" + attribute \generator "Amaranth" + attribute \top 1 + module \top + attribute \enum_base_type "MyEnum" + attribute \enum_value_00 "A" + attribute \enum_value_01 "B" + attribute \enum_value_10 "C" + wire width 2 output 0 \sig + connect \sig 2'00 + end + """) + + def test_struct(self): + class MyEnum(enum.Enum, shape=unsigned(2)): + A = 0 + B = 1 + C = 2 + + class Meow(data.Struct): + a: MyEnum + b: 3 + c: signed(4) + d: data.ArrayLayout(2, 2) + + sig = Signal(Meow) + m = Module() + self.assertRTLIL(m, [sig.as_value()], R""" + attribute \generator "Amaranth" + attribute \top 1 + module \top + wire width 13 input 0 \sig + attribute \enum_base_type "MyEnum" + attribute \enum_value_00 "A" + attribute \enum_value_01 "B" + attribute \enum_value_10 "C" + wire width 2 \sig.a + wire width 3 \sig.b + wire width 4 signed \sig.c + wire width 4 \sig.d + wire width 2 \sig.d[0] + wire width 2 \sig.d[1] + connect \sig.a \sig [1:0] + connect \sig.b \sig [4:2] + connect \sig.c \sig [8:5] + connect \sig.d \sig [12:9] + connect \sig.d[0] \sig [10:9] + connect \sig.d[1] \sig [12:11] + end + """) + class ComponentTestCase(RTLILTestCase): def test_component(self): class MyComponent(wiring.Component): diff --git a/tests/test_hdl_ir.py b/tests/test_hdl_ir.py index 6cb3071..4795551 100644 --- a/tests/test_hdl_ir.py +++ b/tests/test_hdl_ir.py @@ -7,6 +7,9 @@ from amaranth.hdl._cd import * from amaranth.hdl._dsl import * from amaranth.hdl._ir import * from amaranth.hdl._mem import * +from amaranth.hdl._nir import SignalField + +from amaranth.lib import enum, data from .utils import * @@ -3501,3 +3504,41 @@ class UndrivenTestCase(FHDLTestCase): (cell 3 0 (flipflop 3.0:5 10 pos 0 0)) ) """) + + +class FieldsTestCase(FHDLTestCase): + def test_fields(self): + class MyEnum(enum.Enum, shape=unsigned(2)): + A = 0 + B = 1 + C = 2 + l = data.StructLayout({"a": MyEnum, "b": signed(3)}) + s1 = Signal(l) + s2 = Signal(MyEnum) + s3 = Signal(signed(3)) + s4 = Signal(unsigned(4)) + nl = build_netlist(Fragment.get(Module(), None), [ + s1.as_value(), s2.as_value(), s3, s4, + ]) + self.assertEqual(nl.signal_fields[s1.as_value()], { + (): SignalField(nl.signals[s1.as_value()], signed=False), + ('a',): SignalField(nl.signals[s1.as_value()][0:2], signed=False, enum_name="MyEnum", enum_variants={ + 0: "A", + 1: "B", + 2: "C", + }), + ('b',): SignalField(nl.signals[s1.as_value()][2:5], signed=True) + }) + self.assertEqual(nl.signal_fields[s2.as_value()], { + (): SignalField(nl.signals[s2.as_value()], signed=False, enum_name="MyEnum", enum_variants={ + 0: "A", + 1: "B", + 2: "C", + }), + }) + self.assertEqual(nl.signal_fields[s3], { + (): SignalField(nl.signals[s3], signed=True), + }) + self.assertEqual(nl.signal_fields[s4], { + (): SignalField(nl.signals[s4], signed=False), + }) diff --git a/tests/test_lib_data.py b/tests/test_lib_data.py index 3384935..b125803 100644 --- a/tests/test_lib_data.py +++ b/tests/test_lib_data.py @@ -647,6 +647,9 @@ class ViewTestCase(FHDLTestCase): def from_bits(self, bits): return bits + def format(self, value, spec): + return Format("") + v = Signal(data.StructLayout({ "f": WrongCastable() }))