Skip to content

Commit 3ff03f9

Browse files
committed
hdl._nir, back.rtlil: use Format.* to emit enum attributes and wires for fields.
1 parent 4cb2dde commit 3ff03f9

File tree

7 files changed

+188
-9
lines changed

7 files changed

+188
-9
lines changed

amaranth/back/rtlil.py

Lines changed: 35 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
import io
44

55
from ..utils import bits_for
6+
from .._utils import to_binary
67
from ..lib import wiring
78
from ..hdl import _repr, _ast, _ir, _nir
89

@@ -421,6 +422,7 @@ def emit(self):
421422
self.emit_cell_wires()
422423
self.emit_submodule_wires()
423424
self.emit_connects()
425+
self.emit_signal_details()
424426
self.emit_submodules()
425427
self.emit_cells()
426428

@@ -491,12 +493,13 @@ def emit_signal_wires(self):
491493
attrs.update(signal.attrs)
492494
self.value_src_loc[value] = signal.src_loc
493495

494-
for repr in signal._value_repr:
495-
if repr.path == () and isinstance(repr.format, _repr.FormatEnum):
496-
enum = repr.format.enum
497-
attrs["enum_base_type"] = enum.__name__
498-
for enum_value in enum:
499-
attrs["enum_value_{:0{}b}".format(enum_value.value, len(signal))] = enum_value.name
496+
_value, _signed, enum = self.netlist.signal_details[signal][()]
497+
if enum is not None:
498+
enum_name, variants = enum
499+
if enum_name is not None:
500+
attrs["enum_base_type"] = enum_name
501+
for var_val, var_name in variants.items():
502+
attrs["enum_value_" + to_binary(var_val, len(signal))] = var_name
500503

501504
if name in self.module.ports:
502505
port_value, _flow = self.module.ports[name]
@@ -666,6 +669,32 @@ def emit_connects(self):
666669
if name not in self.driven_sigports:
667670
self.builder.connect(wire.name, self.sigspec(value))
668671

672+
def emit_signal_details(self):
673+
for signal, name in self.module.signal_names.items():
674+
details = self.netlist.signal_details[signal]
675+
for path, (value, signed, enum) in details.items():
676+
if path == ():
677+
continue
678+
name_parts = [name]
679+
for comp in path:
680+
if isinstance(comp, str):
681+
name_parts.append(f".{comp}")
682+
elif isinstance(comp, int):
683+
name_parts.append(f"[{comp}]")
684+
else:
685+
assert False # :nocov:
686+
attrs = {}
687+
if enum is not None:
688+
enum_name, variants = enum
689+
if enum_name is not None:
690+
attrs["enum_base_type"] = enum_name
691+
for var_val, var_name in variants.items():
692+
attrs["enum_value_" + to_binary(var_val, len(value))] = var_name
693+
wire = self.builder.wire(width=len(value), signed=signed, attrs=attrs,
694+
name="".join(name_parts),
695+
src_loc=signal.src_loc)
696+
self.builder.connect(wire.name, self.sigspec(value))
697+
669698
def emit_submodules(self):
670699
for submodule_idx in self.module.submodules:
671700
submodule = self.netlist.modules[submodule_idx]

