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
4 changes: 4 additions & 0 deletions docs/user/AutoMemories.md
Original file line number Diff line number Diff line change
Expand Up @@ -185,6 +185,10 @@ cell flop DFFHQNx1_ASAP7_75t_R
...
```

A listed module is a register file even where the idiomatic gate would
convert it: FakeRAM builds no view for it, and `memories.json` records
it once, as a register file.

`memories/regfile_ram.py` translates the spec to `generate_ram_netlist`
options (ports, banks, folds, the zero word, reset, read latency, write
style and priority, cells) and checks it against the module's ports,
Expand Down
83 changes: 59 additions & 24 deletions flow/scripts/memories/gen_memories.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,10 @@
the .lib states it — what synthesis
blackboxes and the cost it gives each

FakeRAM writes the .lib/.lef of each idiomatic memory and generate_ram
those of each register file; this script alone writes the lists, once
every view exists.

Everything downstream consumes these files; nothing else is passed
between the generator and the flow.
"""
Expand Down Expand Up @@ -93,13 +97,20 @@ def _spec_mode(spec: Path) -> str:

def _lib_area(lib: Path) -> str:
"""The cell area `lib` states, in um^2: what blackboxes.txt gives a
register file beside its name, as FakeRAM does for its macros."""
macro beside its name."""
m = re.search(r"^\s*area\s*:\s*([0-9.eE+-]+)\s*;", lib.read_text(), re.M)
if not m:
raise SystemExit(f"gen_memories: {lib} states no area")
return m.group(1)


def _shape(spec: Path, module: str, macro_place: set[str]) -> str:
"""How a register file stands in for its module: its spec's mode when
AUTO_MEMORIES_MACRO_PLACE names it, else inline."""
mode = _spec_mode(spec)
return mode if module in macro_place else "inline"


def _regfile_memory(spec: Path, module: str, reason: str) -> schema.Memory:
"""A register file's memories.json record, its shape from its spec."""
mem = schema.Memory(name=module, kind="regfile", source="listed", spec=str(spec))
Expand All @@ -115,13 +126,43 @@ def _regfile_memory(spec: Path, module: str, reason: str) -> schema.Memory:
return mem


def regfile_memories(
specs: list[Path], macro_place: list[str] | None = None
) -> list[schema.Memory]:
"""The memories.json record of every listed register file."""
macro_place = set(macro_place or [])
memories = []
for spec in specs:
module = _spec_module(spec)
shape = _shape(spec, module, macro_place)
memories.append(
_regfile_memory(spec, module, f"register file, generate_ram, {shape}")
)
return memories


def write_blackboxes(out_dir: Path, macros: list[str]) -> None:
"""blackboxes.txt: each macro once, with the area its .lib states."""
areas: dict[str, str] = {}
for name in macros:
if name in areas:
raise SystemExit(
f"gen_memories: two macros are named {name}, and the "
"second's views overwrote the first's"
)
areas[name] = _lib_area(out_dir / f"{name}.lib")
(out_dir / "blackboxes.txt").write_text(
"".join(f"{name} {area}\n" for name, area in areas.items())
)


def run_regfiles(
specs: list[Path],
openroad: str | None,
lefs: list[Path],
out_dir: Path,
macro_place: list[str] | None = None,
) -> list[schema.Memory]:
) -> list[str]:
"""Generate every listed register file with OpenROAD's generate_ram,
through regfile_ram.py: a wrapper in the RTL module's place connects
its ports to the generated RAM's.
Expand All @@ -136,7 +177,8 @@ def run_regfiles(
A register file named in AUTO_MEMORIES_MACRO_PLACE is a macro to
synthesis and to macro placement: its
abstract LEF and model liberty land beside the FakeRAM views and the
module joins blackboxes.txt. In `mode netlist` it then dissolves into
RAM it wraps is one of the macros returned, for blackboxes.txt. In
`mode netlist` it then dissolves into
its placed cells after macro placement (regfile_dissolve.tcl), which
reads its structural netlist and the placement of its core
(<m>.v, <m>.place) and finds it in regfiles.txt. A spec whose ports
Expand Down Expand Up @@ -172,19 +214,14 @@ def run_regfiles(
+ ", which no AUTO_MEMORIES_REGFILES spec builds; it lists "
+ " ".join(modules)
)
memories = []
dissolve = []
cores = []
inline = []
for spec, module in zip(specs, modules):
mode = _spec_mode(spec)
# the RTL module's place taken by a wrapper around the RAM
shape = mode if module in macro_place else "inline"
shape = _shape(spec, module, macro_place)
regfile_ram.check(spec)
ram = regfile_ram.generate(spec, module, shape, openroad, lefs, out_dir)
memories.append(
_regfile_memory(spec, module, f"register file, generate_ram, {shape}")
)
inline.append(module)
if ram["macro"]:
cores.append(ram["block"])
Expand All @@ -194,17 +231,10 @@ def run_regfiles(
f"gen_memories: {module} -> register file, generate_ram, "
f"{shape} ({spec.name})\n"
)
with (out_dir / "blackboxes.txt").open("a") as f:
f.write(
"".join(
f"{name} {_lib_area(out_dir / f'{name}.lib')}\n"
for name in [m.name for m in memories if m.name not in inline] + cores
)
)
(out_dir / "regfiles.txt").write_text("".join(f"{m}\n" for m in dissolve))
(out_dir / "inline.txt").write_text("".join(f"{m}\n" for m in inline))
(out_dir / "regfile_cores.txt").write_text("".join(f"{m}\n" for m in cores))
return memories
return cores


def run(
Expand Down Expand Up @@ -249,6 +279,13 @@ def run(
if mem.name in override_names:
mem.source = "override"

# A listed register file is that, whatever the detector made of its
# module: its record replaces the module's before FakeRAM, which builds
# views for every idiomatic record of memories.json, reads it.
regfiles = regfile_memories(regfile_specs or [], macro_place)
listed = {m.name for m in regfiles}
memories = [m for m in memories if m.name not in listed] + regfiles

out_dir.mkdir(parents=True, exist_ok=True)
json_path.parent.mkdir(parents=True, exist_ok=True)
schema.dump(memories, json_path, platform)
Expand All @@ -268,15 +305,13 @@ def run(
]
)

# After FakeRAM has written blackboxes.txt for the inferred memories,
# and recorded in memories.json beside them.
regfiles = run_regfiles(
cores = run_regfiles(
regfile_specs or [], openroad, lefs or [], out_dir, macro_place
)
if regfiles:
listed = {m.name for m in regfiles}
memories = [m for m in memories if m.name not in listed] + regfiles
schema.dump(memories, json_path, platform)

# once every view exists: the macros FakeRAM built, then the register
# files' RAMs
write_blackboxes(out_dir, [m.name for m in converted] + cores)

for mem in sorted(memories, key=lambda m: m.name):
verdict = mem.kind
Expand Down
83 changes: 81 additions & 2 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 @@ -61,6 +62,16 @@
}
}

# a register-file spec for the idiomatic mem_128x32
REGFILE_SPEC = """\
module mem_128x32
words 128
bits 32
clock clk
read ra rd
write wa wd we
"""

FORCE_TAGS = """\
{
"version": 1,
Expand Down Expand Up @@ -123,7 +134,7 @@ def test_flow_contract(self):
],
)
# one `<module> <area>` line; the area is the one the .lib states,
# which synthesis costs the blackbox by
# as it states it, which synthesis costs the blackbox by
[line] = (mems_dir / "blackboxes.txt").read_text().splitlines()
name, area = line.split()
self.assertEqual(name, "mem_128x32")
Expand All @@ -132,9 +143,77 @@ def test_flow_contract(self):
(mems_dir / "mem_128x32.lib").read_text(),
re.M,
).group(1)
self.assertEqual(float(area), float(lib_area))
self.assertEqual(area, lib_area)
self.assertGreater(float(area), 0)

def run_with_regfile(self, macro_place):
"""mem_128x32, idiomatic, also listed as a register file; a stub
stands in for generate_ram and writes the RAM's .lib."""
d = Path(self.tmp.name)
netlist_json = d / "memories_inferred.json"
netlist_json.write_text(json.dumps(SAMPLE_YOSYS_NETLIST))
spec = d / "mem_128x32.regfile"
spec.write_text(REGFILE_SPEC)

def generate(spec, module, shape, openroad, lefs, out_dir):
block = f"{module}_ram"
if shape != "inline":
(out_dir / f"{block}.lib").write_text(
f"library ({block}) {{\n cell ({block}) {{\n"
" area : 42.5;\n }\n}\n"
)
return {
"block": block,
"macro": shape != "inline",
"dissolve": shape == "netlist",
}

with mock.patch.object(gen_memories.regfile_ram, "generate", generate):
code = gen_memories.run(
netlist_json,
[],
"asap7",
d / "memories",
d / "memories.json",
regfile_specs=[spec],
openroad="openroad",
lefs=[d / "tech.lef"],
macro_place=macro_place,
)
self.assertEqual(code, 0)
return d

def test_listed_regfile_is_not_a_fakeram(self):
# a module both idiomatic and listed is a register file, once: no
# FakeRAM views, one memories.json record, and blackboxes.txt
# names the RAM that stands in for it, never the module
for macro_place, blackboxes in (
([], ""),
(["mem_128x32"], "mem_128x32_ram 42.5\n"),
):
with self.subTest(macro_place=macro_place):
d = self.run_with_regfile(macro_place)
mems_dir = d / "memories"
for view in ("mem_128x32.lib", "mem_128x32_pre_layout.lib"):
self.assertFalse((mems_dir / view).exists(), view)
self.assertFalse((mems_dir / "mem_128x32.lef").exists())
self.assertEqual((mems_dir / "blackboxes.txt").read_text(), blackboxes)
doc = json.loads((d / "memories.json").read_text())
records = [m for m in doc["memories"] if m["name"] == "mem_128x32"]
self.assertEqual(len(records), 1)
self.assertEqual(records[0]["kind"], "regfile")
self.assertFalse(records[0]["idiomatic"])

def test_blackboxes_names_each_macro_once(self):
# two macros of one name would share one set of views
d = Path(self.tmp.name)
(d / "ram.lib").write_text("cell (ram) {\n area : 1.0;\n}\n")
gen_memories.write_blackboxes(d, ["ram"])
self.assertEqual((d / "blackboxes.txt").read_text(), "ram 1.0\n")
with self.assertRaises(SystemExit) as stop:
gen_memories.write_blackboxes(d, ["ram", "ram"])
self.assertIn("two macros are named ram", str(stop.exception))

def test_additional_memories_forces_conversion(self):
force = Path(self.tmp.name) / "force.memories"
force.write_text(FORCE_TAGS)
Expand Down
4 changes: 1 addition & 3 deletions flow/scripts/read_liberty.tcl
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,7 @@ if { [env_var_exists_and_non_empty CORNERS] } {
}

# AUTO_MEMORIES: liberty views generated pre-synthesis by
# scripts/memories/gen_memories.py, named by blackboxes.txt. The
# _pre_layout variants (ideal clock) are for pre-CTS consumers that
# select lib files themselves.
# scripts/memories/gen_memories.py, named by blackboxes.txt.
foreach libFile [auto_memories_views .lib] {
if { [env_var_exists_and_non_empty CORNERS] } {
foreach corner $::env(CORNERS) {
Expand Down
11 changes: 2 additions & 9 deletions flow/scripts/synth_preamble.tcl
Original file line number Diff line number Diff line change
Expand Up @@ -43,19 +43,12 @@ proc read_checkpoint { file } {
}
}

# AUTO_MEMORIES: memory modules whose generated liberty view replaces
# their behavioral body during synthesis, each with that view's area in
# um^2. The list is produced by scripts/memories/gen_memories.py before
# canonicalization, one `<module> <area>` per line; see
# docs/user/AutoMemories.md.
# AUTO_MEMORIES: what synthesis does to the modules util.tcl's
# auto_memories_blackboxes names; see docs/user/AutoMemories.md.
source $::env(SCRIPTS_DIR)/memories/check_blackboxed.tcl
source $::env(SCRIPTS_DIR)/memories/max_bits.tcl
source $::env(SCRIPTS_DIR)/memories/drop_parameters.tcl

proc auto_memories_blackboxes { } {
return [dict keys [auto_memories_areas]]
}

proc read_auto_memories_inline { } {
foreach m [auto_memories_names inline.txt] {
# A parameterized RTL module (ibex_register_file_ff #(...)) is
Expand Down
54 changes: 22 additions & 32 deletions flow/scripts/util.tcl
Original file line number Diff line number Diff line change
Expand Up @@ -114,33 +114,30 @@ proc env_var_exists_and_non_empty { env_var } {

# AUTO_MEMORIES: do-auto-memories writes into $RESULTS_DIR/memories the
# lists below, and every later step reads the files they name, never
# what a directory happens to hold.
#
# blackboxes.txt, `<module> <area>` a line: the macros, each with its
# <module>.lib and <module>.lef, and the area synthesis costs it by.
proc auto_memories_areas { } {
# what a directory happens to hold. gen_memories.py writes each list
# whole, after every view exists.

# The words of one of those lists; none without AUTO_MEMORIES.
proc auto_memories_words { list } {
if { ![env_var_equals AUTO_MEMORIES 1] } {
return {}
}
set f "$::env(RESULTS_DIR)/memories/blackboxes.txt"
set fh [open $f r]
set content [string map {\r ""} [read $fh]]
set fh [open "$::env(RESULTS_DIR)/memories/$list" r]
set content [read $fh]
close $fh
set areas [dict create]
foreach line [split $content "\n"] {
if { [string trim $line] == "" } {
continue
}
set fields [regexp -all -inline {\S+} $line]
if {
[llength $fields] != 2
|| ![string is double -strict [lindex $fields 1]]
} {
error "$f: expected `<module> <area>`, got `$line`"
}
dict set areas {*}$fields
}
return $areas
return [regexp -all -inline {\S+} $content]
}

# blackboxes.txt, `<module> <area>` a line: the macros, each with its
# <module>.lib and <module>.lef, and the area synthesis costs it by. Its
# words are a dict of the area of each module.
proc auto_memories_areas { } {
return [auto_memories_words blackboxes.txt]
}

# The modules synthesis blackboxes, the macros of blackboxes.txt.
proc auto_memories_blackboxes { } {
return [dict keys [auto_memories_areas]]
}

# A list of module names, one a line:
Expand All @@ -151,19 +148,12 @@ proc auto_memories_areas { } {
# - regfiles.txt: register files that dissolve into their cells after
# macro placement (<module>.v, <module>.place).
proc auto_memories_names { list } {
if { ![env_var_equals AUTO_MEMORIES 1] } {
return {}
}
set f "$::env(RESULTS_DIR)/memories/$list"
set fh [open $f r]
set content [read $fh]
close $fh
return [regexp -all -inline {\S+} $content]
return [auto_memories_words $list]
}

# The view of every macro in blackboxes.txt, by extension (.lib, .lef).
proc auto_memories_views { ext } {
return [lmap m [dict keys [auto_memories_areas]] {
return [lmap m [auto_memories_blackboxes] {
file join $::env(RESULTS_DIR) memories $m$ext
}]
}
Expand Down
Loading