From eba85f667bd394c8be7d48781c7ce84adfc9b2cb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=98yvind=20Harboe?= Date: Tue, 7 Jul 2026 05:52:15 +0200 Subject: [PATCH 1/3] bazel: mirror tools/OpenROAD's rules_pycross Python 3.8 workaround MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit sv-lang (via tools/OpenROAD) pulls in rules_pycross, whose toolchain extension fails with "Unknown Python version: 3.8" (transitively registered via or-tools -> pybind11_abseil; 3.8 was dropped from rules_python 2.0.0's MINOR_MAPPING). tools/OpenROAD/MODULE.bazel already carries the pycross.configure_environments workaround, but the extension configuration is only honored in the root module, so any bazel analysis in this repository fails without mirroring it here. Co-Authored-By: Claude Fable 5 Signed-off-by: Øyvind Harboe --- MODULE.bazel | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/MODULE.bazel b/MODULE.bazel index 1d21ea2f79..90161b23f2 100644 --- a/MODULE.bazel +++ b/MODULE.bazel @@ -83,6 +83,20 @@ register_toolchains( dev_dependency = True, ) +# sv-lang (via tools/OpenROAD) pulls in rules_pycross, whose toolchain +# extension fails on Python 3.8 (transitively registered via or-tools -> +# pybind11_abseil, dropped from rules_python 2.0.0's MINOR_MAPPING). +# pycross.configure_environments is root-honored only, so the workaround +# must be mirrored here from tools/OpenROAD/MODULE.bazel. +bazel_dep(name = "rules_pycross", version = "0.8.1", dev_dependency = True) + +pycross = use_extension( + "@rules_pycross//pycross/extensions:pycross.bzl", + "pycross", + dev_dependency = True, +) +pycross.configure_environments(python_versions = ["3.13"]) + python = use_extension("@rules_python//python/extensions:python.bzl", "python") python.toolchain( ignore_root_user_error = True, From 940cbd5601a13c9bc574345329c47b018f0b4a25 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=98yvind=20Harboe?= Date: Tue, 7 Jul 2026 05:52:34 +0200 Subject: [PATCH 2/3] flow: test the single-OpenROAD-process flow against the staged flow MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ORFS supports running all OpenROAD stages in a single OpenROAD process by sourcing the per-stage scripts into one Tcl script: load_design skips reloading when a design is already in memory, and KEEP_VARS=1 keeps variables across stages. floorplan_to_place.tcl uses this for floorplan through place; the PR #4333 discussion noted the mechanism has no automated test coverage. Add scripts/flow.tcl, which runs floorplan through the final report in one process and mirrors the Makefile do-copy recipes so $RESULTS_DIR ends up with the same .odb/.sdc file set as the stage-per-process flow. Cover it with bazel tests on asap7/gcd: an orfs_run executes the whole flow off the synthesis result under FLOW_VARIANT=single, and per-stage sh_tests byte-compare the stage-boundary .odb/.sdc files against the regular per-stage flow targets, using tools/OpenROAD's existing check_same.sh. Equal bytes proves the single-process mode is equivalent to the per-stage flow, not merely that it runs to completion. bazelisk test //flow/designs/asap7/gcd:gcd_single_flow_test Findings on asap7/gcd, recorded in the BUILD file: - floorplan, place and cts are byte-identical between the two flows; those comparisons are enforced by the gcd_single_flow_test suite. - From grt on the flows diverge: the grt-stage repair_timing inserts one extra buffer (8 vs 7) when the process continues from in-memory CTS state instead of reloading 4_cts.odb, even though the 4_cts.odb/.sdc it starts from are byte-identical. The grt, route and final comparisons are kept as manual test targets so the gap stays measurable. The design's full argument dict rides along on the orfs_run because bazel-orfs scopes each stage config to that stage's variables, so the synth src config lacks the floorplan/place/cts variables the later stages need. SYNTH_USE_SYN is declared make-only (local_arguments): bazel-orfs's synth stage always runs the yosys flow and neither stages the Verilog sources nor sets VERILOG_FILES for the OpenROAD synthesis step, so the built-in synthesizer opt-in must not reach the bazel arguments (with it, bazelisk build :gcd_synth fails outright). Co-Authored-By: Claude Fable 5 Signed-off-by: Øyvind Harboe --- flow/designs/asap7/gcd/BUILD | 149 ++++++++++++++++++++++++- flow/designs/asap7/gcd/single_flow.tcl | 10 ++ flow/scripts/flow.tcl | 41 +++++++ 3 files changed, 199 insertions(+), 1 deletion(-) create mode 100644 flow/designs/asap7/gcd/single_flow.tcl create mode 100644 flow/scripts/flow.tcl diff --git a/flow/designs/asap7/gcd/BUILD b/flow/designs/asap7/gcd/BUILD index 527d6542e1..172645f8c2 100644 --- a/flow/designs/asap7/gcd/BUILD +++ b/flow/designs/asap7/gcd/BUILD @@ -1,3 +1,150 @@ +load("@bazel-orfs//:openroad.bzl", "orfs_run") +load("@orfs_designs//:designs.bzl", "DESIGNS") +load("@rules_shell//shell:sh_test.bzl", "sh_test") load("//flow/designs:design.bzl", "design") -design(config = "config.mk") +# SYNTH_USE_SYN is make-only for now: bazel-orfs's synth stage always +# runs the yosys flow and neither stages the Verilog sources nor sets +# VERILOG_FILES for the OpenROAD synthesis step, so the built-in +# synthesizer opt-in must not reach the bazel arguments. +design( + config = "config.mk", + local_arguments = ["SYNTH_USE_SYN"], +) + +# Stage-boundary .odb/.sdc stems shared by the normal per-stage flow +# targets and the single-process flow below; gcd_single_flow_test +# compares each of these files pairwise between the two flows. +SINGLE_FLOW_STAGES = { + "floorplan": "2_floorplan", + "place": "3_place", + "cts": "4_cts", + "grt": "5_1_grt", + "route": "5_route", + "final": "6_final", +} + +# Substep files the single-process flow also writes, declared so the +# action keeps them and a human can inspect them on failure. +SINGLE_FLOW_EXTRA_OUTS = [ + "2_1_floorplan.odb", + "2_1_floorplan.sdc", + "2_2_floorplan_macro.odb", + "2_3_floorplan_tapcell.odb", + "2_4_floorplan_pdn.odb", + "3_1_place_gp_skip_io.odb", + "3_2_place_iop.odb", + "3_3_place_gp.odb", + "3_4_place_resized.odb", + "3_5_place_dp.odb", + "4_1_cts.odb", + "5_2_route.odb", + "5_3_fillcell.odb", + "6_1_fill.odb", + "6_1_fill.sdc", +] + +# The whole flow, floorplan through finish, as one Tcl script in a +# single OpenROAD process (scripts/flow.tcl). The synth stage config +# only carries synth-scoped variables, so the design's full argument +# set rides along; FLOW_VARIANT=single keeps the output paths clear of +# the regular stage targets' declared outputs in this package. +orfs_run( + name = "gcd_single_flow", + src = ":gcd_synth", + outs = [ + "results/asap7/gcd/single/{}.{}".format(stem, ext) + for stem in SINGLE_FLOW_STAGES.values() + for ext in [ + "odb", + "sdc", + ] + ] + [ + "results/asap7/gcd/single/" + name + for name in SINGLE_FLOW_EXTRA_OUTS + ], + arguments = { + k: v + for k, v in DESIGNS["asap7/gcd"]["arguments"].items() + if k != "SYNTH_USE_SYN" + } | { + "FLOW_VARIANT": "single", + }, + script = ":single_flow.tcl", + variant = "single", +) + +# Normal-flow stage outputs, one filegroup per compared file. +[ + filegroup( + name = "gcd_base_{}_{}".format(stage, ext), + srcs = [":gcd_" + stage], + output_group = "{}.{}".format(stem, ext), + ) + for stage, stem in SINGLE_FLOW_STAGES.items() + for ext in [ + "odb", + "sdc", + ] +] + +# From the grt stage on, the single-process flow is known not to be +# byte-equivalent to the per-stage flow yet: the grt-stage repair_timing +# inserts one extra buffer (8 vs 7 on asap7/gcd) when the process +# continues from the in-memory CTS state instead of reloading 4_cts.odb, +# even though the 4_cts.odb/.sdc it starts from are byte-identical. The +# comparisons are kept as manual targets so the gap stays measurable; +# drop the tag once single-process equivalence holds through the flow. +KNOWN_DIVERGENT_STAGES = [ + "grt", + "route", + "final", +] + +# Byte-compare each stage boundary's .odb/.sdc between the per-stage +# flow and the single-process flow: equal files prove the +# single-process mode is equivalent to the per-stage flow, not merely +# that it runs to completion. check_same.sh takes file pairs: +# file_a file_b ... (.sdc pair first: check_same.sh stops at a binary +# diff, and the .sdc verdict should be reported before that). +[ + sh_test( + name = "gcd_single_flow_{}_test".format(stage), + srcs = ["@openroad//test/orfs:check_same.sh"], + args = [ + arg + for ext in [ + "sdc", + "odb", + ] + for arg in [ + "$(location :gcd_base_{}_{})".format(stage, ext), + "$(location :results/asap7/gcd/single/{}.{})".format(stem, ext), + ] + ], + data = [ + ":gcd_base_{}_{}".format(stage, ext) + for ext in [ + "sdc", + "odb", + ] + ] + [ + ":results/asap7/gcd/single/{}.{}".format(stem, ext) + for ext in [ + "sdc", + "odb", + ] + ], + tags = ["orfs"] + (["manual"] if stage in KNOWN_DIVERGENT_STAGES else []), + ) + for stage, stem in SINGLE_FLOW_STAGES.items() +] + +test_suite( + name = "gcd_single_flow_test", + tests = [ + ":gcd_single_flow_{}_test".format(stage) + for stage in SINGLE_FLOW_STAGES + if stage not in KNOWN_DIVERGENT_STAGES + ], +) diff --git a/flow/designs/asap7/gcd/single_flow.tcl b/flow/designs/asap7/gcd/single_flow.tcl new file mode 100644 index 0000000000..387e936cff --- /dev/null +++ b/flow/designs/asap7/gcd/single_flow.tcl @@ -0,0 +1,10 @@ +# Seed this variant's results dir with the synthesis outputs, then run +# the whole flow in this single OpenROAD process. Under bazel-orfs the +# synthesis results are staged in the src stage's variant folder and +# exposed via ODB_FILE, while RESULTS_DIR points at this run's own +# variant folder. +file mkdir $::env(RESULTS_DIR) +exec cp $::env(ODB_FILE) $::env(RESULTS_DIR)/1_synth.odb +exec cp [file rootname $::env(ODB_FILE)].sdc $::env(RESULTS_DIR)/1_synth.sdc + +source $::env(SCRIPTS_DIR)/flow.tcl diff --git a/flow/scripts/flow.tcl b/flow/scripts/flow.tcl new file mode 100644 index 0000000000..e6e71beb88 --- /dev/null +++ b/flow/scripts/flow.tcl @@ -0,0 +1,41 @@ +# Runs all OpenROAD stages of the flow, from 1_synth.odb/.sdc through +# the final report, in a single OpenROAD process. Produces the same +# .odb/.sdc files in $RESULTS_DIR as the stage-per-process make flow: +# the exec cp lines mirror the Makefile do-copy recipes. + +# Enable keeping variables between stages +set ::env(KEEP_VARS) 1 + +# Floorplan +source $::env(SCRIPTS_DIR)/floorplan.tcl +source $::env(SCRIPTS_DIR)/macro_place.tcl +source $::env(SCRIPTS_DIR)/tapcell.tcl +source $::env(SCRIPTS_DIR)/pdn.tcl +exec cp $::env(RESULTS_DIR)/2_4_floorplan_pdn.odb $::env(RESULTS_DIR)/2_floorplan.odb +exec cp $::env(RESULTS_DIR)/2_1_floorplan.sdc $::env(RESULTS_DIR)/2_floorplan.sdc + +# Place +source $::env(SCRIPTS_DIR)/global_place_skip_io.tcl +source $::env(SCRIPTS_DIR)/io_placement.tcl +source $::env(SCRIPTS_DIR)/global_place.tcl +source $::env(SCRIPTS_DIR)/resize.tcl +source $::env(SCRIPTS_DIR)/detail_place.tcl +exec cp $::env(RESULTS_DIR)/3_5_place_dp.odb $::env(RESULTS_DIR)/3_place.odb +exec cp $::env(RESULTS_DIR)/2_floorplan.sdc $::env(RESULTS_DIR)/3_place.sdc + +# CTS (cts.tcl itself writes 4_cts.sdc) +source $::env(SCRIPTS_DIR)/cts.tcl +exec cp $::env(RESULTS_DIR)/4_1_cts.odb $::env(RESULTS_DIR)/4_cts.odb + +# Route +source $::env(SCRIPTS_DIR)/global_route.tcl +source $::env(SCRIPTS_DIR)/detail_route.tcl +source $::env(SCRIPTS_DIR)/fillcell.tcl +exec cp $::env(RESULTS_DIR)/5_3_fillcell.odb $::env(RESULTS_DIR)/5_route.odb +exec cp $::env(RESULTS_DIR)/5_1_grt.sdc $::env(RESULTS_DIR)/5_route.sdc + +# Finish +source $::env(SCRIPTS_DIR)/density_fill.tcl +exec cp $::env(RESULTS_DIR)/5_route.sdc $::env(RESULTS_DIR)/6_1_fill.sdc +exec cp $::env(RESULTS_DIR)/5_route.sdc $::env(RESULTS_DIR)/6_final.sdc +source $::env(SCRIPTS_DIR)/final_report.tcl From 2549975a1b59bdc28fd2e3d42afcbdc2f34c6c17 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=98yvind=20Harboe?= Date: Tue, 7 Jul 2026 06:40:07 +0200 Subject: [PATCH 3/3] flow: single-flow test review feedback: file copy, no manual tags MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Address review feedback on the single-process flow test: - Use Tcl's built-in "file copy -force" instead of "exec cp" for the do-copy mirror steps. Nothing downstream relies on file dates, so cp's attribute preservation isn't needed. - Drop the "manual" tag from the grt/route/final comparisons and include them in the gcd_single_flow_test suite. These tests are not run in CI, and the point of the divergence (grt-stage repair_timing inserts one extra buffer from in-memory CTS state than from a byte-identical reloaded 4_cts.odb) is to be root-caused, not hidden. Co-Authored-By: Claude Fable 5 Signed-off-by: Øyvind Harboe --- flow/designs/asap7/gcd/BUILD | 23 ++++++++--------------- flow/designs/asap7/gcd/single_flow.tcl | 4 ++-- flow/scripts/flow.tcl | 20 ++++++++++---------- 3 files changed, 20 insertions(+), 27 deletions(-) diff --git a/flow/designs/asap7/gcd/BUILD b/flow/designs/asap7/gcd/BUILD index 172645f8c2..9674a4de33 100644 --- a/flow/designs/asap7/gcd/BUILD +++ b/flow/designs/asap7/gcd/BUILD @@ -88,25 +88,19 @@ orfs_run( ] ] -# From the grt stage on, the single-process flow is known not to be -# byte-equivalent to the per-stage flow yet: the grt-stage repair_timing -# inserts one extra buffer (8 vs 7 on asap7/gcd) when the process -# continues from the in-memory CTS state instead of reloading 4_cts.odb, -# even though the 4_cts.odb/.sdc it starts from are byte-identical. The -# comparisons are kept as manual targets so the gap stays measurable; -# drop the tag once single-process equivalence holds through the flow. -KNOWN_DIVERGENT_STAGES = [ - "grt", - "route", - "final", -] - # Byte-compare each stage boundary's .odb/.sdc between the per-stage # flow and the single-process flow: equal files prove the # single-process mode is equivalent to the per-stage flow, not merely # that it runs to completion. check_same.sh takes file pairs: # file_a file_b ... (.sdc pair first: check_same.sh stops at a binary # diff, and the .sdc verdict should be reported before that). +# +# Known divergence, deliberately left failing so it gets root-caused +# rather than papered over: from the grt stage on, the grt-stage +# repair_timing inserts one extra buffer (8 vs 7 on asap7/gcd) when the +# process continues from the in-memory CTS state instead of reloading +# 4_cts.odb, even though the 4_cts.odb/.sdc it starts from are +# byte-identical (all .sdc match through the whole flow). [ sh_test( name = "gcd_single_flow_{}_test".format(stage), @@ -135,7 +129,7 @@ KNOWN_DIVERGENT_STAGES = [ "odb", ] ], - tags = ["orfs"] + (["manual"] if stage in KNOWN_DIVERGENT_STAGES else []), + tags = ["orfs"], ) for stage, stem in SINGLE_FLOW_STAGES.items() ] @@ -145,6 +139,5 @@ test_suite( tests = [ ":gcd_single_flow_{}_test".format(stage) for stage in SINGLE_FLOW_STAGES - if stage not in KNOWN_DIVERGENT_STAGES ], ) diff --git a/flow/designs/asap7/gcd/single_flow.tcl b/flow/designs/asap7/gcd/single_flow.tcl index 387e936cff..64fe2466b5 100644 --- a/flow/designs/asap7/gcd/single_flow.tcl +++ b/flow/designs/asap7/gcd/single_flow.tcl @@ -4,7 +4,7 @@ # exposed via ODB_FILE, while RESULTS_DIR points at this run's own # variant folder. file mkdir $::env(RESULTS_DIR) -exec cp $::env(ODB_FILE) $::env(RESULTS_DIR)/1_synth.odb -exec cp [file rootname $::env(ODB_FILE)].sdc $::env(RESULTS_DIR)/1_synth.sdc +file copy -force $::env(ODB_FILE) $::env(RESULTS_DIR)/1_synth.odb +file copy -force [file rootname $::env(ODB_FILE)].sdc $::env(RESULTS_DIR)/1_synth.sdc source $::env(SCRIPTS_DIR)/flow.tcl diff --git a/flow/scripts/flow.tcl b/flow/scripts/flow.tcl index e6e71beb88..400b172981 100644 --- a/flow/scripts/flow.tcl +++ b/flow/scripts/flow.tcl @@ -1,7 +1,7 @@ # Runs all OpenROAD stages of the flow, from 1_synth.odb/.sdc through # the final report, in a single OpenROAD process. Produces the same # .odb/.sdc files in $RESULTS_DIR as the stage-per-process make flow: -# the exec cp lines mirror the Makefile do-copy recipes. +# the file copy -force lines mirror the Makefile do-copy recipes. # Enable keeping variables between stages set ::env(KEEP_VARS) 1 @@ -11,8 +11,8 @@ source $::env(SCRIPTS_DIR)/floorplan.tcl source $::env(SCRIPTS_DIR)/macro_place.tcl source $::env(SCRIPTS_DIR)/tapcell.tcl source $::env(SCRIPTS_DIR)/pdn.tcl -exec cp $::env(RESULTS_DIR)/2_4_floorplan_pdn.odb $::env(RESULTS_DIR)/2_floorplan.odb -exec cp $::env(RESULTS_DIR)/2_1_floorplan.sdc $::env(RESULTS_DIR)/2_floorplan.sdc +file copy -force $::env(RESULTS_DIR)/2_4_floorplan_pdn.odb $::env(RESULTS_DIR)/2_floorplan.odb +file copy -force $::env(RESULTS_DIR)/2_1_floorplan.sdc $::env(RESULTS_DIR)/2_floorplan.sdc # Place source $::env(SCRIPTS_DIR)/global_place_skip_io.tcl @@ -20,22 +20,22 @@ source $::env(SCRIPTS_DIR)/io_placement.tcl source $::env(SCRIPTS_DIR)/global_place.tcl source $::env(SCRIPTS_DIR)/resize.tcl source $::env(SCRIPTS_DIR)/detail_place.tcl -exec cp $::env(RESULTS_DIR)/3_5_place_dp.odb $::env(RESULTS_DIR)/3_place.odb -exec cp $::env(RESULTS_DIR)/2_floorplan.sdc $::env(RESULTS_DIR)/3_place.sdc +file copy -force $::env(RESULTS_DIR)/3_5_place_dp.odb $::env(RESULTS_DIR)/3_place.odb +file copy -force $::env(RESULTS_DIR)/2_floorplan.sdc $::env(RESULTS_DIR)/3_place.sdc # CTS (cts.tcl itself writes 4_cts.sdc) source $::env(SCRIPTS_DIR)/cts.tcl -exec cp $::env(RESULTS_DIR)/4_1_cts.odb $::env(RESULTS_DIR)/4_cts.odb +file copy -force $::env(RESULTS_DIR)/4_1_cts.odb $::env(RESULTS_DIR)/4_cts.odb # Route source $::env(SCRIPTS_DIR)/global_route.tcl source $::env(SCRIPTS_DIR)/detail_route.tcl source $::env(SCRIPTS_DIR)/fillcell.tcl -exec cp $::env(RESULTS_DIR)/5_3_fillcell.odb $::env(RESULTS_DIR)/5_route.odb -exec cp $::env(RESULTS_DIR)/5_1_grt.sdc $::env(RESULTS_DIR)/5_route.sdc +file copy -force $::env(RESULTS_DIR)/5_3_fillcell.odb $::env(RESULTS_DIR)/5_route.odb +file copy -force $::env(RESULTS_DIR)/5_1_grt.sdc $::env(RESULTS_DIR)/5_route.sdc # Finish source $::env(SCRIPTS_DIR)/density_fill.tcl -exec cp $::env(RESULTS_DIR)/5_route.sdc $::env(RESULTS_DIR)/6_1_fill.sdc -exec cp $::env(RESULTS_DIR)/5_route.sdc $::env(RESULTS_DIR)/6_final.sdc +file copy -force $::env(RESULTS_DIR)/5_route.sdc $::env(RESULTS_DIR)/6_1_fill.sdc +file copy -force $::env(RESULTS_DIR)/5_route.sdc $::env(RESULTS_DIR)/6_final.sdc source $::env(SCRIPTS_DIR)/final_report.tcl