From 9b7e836e03e1c28a2a937851ee25f8377bd05213 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=98yvind=20Harboe?= Date: Tue, 3 Nov 2026 09:09:22 +0100 Subject: [PATCH] memories: a parameterized module as a macro MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 Signed-off-by: Øyvind Harboe --- flow/scripts/memories/drop_parameters.tcl | 20 +++++ flow/scripts/synth_preamble.tcl | 2 + flow/test/test_auto_memories_parameters.py | 100 +++++++++++++++++++++ 3 files changed, 122 insertions(+) create mode 100644 flow/scripts/memories/drop_parameters.tcl create mode 100644 flow/test/test_auto_memories_parameters.py diff --git a/flow/scripts/memories/drop_parameters.tcl b/flow/scripts/memories/drop_parameters.tcl new file mode 100644 index 0000000000..1dbe2c81b4 --- /dev/null +++ b/flow/scripts/memories/drop_parameters.tcl @@ -0,0 +1,20 @@ +# AUTO_MEMORIES: the instances of a parameterized memory module (Sram +# #(NUM_ENTRIES)) keep their parameters when the module becomes a black +# box. The netlist then writes them out (`Sram #(.NUM_ENTRIES(32)) u0`), +# and OpenROAD's Verilog reader stops on a parameterized instance of a +# macro (STA-0171). The macro is the module at the widths detection +# sized it: drop the parameters from its instances. Run after the +# sources are read; `names` is the blackboxes.txt list. +proc auto_memories_drop_parameters { names } { + foreach m $names { + set dump [tee -q -s result.string dump t:$m] + set params {} + foreach {- name} [regexp -all -inline -- {parameter \\(\S+)} $dump] { + lappend params $name + } + # once per name: dump lists it once per instance + foreach name [lsort -unique $params] { + setparam -unset $name t:$m + } + } +} diff --git a/flow/scripts/synth_preamble.tcl b/flow/scripts/synth_preamble.tcl index 268f5cd9da..8e41f47295 100644 --- a/flow/scripts/synth_preamble.tcl +++ b/flow/scripts/synth_preamble.tcl @@ -49,6 +49,7 @@ proc read_checkpoint { file } { # canonicalization, one ` ` per line; see # docs/user/AutoMemories.md. source $::env(SCRIPTS_DIR)/memories/check_blackboxed.tcl +source $::env(SCRIPTS_DIR)/memories/drop_parameters.tcl proc auto_memories_areas { } { if { ![env_var_equals AUTO_MEMORIES 1] } { @@ -145,6 +146,7 @@ proc read_design_sources { } { yosys read_slang {*}$slang_args auto_memories_check_blackboxed [auto_memories_blackboxes] + auto_memories_drop_parameters [auto_memories_blackboxes] # Workaround for yosys-slang#119 setattr -unset init diff --git a/flow/test/test_auto_memories_parameters.py b/flow/test/test_auto_memories_parameters.py new file mode 100644 index 0000000000..f208c6a265 --- /dev/null +++ b/flow/test/test_auto_memories_parameters.py @@ -0,0 +1,100 @@ +#!/usr/bin/env python3 +"""AUTO_MEMORIES drops the parameters from the instances of a +parameterized memory module it blackboxes. + +A black box keeps its instances' parameters, and the netlist writes them +out (`Sram #(.NUM_ENTRIES(32)) u0`), which OpenROAD's Verilog reader +refuses for a macro. After auto_memories_drop_parameters the instance is +written as a plain instance of the macro, its ports unchanged. + +Skipped when no yosys with read_slang is available: YOSYS_EXE or yosys on +PATH, with the plugin from SLANG_PLUGIN_PATH when read_slang is not built +in. +""" + +import os +import shutil +import subprocess +import tempfile +import unittest + +DROP_TCL = os.path.join( + os.path.dirname(os.path.abspath(__file__)), + "..", + "scripts", + "memories", + "drop_parameters.tcl", +) + +DESIGN = """\ +module Sram #(parameter NUM_ENTRIES = 64) ( + input clk, input we, input [$clog2(NUM_ENTRIES)-1:0] addr, + input [15:0] d, output logic [15:0] q); + logic [15:0] mem [0:NUM_ENTRIES-1]; + always_ff @(posedge clk) begin + if (we) mem[addr] <= d; + q <= mem[addr]; + end +endmodule +module top (input clk, input we, input [4:0] addr, input [15:0] d, + output [15:0] q0, q1); + Sram #(.NUM_ENTRIES(32)) u0 (.clk, .we, .addr, .d, .q(q0)); + Sram #(.NUM_ENTRIES(32)) u1 (.clk, .we, .addr, .d, .q(q1)); +endmodule +""" + + +def yosys_command(): + yosys = os.environ.get("YOSYS_EXE") or shutil.which("yosys") + if not yosys: + return None + cmd = [yosys, "-q"] + plugin = os.environ.get("SLANG_PLUGIN_PATH") + if plugin: + cmd += ["-m", plugin] + probe = subprocess.run(cmd + ["-p", "help read_slang"], capture_output=True) + return cmd if probe.returncode == 0 else None + + +class TestAutoMemoriesParameters(unittest.TestCase): + def netlist(self, drop): + cmd = yosys_command() + if cmd is None: + self.skipTest("no yosys with read_slang") + with tempfile.TemporaryDirectory() as tmp: + design = os.path.join(tmp, "top.sv") + with open(design, "w") as f: + f.write(DESIGN) + netlist = os.path.join(tmp, "out.v") + script = os.path.join(tmp, "drop.tcl") + with open(script, "w") as f: + f.write( + "yosys -import\n" + f"source {{{DROP_TCL}}}\n" + "yosys read_slang --keep-hierarchy --top top" + f" --blackboxed-module Sram {{{design}}}\n" + + ("auto_memories_drop_parameters [list Sram]\n" if drop else "") + + "hierarchy -top top\n" + f"write_verilog -noattr {{{netlist}}}\n" + ) + result = subprocess.run( + cmd + ["-c", script], capture_output=True, text=True + ) + self.assertEqual(result.returncode, 0, result.stdout + result.stderr) + with open(netlist) as f: + return f.read() + + def test_black_box_keeps_parameters(self): + # The problem: without the proc the instance is parameterized. + self.assertIn("Sram #(", self.netlist(drop=False)) + + def test_parameters_dropped(self): + netlist = self.netlist(drop=True) + self.assertNotIn("#(", netlist) + self.assertIn("Sram u0", netlist) + self.assertIn("Sram u1", netlist) + self.assertIn(".addr(addr)", netlist) + + +if __name__ == "__main__": + unittest.main()