Goal
When you are changing OpenROAD source and need to see the effect through a bazel-orfs flow stage, run the flow against a binary you built directly from your OpenROAD checkout — bring your own OpenROAD — rather than routing the change through the module-graph patch mechanism.
The two mechanisms, and why BYO wins for a debug loop
There are two ways to get a local OpenROAD change into a flow run:
- Patch the archive_override — encode your
git diff(often base64) and splice it into the OpenROADarchive_override/git_overrideinMODULE.bazelso bazel applies it at fetch time withpatch -p1. - BYO — build openroad straight from the checkout and hand the flow that
binary via an
OPENROAD_EXE=override.
The patch mechanism is the right thing for the final, carried change (it is reproducible and hermetic). It is the wrong thing for an iteration loop:
patch -p1silently rejects hunks on context drift. The build keeps going, and you end up debugging a binary that does not contain your change — the single most expensive failure mode, because nothing tells you.- The regenerate-diff → re-encode → swap → re-fetch → rebuild cycle is applied on every fetch and taxes every iteration.
BYO removes both: the working tree is the source, so there is nothing to re-encode and nothing to silently reject.
Prove the swap before you trust it
BYO removes the silent-reject failure, it does not remove the silently wrong binary failure — a stale build, a wrong path, or a rebuild that did not happen all leave you measuring one binary twice. Before any long run, execute a cheap in-tree regression whose goldens your change moves, once per binary, and diff the two outputs:
cd src/<tool>/test
for arm in a b; do "$ARM_A_OR_B/openroad" -no_init -exit <test>.tcl > /tmp/$arm.log; done
diff /tmp/a.log /tmp/b.log # identical output means the swap did not take
Seconds of work, and it converts the most expensive failure mode into an immediate one.
A .odb freezes the cell masters — a library change cannot be a runtime override
load_design on a .odb calls read_db, not read_lef: the master set
was fixed when the design was floorplanned. So handing a later stage a different
library selection does not give the tools new cells to use.
The failure is completely silent. Setting ASAP7_USE_VT="RVT LVT SLVT" on a run
over a pre-built ODB reads all fifteen liberty files without complaint and adds
zero masters — measured as RVT 202 liberty cells / 212 db masters, LVT 202 /
0, SLVT 202 / 0. The resizer cannot instantiate a cell with no master,
so hasVtSwapCells() stays false, VtSwapMove never enters the sequence, and
the run finishes with bit-identical results and no diagnostic of any kind.
Count masters, not liberty cells, when checking whether a library actually arrived:
foreach lib [[ord::get_db] getLibs] { foreach m [$lib getMasters] { ... } }
A library variant is a whole flow from synthesis onward — in ORFS, a sibling design directory.
The BYO loop
# 1. Build openroad from your checkout (the working tree is the source).
cd /path/to/OpenROAD # your OpenROAD checkout / ORFS tools/OpenROAD
bazelisk build //:openroad # bazel only -- see below
# 2. Point the flow / extracted stage harness at it.
export OPENROAD_EXE="$(readlink -f bazel-bin/openroad)"
# ... then run the ORFS stage (make do-<stage>, or an extracted _deps run).
Building with CMake is VERBOTEN, and the guard blocks it. Not a style
preference: a cmake build uses whatever compiler, flags and system libraries
the machine happens to have, while the flow's binary comes from the pinned
hermetic bazel toolchain. Swap one for the other and the binary under test is
no longer the binary the flow builds, so every number measured with it is
measuring the toolchain as much as the change -- silently, because it still
runs and still produces plausible output. bazelisk build //:openroad in the
checkout, or bazelisk build @openroad//:openroad here.
A raw git clone of OpenROAD will NOT build: the GitHub tarball and a
blobless clone both arrive without src/sta and third-party/abc. bazel-orfs
vendors those in the @openroad archive_override's patch_cmds, which is
the other reason to build through this repository rather than beside it.
The ODB is compatible across the two binaries as long as your checkout and the flow's pinned OpenROAD share a base revision.
Three-tier test loop — climb only as far as you must
Pick the cheapest tier that can reproduce what you are chasing; escalate only when it cannot:
- Pure unit gtest (sub-second). A dependency-free kernel test on the algorithm/math you changed. No STA, no ODB, no relink of the tool library. This catches most logic errors in milliseconds.
- In-checkout regression test (seconds). OpenROAD ships
//src/<tool>/test:*targets — real STA on a tiny design. These are maintained and need noOPENROAD_EXEplumbing, so for anything they cover, prefer them over BYO. - Flow-level BYO run (minutes+). The full ORFS stage on a real design with your BYO binary — the final confirm. BYO earns its setup cost only here, for loops the wired tests in tiers 1–2 cannot reach (e.g. a specific placed ODB).
It is a judgement call, not a rule
Weigh BYO against just using the already-wired tests. Tiers 1–2 are maintained and plumbing-free; use them whenever they cover the change. BYO is for the long / flow-level loops they can't reach.
The real fix: make a rejected patch LOUD
The root hazard is that patch -p1 returns nonzero on a reject but the build
swallows it. Whatever your carry mechanism, make a mis-applied patch fail the
build hard rather than masquerade as a working binary:
- check the
patchexit status and abort the build on nonzero, or - add a post-patch sentinel: grep the patched file for a known unique line from your patch and fail if it is absent.
A loud patch failure turns the worst BYO-motivating failure mode (a silent no-op binary) into an immediate, obvious build error — at which point the patch path is safe for iteration too.