Skip to content

flow/bazel: synth-stage stub LEFs so a parent's synth doesn't block on macro PD#4258

Closed
oharboe wants to merge 1 commit into
The-OpenROAD-Project:masterfrom
oharboe:synth-stub-masters
Closed

flow/bazel: synth-stage stub LEFs so a parent's synth doesn't block on macro PD#4258
oharboe wants to merge 1 commit into
The-OpenROAD-Project:masterfrom
oharboe:synth-stub-masters

Conversation

@oharboe

@oharboe oharboe commented May 27, 2026

Copy link
Copy Markdown
Collaborator

Summary

ORFS + vendored bazel-orfs patches so a parent's synth Bazel action depends only on each child macro's _synth target — 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_synth on 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's synth_odb finishes. CI/automation wait time is a secondary benefit; the load-bearing win is human iteration time on gui_synth.

Mechanism

At the macro's synth_odb stage, with ORFS_WRITE_SYNTH_STUBS=1, emit ${design}_1_synth.lef (port-only, SIZE 1 BY 1) plus ${design}_1_synth.lib (OpenSTA write_timing_model with ideal clocks). The stubs carry the macro's port set and arc delays — enough for the parent's read_lef + read_liberty + read_verilog + dbInst::create at synth_odb, and enough for the parent's synth-time STA to compute path delays through macro arcs.

On the bazel-orfs side, _orfs_pass substitutes <name>_generate_abstract macro labels with <name>_synth siblings for the synth step only; floorplan onwards still pulls real LEF + post-place .lib via 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_model is 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 by MODULE.bazel): both env vars set to 1 so 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=1 activates a load.tcl path that re-reads ADDITIONAL_LEFS after read_db so lefin can augment the 1×1 stub masters baked into 1_synth.odb with real geometry. lefin updates dbMaster.width/height in place — but each dbInst.bbox was cached at create-time from the stub dims, and OpenROAD has no public API to refresh it. dbInst.setLocation does 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:

[ERROR EST-0092] Steiner tree creation error.

A fix needs either:

  1. A dbInst::resetBox() / dbInst::setMaster(master) API in OpenROAD that reinitialises the inst's bbox from the (now-augmented) master, or
  2. Skipping 1_synth.odb at the parent's synth stage entirely and reading the .v netlist directly at floorplan (changes synth_odb.tcl / floorplan.tcl responsibilities).

ORFS_AUGMENT_STUB_MASTERS is 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_synth on a hierarchical design with macro children — parent synth completes once each child's synth_odb finishes; macro PD does not run.
  • Bit-identical synth-stage WNS report vs the unpatched flow.
  • Full PD on a hierarchical design — currently expected to fail with EST-0092 until OpenROAD bbox-refresh lands.
  • Flat designs — env vars are still set but no macro deps to substitute; no behavior change.

Files

Path Change
flow/scripts/synth_odb.tcl Emit stub LEF + Liberty when ORFS_WRITE_SYNTH_STUBS=1.
flow/scripts/load.tcl .odb branch re-reads ADDITIONAL_LEFS for stub-master LEFs when ORFS_AUGMENT_STUB_MASTERS=1.
orfs-bazel-patches/synth-stubs-env.patch bazel-orfs flow_environment sets both env vars.
orfs-bazel-patches/synth-stubs-rules.patch _yosys_impl declares stub outputs and populates OrfsInfo.lef + OrfsInfo.lib_pre_layout.
orfs-bazel-patches/synth-stubs-flow.patch _orfs_pass substitutes <name>_generate_abstract<name>_synth for synth deps.
MODULE.bazel Apply the three bazel-orfs patches via git_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

…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>
@oharboe oharboe closed this May 27, 2026

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Comment thread flow/scripts/load.tcl
Comment on lines +61 to +68
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]
}
}
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

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]
          }
        }
      }

Comment thread flow/scripts/load.tcl
Comment on lines +72 to +79
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
}
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

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
            }
          }
        }

Comment on lines +65 to +75
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"
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

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]

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

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
  }

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant