diff --git a/docs/user/AutoMemories.md b/docs/user/AutoMemories.md index 8bc25c1d77..0ae2672e7c 100644 --- a/docs/user/AutoMemories.md +++ b/docs/user/AutoMemories.md @@ -25,7 +25,7 @@ into `$(RESULTS_DIR)/memories_inferred.json`, and | `$(RESULTS_DIR)/memories/.lib` | Generated Liberty view per converted memory. | | `$(RESULTS_DIR)/memories/_pre_layout.lib` | Ideal-clock (zero clock-tree insertion) variant for pre-CTS consumers that select lib files themselves. The Makefile flow uses `.lib` throughout. | | `$(RESULTS_DIR)/memories/.lef` | Abstract LEF per converted memory. | -| `$(RESULTS_DIR)/memories/blackboxes.txt` | Names of the converted modules — what synthesis blackboxes. | +| `$(RESULTS_DIR)/memories/blackboxes.txt` | ` ` per converted module — what synthesis blackboxes, and the area in um² its `.lib` states, which `SYNTH_MINIMUM_KEEP_SIZE` costs it by. | Synthesis (canonicalization) blackboxes the converted modules so the liberty view wins over their behavioral bodies; floorplan through final diff --git a/flow/scripts/memories/gen_memories.py b/flow/scripts/memories/gen_memories.py index 9d179d7908..e6bfd2a2bc 100644 --- a/flow/scripts/memories/gen_memories.py +++ b/flow/scripts/memories/gen_memories.py @@ -10,8 +10,9 @@ /.lib Liberty view per converted memory /_pre_layout.lib ideal-clock variant for pre-CTS consumers /.lef abstract LEF per converted memory - /blackboxes.txt converted module names, one per line — - what synthesis blackboxes + /blackboxes.txt ` ` per line, area in um^2 as + the .lib states it — what synthesis + blackboxes and the cost it gives each Everything downstream consumes these files; nothing else is passed between the generator and the flow. diff --git a/flow/scripts/memories/gen_memories_test.py b/flow/scripts/memories/gen_memories_test.py index ffd02593d9..74a5929a0f 100644 --- a/flow/scripts/memories/gen_memories_test.py +++ b/flow/scripts/memories/gen_memories_test.py @@ -1,5 +1,6 @@ #!/usr/bin/env python3 import json +import re import sys import tempfile import unittest @@ -118,14 +119,28 @@ def test_flow_contract(self): "mem_128x32_pre_layout.lib", ], ) - self.assertEqual((mems_dir / "blackboxes.txt").read_text(), "mem_128x32\n") + # one ` ` line; the area is the one the .lib states, + # which synthesis costs the blackbox by + [line] = (mems_dir / "blackboxes.txt").read_text().splitlines() + name, area = line.split() + self.assertEqual(name, "mem_128x32") + lib_area = re.search( + r"^\s*area\s*:\s*([0-9.]+)\s*;", + (mems_dir / "mem_128x32.lib").read_text(), + re.M, + ).group(1) + self.assertEqual(float(area), float(lib_area)) + self.assertGreater(float(area), 0) def test_additional_memories_forces_conversion(self): force = Path(self.tmp.name) / "force.memories" force.write_text(FORCE_TAGS) code, d = self.run_generator(memories_files=[force]) self.assertEqual(code, 0) - blackboxes = (d / "memories" / "blackboxes.txt").read_text().split() + blackboxes = [ + line.split()[0] + for line in (d / "memories" / "blackboxes.txt").read_text().splitlines() + ] self.assertEqual(sorted(blackboxes), ["cache_tags", "mem_128x32"]) self.assertTrue((d / "memories" / "cache_tags.lib").exists()) self.assertTrue((d / "memories" / "cache_tags.lef").exists()) diff --git a/flow/scripts/synth_preamble.tcl b/flow/scripts/synth_preamble.tcl index 53353ee46a..cfacc639dd 100644 --- a/flow/scripts/synth_preamble.tcl +++ b/flow/scripts/synth_preamble.tcl @@ -44,10 +44,11 @@ proc read_checkpoint { file } { } # AUTO_MEMORIES: memory modules whose generated liberty view replaces -# their behavioral body during synthesis. The list is produced by -# scripts/memories/gen_memories.py before canonicalization; see +# 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 ` ` per line; see # docs/user/AutoMemories.md. -proc auto_memories_blackboxes { } { +proc auto_memories_areas { } { if { ![env_var_equals AUTO_MEMORIES 1] } { return {} } @@ -59,7 +60,25 @@ proc auto_memories_blackboxes { } { set fh [open $f r] set content [string map {\r ""} [read $fh]] close $fh - return [regexp -all -inline {\S+} $content] + 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 ` `, got `$line`" + } + dict set areas {*}$fields + } + return $areas +} + +proc auto_memories_blackboxes { } { + return [dict keys [auto_memories_areas]] } proc read_design_sources { } { @@ -248,4 +267,34 @@ proc convert_liberty_areas { } { set gate_eq [expr int($area / $found_cell_area)] rtlil::set_attr -mod -uint $box gate_cost_equivalent $gate_eq } + + # An AUTO_MEMORIES macro is a blackbox from the RTL's own module, not a + # Liberty cell: it costs its generated view's area, or + # keep_hierarchy -min_cost refuses it ("Missing cost information on + # instanced blackbox"). Its view is not read in its place, as its pins + # need not be the module's (FakeRAM uses its own names). + # One name per line, not a Tcl list: yosys lists an escaped module with + # its leading backslash (`\1d_ram`), which list parsing reads as an + # escape, and blackboxes.txt names it as read_slang takes it (`1d_ram`). + set modules {} + foreach line [split [tee -q -s result.string select -list-mod =*] "\n"] { + set line [string trim $line] + if { $line ne "" } { + lappend modules [regsub {^\\} $line {}] + } + } + dict for {m area} [auto_memories_areas] { + if { [lsearch -exact $modules $m] < 0 } { + # slang imports a blackboxed module only where it is instanced + continue + } + if { ![rtlil::has_attr -mod $m blackbox] } { + # e.g. slang's per-instance `$`, which + # --blackboxed-module does not match: the body was elaborated + error "AUTO_MEMORIES macro $m is in the design but was not\ + blackboxed; its name does not match the module's definition" + } + rtlil::set_attr -mod -uint $m gate_cost_equivalent \ + [expr { int($area / $found_cell_area) }] + } } diff --git a/tools/FakeRAM2.0/orfs_asap7/generate.py b/tools/FakeRAM2.0/orfs_asap7/generate.py index be30f8f927..42ac55a8a0 100644 --- a/tools/FakeRAM2.0/orfs_asap7/generate.py +++ b/tools/FakeRAM2.0/orfs_asap7/generate.py @@ -54,6 +54,7 @@ def run_orfs_asap7(platform: str, out_dir: Path, json_path: Path): process = Process(ASAP7_PROCESS_CONFIG) timing_data = TimingData(ASAP7_TIMING_CONFIG) + blackboxes = [] for m in converted: name = m["name"] bits = m.get("bits", 32) @@ -82,7 +83,9 @@ def run_orfs_asap7(platform: str, out_dir: Path, json_path: Path): with open(lef_path, "w") as f: LefExporter(ram).export(f) - (out_dir / "blackboxes.txt").write_text( - "".join(f"{m['name']}\n" for m in converted) - ) + # the area the .lib states, beside the name: synthesis costs the + # blackbox by it + blackboxes.append(f"{name} {ram.physical.get_area(False):.6f}\n") + + (out_dir / "blackboxes.txt").write_text("".join(blackboxes)) return 0