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