amaranth/hdl/_ast.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2095,6 +2095,11 @@ def __init__(self, shape=None, *, name=None, init=None, reset=None, reset_less=F
20952095

20962096
self._attrs = OrderedDict(() if attrs is None else attrs)
20972097

2098+
if isinstance(orig_shape, ShapeCastable):
2099+
self._format = orig_shape.format(orig_shape(self), "")
2100+
else:
2101+
self._format = Format("{}", self)
2102+
20982103
if decoder is not None:
20992104
# The value representation is specified explicitly. Since we do not expose `hdl._repr`,
21002105
# this is the only way to add a custom filter to the signal right now.

amaranth/hdl/_ir.py

Lines changed: 37 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -690,7 +690,7 @@ def emit_value(self, builder):
690690

691691

692692
class NetlistEmitter:
693-
def __init__(self, netlist: _nir.Netlist, design, *, all_undef_to_ff=False):
693+
def __init__(self, netlist: _nir.Netlist, design: Design, *, all_undef_to_ff=False):
694694
self.netlist = netlist
695695
self.design = design
696696
self.all_undef_to_ff = all_undef_to_ff
@@ -776,7 +776,7 @@ def extend(self, value: _nir.Value, signed: bool, width: int):
776776
def emit_operator(self, module_idx: int, operator: str, *inputs: _nir.Value, src_loc):
777777
op = _nir.Operator(module_idx, operator=operator, inputs=inputs, src_loc=src_loc)
778778
return self.netlist.add_value_cell(op.width, op)
779-
779+
780780
def emit_matches(self, module_idx: int, value: _nir.Value, patterns, *, src_loc):
781781
key = module_idx, value, patterns, src_loc
782782
try:
@@ -1334,6 +1334,40 @@ def emit_top_ports(self, fragment: _ir.Fragment):
13341334
else:
13351335
raise ValueError(f"Invalid port direction {dir!r}")
13361336

1337+
def emit_signal_details(self):
1338+
for signal, fragment in self.design.signal_lca.items():
1339+
module_idx = self.fragment_module_idx[fragment]
1340+
details = {}
1341+
def emit_format(path, fmt):
1342+
if isinstance(fmt, _ast.Format):
1343+
specs = [
1344+
chunk[0]
1345+
for chunk in fmt._chunks
1346+
if not isinstance(chunk, str)
1347+
]
1348+
if len(specs) != 1:
1349+
return
1350+
val, signed = self.emit_rhs(module_idx, specs[0])
1351+
details[path] = (val, signed, None)
1352+
elif isinstance(fmt, _ast.Format.Enum):
1353+
val, signed = self.emit_rhs(module_idx, fmt._value)
1354+
details[path] = (val, signed, (fmt._name, fmt._variants))
1355+
elif isinstance(fmt, _ast.Format.Struct):
1356+
val, signed = self.emit_rhs(module_idx, fmt._value)
1357+
details[path] = (val, signed, None)
1358+
for name, subfmt in fmt._fields.items():
1359+
emit_format(path + (name,), subfmt)
1360+
elif isinstance(fmt, _ast.Format.Array):
1361+
val, signed = self.emit_rhs(module_idx, fmt._value)
1362+
details[path] = (val, signed, None)
1363+
for idx, subfmt in enumerate(fmt._fields):
1364+
emit_format(path + (idx,), subfmt)
1365+
emit_format((), signal._format)
1366+
val, signed = self.emit_rhs(module_idx, signal)
1367+
if () not in details or details[()][0] != val:
1368+
details[()] = (val, signed, None)
1369+
self.netlist.signal_details[signal] = details
1370+
13371371
def emit_drivers(self):
13381372
for driver in self.drivers.values():
13391373
if (driver.domain is not None and
@@ -1452,6 +1486,7 @@ def emit_fragment(self, fragment: _ir.Fragment, parent_module_idx: 'int | None',
14521486
for subfragment, _name, sub_src_loc in fragment.subfragments:
14531487
self.emit_fragment(subfragment, module_idx, cell_src_loc=sub_src_loc)
14541488
if parent_module_idx is None:
1489+
self.emit_signal_details()
14551490
self.emit_drivers()
14561491
self.emit_top_ports(fragment)
14571492
if self.all_undef_to_ff:

amaranth/hdl/_nir.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -329,6 +329,7 @@ def __init__(self):
329329
self.connections: dict[Net, Net] = {}
330330
self.io_ports: list[_ast.IOPort] = []
331331
self.signals = SignalDict()
332+
self.signal_details = SignalDict()
332333
self.last_late_net = 0
333334

334335
def resolve_net(self, net: Net):
@@ -345,6 +346,11 @@ def resolve_all_nets(self):
345346
cell.resolve_nets(self)
346347
for sig in self.signals:
347348
self.signals[sig] = self.resolve_value(self.signals[sig])
349+
for signal in self.signals:
350+
self.signal_details[signal] = {
351+
path: (self.resolve_value(value), signed, enum)
352+
for path, (value, signed, enum) in self.signal_details[signal].items()
353+
}
348354

349355
def __repr__(self):
350356
result = ["("]

tests/test_back_rtlil.py

Lines changed: 62 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
from amaranth.back import rtlil
55
from amaranth.hdl import *
66
from amaranth.hdl._ast import *
7-
from amaranth.lib import memory, wiring
7+
from amaranth.lib import memory, wiring, data, enum
88

99
from .utils import *
1010

@@ -2010,6 +2010,67 @@ def test_print_align(self):
20102010
""")
20112011

20122012

2013+
class DetailTestCase(RTLILTestCase):
2014+
def test_enum(self):
2015+
class MyEnum(enum.Enum, shape=unsigned(2)):
2016+
A = 0
2017+
B = 1
2018+
C = 2
2019+
2020+
sig = Signal(MyEnum)
2021+
m = Module()
2022+
m.d.comb += sig.eq(MyEnum.A)
2023+
self.assertRTLIL(m, [sig.as_value()], R"""
2024+
attribute \generator "Amaranth"
2025+
attribute \top 1
2026+
module \top
2027+
attribute \enum_base_type "MyEnum"
2028+
attribute \enum_value_00 "A"
2029+
attribute \enum_value_01 "B"
2030+
attribute \enum_value_10 "C"
2031+
wire width 2 output 0 \sig
2032+
connect \sig 2'00
2033+
end
2034+
""")
2035+
2036+
def test_struct(self):
2037+
class MyEnum(enum.Enum, shape=unsigned(2)):
2038+
A = 0
2039+
B = 1
2040+
C = 2
2041+
2042+
class Meow(data.Struct):
2043+
a: MyEnum
2044+
b: 3
2045+
c: signed(4)
2046+
d: data.ArrayLayout(2, 2)
2047+
2048+
sig = Signal(Meow)
2049+
m = Module()
2050+
self.assertRTLIL(m, [sig.as_value()], R"""
2051+
attribute \generator "Amaranth"
2052+
attribute \top 1
2053+
module \top
2054+
wire width 13 input 0 \sig
2055+
attribute \enum_base_type "MyEnum"
2056+
attribute \enum_value_00 "A"
2057+
attribute \enum_value_01 "B"
2058+
attribute \enum_value_10 "C"
2059+
wire width 2 \sig.a
2060+
wire width 3 \sig.b
2061+
wire width 4 signed \sig.c
2062+
wire width 4 \sig.d
2063+
wire width 2 \sig.d[0]
2064+
wire width 2 \sig.d[1]
2065+
connect \sig.a \sig [1:0]
2066+
connect \sig.b \sig [4:2]
2067+
connect \sig.c \sig [8:5]
2068+
connect \sig.d \sig [12:9]
2069+
connect \sig.d[0] \sig [10:9]
2070+
connect \sig.d[1] \sig [12:11]
2071+
end
2072+
""")
2073+
20132074
class ComponentTestCase(RTLILTestCase):
20142075
def test_component(self):
20152076
class MyComponent(wiring.Component):

tests/test_hdl_ir.py

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@
88
from amaranth.hdl._ir import *
99
from amaranth.hdl._mem import *
1010

11+
from amaranth.lib import enum, data
12+
1113
from .utils import *
1214

1315

@@ -3501,3 +3503,41 @@ def test_undef_to_ff_partial(self):
35013503
(cell 3 0 (flipflop 3.0:5 10 pos 0 0))
35023504
)
35033505
""")
3506+
3507+
3508+
class DetailsTestCase(FHDLTestCase):
3509+
def test_details(self):
3510+
class MyEnum(enum.Enum, shape=unsigned(2)):
3511+
A = 0
3512+
B = 1
3513+
C = 2
3514+
l = data.StructLayout({"a": MyEnum, "b": signed(3)})
3515+
s1 = Signal(l)
3516+
s2 = Signal(MyEnum)
3517+
s3 = Signal(signed(3))
3518+
s4 = Signal(unsigned(4))
3519+
nl = build_netlist(Fragment.get(Module(), None), [
3520+
s1.as_value(), s2.as_value(), s3, s4,
3521+
])
3522+
self.assertEqual(nl.signal_details[s1.as_value()], {
3523+
(): (nl.signals[s1.as_value()], False, None),
3524+
('a',): (nl.signals[s1.as_value()][0:2], False, ("MyEnum", {
3525+
0: "A",
3526+
1: "B",
3527+
2: "C",
3528+
})),
3529+
('b',): (nl.signals[s1.as_value()][2:5], True, None)
3530+
})
3531+
self.assertEqual(nl.signal_details[s2.as_value()], {
3532+
(): (nl.signals[s2.as_value()], False, ("MyEnum", {
3533+
0: "A",
3534+
1: "B",
3535+
2: "C",
3536+
})),
3537+
})
3538+
self.assertEqual(nl.signal_details[s3], {
3539+
(): (nl.signals[s3], True, None),
3540+
})
3541+
self.assertEqual(nl.signal_details[s4], {
3542+
(): (nl.signals[s4], False, None),
3543+
})

tests/test_lib_data.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -647,6 +647,9 @@ def const(self, init):
647647
def from_bits(self, bits):
648648
return bits
649649

650+
def format(self, value, spec):
651+
return Format("")
652+
650653
v = Signal(data.StructLayout({
651654
"f": WrongCastable()
652655
}))

0 commit comments

Comments
 (0)