diff --git a/.bazelrc b/.bazelrc index 1d550b988f..e9f83d934f 100644 --- a/.bazelrc +++ b/.bazelrc @@ -11,6 +11,15 @@ common --registry=https://raw.githubusercontent.com/oharboe/bazel-central-regist build --incompatible_strict_action_env build --cxxopt "-std=c++20" --host_cxxopt "-std=c++20" +# Refuse to autodetect a local C++ toolchain (/usr/bin/gcc); the hermetic +# toolchain must win resolution. Mirrors tools/OpenROAD/.bazelrc. +common --repo_env=BAZEL_DO_NOT_DETECT_CPP_TOOLCHAIN=1 + +# Launch py_binary via a shell stub instead of rules_python's legacy python +# stub, whose '#!/usr/bin/env python3' shebang needs a host python3 before +# the hermetic interpreter takes over. Mirrors tools/OpenROAD/.bazelrc. +common --@rules_python//python/config_settings:bootstrap_impl=script + # Don't track MODULE.bazel.lock. Resolved versions ride along with the # pinned BCR + git_override(commit=…) coordinates in MODULE.bazel and # downstream cache hits are what we actually care about. Tracking the diff --git a/MODULE.bazel b/MODULE.bazel index 1d21ea2f79..894fdb6318 100644 --- a/MODULE.bazel +++ b/MODULE.bazel @@ -19,7 +19,64 @@ bazel_dep(name = "rules_shell", version = "0.6.1") # our MODULE.bazel doesn't need to be patched at non-root consumption # time. -bazel_dep(name = "toolchains_llvm", version = "1.5.0", dev_dependency = True) +# hermetic-llvm (BCR module "llvm") provides statically linked LLVM +# binaries and a zero-sysroot cc_toolchain: no host compiler, linker or +# /usr/include involved, so host glibc changes cannot break the build. +# Mirrors tools/OpenROAD/MODULE.bazel (its llvm dep is dev-only, so it +# does not propagate here). +bazel_dep(name = "llvm", version = "0.8.11", dev_dependency = True) + +# The gnulib-based GNU tools (sed, gawk, m4, bison) vendor wrapper +# headers that must shadow libc headers, which breaks against +# hermetic-llvm's explicit libc -isystem entries (BCR #7642). Mirror +# tools/OpenROAD's fixed-version overrides (single_version_override is +# root-honored only); drop each when tools/OpenROAD drops its copy. +# +# sed is transitive (bazel-orfs -> abc -> readline -> ncurses -> sed); +# 4.9.bcr.5 carries the upstream fix (BCR #7915). +single_version_override( + module_name = "sed", + version = "4.9.bcr.5", +) + +# gawk is transitive (abc -> ncurses -> gawk; yosys -> gawk); +# 5.3.2.bcr.7 carries the upstream fix (BCR #7989). +single_version_override( + module_name = "gawk", + version = "5.3.2.bcr.7", +) + +# m4 is transitive (rules_bison/rules_flex -> m4). +single_version_override( + module_name = "m4", + version = "1.4.21.bcr.4", +) + +# bison is transitive (rules_bison -> bison). 3.8.2.bcr.7 fixes lib/ +# but src/ still picks up raw libc headers; the patch restores gnulib +# wrapper-header precedence via -I (to be proposed upstream as +# 3.8.2.bcr.8, then drop). +single_version_override( + module_name = "bison", + patch_strip = 1, + patches = [ + "//patches/bison:0001-gnulib-wrapper-headers-use-I-not-isystem.patch", + ], + version = "3.8.2.bcr.7", +) + +# tclZipfs.c's '#include "crypt.h"' picks up glibc's crypt.h instead of +# the vendored minizip one when glibc headers are explicit -isystem +# directories (hermetic-llvm zero-sysroot toolchain). Drop when fixed +# in a tcl_lang BCR release. +single_version_override( + module_name = "tcl_lang", + patch_strip = 1, + patches = [ + "//patches/tcl_lang:0001-tclZipfs-include-vendored-minizip-crypt.h-by-path.patch", + ], +) + bazel_dep(name = "openroad", dev_dependency = True) local_path_override( module_name = "openroad", @@ -68,20 +125,24 @@ git_override( # --- Extensions --- -llvm = use_extension( - "@toolchains_llvm//toolchain/extensions:llvm.bzl", - "llvm", +register_toolchains( + "@llvm//toolchain:all", dev_dependency = True, ) -llvm.toolchain( - llvm_version = "20.1.8", -) -use_repo(llvm, "llvm_toolchain") -register_toolchains( - "@llvm_toolchain//:all", +# 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( diff --git a/bazel/slang-compat/README b/bazel/slang-compat/README new file mode 100644 index 0000000000..03051c7e29 --- /dev/null +++ b/bazel/slang-compat/README @@ -0,0 +1,6 @@ +Placeholder directory for the @slang alias build file to internally +manifest. tools/OpenROAD's MODULE.bazel declares +new_local_repository(name="slang", path="bazel/slang-compat"); the +relative path resolves against the root workspace, so when ORFS is the +root module consuming openroad via git_override the directory must +exist here too. Mirrors tools/OpenROAD/bazel/slang-compat. diff --git a/flow/designs/asap7/gcd/BUILD b/flow/designs/asap7/gcd/BUILD index 527d6542e1..b8c3ddc518 100644 --- a/flow/designs/asap7/gcd/BUILD +++ b/flow/designs/asap7/gcd/BUILD @@ -1,3 +1,168 @@ +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; the gcd_single_flow_test +# suite compares each of these files pairwise between the two flows. +SINGLE_FLOW_STAGES = { + "synth": "1_synth", + "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, yosys netlist through finish, as one Tcl script in a +# single OpenROAD process (scripts/flow.tcl) with +# WRITE_ODB_AND_SDC_EACH_STAGE=0: the stage scripts write no .odb/.sdc +# files (flow.tcl errors if one does), the top level writes every file +# the per-stage flow produces via explicit write_db/write_sdc. 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", + ] +] + +# 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). +# +# The synth through grt boundaries are byte-identical. The grt stage +# used to diverge (an extra repair_timing buffer): STA endpoint state +# for CTS dummy-load pins differed between a process that inserted them +# and a process that read the same design from disk, and +# resistance-aware global routing consumed the resulting bogus net +# slacks — fixed by patches/openroad/0001-sta-register-new-instance- +# pin-vertices-as-endpoints.patch. +# +# The route stage used to route differently in-process (net/instance +# iteration order and via emission order in drt reflected the process +# history) — fixed by patches/openroad/0002-drt-make-incremental- +# design-updates-match-a-fresh-read.patch; the routed layout is now +# identical (equal DEF). +# +# The route .odb byte difference was NOT an odb id/freelist issue +# (free lists serialize and reload): the per-stage flow accumulated +# stale block-terminal access points on every pin access rerun — fixed +# by patches/openroad/0003-drt-do-not-duplicate-bterm-access-points. +# patch; route is byte-identical now. +# +# The final stage used to diverge because the per-stage flow writes +# 6_final.odb mid-final_report (before the RCX extraction reorders +# every dbWire via odb orderWires) while this flow wrote at end of +# script — resolved by splitting final_report.tcl around its write so +# flow.tcl writes at the same point. All seven stage boundaries are +# byte-identical between the two flows. +[ + 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"], + ) + 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 + ], +) diff --git a/flow/designs/asap7/gcd/single_flow.tcl b/flow/designs/asap7/gcd/single_flow.tcl new file mode 100644 index 0000000000..4e6ab51b0a --- /dev/null +++ b/flow/designs/asap7/gcd/single_flow.tcl @@ -0,0 +1,11 @@ +# Seed this variant's results dir with the yosys 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 its odb is exposed via ODB_FILE, while RESULTS_DIR +# points at this run's own variant folder. +file mkdir $::env(RESULTS_DIR) +set src_results [file dirname $::env(ODB_FILE)] +file copy -force $src_results/1_2_yosys.v $::env(RESULTS_DIR)/1_2_yosys.v +file copy -force $src_results/1_2_yosys.sdc $::env(RESULTS_DIR)/1_2_yosys.sdc + +source $::env(SCRIPTS_DIR)/flow.tcl diff --git a/flow/scripts/density_fill.tcl b/flow/scripts/density_fill.tcl index 8b1d019eab..1f93ed4c52 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 { - log_cmd exec cp $::env(RESULTS_DIR)/5_route.odb $::env(RESULTS_DIR)/6_1_fill.odb + orfs_copy_db $::env(RESULTS_DIR)/5_route.odb $::env(RESULTS_DIR)/6_1_fill.odb # There is no 5_route.v file to copy } diff --git a/flow/scripts/detail_route.tcl b/flow/scripts/detail_route.tcl index c3503558f6..907f4b708b 100644 --- a/flow/scripts/detail_route.tcl +++ b/flow/scripts/detail_route.tcl @@ -7,80 +7,79 @@ if { ![grt::have_routes] } { in DRC viewer to view congestion" } -if { $::env(SKIP_DETAILED_ROUTE) } { - orfs_write_db $::env(RESULTS_DIR)/5_2_route.odb - exit -} - -erase_non_stage_variables route -set_propagated_clock [all_clocks] +if { !$::env(SKIP_DETAILED_ROUTE) } { + erase_non_stage_variables route + set_propagated_clock [all_clocks] -set additional_args "" -append_env_var additional_args dbProcessNode -db_process_node 1 -append_env_var additional_args OR_SEED -or_seed 1 -append_env_var additional_args OR_K -or_k 1 -append_env_var additional_args VIA_IN_PIN_MIN_LAYER -via_in_pin_bottom_layer 1 -append_env_var additional_args VIA_IN_PIN_MAX_LAYER -via_in_pin_top_layer 1 -append_env_var additional_args DISABLE_VIA_GEN -disable_via_gen 0 -append_env_var additional_args REPAIR_PDN_VIA_LAYER -repair_pdn_vias 1 -append_env_var additional_args DETAILED_ROUTE_END_ITERATION -droute_end_iter 1 + set additional_args "" + append_env_var additional_args dbProcessNode -db_process_node 1 + append_env_var additional_args OR_SEED -or_seed 1 + append_env_var additional_args OR_K -or_k 1 + append_env_var additional_args VIA_IN_PIN_MIN_LAYER -via_in_pin_bottom_layer 1 + append_env_var additional_args VIA_IN_PIN_MAX_LAYER -via_in_pin_top_layer 1 + append_env_var additional_args DISABLE_VIA_GEN -disable_via_gen 0 + append_env_var additional_args REPAIR_PDN_VIA_LAYER -repair_pdn_vias 1 + append_env_var additional_args DETAILED_ROUTE_END_ITERATION -droute_end_iter 1 -append additional_args " -verbose 1" + append additional_args " -verbose 1" -# DETAILED_ROUTE_ARGS is used when debugging detailed, route, e.g. append -# "-droute_end_iter 5" to look at routing violations after only 5 iterations, -# speeding up iterations on a problem where detailed routing doesn't converge -# or converges slower than expected. -# -# If DETAILED_ROUTE_ARGS is not specified, save out progress report a -# few iterations after the first two iterations. The first couple of -# iterations would produce very large .drc reports without interesting -# information for the user. -# -# The idea is to have a policy that gives progress information soon without -# having to go spelunking in Tcl or modify configuration scripts, while -# not having to wait too long or generating large useless reports. + # DETAILED_ROUTE_ARGS is used when debugging detailed, route, e.g. append + # "-droute_end_iter 5" to look at routing violations after only 5 iterations, + # speeding up iterations on a problem where detailed routing doesn't converge + # or converges slower than expected. + # + # If DETAILED_ROUTE_ARGS is not specified, save out progress report a + # few iterations after the first two iterations. The first couple of + # iterations would produce very large .drc reports without interesting + # information for the user. + # + # The idea is to have a policy that gives progress information soon without + # having to go spelunking in Tcl or modify configuration scripts, while + # not having to wait too long or generating large useless reports. -set arguments [expr { - [env_var_exists_and_non_empty DETAILED_ROUTE_ARGS] ? $::env(DETAILED_ROUTE_ARGS) : - [concat $additional_args {-drc_report_iter_step 5}] -}] + set arguments [expr { + [env_var_exists_and_non_empty DETAILED_ROUTE_ARGS] ? $::env(DETAILED_ROUTE_ARGS) : + [concat $additional_args {-drc_report_iter_step 5}] + }] -set all_args [concat [list \ - -output_drc $::env(REPORTS_DIR)/5_route_drc.rpt \ - -output_maze $::env(RESULTS_DIR)/maze.log] \ - $arguments] + set all_args [concat [list \ + -output_drc $::env(REPORTS_DIR)/5_route_drc.rpt \ + -output_maze $::env(RESULTS_DIR)/maze.log] \ + $arguments] -log_cmd detailed_route {*}$all_args + log_cmd detailed_route {*}$all_args -if { - !$::env(SKIP_ANTENNA_REPAIR_POST_DRT) && - [env_var_exists_and_non_empty MAX_REPAIR_ANTENNAS_ITER_DRT] -} { - set repair_antennas_iters 1 - if { [repair_antennas] } { - detailed_route {*}$all_args + if { + !$::env(SKIP_ANTENNA_REPAIR_POST_DRT) && + [env_var_exists_and_non_empty MAX_REPAIR_ANTENNAS_ITER_DRT] + } { + set repair_antennas_iters 1 + if { [repair_antennas] } { + detailed_route {*}$all_args + } + while { + [check_antennas] && $repair_antennas_iters < $::env(MAX_REPAIR_ANTENNAS_ITER_DRT) + } { + repair_antennas + detailed_route {*}$all_args + incr repair_antennas_iters + } + } else { + utl::metric_int "antenna_diodes_count" -1 } - while { [check_antennas] && $repair_antennas_iters < $::env(MAX_REPAIR_ANTENNAS_ITER_DRT) } { - repair_antennas - detailed_route {*}$all_args - incr repair_antennas_iters - } -} else { - utl::metric_int "antenna_diodes_count" -1 -} -source_step_tcl POST DETAIL_ROUTE + source_step_tcl POST DETAIL_ROUTE -check_antennas -report_file $env(REPORTS_DIR)/drt_antennas.log + check_antennas -report_file $env(REPORTS_DIR)/drt_antennas.log -if { ![design_is_routed] } { - error "Design has unrouted nets." -} + if { ![design_is_routed] } { + error "Design has unrouted nets." + } -report_design_area + report_design_area -# Don't report metrics as we have not extracted parasitics, which will happen -# in final so there is no need to repeat it here. + # Don't report metrics as we have not extracted parasitics, which will + # happen in final so there is no need to repeat it here. +} orfs_write_db $::env(RESULTS_DIR)/5_2_route.odb diff --git a/flow/scripts/fillcell.tcl b/flow/scripts/fillcell.tcl index b778ccb717..032c1ab58b 100644 --- a/flow/scripts/fillcell.tcl +++ b/flow/scripts/fillcell.tcl @@ -12,7 +12,7 @@ if { [env_var_exists_and_non_empty FILL_CELLS] } { report_design_area orfs_write_db $::env(RESULTS_DIR)/5_3_fillcell.odb } else { - log_cmd exec cp $::env(RESULTS_DIR)/5_2_route.odb $::env(RESULTS_DIR)/5_3_fillcell.odb + orfs_copy_db $::env(RESULTS_DIR)/5_2_route.odb $::env(RESULTS_DIR)/5_3_fillcell.odb } source_step_tcl POST FILLCELL diff --git a/flow/scripts/final_connect.tcl b/flow/scripts/final_connect.tcl new file mode 100644 index 0000000000..22324613c6 --- /dev/null +++ b/flow/scripts/final_connect.tcl @@ -0,0 +1,10 @@ +utl::set_metrics_stage "finish__{}" +source $::env(SCRIPTS_DIR)/load.tcl +erase_non_stage_variables final +load_design 6_1_fill.odb 6_1_fill.sdc +source_step_tcl PRE FINAL_REPORT + +set_propagated_clock [all_clocks] + +# Ensure all OR created (rsz/cts) instances are connected +global_connect diff --git a/flow/scripts/final_outputs.tcl b/flow/scripts/final_outputs.tcl new file mode 100644 index 0000000000..b264b293b0 --- /dev/null +++ b/flow/scripts/final_outputs.tcl @@ -0,0 +1,59 @@ +# Delete routing obstructions for final DEF +source $::env(SCRIPTS_DIR)/deleteRoutingObstructions.tcl +deleteRoutingObstructions + +write_def $::env(RESULTS_DIR)/6_final.def +write_verilog $::env(RESULTS_DIR)/6_final.v \ + -remove_cells [find_physical_only_masters] + +# Run extraction and STA +if { + [env_var_exists_and_non_empty RCX_RULES] + && !$::env(SKIP_DETAILED_ROUTE) +} { + # RCX section + define_process_corner -ext_model_index 0 X + extract_parasitics -ext_model_file $::env(RCX_RULES) + + # Write Spef + write_spef $::env(RESULTS_DIR)/6_final.spef + file delete $::env(DESIGN_NAME).totCap + + # Read Spef for OpenSTA + read_spef $::env(RESULTS_DIR)/6_final.spef + + # Static IR drop analysis + if { [env_var_exists_and_non_empty PWR_NETS_VOLTAGES] } { + dict for {pwrNetName pwrNetVoltage} $::env(PWR_NETS_VOLTAGES) { + set_pdnsim_net_voltage -net ${pwrNetName} -voltage ${pwrNetVoltage} + analyze_power_grid -net ${pwrNetName} \ + -error_file $::env(REPORTS_DIR)/${pwrNetName}.rpt + } + } else { + puts "IR drop analysis for power nets is skipped because PWR_NETS_VOLTAGES is undefined" + } + if { [env_var_exists_and_non_empty GND_NETS_VOLTAGES] } { + dict for {gndNetName gndNetVoltage} $::env(GND_NETS_VOLTAGES) { + set_pdnsim_net_voltage -net ${gndNetName} -voltage ${gndNetVoltage} + analyze_power_grid -net ${gndNetName} \ + -error_file $::env(REPORTS_DIR)/${gndNetName}.rpt + } + } else { + puts "IR drop analysis for ground nets is skipped because GND_NETS_VOLTAGES is undefined" + } +} else { + puts "OpenRCX is not enabled for this platform." + puts "Falling back to global route-based estimates." + log_cmd estimate_parasitics -global_routing +} + +report_cell_usage + +report_metrics 6 "finish" + +source_step_tcl POST FINAL_REPORT + +# Save a final image if openroad is compiled with the gui +if { [ord::openroad_gui_compiled] } { + gui::show "source $::env(SCRIPTS_DIR)/save_images.tcl" false +} diff --git a/flow/scripts/final_report.tcl b/flow/scripts/final_report.tcl index d81acc986e..174e4b153a 100644 --- a/flow/scripts/final_report.tcl +++ b/flow/scripts/final_report.tcl @@ -1,72 +1,6 @@ -utl::set_metrics_stage "finish__{}" -source $::env(SCRIPTS_DIR)/load.tcl -erase_non_stage_variables final -load_design 6_1_fill.odb 6_1_fill.sdc -source_step_tcl PRE FINAL_REPORT - -set_propagated_clock [all_clocks] - -# Ensure all OR created (rsz/cts) instances are connected -global_connect - +# Split around the 6_final.odb write so a single-process flow +# (scripts/flow.tcl) can source the parts and perform the write itself +# at the same point. +source $::env(SCRIPTS_DIR)/final_connect.tcl orfs_write_db $::env(RESULTS_DIR)/6_final.odb - -# Delete routing obstructions for final DEF -source $::env(SCRIPTS_DIR)/deleteRoutingObstructions.tcl -deleteRoutingObstructions - -write_def $::env(RESULTS_DIR)/6_final.def -write_verilog $::env(RESULTS_DIR)/6_final.v \ - -remove_cells [find_physical_only_masters] - -# Run extraction and STA -if { - [env_var_exists_and_non_empty RCX_RULES] - && !$::env(SKIP_DETAILED_ROUTE) -} { - # RCX section - define_process_corner -ext_model_index 0 X - extract_parasitics -ext_model_file $::env(RCX_RULES) - - # Write Spef - write_spef $::env(RESULTS_DIR)/6_final.spef - file delete $::env(DESIGN_NAME).totCap - - # Read Spef for OpenSTA - read_spef $::env(RESULTS_DIR)/6_final.spef - - # Static IR drop analysis - if { [env_var_exists_and_non_empty PWR_NETS_VOLTAGES] } { - dict for {pwrNetName pwrNetVoltage} $::env(PWR_NETS_VOLTAGES) { - set_pdnsim_net_voltage -net ${pwrNetName} -voltage ${pwrNetVoltage} - analyze_power_grid -net ${pwrNetName} \ - -error_file $::env(REPORTS_DIR)/${pwrNetName}.rpt - } - } else { - puts "IR drop analysis for power nets is skipped because PWR_NETS_VOLTAGES is undefined" - } - if { [env_var_exists_and_non_empty GND_NETS_VOLTAGES] } { - dict for {gndNetName gndNetVoltage} $::env(GND_NETS_VOLTAGES) { - set_pdnsim_net_voltage -net ${gndNetName} -voltage ${gndNetVoltage} - analyze_power_grid -net ${gndNetName} \ - -error_file $::env(REPORTS_DIR)/${gndNetName}.rpt - } - } else { - puts "IR drop analysis for ground nets is skipped because GND_NETS_VOLTAGES is undefined" - } -} else { - puts "OpenRCX is not enabled for this platform." - puts "Falling back to global route-based estimates." - log_cmd estimate_parasitics -global_routing -} - -report_cell_usage - -report_metrics 6 "finish" - -source_step_tcl POST FINAL_REPORT - -# Save a final image if openroad is compiled with the gui -if { [ord::openroad_gui_compiled] } { - gui::show "source $::env(SCRIPTS_DIR)/save_images.tcl" false -} +source $::env(SCRIPTS_DIR)/final_outputs.tcl diff --git a/flow/scripts/flow.tcl b/flow/scripts/flow.tcl new file mode 100644 index 0000000000..c7eb1b56e2 --- /dev/null +++ b/flow/scripts/flow.tcl @@ -0,0 +1,100 @@ +# Runs all OpenROAD stages of the flow, from the yosys netlist through +# the final report, in a single OpenROAD process. +# +# With WRITE_ODB_AND_SDC_EACH_STAGE=0 the per-stage orfs_write_db / +# orfs_write_sdc / orfs_copy_db helpers are no-ops, so this top level is +# the sole writer of .odb/.sdc files. flow_write_db/flow_write_sdc +# produce the same file set as the stage-per-process make flow (the +# extra writes after a stage's own mirror the Makefile do-copy +# recipes), and flow_source enforces that stage scripts write no +# .odb/.sdc files themselves. + +# Enable keeping variables between stages +set ::env(KEEP_VARS) 1 +set ::env(WRITE_ODB_AND_SDC_EACH_STAGE) 0 + +set ::flow_expected [glob -nocomplain -directory $::env(RESULTS_DIR) *.odb *.sdc] + +proc flow_source { script } { + # Source in the global scope: stage scripts set top-level variables + # (e.g. util.tcl's global_route_congestion_report) that later stages + # read via $::, which a proc-scoped source would silently shadow. + uplevel #0 [list source $::env(SCRIPTS_DIR)/$script] + foreach f [glob -nocomplain -directory $::env(RESULTS_DIR) *.odb *.sdc] { + if { [lsearch -exact $::flow_expected $f] == -1 } { + error "$script wrote $f: with WRITE_ODB_AND_SDC_EACH_STAGE=0 stage\ + scripts must not write .odb/.sdc files" + } + } +} + +proc flow_write_db { name } { + set path [file join $::env(RESULTS_DIR) $name] + log_cmd write_db $path + lappend ::flow_expected $path +} + +proc flow_write_sdc { name } { + set path [file join $::env(RESULTS_DIR) $name] + log_cmd write_sdc -no_timestamp $path + lappend ::flow_expected $path +} + +# Synthesis odb (from the yosys netlist) +flow_source synth_odb.tcl +flow_write_db 1_synth.odb +flow_write_sdc 1_synth.sdc + +# Floorplan +flow_source floorplan.tcl +flow_write_db 2_1_floorplan.odb +flow_write_sdc 2_1_floorplan.sdc +flow_source macro_place.tcl +flow_write_db 2_2_floorplan_macro.odb +flow_source tapcell.tcl +flow_write_db 2_3_floorplan_tapcell.odb +flow_source pdn.tcl +flow_write_db 2_4_floorplan_pdn.odb +flow_write_db 2_floorplan.odb +flow_write_sdc 2_floorplan.sdc + +# Place +flow_source global_place_skip_io.tcl +flow_write_db 3_1_place_gp_skip_io.odb +flow_source io_placement.tcl +flow_write_db 3_2_place_iop.odb +flow_source global_place.tcl +flow_write_db 3_3_place_gp.odb +flow_source resize.tcl +flow_write_db 3_4_place_resized.odb +flow_source detail_place.tcl +flow_write_db 3_5_place_dp.odb +flow_write_db 3_place.odb +flow_write_sdc 3_place.sdc + +# CTS +flow_source cts.tcl +flow_write_db 4_1_cts.odb +flow_write_db 4_cts.odb +flow_write_sdc 4_cts.sdc + +# Route +flow_source global_route.tcl +flow_write_db 5_1_grt.odb +flow_write_sdc 5_1_grt.sdc +flow_source detail_route.tcl +flow_write_db 5_2_route.odb +flow_source fillcell.tcl +flow_write_db 5_3_fillcell.odb +flow_write_db 5_route.odb +flow_write_sdc 5_route.sdc + +# Finish. final_report.tcl is split around its 6_final.odb write so +# this top level can write at the same point. +flow_source density_fill.tcl +flow_write_db 6_1_fill.odb +flow_write_sdc 6_1_fill.sdc +flow_write_sdc 6_final.sdc +flow_source final_connect.tcl +flow_write_db 6_final.odb +flow_source final_outputs.tcl diff --git a/flow/scripts/io_placement.tcl b/flow/scripts/io_placement.tcl index 2241d997e1..5ad95bad51 100644 --- a/flow/scripts/io_placement.tcl +++ b/flow/scripts/io_placement.tcl @@ -17,7 +17,7 @@ if { orfs_write_db $::env(RESULTS_DIR)/3_2_place_iop.odb write_pin_placement $::env(RESULTS_DIR)/3_2_place_iop.tcl } else { - log_cmd exec cp $::env(RESULTS_DIR)/3_1_place_gp_skip_io.odb $::env(RESULTS_DIR)/3_2_place_iop.odb + orfs_copy_db $::env(RESULTS_DIR)/3_1_place_gp_skip_io.odb $::env(RESULTS_DIR)/3_2_place_iop.odb } source_step_tcl POST IO_PLACEMENT diff --git a/flow/scripts/synth.tcl b/flow/scripts/synth.tcl index fa79829904..8f22a9c99c 100644 --- a/flow/scripts/synth.tcl +++ b/flow/scripts/synth.tcl @@ -299,7 +299,3 @@ if { # Write synthesized design write_verilog -nohex -nodec $::env(RESULTS_DIR)/1_2_yosys.v -# One day a more sophisticated synthesis will write out a modified -# .sdc file after synthesis. For now, just copy the input .sdc file, -# making synthesis more consistent with other stages. -log_cmd exec cp $::env(SDC_FILE) $::env(RESULTS_DIR)/1_synth.sdc diff --git a/flow/scripts/synth_odb.tcl b/flow/scripts/synth_odb.tcl index 63bdc98cda..36280fa761 100644 --- a/flow/scripts/synth_odb.tcl +++ b/flow/scripts/synth_odb.tcl @@ -32,5 +32,6 @@ orfs_write_db $::env(RESULTS_DIR)/1_synth.odb # Canonicalize 1_synth.sdc. The original SDC_FILE provided by # the user could have dependencies, such as sourcing util.tcl, # which are read in here and a canonicalized version is written -# out by OpenSTA that has no dependencies. +# out by OpenSTA that has no dependencies. Sole writer of +# 1_synth.sdc. orfs_write_sdc $::env(RESULTS_DIR)/1_synth.sdc diff --git a/flow/scripts/synth_preamble.tcl b/flow/scripts/synth_preamble.tcl index af43512c0f..58c016a7a8 100644 --- a/flow/scripts/synth_preamble.tcl +++ b/flow/scripts/synth_preamble.tcl @@ -14,7 +14,6 @@ if { [env_var_exists_and_non_empty SYNTH_NETLIST_FILES] } { # keep things simple we just use the creation date log_cmd exec cat {*}$::env(SYNTH_NETLIST_FILES) > $::env(RESULTS_DIR)/1_2_yosys.v } - log_cmd exec cp -p $::env(SDC_FILE) $::env(RESULTS_DIR)/1_synth.sdc if { [env_var_exists_and_non_empty CACHED_REPORTS] } { log_cmd exec cp -p {*}$::env(CACHED_REPORTS) $::env(REPORTS_DIR)/. } diff --git a/flow/scripts/synth_syn.tcl b/flow/scripts/synth_syn.tcl index f0469dce0a..efe1f7779b 100644 --- a/flow/scripts/synth_syn.tcl +++ b/flow/scripts/synth_syn.tcl @@ -72,5 +72,6 @@ orfs_write_db $::env(RESULTS_DIR)/1_synth.odb # Canonicalize 1_synth.sdc. The original SDC_FILE provided by # the user could have dependencies, such as sourcing util.tcl, # which are read in here and a canonicalized version is written -# out by OpenSTA that has no dependencies. +# out by OpenSTA that has no dependencies. Sole writer of +# 1_synth.sdc. orfs_write_sdc $::env(RESULTS_DIR)/1_synth.sdc diff --git a/flow/scripts/util.tcl b/flow/scripts/util.tcl index e2ea5ae6ee..5b7d98c8d1 100644 --- a/flow/scripts/util.tcl +++ b/flow/scripts/util.tcl @@ -309,6 +309,22 @@ proc orfs_write_sdc { output_file } { log_cmd write_sdc -no_timestamp $output_file } +# For a stage that leaves the design unchanged, copying the input .odb +# forward is much faster than serializing the in-memory database again +# with write_db. Gated like orfs_write_db: in a single-process flow +# (WRITE_ODB_AND_SDC_EACH_STAGE=0) the input file may not exist and no +# stage file should be produced. +# +# exec cp rather than Tcl's file copy: file copy sets the destination +# mtime with second resolution, which breaks make's timestamp-based +# up-to-date checks for fast builds. +proc orfs_copy_db { input_file output_file } { + if { !$::env(WRITE_ODB_AND_SDC_EACH_STAGE) } { + return + } + log_cmd exec cp $input_file $output_file +} + proc source_step_tcl { hook_type step_name } { set env_var "${hook_type}_${step_name}_TCL" source_env_var_if_exists $env_var diff --git a/flow/scripts/write_ref_sdc.tcl b/flow/scripts/write_ref_sdc.tcl index 82565f375f..8dcec9be5c 100644 --- a/flow/scripts/write_ref_sdc.tcl +++ b/flow/scripts/write_ref_sdc.tcl @@ -25,7 +25,7 @@ if { [llength $clks] == 0 } { create_clock -name $clk_name -period $ref_period $sources # Undo the set_propagated_clock so SDC at beginning of flow uses ideal clocks. unset_propagated_clock [all_clocks] - write_sdc -no_timestamp [file join $::env(RESULTS_DIR) "updated_clks.sdc"] + orfs_write_sdc [file join $::env(RESULTS_DIR) "updated_clks.sdc"] # Reset create_clock -name $clk_name -period $period $sources set_propagated_clock [all_clocks] diff --git a/patches/bison/0001-gnulib-wrapper-headers-use-I-not-isystem.patch b/patches/bison/0001-gnulib-wrapper-headers-use-I-not-isystem.patch new file mode 100644 index 0000000000..feb3911152 --- /dev/null +++ b/patches/bison/0001-gnulib-wrapper-headers-use-I-not-isystem.patch @@ -0,0 +1,61 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Oyvind Harboe +Date: Mon, 6 Jul 2026 00:00:00 +0000 +Subject: [PATCH] BUILD: let gnulib wrapper headers shadow libc via -I + +Gnulib generates wrapper headers (lib/stdio.h, lib/fcntl.h, ...) that +must be found before the libc ones; they forward with #include_next and +add gnulib decorations (_GL_ATTRIBUTE_DEALLOC etc.) that lib/*-safer.h +and src/*.c rely on. The overlay's 'includes = ["lib"]' emits -isystem, +which loses to toolchains that inject libc headers as earlier -isystem +entries (e.g. the zero-sysroot hermetic-llvm BCR toolchain), and a dir +listed as both -I and -isystem is kept as -isystem, so the -I must +replace it, not merely accompany it. + +Plain -I outranks every -isystem, restoring autotools' AM_CPPFLAGS = +-I./lib ordering. $(BINDIR) covers the generated wrappers, the source +path the checked-in ones. + +Same class of fix as sed@4.9.bcr.5 (BCR #7915) and gawk@5.3.2.bcr.7 +(BCR #7989); to be proposed upstream as bison@3.8.2.bcr.8. +--- +diff --git a/BUILD.bazel b/BUILD.bazel +--- a/BUILD.bazel ++++ b/BUILD.bazel +@@ -711,7 +711,19 @@ + tags = ["manual"], + ) + +-BISON_COPTS = select({ ++# Gnulib's wrapper headers (generated lib/stdio.h, lib/fcntl.h, ... plus ++# the checked-in lib/*-safer.h helpers) must shadow the libc headers: they ++# forward via #include_next and add gnulib decorations ++# (_GL_ATTRIBUTE_DEALLOC etc.) that lib/ and src/ sources rely on. ++# `includes = ["lib"]` emits -isystem, which loses against toolchains that ++# inject libc headers as earlier -isystem entries (e.g. the zero-sysroot ++# hermetic-llvm BCR toolchain). Plain -I outranks every -isystem (and a dir ++# listed as both -I and -isystem stays -isystem, so the -isystem form must ++# go entirely). This restores autotools' AM_CPPFLAGS = -I./lib ordering. ++BISON_COPTS = [ ++ "-Iexternal/" + repo_name() + "/lib", ++ "-I$(BINDIR)/external/" + repo_name() + "/lib", ++] + select({ + "@rules_cc//cc/compiler:clang": [ + "-w", + "-std=c11", +@@ -813,7 +825,6 @@ + "src/scan-skel.c", + ":configmake_h_src", + ], +- includes = ["lib"], + textual_hdrs = [ + "lib/timevar.def", + "lib/printf-frexp.c", +@@ -997,6 +1008,7 @@ + "lib/main.c", + "lib/yyerror.c", + ], ++ copts = BISON_COPTS, + implementation_deps = [":headers"], + local_defines = LOCAL_DEFINES, + ) diff --git a/patches/bison/BUILD.bazel b/patches/bison/BUILD.bazel new file mode 100644 index 0000000000..9a8f39982d --- /dev/null +++ b/patches/bison/BUILD.bazel @@ -0,0 +1,7 @@ +"""bison fixes carried as single_version_override patches. + +Mirrored from tools/OpenROAD/bazel/bison-patches (overrides are +root-honored only). Drop when tools/OpenROAD drops its copy. +""" + +exports_files(glob(["*.patch"])) diff --git a/patches/tcl_lang/0001-tclZipfs-include-vendored-minizip-crypt.h-by-path.patch b/patches/tcl_lang/0001-tclZipfs-include-vendored-minizip-crypt.h-by-path.patch new file mode 100644 index 0000000000..11bf1388c8 --- /dev/null +++ b/patches/tcl_lang/0001-tclZipfs-include-vendored-minizip-crypt.h-by-path.patch @@ -0,0 +1,20 @@ +tclZipfs.c includes the vendored minizip crypt.h as #include "crypt.h", +relying on compat/zlib/contrib/minizip preceding any system directory +that also has a crypt.h. Hermetic toolchains that provide glibc headers +as explicit -isystem directories (e.g. the hermetic-llvm cc_toolchain) +search glibc's crypt.h (Unix password hashing) first, and the build +fails with implicit declarations of init_keys/decrypt_byte/zencode. +Include the intended header by its unambiguous path relative to the +compat/zlib include root instead. + +--- a/generic/tclZipfs.c ++++ b/generic/tclZipfs.c +@@ -75,7 +75,7 @@ + } while (0) + + #include "zlib.h" +-#include "crypt.h" ++#include "contrib/minizip/crypt.h" + #include "zutil.h" + #include "crc32.h" + diff --git a/patches/tcl_lang/BUILD.bazel b/patches/tcl_lang/BUILD.bazel new file mode 100644 index 0000000000..73513a9ed2 --- /dev/null +++ b/patches/tcl_lang/BUILD.bazel @@ -0,0 +1,7 @@ +"""tcl_lang fixes carried as single_version_override patches. + +Mirrored from tools/OpenROAD/bazel/tcl-patches (overrides are +root-honored only). Drop when tools/OpenROAD drops its copy. +""" + +exports_files(glob(["*.patch"]))