Repository navigation
Conversation
There was a problem hiding this comment.
Code Review
This pull request introduces a unified command argument editing mechanism for ORFS, replacing numerous deprecated individual variables with structured <COMMAND>_ARGS and PLATFORM_ARGS variables. This is supported by a new Python/Starlark parser (args_grammar.bzl) and runner script (args.py), along with updated documentation and flow scripts. The review identified two critical Starlark compatibility issues in args_grammar.bzl that will cause syntax and runtime errors when loaded by Bazel: the use of an anonymous lambda function and the list remove method. Both must be refactored to use Starlark-compliant alternatives.
A <COMMAND>_ARGS variable holds edits to the arguments ORFS passes to an OpenROAD command, written with the command's own options: -option value, -flag, ~-option to delete, on/off to run the command or not, each scoped to a stage (cts:) or one call (grt.wns:) when needed. PLATFORM_ARGS holds a platform's defaults, each edit naming its command. Layers merge lowest first: the flow's arguments, PLATFORM_ARGS, deprecated variables, <COMMAND>_ARGS. args_grammar.bzl is written in the subset of Python that is also Starlark, so a Bazel build can load() it to check the grammar at analysis and give each stage only its own edits. args.py runs it for the stage scripts and checks every variable before a stage starts. test_args.py tests both. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: Øyvind Harboe <oyvind.harboe@zylin.com>
Every command a stage script runs with arguments a user could change, 16 in all, takes them from command_args and command_runs in util.tcl, which call args.py with $PYTHON_EXE so the merge has unit tests. Each call has a name, stage.call, that an edit can be scoped to. Nothing changes for a design that sets no new variable: the flow's arguments, deprecated variables included, are the command line as before. erase_non_stage_variables checks every <COMMAND>_ARGS and PLATFORM_ARGS first, so a syntax error stops the stage before it runs. variables.yaml gains a <COMMAND>_ARGS for each command that had none, and PLATFORM_ARGS. The 63 variables that passed one option to one command, or switched one step on or off, are marked deprecated with the line that replaces them: they keep working, and a stage that reads one warns with that line. CTS_ARGS and RTLMP_ARGS still replace the flow's arguments, and so does DETAILED_ROUTE_ARGS while it holds a plain command line. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: Øyvind Harboe <oyvind.harboe@zylin.com>
How to change the arguments ORFS passes to an OpenROAD command: the grammar, the layers and how Make picks each one's value, use cases from a design, a platform, a flow variant, the command line and the shell, the name of every call, and the deprecated variables. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: Øyvind Harboe <oyvind.harboe@zylin.com>
|
@luarss FYI |
🔍 QoR checkMetrics reflect the PR merge build — i.e. what will land on the target branch. Commit 62 design(s) checked — 0 with regression(s), 0 without a comparable baseline. |
Signed-off-by: Øyvind Harboe <oyvind.harboe@zylin.com>
This is a problem well worth solving once and for all. Of course I had AI help me here, but I've read all of the below, I couldn't have written it better or shorter, and I stand by it.
Backwards compatible, minimum congitive load and introduce necessary complexity slowly to the user, POLA and with a deprecation strategy
If you use ORFS variables and find it hard to remember which option has which name, and whether there's any system to them, this is for you. Nobody did anything wrong: adding a variable when one was needed, and putting off a system until a clear pattern and a real problem showed up, has worked. Both are here now. There are 253 variables, added one at a time, and no rules a user can learn once.
This pull request offers a single system. To change what ORFS passes to an OpenROAD command, you set the variable named after the command and write that command's own options. It's the same for every command, with nothing to look up, and every existing configuration keeps working.
It is a complicated subject, which is why the description is long. Please don't skip it for being long or AI-written; the details are where the problem is.
It started one morning when I saw the much awaited
clock_tree_synthesis -useful_skewand went to try it in ORFS. There was no variable for it, and the only way in wasCTS_ARGS. Let's go down the rabbit hole of fixing this once and for all.CTS_ARGSreplaces the flow's arguments: it discardsCTS_CLUSTER_SIZE, the four otherCTS_*variables and the flow's own-sink_clustering_enable -repair_clock_nets, so a user first runs the flow to learn what to put in it.ORFS has 253 variables. "Off" is spelled
SKIP_,DISABLE_,DONT_andENABLE_DPO=0. Giving each option of the 11 OpenROAD commands ORFS already wraps its own variable in that style would take at least 9,856 names; that number grows by about 1,250 a year, and ORFS adds about 66 variables a year.Counted each October on the commit bazel-orfs pins; the table is under "Names over time".
The current rules were never written down. They grew one variable at a time. To configure a command today, a user has to know:
-sink_clustering_sizeisCTS_CLUSTER_SIZE,-notch_weightisRTLMP_NOTCH_WT, and-useful_skewhas no name at all.*_ARGSreplaces or adds. Three replace what the flow passes and ten add to it. SettingRTLMP_ARGSsilently discards all 21RTLMP_*variables, and-target_utiltoo, which comes fromPLACE_DENSITY.repair_timing, is spelled three ways depending on the stage:SKIP_FLOORPLAN_REPAIR_TIMING,ENABLE_PLACE_REPAIR_TIMINGandSKIP_CTS_REPAIR_TIMING. Two of them have one polarity and the third has the other.RTLMP_MIN_CHANNEL_SIZEunset turnsRTLMP_PIN_AWARE_CHANNELSoff.Some options have two names:
GPL_TIMING_DRIVENand-timing_driveninGLOBAL_PLACEMENT_ARGS,GLOBAL_ROUTE_USE_CUGRand-use_cugr,CTS_CLUSTER_SIZEand-sink_clustering_sizeinCTS_ARGS. Each pair is a question about which one wins. Twelve of the 21RTLMP_*variables are set by no design or platform in ORFS, and two more are set by the flow and never read.Here is how one command,
clock_tree_synthesis, gets its arguments today:flowchart LR A[CTS_BUF_DISTANCE] --> L[cts_args] B[CTS_CLUSTER_SIZE] --> L C[CTS_CLUSTER_DIAMETER] --> L D[CTS_BUF_LIST] --> L E[CTS_LIB_NAME] --> L F["-sink_clustering_enable<br/>-repair_clock_nets<br/>(hard-coded)"] --> L L --> X{CTS_ARGS set?} G[CTS_ARGS] --> X X -- no --> CMD[clock_tree_synthesis] X -- "yes: everything above is discarded" --> CMDResearch on naming:
SKIP_CTS_REPAIR_TIMING=0is a small puzzle, solved again every time it's read.-sink_clustering_size 30, the config saysCTS_CLUSTER_SIZE=30, and the user translates between them from memory, in both directions, every time.CTS_CLUSTER_SIZEmeans nothing in OpenROAD's documentation, its tests, its Tcl prompt or its issues.Research on making names:
_WTagainst_WEIGHT. Every new variable invites that argument.This pull request names a variable after the command it's for, in upper case. It holds that command's own options, spelled as OpenROAD spells them, and only the ones that differ, in one grammar written down in one place.
Background: stages
ORFS runs a design through seven stages. Each stage is a separate tool run: it reads the previous stage's database (an
.odbfile) and writes its own.Several commands run in more than one stage:
repair_timinginfloorplan,place(whenENABLE_PLACE_REPAIR_TIMINGis set),ctsandgrt,detailed_placementinplace,ctsandgrt, andreport_metricsat the end of nearly every stage. The same command sees a different design each time, because what the tool knows about wires gets better stage by stage. Atfloorplannothing is placed, atctswire lengths are estimated from placement, and atgrtthey come from planned routes. So a user has good reasons to set the same command differently per stage:repair_timingatfloorplancan take long and decide little, so in early exploration it's turned off there and kept atctsandgrt;Today each such choice needs its own variable per stage:
SKIP_FLOORPLAN_REPAIR_TIMING,ENABLE_PLACE_REPAIR_TIMING,SKIP_CTS_REPAIR_TIMING,DETAILED_METRICS,SKIP_ANTENNA_REPAIRandSKIP_ANTENNA_REPAIR_POST_DRT. The grammar says it with a scope:REPAIR_TIMING_ARGS = floorplan:off.Background:
variables.yamlEvery ORFS variable has an entry in
flow/scripts/variables.yaml:All 249 entries have a
description, which generates the documentation. 210 list thestagesthat read them; before a stage starts, ORFS erases every variable the stage doesn't read (erase_non_stage_variables), so a stage's result depends only on its own variables. 85 have adefault, which is fed back into Make as?=. A few also have atype,tunable(for automatic parameter search) orrequired.The
stageslist is why a stage scope inside a value matters: a variable read by several stages, such asREPAIR_TIMING_ARGS, reaches each of them, and each stage uses only the edits for it.Background: how ORFS picks a variable's value today
ORFS is a Makefile, and a variable's value is whatever Make's rules leave standing. Make reads the files in this order:
VAR = valuesets a value;VAR ?= valuesets it only if nothing has yet. On top of the files, Make has two more sources: the command line, which beats every assignment in every file, and the shell environment, which beats?=but loses to=. For one variable, the winner is the first of these that applies:makecommand linemake DESIGN_CONFIG=... CORE_UTILIZATION=40config.mk, with=export CORE_UTILIZATION = 30export CORE_UTILIZATION=40; make ...config.mk, with?=export CORE_UTILIZATION ?= 30config.mk, with?=export RTLMP_MIN_CHANNEL_SIZE ?= 20 20variables.yamldefaultCORE_UTILIZATION: default: ...This works well for a variable with one value. It doesn't work for a list of options. Whoever wins takes the whole value, and every layer below is lost. That's why
CTS_ARGSdiscards the fiveCTS_*variables and the flow's own defaults: Make has no way to say "these defaults, except this one option", so ORFS grew a variable per option to get round it.Before and now
Before: to change one argument, a user runs the flow to see what it passes, copies all of it, and edits the copy, or looks for the ORFS name of the option, if there is one.
Now: one variable per command, holding only what is different:
Reading a variable takes no ORFS documentation. The name says which command it's for, and the contents are that command's options as its help prints them.
Complexity only when it's needed
The simplest use is a command line:
CLOCK_TREE_SYNTHESIS_ARGS = -sink_clustering_size 30is what you would type at OpenROAD's prompt. Deleting, stages, calls and platform defaults are there when a user needs them, and invisible when not. Every existing config keeps working unchanged until the deprecation date, and the warnings say what to write instead.The grammar
-option value-flag~-optionon/offstage:stage.call:{a b}or"a b"Familiar ideas
We're taking familiar ideas rather than inventing new ones. The edits are written in OpenROAD's own Tcl option syntax (
-key value,-flag), so a user writes what the command's help prints, with Tcl's braces or quotes for a value with spaces. Deleting an option borrows Hydra's~key, and scoping an edit to a stage borrows thebuild:cts --flagqualifier from Bazel's.bazelrc. Edits combine the way a JSON Merge Patch or a Helm values file does: each layer holds only what differs from the one below. Layers are applied in a fixed order, as with git config levels or Nix module priorities. Within a layer, the more specific scope wins and then the later edit, as in the CSS cascade. Make still chooses each variable's value exactly as before, and the grammar only merges the layers afterwards.Defaults are layers, written in the same grammar:
For each option, the highest layer that mentions it decides. Within a layer, a call edit beats a stage edit, a stage edit beats an unscoped one, and later beats earlier. In
PLATFORM_ARGSeach edit starts with its command:rtl_macro_placer:-min_channel_size {20 20}.Make still decides each variable's value, exactly as above. What changes is that the layers are separate variables, so no layer's value can wipe out another's: the grammar merges them after Make is done.
For one option of one command, here is the full order, highest first, using
-sink_clustering_sizeas the example:makecommand linemake DESIGN_CONFIG=... CLOCK_TREE_SYNTHESIS_ARGS='-sink_clustering_size 40'config.mk, with=, also inside anifeq FLOW_VARIANTblockexport CLOCK_TREE_SYNTHESIS_ARGS = -sink_clustering_size 30export CLOCK_TREE_SYNTHESIS_ARGS='-sink_clustering_size 40'; make ...config.mk, with?=export CLOCK_TREE_SYNTHESIS_ARGS ?= -sink_clustering_size 30make DESIGN_CONFIG=... CTS_CLUSTER_SIZE=40PLATFORM_ARGS, platformconfig.mkexport PLATFORM_ARGS = clock_tree_synthesis:-sink_clustering_size 20cts.tcl-sink_clustering_enable -repair_clock_netsRows 1 to 4 are Make choosing the one value of
CLOCK_TREE_SYNTHESIS_ARGS, as it does for every variable, somake CLOCK_TREE_SYNTHESIS_ARGS=...replaces the design's whole value, not only the options it names. Rows 5 to 8 are separate variables, merged option by option. One consequence: a deprecated variable on the command line,make CTS_CLUSTER_SIZE=40, loses to a design that sets-sink_clustering_sizeinCLOCK_TREE_SYNTHESIS_ARGS.Commands that run more than once
Some commands run more than once in a stage:
grtcallsrepair_timingtwice, the second time either with a sequence the flow hard-codes (grt.wns) or to recover power (grt.recover_power), anddetailed_placementtwice. An edit scopedgrt:applies to every call of its command in that stage.To reach one call, each call has a name, and the log prints it in front of the command, with each edit that changed it and the variable the edit came from:
The scope is copied from the log:
REPAIR_TIMING_ARGS = grt.wns:-setup_margin 0.1changes only the second. A name says what a call is for, not where it is in the script, so adding a call doesn't move the others.docs/user/CommandArguments.mdlists every call.Why
~, and not-no_option~deletes. It doesn't negate:~-setup_marginmeans "don't pass-setup_margin", so OpenROAD uses its own default.-no_setup_marginwould read as "no margin", a different and wrong meaning. It would also be a name OpenROAD doesn't have, and inventing names is the habit this pull request ends. A prefix that can never start a real option can't collide with one.The prefix also has to survive every language a value passes through before OpenROAD sees it: Make, the shell, Tcl, Starlark and YAML. Of the prefixes that could work,
~is the only one none of them needs escaped:!-option#-option$-option$-expands to the shell's flags)@-option~-optionThe shell expands
~only at the start of a word, and only when what follows is empty, a user name,+,-or a number;-optionis none of these, and a value with spaces is quoted anyway. Tcl 8 expanded~only in file names, and Tcl 9 doesn't at all. YAML reads~as null only when it stands alone.Why a variable per command, and not one
ARGSPLATFORM_ARGSshows that one variable withcommand:prefixes would work. Designs get a variable per command anyway. The first reason is least surprise: a user who seesCLOCK_TREE_SYNTHESIS_ARGSexpects it to holdclock_tree_synthesisoptions and nothing else, and it does. The second is that one variable would lose three things Make and ORFS already give every variable:make REPAIR_TIMING_ARGS='grt:-setup_margin 0.1'replaces the design'srepair_timingedits and nothing else. Make replaces a variable's whole value, so with oneARGSthe same line would drop the design's edits to every command.*_ARGSentry invariables.yamllists the stages that run its command, soerase_non_stage_variablesand bazel-orfs keepCLOCK_TREE_SYNTHESIS_ARGSout ofplacewith no new code. One variable would be read by every stage.grep -r CLOCK_TREE_SYNTHESIS_ARGS flow/designsfinds every design that changes clock tree synthesis.PLATFORM_ARGSis one variable because a platform sets a few defaults once, and the layers above override them option by option.Where edits come from
A design writes the differences in its
config.mk:A platform sets defaults for every design on it, each edit starting with its command:
A flow variant of a design is an ordinary
ifeqblock, as today:The command line, for a one-off experiment, replaces the design's value of that variable for this run, as Make does for every variable. The platform's and ORFS's defaults stay. Quote the value for the shell:
make DESIGN_CONFIG=designs/asap7/gcd/config.mk REPAIR_TIMING_ARGS='grt:-setup_margin 0.1'The shell environment works as it does for every ORFS variable: it is used when the design doesn't set the variable with
=.A deprecated variable, wherever it's set, still works. It's translated into its edit and lands in the layer between the platform and the design:
Errors stop the flow early:
erase_non_stage_variables: every<COMMAND>_ARGSandPLATFORM_ARGSis checked, not only the ones the stage reads.~-optiondeletes an option where it is passed, and does nothing at a call that doesn't pass it, so one unscoped deletion covers every call.One implementation
flow/scripts/args_grammar.bzlis written in the subset of Python that is also Starlark. ORFS runs it as Python: the stage scripts callflow/scripts/args.pywith$PYTHON_EXE, throughcommand_argsandcommand_runsinutil.tcl, so all of the argument handling is in Python with unit tests, which Tcl doesn't offer. Bazelload()s it, so a Bazel build can stop at analysis on a syntax error and key each stage's action only on the edits that apply to that stage.flow/test/test_args.pytests both, and checks thatargs_grammar.bzlstays in the common subset: ORFS has no Bazel, so a test walks the file's syntax tree and fails on what Starlark lacks (while, exceptions, classes, f-strings, sets, generators,import,is, recursion, iterating a string, anything at the top level but definitions and assignments). If the grammar ever needs Python that Starlark can't run, the Python here stays the ground truth and bazel-orfs carries its own Starlark copy.Hooks and other files stay variables
ORFS has 32 hook variables: a
PRE_<STEP>_TCLand aPOST_<STEP>_TCLfor each of 16 steps, sourced at the top and bottom of the step's script. A stage runs one or more steps, and each step runs once, so each hook runs once. The hooks stay as they are, for two reasons.PRE_orPOST_, then the step, then_TCL. A user who learns it once can name all 32, so none of them is an invented name.sources = {"PRE_GLOBAL_PLACE_TCL": [":a.tcl"]}: a variable name mapped to a Bazel label, which Bazel turns into a path and declares as an input of the stage that reads it. A scoped value such asplace.global_place:a.tclwould mix a scope into the file name. Bazel labels use:themselves (//flow/designs:a.tcl), so the two collide, and bazel-orfs would have to build the string before it could pass the file.The same rule holds for every variable that names a file (
PDN_TCL,MACRO_PLACEMENT_TCL,FASTROUTE_TCL, ...): one variable, one file, no grammar. For the same reason, an*_ARGSvalue doesn't name input files. An option that reads a file gets its own variable, so bazel-orfs can declare the file as an input.synthsynthPRE_SYNTH_TCL,POST_SYNTH_TCLfloorplanfloorplanPRE_FLOORPLAN_TCL,POST_FLOORPLAN_TCLfloorplanmacro_placePRE_MACRO_PLACE_TCL,POST_MACRO_PLACE_TCLfloorplantapcellPRE_TAPCELL_TCL,POST_TAPCELL_TCLfloorplanpdnPRE_PDN_TCL,POST_PDN_TCLplaceglobal_place_skip_ioPRE_GLOBAL_PLACE_SKIP_IO_TCL,POST_GLOBAL_PLACE_SKIP_IO_TCLplaceio_placementPRE_IO_PLACEMENT_TCL,POST_IO_PLACEMENT_TCLplaceglobal_placePRE_GLOBAL_PLACE_TCL,POST_GLOBAL_PLACE_TCLplaceresizePRE_RESIZE_TCL,POST_RESIZE_TCLplacedetail_placePRE_DETAIL_PLACE_TCL,POST_DETAIL_PLACE_TCLctsctsPRE_CTS_TCL,POST_CTS_TCLgrtglobal_routePRE_GLOBAL_ROUTE_TCL,POST_GLOBAL_ROUTE_TCLroutedetail_routePRE_DETAIL_ROUTE_TCL,POST_DETAIL_ROUTE_TCLroutefillcellPRE_FILLCELL_TCL,POST_FILLCELL_TCLfinaldensity_fillPRE_DENSITY_FILL_TCL,POST_DENSITY_FILL_TCLfinalfinal_reportPRE_FINAL_REPORT_TCL,POST_FINAL_REPORT_TCLDeprecation
Every deprecated variable keeps working. It counts as an edit in the layer between the platform's and the design's. A stage that reads one set to anything but its default warns, with the line that replaces it:
variables.yamlholds the replacement next to each one (deprecated: 1,replacement:), and the generated variable documentation shows it. A removal date is for review to set.CTS_ARGSandRTLMP_ARGSreplace what the flow passes, as before.DETAILED_ROUTE_ARGSkeeps the same name for the new meaning, so while it holds a plain command line, with no~,on,offor scope, it still replaces the flow's arguments; where that differs from reading the same text as edits, the log names the~-optiondeletions that make the two agree.Numbers (ORFS
75cd896b,masterwhen this was written)variables.yamltoday*_ARGSrenamed to their command's namePLATFORM_ARGS16 commands take a
<COMMAND>_ARGS:balance_row_usage,buffer_ports,clock_tree_synthesis,cluster_flops,detailed_placement,detailed_route,global_placement,global_route,improve_placement,place_pins,repair_antennas,repair_design,repair_tie_fanout,repair_timing,report_metricsandrtl_macro_placer. Six of them had one already, under that name or another.Kept: design inputs, platform facts, choices that span commands (
CELL_PAD_IN_SITES_*,GPL_PLACE_IOS,MATCH_CELL_FOOTPRINT), hooks and other variables that name files, and synthesis. The next section lists what is left to make the rest consistent.Name conflicts, and what is left to be fully consistent
This pull request changes nothing for an existing configuration. Where an old name and the new rule collide, the old meaning wins for now. Being fully consistent takes these follow-ups, each one a behaviour change of its own:
DETAILED_ROUTE_ARGS~-optiondeletions a design needsGLOBAL_ROUTE_ARGS-congestion_report_iter_step 5 -verbose, lives invariables.yaml, not the flow; it edits only the first of up to sevenglobal_routecalls, and the incremental reroutes take only-allow_congestionfrom itglobal_route.tcl, name every call, and let an unscoped edit reach them allCTS_ARGS,RTLMP_ARGS,DETAIL_PLACEMENT_ARGSGLOBAL_PLACEMENT_ARGS,PLACE_PINS_ARGS,BUFFER_PORTS_ARGS,CLUSTER_FLOPS_ARGSTNS_END_PERCENTandSETUP_REPAIR_PHASESthe most)<COMMAND>_ARGSand each platform toPLATFORM_ARGS, then remove the variablesSKIP_INCREMENTAL_REPAIR,OPT_POST_GRT_WNS,SKIP_DETAILED_ROUTE,CTS_SNAPSHOTS,REPORT_CLOCK_SKEW,REPORT_ENDPOINT_SLACKon/offGPL_TIMING_DRIVEN,GLOBAL_ROUTE_USE_CUGR,GPL_KEEP_OVERFLOW,MIN_PLACE_STEP_COEF,MAX_PLACE_STEP_COEF,RECOVER_POWER,EARLY_SIZING_CAP_RATIO,GRT_SEED,MAX_REPAIR_ANTENNAS_ITER_DRT-allow_congestionSYNTH_*andABC_*switchesNames over time
variables.yamlMeasured on the commit bazel-orfs pins (ORFS
0fcc4186, OpenROAD9ddb60d9); the chart at the top plots the options columns. Since 2020 ORFS has added 148 names its scripts read, and the options without one still went from 48 to 151. Two commands have no*_ARGSat all, so 21 options ofrepair_timing(-effort,-max_passes,-policy, ...) and 5 ofrepair_design(-max_wire_length,-buffer_gain, ...) can't be set from a config today, under any name.-useful_skewis one of them. With this change it'sCLOCK_TREE_SYNTHESIS_ARGS = -useful_skew, and the same goes for every option OpenROAD adds next. A new option works in ORFS as soon as OpenROAD has it: novariables.yamlentry, no Tcl to pass it on, no name to agree on in review. ORFS changes only when OpenROAD adds a command, or an option that reads a file.AutoTuner
Today. All 20
autotuner.jsonfiles underflow/designstuneCTS_CLUSTER_SIZEandCTS_CLUSTER_DIAMETER, and both are deprecated here. AutoTuner passes each tuned value on themakecommand line (parse_config()intools/AutoTuner/src/autotuner/utils.py).This pull request breaks nothing. A deprecated variable still works from the command line, ranked above
PLATFORM_ARGSand below a design'sCLOCK_TREE_SYNTHESIS_ARGS. AutoTuner keeps tuningCTS_CLUSTER_SIZEas before. The trials print a deprecation warning, and no design in ORFS sets-sink_clustering_sizeinCLOCK_TREE_SYNTHESIS_ARGSthat would override the tuned value.Proposal: tune an option, not a variable.
autotuner.jsonkey can name an option the way the grammar writes it:replacement:invariables.yamlwithout the value.parse_config()collects the keys for one variable into one value and appends it to the design's own, because a value on the command line replaces the design's whole value, and the later edit wins. The design's value comes frommake print-CLOCK_TREE_SYNTHESIS_ARGS(flow/scripts/variables.mk), with theDESIGN_CONFIG,PLATFORMandFLOW_VARIANTAutoTuner already passes. It passesCLOCK_TREE_SYNTHESIS_ARGS="<design's edits> -sink_clustering_size 23". A value with spaces is quoted withquote_value()fromargs_grammar.bzl.replacement:field the warnings use, so the 20autotuner.jsonfiles can move when convenient, or never.tunable:entry. OpenROAD rejects an option it doesn't know on the first trial. Scopes give AutoTuner knobs it can't reach today: a margin in one stage, or one call._PINS_DISTANCE, which AutoTuner turns intoPLACE_PINS_ARGS="-min_distance <value>", already tunes one option through a*_ARGSvariable. It becomes"PLACE_PINS_ARGS = -min_distance".This is an AutoTuner change and isn't part of this pull request.
Tests
flow/test/test_args.py: parsing, every syntax error and its message, per-stage selection, layer merging and its order, scopes, on/off, deletion, flag/value conflicts, the round trip offormat_args(parse_args(x)), the old meaning ofDETAILED_ROUTE_ARGS, the deprecation warnings, and thatvariables.yamlagrees with the grammar's table of commands. The same module also loads in Bazel; the subset test fails on an f-string, awhileloop and a recursive function added to it.route, onmasterand on this branch: every stage's.odbis bit-identical (18 of 18 hashes on each platform). Two command lines read differently with the same meaning: asap7's floorplanrepair_timingpassed-skip_last_gasptwice and now passes it once, and the firstglobal_routelists-resistance_awarebeforeGLOBAL_ROUTE_ARGS's default instead of after it. The only other difference in the logs is the new name in front of each call, and the deprecation warnings for the variables the platform and design set:RTLMP_MIN_CHANNEL_SIZEon both, andSETUP_REPAIR_PHASES,SKIP_LAST_GASPandENABLE_RESISTANCE_AWAREon asap7, once per step that reads them.This works for bazel-orfs too
bazel-orfs hands each stage only the variables that stage reads, so an edit for one stage doesn't rebuild the others. A
stage:scope inside a variable would defeat that if bazel-orfs couldn't read it. Because the grammar is Starlark, bazel-orfsload()s it from ORFS, with no second implementation:BUILDfile's arguments stopsbazelisk build --nobuildat analysis, before any action is scheduled;grt:-setup_margin 0.1doesn't rebuildcts.🤖 Generated with Claude Code