flow/bazel: synth-stage stub LEFs so a parent's synth doesn't block on macro PD#4258
flow/bazel: synth-stage stub LEFs so a parent's synth doesn't block on macro PD#4258oharboe wants to merge 1 commit into
Conversation
…n macro PD
Add a synth_odb.tcl path that emits a port-only stub LEF and a
write_timing_model Liberty for the synthesized design, plus a
matching .odb-side path in load.tcl that re-reads ADDITIONAL_LEFS
so lefin can augment stub masters with real geometry at floorplan
time. Both paths are gated on env vars (ORFS_WRITE_SYNTH_STUBS,
ORFS_AUGMENT_STUB_MASTERS) and are no-ops when unset.
Companion bazel-orfs patches are vendored under orfs-bazel-patches/
and applied via MODULE.bazel's bazel-orfs git_override: they set
the env vars, declare stub artifacts in OrfsInfo, and substitute
each <name>_generate_abstract macro dep with <name>_synth for the
synth Bazel action only - so the parent's synth depends on each
child's synth target instead of the child's full PD flow.
Motivation: make gui_synth on a parent of a large hierarchical
bazel-orfs design. Today the parent's synthesis blocks on every
child macro completing floorplan/place/cts (hours on big designs)
even though the GUI only needs the synthesized netlist. With this
change a parent's synth starts as soon as each child's synth_odb
finishes. The primary win is engineer iteration time on gui_synth;
CI/automation wait time is a secondary nice-to-have.
Status:
* Synth-only paths (gui_synth, synth reports, synth-stage WNS)
produce bit-identical results to the unpatched flow.
* Full PD against stub-master ODBs is currently broken: lefin
updates dbMaster width/height in place, but each dbInst's
bbox was cached from the 1x1 stub dims and OpenROAD has no
public API to refresh it. ORFS_AUGMENT_STUB_MASTERS is
therefore documented as experimental until OpenROAD adds a
bbox-refresh API.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
Code Review
This pull request introduces support for generating and consuming port-only stub LEF and Liberty files during the synthesis stage (synth_odb.tcl). This allows parent designs to proceed with synthesis without waiting for the full physical design flow of child macros. Several critical issues were identified in the implementation: the stub detection logic in load.tcl incorrectly compares database units (DBUs) directly to micron values, the LEF parser loop breaks prematurely on multi-macro files, pin signal types are hardcoded to SIGNAL instead of querying their actual types, and set_clock_latency can fail on designs without defined clocks.
| set stub_master_names {} | ||
| foreach lib [$db getLibs] { | ||
| foreach master [$lib getMasters] { | ||
| if { [$master getWidth] == 1 && [$master getHeight] == 1 } { | ||
| lappend stub_master_names [$master getName] | ||
| } | ||
| } | ||
| } |
There was a problem hiding this comment.
In OpenROAD/ODB, dbMaster::getWidth() and dbMaster::getHeight() return dimensions in Database Units (DBUs), not microns. Since the stub LEF defines the macro size as 1 BY 1 (microns), its size in DBUs will be equal to the DBUs per micron of the technology (typically 1000 or 2000). Comparing directly to 1 will always evaluate to false, causing the stub detection to fail silently. Query the DBUs per micron from the tech database to perform a correct comparison.
set dbu [[$db getTech] getDbUnitsPerMicron]
set stub_master_names {}
foreach lib [$db getLibs] {
foreach master [$lib getMasters] {
if { [$master getWidth] == $dbu && [$master getHeight] == $dbu } {
lappend stub_master_names [$master getName]
}
}
}
| while { [gets $fh line] >= 0 } { | ||
| if { [regexp {^\s*MACRO\s+([A-Za-z_][A-Za-z0-9_]*)} $line - mname] } { | ||
| if { [lsearch -exact $stub_master_names $mname] >= 0 } { | ||
| set is_stub_lef 1 | ||
| } | ||
| break | ||
| } | ||
| } |
There was a problem hiding this comment.
If a LEF file contains multiple MACRO definitions, breaking on the first MACRO will prevent scanning subsequent macros. If the first macro is not a stub but a later one is, the file will be incorrectly skipped. Only break the loop when a stub macro is actually found.
while { [gets $fh line] >= 0 } {
if { [regexp {^\s*MACRO\s+([A-Za-z_][A-Za-z0-9_]*)} $line - mname] } {
if { [lsearch -exact $stub_master_names $mname] >= 0 } {
set is_stub_lef 1
break
}
}
}
| foreach bterm [$block getBTerms] { | ||
| set pname [$bterm getName] | ||
| set io [$bterm getIoType] | ||
| if { $io eq "FEEDTHRU" } { | ||
| continue | ||
| } | ||
| puts $f " PIN $pname" | ||
| puts $f " DIRECTION $io ;" | ||
| puts $f " USE SIGNAL ;" | ||
| puts $f " END $pname" | ||
| } |
There was a problem hiding this comment.
Hardcoding USE SIGNAL ; for all pins in the stub LEF is incorrect if the pin is a power, ground, or clock pin. Query the actual signal type using [$bterm getSigType] to write the correct LEF property.
foreach bterm [$block getBTerms] {
set pname [$bterm getName]
set io [$bterm getIoType]
if { $io eq "FEEDTHRU" } {
continue
}
set sig_type [$bterm getSigType]
puts $f " PIN $pname"
puts $f " DIRECTION $io ;"
puts $f " USE $sig_type ;"
puts $f " END $pname"
}
| # the macro — enough to keep a parent's synth-stage WNS within | ||
| # noise of the legacy (post-place-lib) flow. | ||
| set lib_file $::env(RESULTS_DIR)/${design}_1_synth.lib | ||
| set_clock_latency -source 0 [all_clocks] |
There was a problem hiding this comment.
If the design is purely combinational and has no clocks defined, [all_clocks] will return an empty list, which may cause set_clock_latency to fail. Guard the call to ensure clocks exist before setting their latency.
set clocks [all_clocks]
if { [llength $clocks] > 0 } {
set_clock_latency -source 0 $clocks
}
Summary
ORFS + vendored bazel-orfs patches so a parent's synth Bazel action depends only on each child macro's
_synthtarget — not the child's full_generate_abstract(synth → floorplan → place → cts). Macros' physical design no longer blocks parent synthesis.Motivation
The primary use case is
make gui_synthon a parent of a large hierarchical bazel-orfs design — engineers iterating on RTL want to open the synthesized netlist in the OpenROAD GUI for fast RTL diagnosis. Today the parent's synthesis blocks on every child macro completing floorplan/place/cts (potentially hours on a big design) even though the GUI only needs the synthesized netlist. After this change the parent's synth schedules as soon as each child'ssynth_odbfinishes. CI/automation wait time is a secondary benefit; the load-bearing win is human iteration time ongui_synth.Mechanism
At the macro's
synth_odbstage, withORFS_WRITE_SYNTH_STUBS=1, emit${design}_1_synth.lef(port-only,SIZE 1 BY 1) plus${design}_1_synth.lib(OpenSTAwrite_timing_modelwith ideal clocks). The stubs carry the macro's port set and arc delays — enough for the parent'sread_lef+read_liberty+read_verilog+dbInst::createatsynth_odb, and enough for the parent's synth-time STA to compute path delays through macro arcs.On the bazel-orfs side,
_orfs_passsubstitutes<name>_generate_abstractmacro labels with<name>_synthsiblings for the synth step only; floorplan onwards still pulls real LEF + post-place.libvia deps on_generate_abstract.Filename is per-design so a parent reading multiple sibling stubs doesn't trip lefin's basename-derived library-name uniqueness check (
ODB-0229 library (1_synth) already exists).write_timing_modelis what keeps the synth-stage WNS numbers identical to the unpatched flow — an earlier port-only stub Liberty without timing arcs shifted the parent's synth-time STA because it treated macro arcs as zero-delay.Default behavior
ORFS itself: both env vars default unset → no behavior change for
make-based flows or for any flow that doesn't opt in.bazel-orfs (vendored patches in
orfs-bazel-patches/referenced byMODULE.bazel): both env vars set to1so the synth-stage speedup is on by default for bazel-orfs builds. This is safe for synth-only targets (gui_synth,1_synth.odb, synth reports) and is bit-identical to the unpatched flow there.Known limitation — full PD against stub-master ODBs
ORFS_AUGMENT_STUB_MASTERS=1activates aload.tclpath that re-readsADDITIONAL_LEFSafterread_dbso lefin can augment the 1×1 stub masters baked into1_synth.odbwith real geometry. lefin updatesdbMaster.width/heightin place — but eachdbInst.bboxwas cached at create-time from the stub dims, and OpenROAD has no public API to refresh it.dbInst.setLocationdoes not retrigger the bbox computation.Result: floorplan/place see macros as 1×1 boxes, the core area computes ~23% too small on the design I tested, CTS fails with:
A fix needs either:
dbInst::resetBox()/dbInst::setMaster(master)API in OpenROAD that reinitialises the inst's bbox from the (now-augmented) master, or1_synth.odbat the parent's synth stage entirely and reading the.vnetlist directly at floorplan (changessynth_odb.tcl/floorplan.tclresponsibilities).ORFS_AUGMENT_STUB_MASTERSis documented as experimental for that reason. The full-PD breakage only kicks in for hierarchical builds that combine WRITE_STUBS=1 at the child with a parent that runs floorplan onward; synth-only targets are unaffected.Test plan
make gui_synthon a hierarchical design with macro children — parent synth completes once each child'ssynth_odbfinishes; macro PD does not run.Files
flow/scripts/synth_odb.tclORFS_WRITE_SYNTH_STUBS=1.flow/scripts/load.tcl.odbbranch re-readsADDITIONAL_LEFSfor stub-master LEFs whenORFS_AUGMENT_STUB_MASTERS=1.orfs-bazel-patches/synth-stubs-env.patchflow_environmentsets both env vars.orfs-bazel-patches/synth-stubs-rules.patch_yosys_impldeclares stub outputs and populatesOrfsInfo.lef+OrfsInfo.lib_pre_layout.orfs-bazel-patches/synth-stubs-flow.patch_orfs_passsubstitutes<name>_generate_abstract→<name>_synthfor synth deps.MODULE.bazelgit_override(patches=...).The bazel-orfs patches are vendored here so this change can land without a coordinated bazel-orfs release; they should be sent upstream to bazel-orfs as a separate PR.
🤖 Generated with Claude Code