Skip to content

Commit f21d3d0

Browse files
wanda-phiwhitequark
authored andcommitted
hdl._ir: add all_undef_to_ff mode.
1 parent 767d69c commit f21d3d0

File tree

2 files changed

+109
-5
lines changed

2 files changed

+109
-5
lines changed

amaranth/hdl/_ir.py

Lines changed: 46 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -669,12 +669,14 @@ def emit_value(self, builder):
669669

670670

671671
class NetlistEmitter:
672-
def __init__(self, netlist: _nir.Netlist, design):
672+
def __init__(self, netlist: _nir.Netlist, design, *, all_undef_to_ff=False):
673673
self.netlist = netlist
674674
self.design = design
675+
self.all_undef_to_ff = all_undef_to_ff
675676
self.drivers = _ast.SignalDict()
676677
self.io_ports: dict[_ast.IOPort, int] = {}
677678
self.rhs_cache: dict[int, Tuple[_nir.Value, bool, _ast.Value]] = {}
679+
self.fragment_module_idx: dict[Fragment, int] = {}
678680

679681
# Collected for driver conflict diagnostics only.
680682
self.late_net_to_signal = {}
@@ -1358,6 +1360,39 @@ def emit_drivers(self):
13581360
src_loc = driver.signal.src_loc
13591361
self.connect(self.emit_signal(driver.signal), value, src_loc=src_loc)
13601362

1363+
def emit_undef_ff(self):
1364+
# Connect all completely undriven signals to flip-flops with const-0 clock. This is used
1365+
# for simulation targets, so that undriven signals have allocated storage that can be
1366+
# used by the testbench to drive them, instead of being hardwired to the init value
1367+
# constant.
1368+
for signal, value in self.netlist.signals.items():
1369+
fragment = self.design.signal_lca[signal]
1370+
module_idx = self.fragment_module_idx[fragment]
1371+
pos = 0
1372+
while pos < len(signal):
1373+
net = value[pos]
1374+
if not net.is_late or net in self.netlist.connections:
1375+
pos += 1
1376+
else:
1377+
end_pos = pos
1378+
while (end_pos < len(signal) and
1379+
value[end_pos].is_late and
1380+
value[end_pos] not in self.netlist.connections):
1381+
end_pos += 1
1382+
init = (signal.init >> pos) & ((1 << (end_pos - pos)) - 1)
1383+
cell = _nir.FlipFlop(module_idx,
1384+
data=value[pos:end_pos],
1385+
init=init,
1386+
clk=_nir.Net.from_const(0),
1387+
clk_edge="pos",
1388+
arst=_nir.Net.from_const(0),
1389+
attributes={},
1390+
src_loc=signal.src_loc,
1391+
)
1392+
ff_value = self.netlist.add_value_cell(end_pos - pos, cell)
1393+
self.connect(value[pos:end_pos], ff_value, src_loc=signal.src_loc)
1394+
pos = end_pos
1395+
13611396
def emit_undriven(self):
13621397
# Connect all undriven signal bits to their initial values. This can only happen for entirely
13631398
# undriven signals, or signals that are partially driven by instances.
@@ -1373,6 +1408,7 @@ def emit_fragment(self, fragment: _ir.Fragment, parent_module_idx: 'int | None',
13731408
if isinstance(fragment, _ir.Instance):
13741409
assert parent_module_idx is not None
13751410
self.emit_instance(parent_module_idx, fragment, name=fragment_name[-1])
1411+
self.fragment_module_idx[fragment] = parent_module_idx
13761412
elif isinstance(fragment, _mem.MemoryInstance):
13771413
assert parent_module_idx is not None
13781414
memory = self.emit_memory(parent_module_idx, fragment, name=fragment_name[-1])
@@ -1381,11 +1417,14 @@ def emit_fragment(self, fragment: _ir.Fragment, parent_module_idx: 'int | None',
13811417
write_ports.append(self.emit_write_port(parent_module_idx, fragment, port, memory))
13821418
for port in fragment._read_ports:
13831419
self.emit_read_port(parent_module_idx, fragment, port, memory, write_ports)
1420+
self.fragment_module_idx[fragment] = parent_module_idx
13841421
elif isinstance(fragment, _ir.IOBufferInstance):
13851422
assert parent_module_idx is not None
13861423
self.emit_iobuffer(parent_module_idx, fragment)
1424+
self.fragment_module_idx[fragment] = parent_module_idx
13871425
elif type(fragment) is _ir.Fragment:
13881426
module_idx = self.netlist.add_module(parent_module_idx, fragment_name, src_loc=fragment.src_loc, cell_src_loc=cell_src_loc)
1427+
self.fragment_module_idx[fragment] = module_idx
13891428
signal_names = self.design.fragments[fragment].signal_names
13901429
self.netlist.modules[module_idx].signal_names = signal_names
13911430
io_port_names = self.design.fragments[fragment].io_port_names
@@ -1402,13 +1441,15 @@ def emit_fragment(self, fragment: _ir.Fragment, parent_module_idx: 'int | None',
14021441
if parent_module_idx is None:
14031442
self.emit_drivers()
14041443
self.emit_top_ports(fragment)
1444+
if self.all_undef_to_ff:
1445+
self.emit_undef_ff()
14051446
self.emit_undriven()
14061447
else:
14071448
assert False # :nocov:
14081449

14091450

1410-
def _emit_netlist(netlist: _nir.Netlist, design):
1411-
NetlistEmitter(netlist, design).emit_fragment(design.fragment, None)
1451+
def _emit_netlist(netlist: _nir.Netlist, design, *, all_undef_to_ff=False):
1452+
NetlistEmitter(netlist, design, all_undef_to_ff=all_undef_to_ff).emit_fragment(design.fragment, None)
14121453

14131454

14141455
def _compute_net_flows(netlist: _nir.Netlist):
@@ -1640,13 +1681,13 @@ def _compute_io_ports(netlist: _nir.Netlist, ports):
16401681
visited.update(value)
16411682

16421683

1643-
def build_netlist(fragment, ports=(), *, name="top", **kwargs):
1684+
def build_netlist(fragment, ports=(), *, name="top", all_undef_to_ff=False, **kwargs):
16441685
if isinstance(fragment, Design):
16451686
design = fragment
16461687
else:
16471688
design = fragment.prepare(ports=ports, hierarchy=(name,), **kwargs)
16481689
netlist = _nir.Netlist()
1649-
_emit_netlist(netlist, design)
1690+
_emit_netlist(netlist, design, all_undef_to_ff=all_undef_to_ff)
16501691
netlist.resolve_all_nets()
16511692
_compute_net_flows(netlist)
16521693
_compute_ports(netlist)

tests/test_hdl_ir.py

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -460,6 +460,7 @@ def test_port_not_iterable(self):
460460
r" \(did you mean `ports=\(<signal>,\)`, rather than `ports=<signal>`\?\)$")):
461461
build_netlist(f, ports=Const(1))
462462

463+
463464
class FragmentDomainsTestCase(FHDLTestCase):
464465
def test_propagate_up(self):
465466
cd = ClockDomain()
@@ -981,6 +982,7 @@ def test_nir_operator(self):
981982
)
982983
""")
983984

985+
984986
class NamesTestCase(FHDLTestCase):
985987
def test_assign_names_to_signals(self):
986988
i = Signal()
@@ -1989,6 +1991,7 @@ def test_sliced_operator(self):
19891991
)
19901992
""")
19911993

