From 736b4ba96966925db573aaed82cbf484934382ba Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=98yvind=20Harboe?= Date: Wed, 11 Nov 2026 05:26:59 +0100 Subject: [PATCH] regfile_ram: refuse a named bit_folds the fold rule would fold further MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit A spec that names bit_folds bypasses the fold rule, so a pinned low fold (bit_folds 1 written out as a default) builds the array as named: a long, thin outline whose word selects and write enables run the full width. repair_design then buffers those long wires inside the array, where the only sites wide enough are the rows above and below it, and detailed placement cannot legalise the buffers. Factor the fold rule into _folds() and, when a spec names bit_folds, apply the rule to the array that fold builds. If the rule would fold it further, stop with the aspect ratio, the word span and the fold the rule would choose, instead of building it. A named fold at or above the rule's choice builds as before. Tests in gen_memories_test.py stub generate_ram to check the rule, the refusal and that a fold at the rule's choice is accepted. Signed-off-by: Øyvind Harboe --- flow/scripts/memories/gen_memories_test.py | 69 ++++++++++++++++++++++ flow/scripts/memories/regfile_ram.py | 43 ++++++++++---- 2 files changed, 100 insertions(+), 12 deletions(-) diff --git a/flow/scripts/memories/gen_memories_test.py b/flow/scripts/memories/gen_memories_test.py index 23dc6f276f..d9b3be9cfc 100644 --- a/flow/scripts/memories/gen_memories_test.py +++ b/flow/scripts/memories/gen_memories_test.py @@ -5,6 +5,7 @@ import tempfile import unittest from pathlib import Path +from unittest import mock sys.path.insert(0, str(Path(__file__).resolve().parent)) @@ -315,6 +316,74 @@ def test_spec_check(self): gen_memories.regfile_ram.check(spec) self.assertIn(message, str(stop.exception)) + def test_fold_rule(self): + # a word folds toward the square while the array is more than 8 + # times as wide as tall, or while a word runs over WORD_SPAN_UM + folds = gen_memories.regfile_ram._folds + self.assertEqual(folds(100, 100, 100, 64), 1) + self.assertEqual(folds(600, 17.6, 100, 64), 8) + self.assertEqual(folds(300, 100, 300, 64), 2) + # never an empty fold: 3 bits fold at most in two + self.assertEqual(folds(600, 17.6, 600, 3), 2) + + def _generate(self, bit_folds, outline): + # generate() with generate_ram stubbed: the generate script writes + # the outline (dx dy dbu), the views script stops the run + d = Path(self.tmp.name) + spec = d / "rf.regfile" + spec.write_text( + "module rf\nwords 32\nbits 64\nclock clk\nread ra rd\n" + "write wa wd we\n" + (f"bit_folds {bit_folds}\n" if bit_folds else "") + ) + + class Views(Exception): + pass + + scripts = [] + + def openroad(cmd): + script = Path(cmd[-1]) + scripts.append(script.name) + if script.name.endswith(".generate.tcl"): + (d / "rf_ram.outline").write_text(outline) + else: + raise Views() + + with mock.patch.dict("os.environ", {"LIB_FILES": "cells.lib"}): + with mock.patch.object( + gen_memories.regfile_ram.subprocess, "check_call", openroad + ): + with self.assertRaises((Views, SystemExit)) as stop: + gen_memories.regfile_ram.generate( + spec, "rf", "inline", "openroad", [], d + ) + return stop.exception, scripts + + def test_named_folds_refused_below_rule(self): + # a long, thin array: the rule folds a word in 8; a spec naming + # fewer is refused with the fold the rule would choose + stop, scripts = self._generate(1, "600000 17600 1000\n") + self.assertIsInstance(stop, SystemExit) + self.assertIn("bit_folds 1", str(stop)) + self.assertIn("34.1 times as wide as tall", str(stop)) + self.assertIn("600 um long", str(stop)) + self.assertIn("folds it 8 times", str(stop)) + self.assertEqual(scripts, ["rf.generate.tcl"]) + + def test_named_folds_accepted_at_rule(self): + # the same array folded as the rule would fold it is built as named + stop, scripts = self._generate(8, "140000 140000 1000\n") + self.assertNotIsInstance(stop, SystemExit) + self.assertEqual(scripts, ["rf.generate.tcl", "rf.views.tcl"]) + + def test_rule_folds_unnamed(self): + # with no bit_folds the rule folds and builds again + stop, scripts = self._generate(None, "600000 17600 1000\n") + self.assertNotIsInstance(stop, SystemExit) + self.assertEqual( + scripts, ["rf.generate.tcl", "rf.generate.tcl", "rf.views.tcl"] + ) + if __name__ == "__main__": unittest.main() diff --git a/flow/scripts/memories/regfile_ram.py b/flow/scripts/memories/regfile_ram.py index 69ad9aa796..44ac053757 100644 --- a/flow/scripts/memories/regfile_ram.py +++ b/flow/scripts/memories/regfile_ram.py @@ -539,6 +539,24 @@ def _module_header_ports(verilog: str, module: str) -> dict[str, tuple[str, int] return ports +def _folds(width: float, height: float, span_um: float, bits: int) -> int: + """How many rows to fold a word of `bits` into, a power of two, for an + array width by height with a word span_um long in a row (see + generate).""" + folds = 1 + # a short last fold is allowed, an empty one is not + while ( + folds * 2 <= bits + and (folds * 2 - 1) * -(-bits // (folds * 2)) < bits + and ( + (width > 8 * height and (folds * 2) ** 2 <= 2 * width / height) + or span_um / folds > WORD_SPAN_UM + ) + ): + folds *= 2 + return folds + + def generate( spec: Path, module: str, @@ -593,7 +611,8 @@ def build(spec_lines: list[list[str]]) -> tuple[float, float]: # (XiangShan's RenameBufferFile, a word a row 258 um wide, failed; # RobEntryFile at 74 um and IntRegFile at 96 um a word did not). # - # A spec names bit_folds to choose. + # A spec may name bit_folds, but not fewer than the rule would fold + # the array it names: that is refused, not overridden. bits = int(_one(lines, "bits")) dbu = float(outline.read_text().split()[2]) # the words side by side in a row, each contiguous (see _args) unless @@ -605,20 +624,20 @@ def build(spec_lines: list[list[str]]) -> tuple[float, float]: ) span_um = width / dbu / row_words if not _values(lines, "bit_folds"): - folds = 1 - # a short last fold is allowed, an empty one is not - while ( - folds * 2 <= bits - and (folds * 2 - 1) * -(-bits // (folds * 2)) < bits - and ( - (width > 8 * height and (folds * 2) ** 2 <= 2 * width / height) - or span_um / folds > WORD_SPAN_UM - ) - ): - folds *= 2 + folds = _folds(width, height, span_um, bits) if folds > 1: lines = lines + [["bit_folds", str(folds)]] build(lines) + else: + named = int(_one(lines, "bit_folds")) + more = _folds(width, height, span_um, -(-bits // named)) + if more > 1: + raise SystemExit( + f"regfile_ram: {spec}: bit_folds {named} leaves the array" + f" {width / height:.1f} times as wide as tall and a word" + f" {span_um:.0f} um long; the fold rule folds it" + f" {named * more} times: leave bit_folds out, or name that" + ) views = out_dir / f"{module}.views.tcl" tcl = reads + f"read_db {out_dir / f'{block}.odb'}\n" # the RTL's flop names, for equivalence checking