From 42542945b480d20ec15ce3f4d1e38bbaa152e89d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=98yvind=20Harboe?= Date: Mon, 9 Nov 2026 15:13:17 +0100 Subject: [PATCH 1/4] AUTO_MEMORIES: read the views the lists name, not what memories/ holds MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Before, every stage after synthesis globbed $(RESULTS_DIR)/memories for *.lef and *.lib, the klayout tech took $(wildcard memories/*.lef), and macro placement, regfile dissolve and synthesis read regfiles.txt, inline.txt and regfile_cores.txt only if they existed. In a tree that kept files from an earlier run, a .lib or .lef no longer generated was read with the current ones, and a missing list was skipped. Now each step reads the .lib/.lef of every module blackboxes.txt lists, and the lists themselves without an existence check: a file no list names is not read, and a missing list stops the step at the read. The readers move from synth_preamble.tcl to util.tcl so OpenROAD and yosys share them, and generate_klayout_tech.py takes --blackboxes. On a clean tree the files read, and the results, are the same. Co-Authored-By: Claude Opus 5.5 Signed-off-by: Øyvind Harboe --- docs/user/AutoMemories.md | 14 +++-- flow/Makefile | 4 +- flow/scripts/load.tcl | 9 +-- flow/scripts/macro_place.tcl | 7 +-- flow/scripts/read_liberty.tcl | 24 +++----- flow/scripts/regfile_dissolve.tcl | 9 +-- flow/scripts/synth_preamble.tcl | 73 ++----------------------- flow/scripts/util.tcl | 56 +++++++++++++++++++ flow/test/test_generate_klayout_tech.py | 29 ++++++++++ flow/util/generate_klayout_tech.py | 26 ++++++++- 10 files changed, 140 insertions(+), 111 deletions(-) diff --git a/docs/user/AutoMemories.md b/docs/user/AutoMemories.md index d73a5b2a93..4fd8e4bb3a 100644 --- a/docs/user/AutoMemories.md +++ b/docs/user/AutoMemories.md @@ -29,12 +29,14 @@ into `$(RESULTS_DIR)/memories_inferred.json`, and Synthesis (canonicalization) blackboxes the converted modules so the liberty view wins over their behavioral bodies; floorplan through final -read the generated `.lib`/`.lef` alongside `ADDITIONAL_LIBS`/ -`ADDITIONAL_LEFS`. Everything downstream keys off the files above — -nothing else passes between the generator and the flow. That file-based -handoff is what lets build systems (e.g. bazel-orfs) declare the -generated artifacts as ordinary stage outputs and transitive -dependencies of the remaining stages. +read the `.lib`/`.lef` of each module `blackboxes.txt` lists, +alongside `ADDITIONAL_LIBS`/`ADDITIONAL_LEFS`. Everything downstream keys +off the files above, by the names the lists give: a file in `memories/` +that no list names is not read, and a list that is missing stops the +step that reads it. Nothing else passes between the generator and the +flow. That file-based handoff is what lets build systems (e.g. +bazel-orfs) declare the generated artifacts as ordinary stage outputs +and transitive dependencies of the remaining stages. Memories that are *not* converted stay in the netlist and synthesize to flip-flops, like any other RTL. diff --git a/flow/Makefile b/flow/Makefile index 2ef5b3b645..27095f4897 100644 --- a/flow/Makefile +++ b/flow/Makefile @@ -204,8 +204,8 @@ do-klayout: --template $(KLAYOUT_TECH_FILE) \ --output $(OBJECTS_DIR)/klayout.lyt \ --lef-files $(OBJECTS_DIR)/klayout_tech.lef $(SC_LEF) $(ADDITIONAL_LEFS) \ - $(if $(filter 1,$(AUTO_MEMORIES)),$(wildcard $(RESULTS_DIR)/memories/*.lef)) \ - --map-files $(wildcard $(FLOW_HOME)/platforms/$(PLATFORM)/*map) + --map-files $(wildcard $(FLOW_HOME)/platforms/$(PLATFORM)/*map) \ + $(if $(filter 1,$(AUTO_MEMORIES)),--blackboxes $(RESULTS_DIR)/memories/blackboxes.txt) $(OBJECTS_DIR)/klayout_wrap.lyt: $(KLAYOUT_TECH_FILE) $(OBJECTS_DIR)/klayout_tech.lef $(UNSET_AND_MAKE) do-klayout_wrap diff --git a/flow/scripts/load.tcl b/flow/scripts/load.tcl index 7d70b67769..c4d27868c7 100644 --- a/flow/scripts/load.tcl +++ b/flow/scripts/load.tcl @@ -23,12 +23,9 @@ proc load_design { design_file sdc_file } { read_lef $lef } } - # AUTO_MEMORIES: abstract LEFs generated pre-synthesis; globbed - # because the file names are only known at run time. - if { [env_var_equals AUTO_MEMORIES 1] } { - foreach lef [glob -nocomplain $::env(RESULTS_DIR)/memories/*.lef] { - read_lef $lef - } + # AUTO_MEMORIES: abstract LEFs generated pre-synthesis + foreach lef [auto_memories_views .lef] { + read_lef $lef } read_verilog $::env(RESULTS_DIR)/$design_file log_cmd link_design {*}[hier_options] $::env(DESIGN_NAME) diff --git a/flow/scripts/macro_place.tcl b/flow/scripts/macro_place.tcl index 556cc30ccc..f267dd586a 100644 --- a/flow/scripts/macro_place.tcl +++ b/flow/scripts/macro_place.tcl @@ -7,11 +7,8 @@ source_step_tcl PRE MACRO_PLACE # after macro placement, and the rows are then cut MACRO_ROWS_HALO_X/Y # around every other macro: a halo that wide keeps them off its core, # which fills its outline to the edge. -set regfile_list "$::env(RESULTS_DIR)/memories/regfiles.txt" -if { [env_var_equals AUTO_MEMORIES 1] && [file exists $regfile_list] } { - set f [open $regfile_list r] - set regfile_masters [split [string trim [read $f]] "\n"] - close $f +set regfile_masters [auto_memories_names regfiles.txt] +if { [llength $regfile_masters] > 0 } { set dbu [[ord::get_db_tech] getDbUnitsPerMicron] set halo {} foreach v {MACRO_ROWS_HALO_X MACRO_ROWS_HALO_Y} { diff --git a/flow/scripts/read_liberty.tcl b/flow/scripts/read_liberty.tcl index 60816842ff..52c8e0c01a 100644 --- a/flow/scripts/read_liberty.tcl +++ b/flow/scripts/read_liberty.tcl @@ -18,21 +18,15 @@ if { [env_var_exists_and_non_empty CORNERS] } { } # AUTO_MEMORIES: liberty views generated pre-synthesis by -# scripts/memories/gen_memories.py. The file names are only known at -# run time, so they are globbed here rather than threaded through -# LIB_FILES. The _pre_layout variants (ideal clock) are for pre-CTS -# consumers that select lib files themselves. -if { [env_var_equals AUTO_MEMORIES 1] } { - foreach libFile [glob -nocomplain $::env(RESULTS_DIR)/memories/*.lib] { - if { [string match *_pre_layout.lib $libFile] } { - continue - } - if { [env_var_exists_and_non_empty CORNERS] } { - foreach corner $::env(CORNERS) { - log_cmd read_liberty -corner $corner $libFile - } - } else { - log_cmd read_liberty $libFile +# 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. +foreach libFile [auto_memories_views .lib] { + if { [env_var_exists_and_non_empty CORNERS] } { + foreach corner $::env(CORNERS) { + log_cmd read_liberty -corner $corner $libFile } + } else { + log_cmd read_liberty $libFile } } diff --git a/flow/scripts/regfile_dissolve.tcl b/flow/scripts/regfile_dissolve.tcl index a797461fbe..ab0898debc 100644 --- a/flow/scripts/regfile_dissolve.tcl +++ b/flow/scripts/regfile_dissolve.tcl @@ -72,14 +72,7 @@ proc regfile_internal_net { net placed } { return 1 } -set regfile_list "$::env(RESULTS_DIR)/memories/regfiles.txt" -set regfile_modules {} -if { [env_var_equals AUTO_MEMORIES 1] && [file exists $regfile_list] } { - set f [open $regfile_list r] - set regfile_modules [split [string trim [read $f]] "\n"] - close $f - set regfile_modules [lsearch -all -inline -not -exact $regfile_modules ""] -} +set regfile_modules [auto_memories_names regfiles.txt] if { [llength $regfile_modules] > 0 } { set block [ord::get_db_block] set db [ord::get_db] diff --git a/flow/scripts/synth_preamble.tcl b/flow/scripts/synth_preamble.tcl index f024281f80..1439b46571 100644 --- a/flow/scripts/synth_preamble.tcl +++ b/flow/scripts/synth_preamble.tcl @@ -52,75 +52,12 @@ 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_areas { } { - if { ![env_var_equals AUTO_MEMORIES 1] } { - return {} - } - set f "$::env(RESULTS_DIR)/memories/blackboxes.txt" - if { ![file exists $f] } { - error "AUTO_MEMORIES=1 but $f is missing;\ - the do-auto-memories step must run before synthesis" - } - set fh [open $f r] - set content [string map {\r ""} [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 ` `, got `$line`" - } - dict set areas {*}$fields - } - return $areas -} - proc auto_memories_blackboxes { } { return [dict keys [auto_memories_areas]] } -# AUTO_MEMORIES: register files inlined (AUTO_MEMORIES_REGFILES not in -# AUTO_MEMORIES_MACRO_PLACE): the RTL module is read as a blackbox and its -# generated netlist of standard cells is read in its place. -proc auto_memories_inline { } { - if { ![env_var_equals AUTO_MEMORIES 1] } { - return {} - } - set f "$::env(RESULTS_DIR)/memories/inline.txt" - if { ![file exists $f] } { - return {} - } - set fh [open $f r] - set content [read $fh] - close $fh - return [regexp -all -inline {\S+} $content] -} - -# AUTO_MEMORIES: the RAMs the register file wrappers instantiate as -# macros. They are in blackboxes.txt but are no module of the sources: -# they arrive with the wrappers' netlists, after read_slang. -proc auto_memories_regfile_cores { } { - if { ![env_var_equals AUTO_MEMORIES 1] } { - return {} - } - set f "$::env(RESULTS_DIR)/memories/regfile_cores.txt" - if { ![file exists $f] } { - return {} - } - set fh [open $f r] - set content [read $fh] - close $fh - return [regexp -all -inline {\S+} $content] -} - proc read_auto_memories_inline { } { - foreach m [auto_memories_inline] { + foreach m [auto_memories_names inline.txt] { # A parameterized RTL module (ibex_register_file_ff #(...)) is # instantiated with parameters the generated netlist has no # declaration for, which hierarchy refuses. The netlist is that module @@ -187,7 +124,7 @@ proc read_design_sources { } { # Blackbox AUTO_MEMORIES-detected memory modules so their generated # liberty view wins over their behavioral bodies. - foreach m [concat [auto_memories_blackboxes] [auto_memories_inline]] { + foreach m [concat [auto_memories_blackboxes] [auto_memories_names inline.txt]] { lappend slang_args --blackboxed-module "$m" } @@ -195,7 +132,7 @@ proc read_design_sources { } { lappend slang_args {*}$::env(SYNTH_SLANG_ARGS) yosys read_slang {*}$slang_args - set cores [auto_memories_regfile_cores] + set cores [auto_memories_names regfile_cores.txt] set sourced {} foreach m [auto_memories_blackboxes] { if { [lsearch -exact $cores $m] < 0 } { @@ -226,7 +163,7 @@ proc read_design_sources { } { if { [env_var_exists_and_non_empty SYNTH_BLACKBOXES] } { error "Non-empty SYNTH_BLACKBOXES unsupported with HDL frontend \"verific\"" } - if { [llength [concat [auto_memories_blackboxes] [auto_memories_inline]]] > 0 } { + if { [llength [concat [auto_memories_blackboxes] [auto_memories_names inline.txt]]] > 0 } { error "AUTO_MEMORIES unsupported with HDL frontend \"$::env(SYNTH_HDL_FRONTEND)\"" } } elseif { ![env_var_exists_and_non_empty SYNTH_HDL_FRONTEND] } { @@ -262,7 +199,7 @@ proc read_design_sources { } { } hierarchy -check -top $::env(DESIGN_NAME) } - if { [llength [auto_memories_inline]] > 0 } { + if { [llength [auto_memories_names inline.txt]] > 0 } { read_auto_memories_inline hierarchy -check -top $::env(DESIGN_NAME) } diff --git a/flow/scripts/util.tcl b/flow/scripts/util.tcl index 09ab88033a..239c33980d 100644 --- a/flow/scripts/util.tcl +++ b/flow/scripts/util.tcl @@ -132,6 +132,62 @@ proc env_var_exists_and_non_empty { env_var } { return [expr { [info exists ::env($env_var)] && ![string equal $::env($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, ` ` a line: the macros, each with its +# .lib and .lef, and the area synthesis costs it by. +proc auto_memories_areas { } { + 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]] + 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 ` `, got `$line`" + } + dict set areas {*}$fields + } + return $areas +} + +# A list of module names, one a line: +# - inline.txt: register files whose generated netlist (.v) +# replaces the RTL module's body in synthesis. +# - regfile_cores.txt: the RAMs the register file wrappers instantiate +# as macros; in blackboxes.txt, but no module of the sources. +# - regfiles.txt: register files that dissolve into their cells after +# macro placement (.v, .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] +} + +# 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]] { + file join $::env(RESULTS_DIR) memories $m$ext + }] +} + proc append_env_var { list_name var_name prefix has_arg } { upvar $list_name list if { diff --git a/flow/test/test_generate_klayout_tech.py b/flow/test/test_generate_klayout_tech.py index 92e884cfc6..cba8fe242a 100644 --- a/flow/test/test_generate_klayout_tech.py +++ b/flow/test/test_generate_klayout_tech.py @@ -226,5 +226,34 @@ def test_sky130hd(self): ) +class TestBlackboxLefs(unittest.TestCase): + def test_lists_the_lef_of_each_macro(self): + with tempfile.TemporaryDirectory() as d: + blackboxes = os.path.join(d, "blackboxes.txt") + with open(blackboxes, "w") as f: + f.write("ram_a 12.5\n\nram_b 3.0\n") + self.assertEqual( + generate_klayout_tech.blackbox_lefs(blackboxes), + [os.path.join(d, "ram_a.lef"), os.path.join(d, "ram_b.lef")], + ) + + def test_ignores_a_lef_the_list_does_not_name(self): + with tempfile.TemporaryDirectory() as d: + blackboxes = os.path.join(d, "blackboxes.txt") + with open(blackboxes, "w") as f: + f.write("ram_a 12.5\n") + for name in ("ram_a.lef", "stale.lef"): + open(os.path.join(d, name), "w").close() + self.assertEqual( + generate_klayout_tech.blackbox_lefs(blackboxes), + [os.path.join(d, "ram_a.lef")], + ) + + def test_missing_list_fails(self): + with tempfile.TemporaryDirectory() as d: + with self.assertRaises(FileNotFoundError): + generate_klayout_tech.blackbox_lefs(os.path.join(d, "blackboxes.txt")) + + if __name__ == "__main__": unittest.main() diff --git a/flow/util/generate_klayout_tech.py b/flow/util/generate_klayout_tech.py index b23a1e0aba..640bdd8b53 100644 --- a/flow/util/generate_klayout_tech.py +++ b/flow/util/generate_klayout_tech.py @@ -30,6 +30,21 @@ def replace_map_files(content, map_files): return content +def blackbox_lefs(blackboxes): + """The LEF of every macro AUTO_MEMORIES' blackboxes.txt lists. + + blackboxes.txt has a ` ` line per macro, and + do-auto-memories writes .lef beside it. + """ + directory = os.path.dirname(blackboxes) + with open(blackboxes, encoding="utf-8") as f: + return [ + os.path.join(directory, line.split()[0] + ".lef") + for line in f + if line.strip() + ] + + def generate_klayout_tech(template_lyt, output_lyt, lef_files, map_files=None): """Generate a klayout .lyt file from a platform template. @@ -76,12 +91,21 @@ def main(): parser.add_argument( "--map-files", nargs="*", default=[], help="Map files to include" ) + parser.add_argument( + "--blackboxes", + help="AUTO_MEMORIES' blackboxes.txt; the LEF of each macro it lists" + " is included after --lef-files", + ) args = parser.parse_args() + lef_files = list(args.lef_files) + if args.blackboxes: + lef_files += blackbox_lefs(args.blackboxes) + generate_klayout_tech( template_lyt=args.template, output_lyt=args.output, - lef_files=args.lef_files, + lef_files=lef_files, map_files=args.map_files, ) From 371ea2603b902c5917bbbb4dcc386704e5b53f31 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=98yvind=20Harboe?= Date: Mon, 9 Nov 2026 15:49:56 +0100 Subject: [PATCH 2/4] flow: FLOW_INPUT_VARIANT, a stage reads the previous stage's result from another variant MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Before, a variant that wanted another variant's synthesis or placement had to copy that variant's results into its own directory first. Now FLOW_INPUT_VARIANT names the variant a stage reads the previous stage's result from: INPUT_RESULTS_DIR. The six places a stage reads its input say so explicitly, in its script (load_stage_input) and in its Makefile prerequisite; every other read stays in RESULTS_DIR. It defaults to FLOW_VARIANT, which leaves every path unchanged. When it names another variant, load_stage_input stops if the input is in both variants: one invocation that ran two stages, or a copy left from an earlier run, where reading the other variant's copy would mix results. It also stops with AUTO_MEMORIES=1: the memories are read from the variant that synthesised them. Co-Authored-By: Claude Opus 5.5 Signed-off-by: Øyvind Harboe --- docs/user/FlowVariables.md | 7 +++++++ flow/Makefile | 14 ++++++------- flow/scripts/cts.tcl | 2 +- flow/scripts/density_fill.tcl | 2 +- flow/scripts/detail_route.tcl | 2 +- flow/scripts/floorplan.tcl | 2 +- flow/scripts/global_place_skip_io.tcl | 4 ++-- flow/scripts/global_route.tcl | 2 +- flow/scripts/load.tcl | 30 ++++++++++++++++++++++++--- flow/scripts/variables.json | 11 ++++++++++ flow/scripts/variables.mk | 3 +++ flow/scripts/variables.yaml | 17 +++++++++++++++ 12 files changed, 79 insertions(+), 17 deletions(-) diff --git a/docs/user/FlowVariables.md b/docs/user/FlowVariables.md index 0df148d71d..afaaa9efe0 100644 --- a/docs/user/FlowVariables.md +++ b/docs/user/FlowVariables.md @@ -152,6 +152,7 @@ configuration file. | FILL_CELLS| Fill cells are used to fill empty sites. If not set or empty, fill cell insertion is skipped.| | | FILL_CONFIG| JSON rule file for metal fill during chip finishing.| | | FLOORPLAN_DEF| Use the DEF file to initialize floorplan. Mutually exclusive with FOOTPRINT or DIE_AREA/CORE_AREA or CORE_UTILIZATION.| | +| FLOW_INPUT_VARIANT| Flow variant a stage reads the previous stage's result from, the .odb and the .sdc beside it. Defaults to FLOW_VARIANT. A variant forked off another sets it to that variant for the first stage it runs, then leaves it at the default: the shared stages run once, and the fork writes only its own results. A stage's later steps read the stage's own results. A stage stops when its input is in both variants. Not with AUTO_MEMORIES=1, whose memories are read from the variant that synthesised them.| | | FLOW_VARIANT| Flow variant to use, used in the flow variant directory name.| base| | FOOTPRINT| Custom footprint definition file for ICeWall-based floorplan initialization. Mutually exclusive with FLOORPLAN_DEF or DIE_AREA/CORE_AREA or CORE_UTILIZATION.| | | FOOTPRINT_TCL| Specifies a Tcl script with custom footprint-related commands for floorplan setup.| | @@ -421,6 +422,7 @@ configuration file. - [CORE_UTILIZATION](#CORE_UTILIZATION) - [DIE_AREA](#DIE_AREA) - [FLOORPLAN_DEF](#FLOORPLAN_DEF) +- [FLOW_INPUT_VARIANT](#FLOW_INPUT_VARIANT) - [FOOTPRINT](#FOOTPRINT) - [FOOTPRINT_TCL](#FOOTPRINT_TCL) - [GLOBAL_CONNECT_TCL](#GLOBAL_CONNECT_TCL) @@ -501,6 +503,7 @@ configuration file. - [ENABLE_DPO](#ENABLE_DPO) - [ENABLE_PLACE_REPAIR_TIMING](#ENABLE_PLACE_REPAIR_TIMING) - [FLOORPLAN_DEF](#FLOORPLAN_DEF) +- [FLOW_INPUT_VARIANT](#FLOW_INPUT_VARIANT) - [GLOBAL_PLACEMENT_ARGS](#GLOBAL_PLACEMENT_ARGS) - [GPL_KEEP_OVERFLOW](#GPL_KEEP_OVERFLOW) - [GPL_PLACE_IOS](#GPL_PLACE_IOS) @@ -545,6 +548,7 @@ configuration file. - [CTS_SNAPSHOTS](#CTS_SNAPSHOTS) - [DETAILED_METRICS](#DETAILED_METRICS) - [DETAIL_PLACEMENT_ARGS](#DETAIL_PLACEMENT_ARGS) +- [FLOW_INPUT_VARIANT](#FLOW_INPUT_VARIANT) - [HOLD_SLACK_MARGIN](#HOLD_SLACK_MARGIN) - [LEC_AUX_VERILOG_FILES](#LEC_AUX_VERILOG_FILES) - [LEC_CHECK](#LEC_CHECK) @@ -572,6 +576,7 @@ configuration file. - [DETAILED_METRICS](#DETAILED_METRICS) - [DETAIL_PLACEMENT_ARGS](#DETAIL_PLACEMENT_ARGS) - [ENABLE_RESISTANCE_AWARE](#ENABLE_RESISTANCE_AWARE) +- [FLOW_INPUT_VARIANT](#FLOW_INPUT_VARIANT) - [GLOBAL_ROUTE_ARGS](#GLOBAL_ROUTE_ARGS) - [GLOBAL_ROUTE_USE_CUGR](#GLOBAL_ROUTE_USE_CUGR) - [GRT_SEED](#GRT_SEED) @@ -607,6 +612,7 @@ configuration file. - [DETAILED_ROUTE_END_ITERATION](#DETAILED_ROUTE_END_ITERATION) - [DISABLE_VIA_GEN](#DISABLE_VIA_GEN) - [FILL_CELLS](#FILL_CELLS) +- [FLOW_INPUT_VARIANT](#FLOW_INPUT_VARIANT) - [MATCH_CELL_FOOTPRINT](#MATCH_CELL_FOOTPRINT) - [MAX_REPAIR_ANTENNAS_ITER_DRT](#MAX_REPAIR_ANTENNAS_ITER_DRT) - [MAX_ROUTING_LAYER](#MAX_ROUTING_LAYER) @@ -630,6 +636,7 @@ configuration file. - [ADDITIONAL_GDS](#ADDITIONAL_GDS) - [CDL_FILE](#CDL_FILE) +- [FLOW_INPUT_VARIANT](#FLOW_INPUT_VARIANT) - [GDS_ALLOW_EMPTY](#GDS_ALLOW_EMPTY) - [GND_NETS_VOLTAGES](#GND_NETS_VOLTAGES) - [LEC_AUX_VERILOG_FILES](#LEC_AUX_VERILOG_FILES) diff --git a/flow/Makefile b/flow/Makefile index e7c2d3457a..8ac020b6c5 100644 --- a/flow/Makefile +++ b/flow/Makefile @@ -452,7 +452,7 @@ endef # ============================================================================== # Custom target to go from synthesis to placement in a single OpenROAD run -$(eval $(call do-step,1_3_floorplan_to_place, $(RESULTS_DIR)/1_synth.odb,floorplan_to_place)) +$(eval $(call do-step,1_3_floorplan_to_place, $(INPUT_RESULTS_DIR)/1_synth.odb,floorplan_to_place)) $(eval $(call OPEN_GUI_SHORTCUT,yosys,1_2_yosys.v)) @@ -488,7 +488,7 @@ ifneq ($(filter 1,$(LEC_CHECK) $(SEC_CHECK)),) $(RESULTS_DIR)/1_synth_lec.v: | check-kepler-formal endif -$(eval $(call do-step,2_1_floorplan,$(RESULTS_DIR)/1_synth.odb $(TECH_LEF) $(SC_LEF) $(ADDITIONAL_LEFS) $(FOOTPRINT) $(SIG_MAP_FILE) $(FOOTPRINT_TCL) $(LIB_FILES) $(IO_CONSTRAINTS),floorplan)) +$(eval $(call do-step,2_1_floorplan,$(INPUT_RESULTS_DIR)/1_synth.odb $(TECH_LEF) $(SC_LEF) $(ADDITIONAL_LEFS) $(FOOTPRINT) $(SIG_MAP_FILE) $(FOOTPRINT_TCL) $(LIB_FILES) $(IO_CONSTRAINTS),floorplan)) # STEP 2: Macro Placement #------------------------------------------------------------------------------- @@ -526,7 +526,7 @@ place: $(RESULTS_DIR)/3_place.odb # ============================================================================== # STEP 1: Global placement without placed IOs, timing-driven, and routability-driven. #------------------------------------------------------------------------------- -$(eval $(call do-step,3_1_place_gp_skip_io,$(RESULTS_DIR)/2_floorplan.odb $(LIB_FILES),global_place_skip_io)) +$(eval $(call do-step,3_1_place_gp_skip_io,$(INPUT_RESULTS_DIR)/2_floorplan.odb $(LIB_FILES),global_place_skip_io)) $(eval $(call do-step,3_2_place_iop,$(RESULTS_DIR)/3_1_place_gp_skip_io.odb,io_placement)) @@ -580,7 +580,7 @@ ifeq ($(LEC_CHECK),1) DO_STEP_DEPS_4_1_cts += check-kepler-formal endif -$(eval $(call do-step,4_1_cts,$(RESULTS_DIR)/3_place.odb,cts)) +$(eval $(call do-step,4_1_cts,$(INPUT_RESULTS_DIR)/3_place.odb,cts)) ifeq ($(LEC_CHECK),1) $(RESULTS_DIR)/4_1_cts.odb: | check-kepler-formal @@ -618,11 +618,11 @@ grt: $(RESULTS_DIR)/5_1_grt.odb # STEP 1: Run global route #------------------------------------------------------------------------------- -$(eval $(call do-step,5_1_grt,$(RESULTS_DIR)/4_cts.odb $(FASTROUTE_TCL) $(PRE_GLOBAL_ROUTE_TCL),global_route)) +$(eval $(call do-step,5_1_grt,$(INPUT_RESULTS_DIR)/4_cts.odb $(FASTROUTE_TCL) $(PRE_GLOBAL_ROUTE_TCL),global_route)) # STEP 2: Run detailed route #------------------------------------------------------------------------------- -$(eval $(call do-step,5_2_route,$(RESULTS_DIR)/5_1_grt.odb,detail_route)) +$(eval $(call do-step,5_2_route,$(INPUT_RESULTS_DIR)/5_1_grt.odb,detail_route)) $(eval $(call do-step,5_3_fillcell,$(RESULTS_DIR)/5_2_route.odb,fillcell)) @@ -680,7 +680,7 @@ elapsed: elapsed-all: @$(PYTHON_EXE) $(UTILS_DIR)/genElapsedTime.py -d $(shell find $(WORK_HOME)/logs/$(PLATFORM)/*/*/ -type d) -$(eval $(call do-step,6_1_fill,$(RESULTS_DIR)/5_route.odb $(FILL_CONFIG),density_fill)) +$(eval $(call do-step,6_1_fill,$(INPUT_RESULTS_DIR)/5_route.odb $(FILL_CONFIG),density_fill)) ifneq ($(filter 1,$(LEC_CHECK) $(SEC_CHECK)),) DO_STEP_DEPS_6_report += check-kepler-formal diff --git a/flow/scripts/cts.tcl b/flow/scripts/cts.tcl index fdb9d54ada..0bbef37c98 100644 --- a/flow/scripts/cts.tcl +++ b/flow/scripts/cts.tcl @@ -2,7 +2,7 @@ utl::set_metrics_stage "cts__{}" source $::env(SCRIPTS_DIR)/load.tcl source $::env(SCRIPTS_DIR)/formal_check.tcl erase_non_stage_variables cts -load_design 3_place.odb +load_stage_input 3_place.odb source_step_tcl PRE CTS # Clone clock tree inverters next to register loads diff --git a/flow/scripts/density_fill.tcl b/flow/scripts/density_fill.tcl index 030c2c3825..84c5080aec 100644 --- a/flow/scripts/density_fill.tcl +++ b/flow/scripts/density_fill.tcl @@ -1,6 +1,6 @@ source $::env(SCRIPTS_DIR)/load.tcl erase_non_stage_variables final -load_design 5_route.odb +load_stage_input 5_route.odb source_step_tcl PRE DENSITY_FILL if { $::env(USE_FILL) } { diff --git a/flow/scripts/detail_route.tcl b/flow/scripts/detail_route.tcl index 40b96eecb6..267ff66baf 100644 --- a/flow/scripts/detail_route.tcl +++ b/flow/scripts/detail_route.tcl @@ -1,6 +1,6 @@ utl::set_metrics_stage "detailedroute__{}" source $::env(SCRIPTS_DIR)/load.tcl -load_design 5_1_grt.odb +load_stage_input 5_1_grt.odb source_step_tcl PRE DETAIL_ROUTE if { !$::env(SKIP_DETAILED_ROUTE) } { diff --git a/flow/scripts/floorplan.tcl b/flow/scripts/floorplan.tcl index 26dea568b0..2488218f07 100644 --- a/flow/scripts/floorplan.tcl +++ b/flow/scripts/floorplan.tcl @@ -1,7 +1,7 @@ utl::set_metrics_stage "floorplan__{}" source $::env(SCRIPTS_DIR)/load.tcl erase_non_stage_variables floorplan -load_design 1_synth.odb +load_stage_input 1_synth.odb # The global connections, before any step can mark an instance # dont_touch: global_connect skips one (ODB-0383). The rules stay in the diff --git a/flow/scripts/global_place_skip_io.tcl b/flow/scripts/global_place_skip_io.tcl index aadaa5f014..1e7095f9bc 100644 --- a/flow/scripts/global_place_skip_io.tcl +++ b/flow/scripts/global_place_skip_io.tcl @@ -1,11 +1,11 @@ source $::env(SCRIPTS_DIR)/load.tcl erase_non_stage_variables place if { $::env(GPL_PLACE_IOS) } { - orfs_copy_db $::env(RESULTS_DIR)/2_floorplan.odb \ + orfs_copy_db $::env(INPUT_RESULTS_DIR)/2_floorplan.odb \ $::env(RESULTS_DIR)/3_1_place_gp_skip_io.odb return } -load_design 2_floorplan.odb +load_stage_input 2_floorplan.odb source_step_tcl PRE GLOBAL_PLACE_SKIP_IO if { [env_var_exists_and_non_empty FLOORPLAN_DEF] } { diff --git a/flow/scripts/global_route.tcl b/flow/scripts/global_route.tcl index 93213b19a5..21ce460a1c 100644 --- a/flow/scripts/global_route.tcl +++ b/flow/scripts/global_route.tcl @@ -1,7 +1,7 @@ utl::set_metrics_stage "globalroute__{}" source $::env(SCRIPTS_DIR)/load.tcl erase_non_stage_variables grt -load_design 4_cts.odb +load_stage_input 4_cts.odb source_step_tcl PRE GLOBAL_ROUTE diff --git a/flow/scripts/load.tcl b/flow/scripts/load.tcl index 0664cf288b..5e01c3ead3 100644 --- a/flow/scripts/load.tcl +++ b/flow/scripts/load.tcl @@ -2,7 +2,31 @@ source $::env(SCRIPTS_DIR)/util.tcl source $::env(SCRIPTS_DIR)/report_metrics.tcl +# A step's input is the result of an earlier step of its stage. proc load_design { design_file } { + load_design_from $::env(RESULTS_DIR) $design_file +} + +# A stage's input is the previous stage's result, read from the +# variant FLOW_INPUT_VARIANT names. +proc load_stage_input { design_file } { + if { $::env(INPUT_RESULTS_DIR) ne $::env(RESULTS_DIR) } { + if { $::env(AUTO_MEMORIES) } { + error "AUTO_MEMORIES=1 reads the memories synthesised in this variant,\ + $::env(FLOW_VARIANT), and FLOW_INPUT_VARIANT=$::env(FLOW_INPUT_VARIANT)\ + reads the design from another" + } + if { [file exists $::env(RESULTS_DIR)/$design_file] } { + error "$design_file is in both FLOW_VARIANT=$::env(FLOW_VARIANT) and\ + FLOW_INPUT_VARIANT=$::env(FLOW_INPUT_VARIANT). Run the stage that\ + reads another variant alone, then the stages after it with\ + FLOW_INPUT_VARIANT unset, and remove a copy left from an earlier run." + } + } + load_design_from $::env(INPUT_RESULTS_DIR) $design_file +} + +proc load_design_from { dir design_file } { # Do not reload if design is already loaded set db [ord::get_db] if { [$db getChip] != "NULL" && [[$db getChip] getBlock] != "NULL" } { @@ -27,15 +51,15 @@ proc load_design { design_file } { foreach lef [auto_memories_views .lef] { read_lef $lef } - read_verilog $::env(RESULTS_DIR)/$design_file + read_verilog $dir/$design_file log_cmd link_design {*}[hier_options] $::env(DESIGN_NAME) } elseif { $ext == ".odb" } { - log_cmd read_db {*}[hier_options] $::env(RESULTS_DIR)/$design_file + log_cmd read_db {*}[hier_options] $dir/$design_file } else { error "Unrecognized input file $design_file" } - log_cmd read_sdc $::env(RESULTS_DIR)/[sdc_file_of $design_file] + log_cmd read_sdc $dir/[sdc_file_of $design_file] if { [file exists $::env(PLATFORM_DIR)/derate.tcl] } { log_cmd source $::env(PLATFORM_DIR)/derate.tcl diff --git a/flow/scripts/variables.json b/flow/scripts/variables.json index 8bebf70c48..739eb19906 100644 --- a/flow/scripts/variables.json +++ b/flow/scripts/variables.json @@ -380,6 +380,17 @@ "place" ] }, + "FLOW_INPUT_VARIANT": { + "description": "Flow variant a stage reads the previous stage's result from, the .odb and the .sdc beside it. Defaults to FLOW_VARIANT. A variant forked off another sets it to that variant for the first stage it runs, then leaves it at the default: the shared stages run once, and the fork writes only its own results. A stage's later steps read the stage's own results. A stage stops when its input is in both variants. Not with AUTO_MEMORIES=1, whose memories are read from the variant that synthesised them.\n", + "stages": [ + "floorplan", + "place", + "cts", + "grt", + "route", + "final" + ] + }, "FLOW_VARIANT": { "default": "base", "description": "Flow variant to use, used in the flow variant directory name.\n" diff --git a/flow/scripts/variables.mk b/flow/scripts/variables.mk index fd48cba625..053937010e 100644 --- a/flow/scripts/variables.mk +++ b/flow/scripts/variables.mk @@ -54,6 +54,9 @@ export LOG_DIR = $(WORK_HOME)/logs/$(PLATFORM)/$(DESIGN_NICKNAME)/$(FLOW_VAR export OBJECTS_DIR = $(WORK_HOME)/objects/$(PLATFORM)/$(DESIGN_NICKNAME)/$(FLOW_VARIANT) export REPORTS_DIR = $(WORK_HOME)/reports/$(PLATFORM)/$(DESIGN_NICKNAME)/$(FLOW_VARIANT) export RESULTS_DIR = $(WORK_HOME)/results/$(PLATFORM)/$(DESIGN_NICKNAME)/$(FLOW_VARIANT) +# Where a stage reads the previous stage's result. +export FLOW_INPUT_VARIANT ?= $(FLOW_VARIANT) +export INPUT_RESULTS_DIR = $(WORK_HOME)/results/$(PLATFORM)/$(DESIGN_NICKNAME)/$(FLOW_INPUT_VARIANT) #------------------------------------------------------------------------------- ifeq (,$(strip $(NUM_CORES))) diff --git a/flow/scripts/variables.yaml b/flow/scripts/variables.yaml index ac9d582494..d4418a02ec 100644 --- a/flow/scripts/variables.yaml +++ b/flow/scripts/variables.yaml @@ -1641,6 +1641,23 @@ FOOTPRINT_TCL: Specifies a Tcl script with custom footprint-related commands for floorplan setup. stages: - floorplan +FLOW_INPUT_VARIANT: + description: > + Flow variant a stage reads the previous stage's result from, the + .odb and the .sdc beside it. Defaults to FLOW_VARIANT. A variant + forked off another sets it to that variant for the first stage it + runs, then leaves it at the default: the shared stages run once, and + the fork writes only its own results. A stage's later steps read the + stage's own results. A stage stops when its input is in both + variants. Not with AUTO_MEMORIES=1, whose memories are read from the + variant that synthesised them. + stages: + - floorplan + - place + - cts + - grt + - route + - final FLOW_VARIANT: description: > Flow variant to use, used in the flow variant directory name. From 097dffc5337e30b9b197410b81a8c4986accade0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=98yvind=20Harboe?= Date: Wed, 11 Nov 2026 07:14:11 +0100 Subject: [PATCH 3/4] flow: leave variables.json alone MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit variables.json is unused and #4805 deletes it; editing it here only causes conflicts. variables.yaml carries the change. Signed-off-by: Øyvind Harboe --- flow/scripts/variables.json | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/flow/scripts/variables.json b/flow/scripts/variables.json index 739eb19906..8bebf70c48 100644 --- a/flow/scripts/variables.json +++ b/flow/scripts/variables.json @@ -380,17 +380,6 @@ "place" ] }, - "FLOW_INPUT_VARIANT": { - "description": "Flow variant a stage reads the previous stage's result from, the .odb and the .sdc beside it. Defaults to FLOW_VARIANT. A variant forked off another sets it to that variant for the first stage it runs, then leaves it at the default: the shared stages run once, and the fork writes only its own results. A stage's later steps read the stage's own results. A stage stops when its input is in both variants. Not with AUTO_MEMORIES=1, whose memories are read from the variant that synthesised them.\n", - "stages": [ - "floorplan", - "place", - "cts", - "grt", - "route", - "final" - ] - }, "FLOW_VARIANT": { "default": "base", "description": "Flow variant to use, used in the flow variant directory name.\n" From 9ea4b42e3d31148caf4fe318a6c5534132c3f368 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=98yvind=20Harboe?= Date: Wed, 11 Nov 2026 12:42:35 +0100 Subject: [PATCH 4/4] flow: density fill with USE_FILL=0 copies 5_route from FLOW_INPUT_VARIANT MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit density_fill.tcl reads 5_route.odb with load_stage_input, from INPUT_RESULTS_DIR, but its USE_FILL=0 branch copied the pair from RESULTS_DIR. In a variant forked at final, RESULTS_DIR holds no 5_route, and 6_1_fill stopped at the copy: cp: cannot stat '...//5_route.sdc': No such file or directory The branch copies from INPUT_RESULTS_DIR, as global_place_skip_io.tcl does for GPL_PLACE_IOS=1. With FLOW_INPUT_VARIANT unset the two directories are the same and nothing changes. Co-Authored-By: Claude Opus 5.5 Signed-off-by: Øyvind Harboe --- flow/scripts/density_fill.tcl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/flow/scripts/density_fill.tcl b/flow/scripts/density_fill.tcl index 84c5080aec..df4b405b4f 100644 --- a/flow/scripts/density_fill.tcl +++ b/flow/scripts/density_fill.tcl @@ -11,7 +11,7 @@ if { $::env(USE_FILL) } { write_verilog $::env(RESULTS_DIR)/6_1_fill.v orfs_write_db $::env(RESULTS_DIR)/6_1_fill.odb } else { - orfs_copy_db $::env(RESULTS_DIR)/5_route.odb $::env(RESULTS_DIR)/6_1_fill.odb + orfs_copy_db $::env(INPUT_RESULTS_DIR)/5_route.odb $::env(RESULTS_DIR)/6_1_fill.odb # There is no 5_route.v file to copy }