Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
69 changes: 69 additions & 0 deletions flow/scripts/memories/gen_memories_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -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))

Expand Down Expand Up @@ -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()
43 changes: 31 additions & 12 deletions flow/scripts/memories/regfile_ram.py
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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
Expand All @@ -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
Expand Down