Skip to content

Commit 9b7e836

Browse files
oharboeclaude
andcommitted
memories: a parameterized module as a macro
AUTO_MEMORIES blackboxes a parameterized memory module (coralnpu's Sram #(NUM_ENTRIES, GLOBAL_BASE_ADDR)), and the black box keeps its instances' parameters: yosys writes `Sram #(.NUM_ENTRIES(...)) ...` into 1_2_yosys.v, and OpenROAD's Verilog reader stops on it (STA-0171, syntax error). The macro is the module at the widths detection sized it: the parameters are dropped from its instances right after the sources are read. flow/test/test_auto_memories_parameters.py checks that the black box keeps the parameters without the change, and that the instance is written without them, its ports unchanged, with it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: Øyvind Harboe <oyvind.harboe@zylin.com>
1 parent c9ac73e commit 9b7e836

3 files changed

Lines changed: 122 additions & 0 deletions

File tree

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
# AUTO_MEMORIES: the instances of a parameterized memory module (Sram
2+
# #(NUM_ENTRIES)) keep their parameters when the module becomes a black
3+
# box. The netlist then writes them out (`Sram #(.NUM_ENTRIES(32)) u0`),
4+
# and OpenROAD's Verilog reader stops on a parameterized instance of a
5+
# macro (STA-0171). The macro is the module at the widths detection
6+
# sized it: drop the parameters from its instances. Run after the
7+
# sources are read; `names` is the blackboxes.txt list.
8+
proc auto_memories_drop_parameters { names } {
9+
foreach m $names {
10+
set dump [tee -q -s result.string dump t:$m]
11+
set params {}
12+
foreach {- name} [regexp -all -inline -- {parameter \\(\S+)} $dump] {
13+
lappend params $name
14+
}
15+
# once per name: dump lists it once per instance
16+
foreach name [lsort -unique $params] {
17+
setparam -unset $name t:$m
18+
}
19+
}
20+
}

‎flow/scripts/synth_preamble.tcl‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@ proc read_checkpoint { file } {
4949
# canonicalization, one `<module> <area>` per line; see
5050
# docs/user/AutoMemories.md.
5151
source $::env(SCRIPTS_DIR)/memories/check_blackboxed.tcl
52+
source $::env(SCRIPTS_DIR)/memories/drop_parameters.tcl
5253

5354
proc auto_memories_areas { } {
5455
if { ![env_var_equals AUTO_MEMORIES 1] } {
@@ -145,6 +146,7 @@ proc read_design_sources { } {
145146

146147
yosys read_slang {*}$slang_args
147148
auto_memories_check_blackboxed [auto_memories_blackboxes]
149+
auto_memories_drop_parameters [auto_memories_blackboxes]
148150

149151
# Workaround for yosys-slang#119
150152
setattr -unset init
Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
#!/usr/bin/env python3
2+
"""AUTO_MEMORIES drops the parameters from the instances of a
3+
parameterized memory module it blackboxes.
4+
5+
A black box keeps its instances' parameters, and the netlist writes them
6+
out (`Sram #(.NUM_ENTRIES(32)) u0`), which OpenROAD's Verilog reader
7+
refuses for a macro. After auto_memories_drop_parameters the instance is
8+
written as a plain instance of the macro, its ports unchanged.
9+
10+
Skipped when no yosys with read_slang is available: YOSYS_EXE or yosys on
11+
PATH, with the plugin from SLANG_PLUGIN_PATH when read_slang is not built
12+
in.
13+
"""
14+
15+
import os
16+
import shutil
17+
import subprocess
18+
import tempfile
19+
import unittest
20+
21+
DROP_TCL = os.path.join(
22+
os.path.dirname(os.path.abspath(__file__)),
23+
"..",
24+
"scripts",
25+
"memories",
26+
"drop_parameters.tcl",
27+
)
28+
29+
DESIGN = """\
30+
module Sram #(parameter NUM_ENTRIES = 64) (
31+
input clk, input we, input [$clog2(NUM_ENTRIES)-1:0] addr,
32+
input [15:0] d, output logic [15:0] q);
33+
logic [15:0] mem [0:NUM_ENTRIES-1];
34+
always_ff @(posedge clk) begin
35+
if (we) mem[addr] <= d;
36+
q <= mem[addr];
37+
end
38+
endmodule
39+
module top (input clk, input we, input [4:0] addr, input [15:0] d,
40+
output [15:0] q0, q1);
41+
Sram #(.NUM_ENTRIES(32)) u0 (.clk, .we, .addr, .d, .q(q0));
42+
Sram #(.NUM_ENTRIES(32)) u1 (.clk, .we, .addr, .d, .q(q1));
43+
endmodule
44+
"""
45+
46+
47+
def yosys_command():
48+
yosys = os.environ.get("YOSYS_EXE") or shutil.which("yosys")
49+
if not yosys:
50+
return None
51+
cmd = [yosys, "-q"]
52+
plugin = os.environ.get("SLANG_PLUGIN_PATH")
53+
if plugin:
54+
cmd += ["-m", plugin]
55+
probe = subprocess.run(cmd + ["-p", "help read_slang"], capture_output=True)
56+
return cmd if probe.returncode == 0 else None
57+
58+
59+
class TestAutoMemoriesParameters(unittest.TestCase):
60+
def netlist(self, drop):
61+
cmd = yosys_command()
62+
if cmd is None:
63+
self.skipTest("no yosys with read_slang")
64+
with tempfile.TemporaryDirectory() as tmp:
65+
design = os.path.join(tmp, "top.sv")
66+
with open(design, "w") as f:
67+
f.write(DESIGN)
68+
netlist = os.path.join(tmp, "out.v")
69+
script = os.path.join(tmp, "drop.tcl")
70+
with open(script, "w") as f:
71+
f.write(
72+
"yosys -import\n"
73+
f"source {{{DROP_TCL}}}\n"
74+
"yosys read_slang --keep-hierarchy --top top"
75+
f" --blackboxed-module Sram {{{design}}}\n"
76+
+ ("auto_memories_drop_parameters [list Sram]\n" if drop else "")
77+
+ "hierarchy -top top\n"
78+
f"write_verilog -noattr {{{netlist}}}\n"
79+
)
80+
result = subprocess.run(
81+
cmd + ["-c", script], capture_output=True, text=True
82+
)
83+
self.assertEqual(result.returncode, 0, result.stdout + result.stderr)
84+
with open(netlist) as f:
85+
return f.read()
86+
87+
def test_black_box_keeps_parameters(self):
88+
# The problem: without the proc the instance is parameterized.
89+
self.assertIn("Sram #(", self.netlist(drop=False))
90+
91+
def test_parameters_dropped(self):
92+
netlist = self.netlist(drop=True)
93+
self.assertNotIn("#(", netlist)
94+
self.assertIn("Sram u0", netlist)
95+
self.assertIn("Sram u1", netlist)
96+
self.assertIn(".addr(addr)", netlist)
97+
98+
99+
if __name__ == "__main__":
100+
unittest.main()

0 commit comments

Comments
 (0)