1994+
19921995
class RhsTestCase(FHDLTestCase):
19931996
def test_const(self):
19941997
o1 = Signal(8)
@@ -3173,6 +3176,7 @@ def test_initial(self):
31733176
)
31743177
""")
31753178

3179+
31763180
class SwitchTestCase(FHDLTestCase):
31773181
def test_comb(self):
31783182
o1 = Signal(8)
@@ -3436,6 +3440,7 @@ def test_assert(self):
34363440
)
34373441
""")
34383442

3443+
34393444
class ConflictTestCase(FHDLTestCase):
34403445
def test_domain_conflict(self):
34413446
s = Signal()
@@ -3472,3 +3477,61 @@ def test_instance_conflict(self):
34723477
r"^Bit 0 of signal \(sig s\) has multiple drivers: "
34733478
r".*test_hdl_ir.py:\d+ and .*test_hdl_ir.py:\d+$"):
34743479
build_netlist(Fragment.get(m, None), [])
3480+
3481+
3482+
class UndrivenTestCase(FHDLTestCase):
3483+
def test_undriven(self):
3484+
o = Signal(8)
3485+
m = Module()
3486+
nl = build_netlist(Fragment.get(m, None), [
3487+
("o", o, PortDirection.Output),
3488+
])
3489+
self.assertRepr(nl, """
3490+
(
3491+
(module 0 None ('top')
3492+
(output 'o' 8'd0)
3493+
)
3494+
(cell 0 0 (top
3495+
(output 'o' 8'd0)
3496+
))
3497+
)
3498+
""")
3499+
3500+
def test_undef_to_ff(self):
3501+
o = Signal(8, init=0x55)
3502+
m = Module()
3503+
nl = build_netlist(Fragment.get(m, None), [
3504+
("o", o, PortDirection.Output),
3505+
], all_undef_to_ff=True)
3506+
self.assertRepr(nl, """
3507+
(
3508+
(module 0 None ('top')
3509+
(output 'o' 1.0:8)
3510+
)
3511+
(cell 0 0 (top
3512+
(output 'o' 1.0:8)
3513+
))
3514+
(cell 1 0 (flipflop 1.0:8 85 pos 0 0))
3515+
)
3516+
""")
3517+
3518+
def test_undef_to_ff_partial(self):
3519+
o = Signal(8, init=0x55)
3520+
m = Module()
3521+
m.submodules.inst = Instance("t", o_o=o[2])
3522+
nl = build_netlist(Fragment.get(m, None), [
3523+
("o", o, PortDirection.Output),
3524+
], all_undef_to_ff=True)
3525+
self.assertRepr(nl, """
3526+
(
3527+
(module 0 None ('top')
3528+
(output 'o' (cat 2.0:2 1.0 3.0:5))
3529+
)
3530+
(cell 0 0 (top
3531+
(output 'o' (cat 2.0:2 1.0 3.0:5))
3532+
))
3533+
(cell 1 0 (instance 't' 'inst' (output 'o' 0:1)))
3534+
(cell 2 0 (flipflop 2.0:2 1 pos 0 0))
3535+
(cell 3 0 (flipflop 3.0:5 10 pos 0 0))
3536+
)
3537+
""")

0 commit comments

Comments
 (0)