Bazel rules for running
OpenROAD-flow-scripts
(ORFS): RTL to GDS with OpenROAD, Yosys and an open PDK, driven by
bazelisk build.
Nothing to install. Bazel downloads, builds and caches every
dependency: OpenROAD, OpenSTA, Yosys, ABC, GNU Make, Qt, Python, the C++
toolchain and the PDKs. A developer machine or a CI runner needs Bazelisk
and a standard Linux install, and gets a bit-for-bit reproducible flow. No
Docker, no apt install, no "works on my machine". The RHEL 8 story in
bazel-orfs #869
is what this buys you: the two things that broke were a host curl and a
host Python that had leaked into the build, and both were fixed by making
them hermetic.
"A standard Linux install" is meant literally, and bazelisk run //:host_tools holds the line. What is used from the host, and nowhere
else, is:
- Base tools —
curl,sha256sum,tar,sed,find,nprocand the rest of coreutils/findutils/gawk. OpenROAD'sarchive_overridevendors three git submodules throughcurl+sha256sum -c, because a GitHub/archive/<sha>.tar.gzdoes not carry submodules andarchive_override'sintegritydoes not cover bytes fetched bypatch_cmds. The curl flags stay within reach of curl 7.52 (2016) on purpose; RHEL 8 ships 7.61. python3— for a fetch-phase repository rule (private/designs.bzlparses every ORFSconfig.mk), the mock tools, and//tools/pin. Every such file stays parseable by Python 3.6, the oldestpython3on a distro still receiving updates.
Not needed, and checked: jq, yq, perl, bc, docker, cmake,
wget, rsync, unzip, xz. yq is fetched by Bazel with a pinned
SHA256 rather than expected on the host; GNU Make is built from source.
klayout is the one opt-in exception — the default is a mock, see
docs/klayout.md.
Plumbing an AI understands. bazel-orfs has turned out to be the
right shape for AI-assisted work. Every input is a label, every stage is a
target, every variable is checked against ORFS's own variables.yaml, and
a stage that fails can be re-run outside the sandbox with one command. An
AI assistant wires up a new design, a new PDK or a patched tool without
being told how, because there is exactly one way to do each of those
things and the build tells you when you got it wrong.
All of Bazel, all of ORFS. Artifacts, parallel builds, remote caching,
remote execution, and a flow that only rebuilds the stages a change
touches. ORFS is used unmodified, so you get the latest OpenROAD and ORFS
features, the ORFS debugging tools such as make issue and
deltaDebug.py, and GitHub issues you file are immediately actionable for
the OpenROAD and ORFS maintainers.
ORFS and OpenROAD are work in progress. For large designs expect to get involved with the community, or to need a support contract with Precision Innovations.
Bazelisk. That is all.
Bazelisk reads .bazelversion and fetches the right
Bazel. That version is also a hard minimum: MODULE.bazel declares
bazel_compatibility = [">=9.1.1"], so an older Bazel stops during module
resolution with an explicit version error rather than failing later in a
way that looks like a build bug. The floor applies to projects that depend
on bazel-orfs too, whatever their own .bazelversion says.
Run these from a clone of this repository:
git clone https://github.com/The-OpenROAD-Project/bazel-orfs.git
cd bazel-orfsbazelisk run @orfs//flow/designs/asap7/gcd:gcd_final gui_finalThis builds gcd, the smallest design shipped with ORFS, from RTL through
synthesis, floorplan, placement, clock tree synthesis, routing and final
reporting on the asap7 PDK, then opens the result in the OpenROAD GUI.
The first run also builds OpenROAD, Yosys and the Qt GUI from source,
which takes 30 to 60 minutes. Every later run is incremental and cached.
What you are looking at is ORFS's own flow/designs/asap7/gcd/config.mk,
read by bazel-orfs and turned into Bazel targets. You can try any other
design under @orfs//flow/designs/<pdk>/<design>. The target prefix is
the design's DESIGN_NAME from its config.mk, which is not always the
directory name, so list before guessing:
bazelisk run @orfs//flow/designs/asap7/uart:uart_final gui_final
bazelisk query '@orfs//flow/designs/nangate45/aes:*' | grep '_final$'
bazelisk run @orfs//flow/designs/nangate45/aes:aes_cipher_top_grt gui_grtThis is not how you describe your own project. The expectation is
idiomatic Bazel: an orfs_flow() in your own BUILD file, as in the next
section and in examples/. Consuming config.mk as a
domain-specific language is a facility for experiments inside bazel-orfs:
try a theory across many real designs, carrying the OpenROAD, ORFS and
bazel-orfs patches it needs in a single pull request, before anything is
upstreamed. The canonical experiment is parameter tuning, such as racing
and pinning floorplan parameters across every ORFS design with the
auto-floorplan targets (docs/auto_floorplan.md).
The ORFS designs are the right test bed for that because the OpenROAD
maintainers know every pebble on that beach: a shift in any of them is
recognised for what it is.
Mileage varies. It is a testing facility, not a supported way to build
every ORFS design: not all designs are hooked up, and an ORFS bump that
upsets one is a fix here, not an incident. See
docs/orfs-design-builds.md for what the
facility is for and how the config.mk files are consumed.
bazelisk run //examples:mac_final gui_finalexamples/ is the clean, fast, idiomatic version: a 20-line
multiply-accumulate in Verilog, a two-constraint SDC, and a BUILD file with one
orfs_flow(). It runs the full flow on asap7 in a couple of minutes and is
the template to copy for your own project.
Stage results land under bazel-bin/<package>/results/<pdk>/<design>/base/
and reports under bazel-bin/<package>/reports/.... Every stage target is
also a bazelisk run entry point:
bazelisk run <target>_<stage> gui_<stage> # OpenROAD GUI
bazelisk run <target>_<stage> open_<stage> # OpenROAD Tcl shellGUI and shell are available for floorplan, place, cts, grt,
route and final.
Not every flow in this repository runs to final. The regression flows
under //test stop as early as they can to keep CI fast, so a target such
as //test:L1MetadataArray_route may not exist. List what a design
provides before guessing:
bazelisk query 'filter("^//test:L1MetadataArray_(synth|floorplan|place|cts|grt|route|final)$", //test:*)'This is examples/BUILD, minus comments:
load("//:openroad.bzl", "orfs_flow")
orfs_flow(
name = "mac",
arguments = FAST_SETTINGS | {
"CORE_UTILIZATION": "40",
"PLACE_DENSITY": "0.65",
},
sources = {
"SDC_FILE": [":constraints.sdc"],
},
verilog_files = ["mac.v"],
)arguments are ORFS variables, validated at build time against ORFS's
variables.yaml, so a typo fails the build instead of silently changing
nothing. sources are ORFS variables whose values are files. FAST_SETTINGS
is a dict of variables that turn off the slow, sign-off-oriented parts of
the flow; the example BUILD spells it out and
docs/performance.md explains each entry.
One orfs_flow() creates one target per stage, plus an abstract target
that emits the LEF and LIB for use as a macro in a parent design:
//examples:mac_synth
//examples:mac_floorplan
//examples:mac_place
//examples:mac_cts
//examples:mac_grt
//examples:mac_route
//examples:mac_final
//examples:mac_generate_abstract
Bazel knows which stage each variable belongs to. Change PLACE_DENSITY
and only placement and later stages rebuild; synthesis and floorplan come
from cache.
Where to go next:
- docs/customize.md: variables, constraints, macros and abstracts, mock areas, iterating on floorplan settings, design space exploration.
- docs/local-flow.md: deploy a stage to a directory
under
tmp/and re-run single substeps outside Bazel, with a locally built ORFS if you like. - docs/performance.md: speeding up builds, monitoring long stages, where CI time goes.
- docs/reference.md: target naming, dependency deployment, how Bazel takes over from the ORFS Makefile.
examples/ and test/ are different things, and the difference is kept
on purpose:
examples/is written to be read. One design, its files next to it, nothing shared with other packages, no variant tricks. CI builds it so it cannot rot, but when clarity and coverage pull in different directions, clarity wins here.test/is written to break when a rule changes. Thousands of lines of fixtures, variants, mocked tools, sweeps and standalone stages, most of it exercising one rule attribute each. It is where coverage goes, and it is not a place to learn from.
Examples may double as tests. Tests are not examples.
Add bazel-orfs, OpenROAD, ORFS and the slang frontend to your
MODULE.bazel. Bazel honours overrides only from the root module, so your
root repeats the pins and overrides bazel-orfs's own MODULE.bazel makes,
starting from the commits bazel-orfs pins; bazelisk run @bazel-orfs//:bump
keeps them current after that. The complete set, with the toolchain,
version overrides, patches and .bazelrc lines a from-source OpenROAD build
needs, is in
docs/openroad.md. The
core of it:
bazel_dep(name = "bazel-orfs")
git_override(
module_name = "bazel-orfs",
remote = "https://github.com/The-OpenROAD-Project/bazel-orfs.git",
commit = "<bazel-orfs commit>",
)
bazel_dep(name = "openroad")
archive_override(
module_name = "openroad",
# Copy this block verbatim from bazel-orfs's MODULE.bazel at the
# bazel-orfs commit above, with the patch files it names: its
# patch_cmds fetch OpenROAD's submodules and apply bazel-orfs's fixes.
# `bazelisk run @bazel-orfs//:bump` regenerates it on every bump.
integrity = "...",
patch_cmds = [...],
patch_strip = 1,
patches = [...],
strip_prefix = "OpenROAD-<commit>",
urls = ["https://github.com/The-OpenROAD-Project/OpenROAD/archive/<commit>.tar.gz"],
)
bazel_dep(name = "qt-bazel")
git_override(
module_name = "qt-bazel",
remote = "https://github.com/The-OpenROAD-Project/qt_bazel_prebuilts",
commit = "<the qt-bazel commit bazel-orfs pins>",
)
bazel_dep(name = "sv-elab", version = "<the version bazel-orfs uses>")
orfs = use_extension("@bazel-orfs//:extension.bzl", "orfs_repositories")
orfs.source(
commit = "<the ORFS commit bazel-orfs pins>",
integrity = "<its integrity>",
)
orfs.default(
yosys_plugins = ["@sv-elab//src/yosys_plugin:slang.so"],
)
use_repo(orfs, "gnumake")Then load("@bazel-orfs//:openroad.bzl", "orfs_flow") in a BUILD file
and write the flow exactly as in examples/BUILD.
All tools build from source by default. To use a system openroad or a
real klayout instead, or to override a tool on one target, see
docs/tools.md. docs/openroad.md
covers the from-source OpenROAD build and its gotchas.
bazel-orfs is built to vendor patches, the way Bazel itself is. ORFS,
OpenROAD, Yosys and every other dependency is a pinned archive with a
patches list, so a project can carry a fix or a policy change for as
long as it needs to, and drop it the day upstream lands something better.
- ORFS:
orfs.source(commit = ..., integrity = ..., patches = [...])in yourMODULE.bazel. bazel-orfs applies its own ORFS patches from patches/ first, then yours. - OpenROAD, Yosys, anything else:
patcheson thegit_overrideorarchive_overridein the rootMODULE.bazel, as for any Bazel module.
This is not only for bugs. Many OpenROAD issues are about policy, where the tool's default is reasonable for most users and wrong for one project, and the upstream fix has to wait for a decision about the right knob. Two from titan73's reports:
- OpenROAD #8558:
the gap between power domains is a hard-coded six row heights in
ifp. Upstream had to settle whether that becomes a global setter or an option oninitialize_floorplanandmake_rows, and whether domains need different gaps. For the project that hit it, the fix is a one-line patch changing a constant, which an AI writes in a minute and the project carries until the option ships. - OpenROAD #8771: a cell library without tie cells. Upstream's answer was that this is a discouraged design style and low priority, which is a fair policy for OpenROAD and no help to a project with the library it has. A patched tool, or a fake tie-cell LEF carried as a source, is the project's problem to solve and bazel-orfs makes it a small one.
Compare OpenROAD #6063, a straightforward bug that titan73 fixed upstream the same week. Bugs should go upstream. Policy can wait upstream while you ship.
The same mechanism is how bazel-orfs itself develops: a PR here carries
the ORFS, OpenROAD and Yosys patches it needs, is tested against real
designs through @orfs//flow/designs/..., and the patches are upstreamed
one by one and deleted here as they land.
| I want to... | Go to |
|---|---|
| Run my first build | Quick start |
| Write a flow for my own design | Write your own flow, examples/ |
| Add bazel-orfs to my project | Use bazel-orfs from your own project |
| Use a system OpenROAD or KLayout | docs/tools.md |
| Pass variables and constraints, force a rebuild | docs/customize.md |
| Create macros with LEF/LIB, estimate macro sizes | docs/customize.md |
| Tweak floorplan or placement and iterate | docs/customize.md |
| Sweep design parameters | docs/customize.md |
| Run a single substep, edit Tcl, use a local ORFS | docs/local-flow.md |
| Speed up CI or development builds | docs/performance.md |
| Monitor a long-running build | docs/performance.md |
| Understand where CI time goes | docs/performance.md |
| Query timing interactively | docs/performance.md |
Experiment across ORFS designs (the config.mk DSL) |
docs/orfs-design-builds.md |
Debug or create a make issue archive |
docs/reference.md, docs/debugging.md |
| Diagnose a build failure on my host | docs/debugging.md |
| Fast PPA estimate, gate a PR against merge-base | docs/estimate.md |
| Pin slow-to-build artifacts | tools/pin/README.md |
| Upgrade bazel-orfs, ORFS or OpenROAD | Upgrade bazel-orfs |
| Target and stage naming, internals | docs/reference.md |
| Tool | Description | Documentation |
|---|---|---|
| Artifact pinning | Cache long-running build results | tools/pin |
| Post-synthesis cleanup | najaeda netlist cleaning (experimental) | naja |
| SRAM macros | fakeram and mock SRAM | sram |
| Equivalence checking (LEC) | kepler-formal logic equivalence, for bazel-orfs's own generators (dev-only) | test/lec |
bazelisk run @bazel-orfs//:bump
A single command that updates all version pins in your MODULE.bazel and
runs bazelisk mod tidy. It detects which project it's running in and does
the right thing — no need to remember which versions to update or where.
What it updates:
- ORFS commit and integrity (latest from GitHub)
- bazel-orfs git commit (latest from GitHub)
- OpenROAD git commit (latest from GitHub, if configured)
Before writing anything it checks that the ORFS patches bazel-orfs itself
carries (ORFS_PATCHES in orfs_source.bzl) still fit the ORFS commit it
is moving you to, and stops naming the patch and the commit if not -- the
alternative is a ctx.patch failure inside bazel-orfs's patch dir during
mod tidy, after MODULE.bazel was already rewritten. --ignore turns
that into a warning.
In downstream projects, it also injects commented-out boilerplate for building OpenROAD from source — uncomment to test the latest OpenROAD before ORFS catches up. This is useful when an OpenROAD bug fix or feature hasn't made it into ORFS yet.
//:bump rewrites the override shapes it recognizes and stops, naming the
block, when it meets one it does not — it never writes a partial
MODULE.bazel. If your file is old enough that a block is unrecognized,
re-seed it from the root MODULE.bazel requirements and re-apply your edits. See
Shapes the bumper recognizes.
The root directory contains only external-facing concerns:
.bzlrule files (openroad.bzl,sweep.bzl,ppa.bzl, etc.) loaded by downstream consumersMODULE.bazelandBUILDwith public tools (bump,plot_clock_period_tool)- Template files consumed by rules (
make.tpl,deploy.tpl,mock_area.tcl) tools/(pin, deploy),extensions/(pin),patches/(vendored ORFS and dependency patches)
Everything else lives in subdirectories, split by purpose:
examples/— the flow to copy. Read this first.test/— CI regression flows (tag_array_64x184, lb_32x128, L1MetadataArray, etc.) and their fixtures. Coverage, not clarity; see Examples versus tests.sram/— SRAM macro tests with fakeram and megaboom variantssubpackage/— cross-package reference testsdocs/— everything linked from Find your way around
Most files under test/ are short implementation details easily derived from
context: Tcl helper scripts, SDC constraint files, and simple RTL are
boilerplate an LLM can regenerate from the BUILD target definitions.
Non-trivial files worth understanding: test/rtl/L1MetadataArray.sv (cache
metadata controller) and the plot scripts.