Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 19 additions & 0 deletions MODULE.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,25 @@ BAZEL_ORFS_REMOTE = "https://github.com/The-OpenROAD-Project/bazel-orfs.git"
git_override(
module_name = "bazel-orfs",
commit = BAZEL_ORFS_COMMIT,
patch_strip = 1,
patches = [
# Parent synth depends only on each macro's _synth target
# (not _generate_abstract). Companion to ORFS-side changes
# in flow/scripts/{synth_odb.tcl,load.tcl} — synth_odb.tcl
# emits port-only stub LEF + Liberty (gated on
# ORFS_WRITE_SYNTH_STUBS=1) that the parent's synth_odb
# consumes in place of waiting for the macro's
# floorplan/place/cts. Floorplan onwards still pulls real
# LEFs via deps on _generate_abstract. The primary motivation
# is `make gui_synth` turnaround for engineers iterating on
# RTL: today a parent's synthesis blocks on every child
# macro's full PD flow, which can be hours on a large
# hierarchical design; with these patches it blocks only on
# each child's synthesis. To upstream into bazel-orfs.
"//orfs-bazel-patches:synth-stubs-env.patch",
"//orfs-bazel-patches:synth-stubs-rules.patch",
"//orfs-bazel-patches:synth-stubs-flow.patch",
],
remote = BAZEL_ORFS_REMOTE,
)

Expand Down
72 changes: 72 additions & 0 deletions flow/scripts/load.tcl
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,78 @@ proc load_design { design_file sdc_file } {
log_cmd link_design {*}[hier_options] $::env(DESIGN_NAME)
} elseif { $ext == ".odb" } {
log_cmd read_db {*}[hier_options] $::env(RESULTS_DIR)/$design_file
# When the parent's synth_odb stage was run against stub macro
# LEFs (port-only, no geometry — see synth_odb.tcl's
# ORFS_WRITE_SYNTH_STUBS path), 1_synth.odb carries those stub
# masters as 1×1 frozen blocks. Read the real ADDITIONAL_LEFS
# here so lefin augments them in place: width/height/class/site
# get updated to the real LEF values, and pin geometry is
# attached to the existing dbMTerms (whose directions came
# from the stub).
#
# Per-LEF gating: only re-read LEFs whose primary MACRO is
# currently a 1×1 stub. ADDITIONAL_LEFS at floorplan may also
# contain non-stub macros (e.g. behavioral memories whose real
# LEF was already read at synth time and is fully baked into
# 1_synth.odb). Re-reading those would duplicate pin geometry
# (lefin's pin handler leaves existing dbMTerms in place but
# still adds the new geometry boxes). 1×1 is a unique stub
# marker — synth_odb.tcl always writes SIZE 1 BY 1 in stub LEFs
# and real macros are orders of magnitude larger.
#
# NOTE: this path is currently experimental. lefin updates the
# dbMaster width/height in place, but each dbInst's cached
# bbox was initialised at create time from the 1×1 stub master
# dims and OpenROAD has no public API to refresh that bbox. As
# a result, floorplan/place still see macros as 1×1 boxes and
# CTS fails with EST-0092. A future fix in OpenROAD (e.g. a
# dbInst::resetBox() or dbInst::setMaster() that reinitialises
# the cached bbox from the master) is needed to unlock the
# full PD flow against synth-stage stub masters.
if { [info exists ::env(ORFS_AUGMENT_STUB_MASTERS)] \
&& $::env(ORFS_AUGMENT_STUB_MASTERS) == "1" \
&& [env_var_exists_and_non_empty ADDITIONAL_LEFS] } {
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]
}
}
}
Comment on lines +61 to +68

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

foreach lef $::env(ADDITIONAL_LEFS) {
set fh [open $lef r]
set is_stub_lef 0
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 +72 to +79

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

close $fh
if { $is_stub_lef } {
read_lef $lef
}
}

# Re-set each dbInst's location to itself so OpenROAD has a
# chance to recompute the bbox from the (now-augmented) master
# dims. This is the only public refresh trigger today; if/when
# OpenROAD adds an explicit bbox refresh API, switch to that.
set block [ord::get_db_block]
if { $block != "NULL" } {
foreach inst [$block getInsts] {
set master [$inst getMaster]
set mname [$master getName]
if { [lsearch -exact $stub_master_names $mname] >= 0 } {
set loc [$inst getLocation]
$inst setLocation [lindex $loc 0] [lindex $loc 1]
}
}
}
}
} else {
error "Unrecognized input file $design_file"
}
Expand Down
63 changes: 63 additions & 0 deletions flow/scripts/synth_odb.tcl
Original file line number Diff line number Diff line change
Expand Up @@ -31,3 +31,66 @@ orfs_write_db $::env(RESULTS_DIR)/1_synth.odb
# which are read in here and a canonicalized version is written
# out by OpenSTA that has no dependencies.
orfs_write_sdc $::env(RESULTS_DIR)/1_synth.sdc

# Emit port-only stub LEF + Liberty for the synthesized design so a
# parent that instantiates this design as a macro can complete its
# synth_odb stage without waiting on our floorplan/place/cts to
# produce real LEF and post-place Liberty. The stub LEF carries only
# port names + directions (no geometry); the Liberty is generated by
# OpenSTA's write_timing_model and carries arc delays computed from
# the loaded netlist with ideal clocks. Together they are enough to
# satisfy read_lef + read_liberty + read_verilog + dbInst::create at
# the parent's synth_odb, and enough for the parent's synth-time STA
# to compute path delays through macro arcs.
if { [info exists ::env(ORFS_WRITE_SYNTH_STUBS)] && $::env(ORFS_WRITE_SYNTH_STUBS) == "1" } {
set design $::env(DESIGN_NAME)
set block [ord::get_db_block]

# Per-design filename so OpenROAD's lefin doesn't reject the second
# stub LEF in a parent's synth_odb with `ODB-0229 Error: library
# (1_synth) already exists` — lefin derives the library name from
# the LEF file's basename, and a parent reading multiple sibling
# stubs would otherwise see colliding "1_synth" libraries.
set lef_file $::env(RESULTS_DIR)/${design}_1_synth.lef
set f [open $lef_file w]
puts $f "VERSION 5.7 ;"
puts $f "BUSBITCHARS \"\[\]\" ;"
puts $f "DIVIDERCHAR \"/\" ;"
puts $f ""
puts $f "MACRO $design"
puts $f " CLASS BLOCK ;"
puts $f " ORIGIN 0 0 ;"
puts $f " SIZE 1 BY 1 ;"
puts $f " SYMMETRY X Y ;"
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"
}
Comment on lines +65 to +75

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

puts $f "END $design"
puts $f ""
puts $f "END LIBRARY"
close $f

# Emit a real-timing Liberty via OpenSTA's write_timing_model with
# ideal clocks. The synthesized netlist is already loaded; PDK
# Liberty is loaded too. write_timing_model characterises the
# design as a Liberty cell with arc delays derived from the
# current STA setup. Without parasitics, the arcs are optimistic
# compared to post-place .lib but include cell delays through
# 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
  }

if { [env_var_exists_and_non_empty CORNERS] } {
log_cmd write_timing_model -scene [lindex $::env(CORNERS) 0] $lib_file
} else {
log_cmd write_timing_model $lib_file
}
}
1 change: 1 addition & 0 deletions orfs-bazel-patches/BUILD.bazel
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
exports_files(glob(["*.patch"]))
29 changes: 29 additions & 0 deletions orfs-bazel-patches/synth-stubs-env.patch
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
private/environment.bzl: ORFS_WRITE_SYNTH_STUBS + ORFS_AUGMENT_STUB_MASTERS env

--- a/private/environment.bzl
+++ b/private/environment.bzl
@@ -56,6 +56,24 @@
"KLAYOUT_CMD": _executable_path(_klayout_attr(ctx)),
"OPENROAD_EXE": _executable_path(ctx.attr.openroad),
"OPENSTA_EXE": _executable_path(ctx.attr.opensta),
+ # See flow/scripts/synth_odb.tcl and flow/scripts/load.tcl in
+ # OpenROAD-flow-scripts:
+ # - WRITE_SYNTH_STUBS=1 tells synth_odb.tcl to emit port-only
+ # stub <design>_1_synth.lef + .lib for this design so parents
+ # can consume them at synth time without waiting on this
+ # macro's floorplan/place/cts.
+ # - AUGMENT_STUB_MASTERS=1 tells load.tcl's .odb branch to
+ # read_lef ADDITIONAL_LEFS after read_db so lefin augments
+ # stub masters (1×1, no geometry) baked into 1_synth.odb
+ # with the real macro LEF at floorplan and later stages.
+ # Both env vars are no-ops on ORFS revisions that don't have
+ # the matching synth-stub-masters changes in load.tcl /
+ # synth_odb.tcl. AUGMENT_STUB_MASTERS is currently experimental
+ # — it requires an OpenROAD bbox-refresh API for the full PD
+ # flow to work end-to-end; until then it's only useful with
+ # synth-only targets such as gui_synth.
+ "ORFS_WRITE_SYNTH_STUBS": "1",
+ "ORFS_AUGMENT_STUB_MASTERS": "1",
} | orfs_environment(ctx)

def yosys_environment(ctx):
43 changes: 43 additions & 0 deletions orfs-bazel-patches/synth-stubs-flow.patch
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
private/flow.bzl: substitute macros->_synth for synth deps in _orfs_pass

--- a/private/flow.bzl
+++ b/private/flow.bzl
@@ -38,6 +38,29 @@
kwargs.pop("yosys", None)
return kwargs

+_GENERATE_ABSTRACT_SUFFIX = "_generate_abstract"
+
+def _synth_macros(macros):
+ """Substitute each `<name>_generate_abstract` macro label with its
+ `<name>_synth` sibling so the parent's synth Bazel action depends
+ only on each child macro's synth target, not the macro's full
+ PD flow.
+
+ The substituted target's OrfsInfo carries a port-only stub LEF
+ (1_synth.lef) and stub Liberty (1_synth.lib) emitted by
+ synth_odb.tcl under ORFS_WRITE_SYNTH_STUBS=1. Labels that
+ don't follow the `_generate_abstract` convention are passed
+ through unchanged (e.g. behavioral macros without a synth flow).
+ """
+ out = []
+ for m in macros:
+ s = str(m)
+ if s.endswith(_GENERATE_ABSTRACT_SUFFIX):
+ out.append(s[:-len(_GENERATE_ABSTRACT_SUFFIX)] + "_synth")
+ else:
+ out.append(m)
+ return out
+
def _merge_extra_arguments(a, b):
"""Merge two {stage: [label, ...]} dicts, concatenating per-stage lists."""
merged = dict(a)
@@ -518,7 +541,7 @@
stage_arguments = stage_arguments,
arguments = arguments,
sources = sources,
- deps = macros,
+ deps = _synth_macros(macros),
kept_macros = kept_macros if kept_macros != None else {},
kept_macros_enabled = kept_macros != None,
module_top = top,
69 changes: 69 additions & 0 deletions orfs-bazel-patches/synth-stubs-rules.patch
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
private/rules.bzl: SYNTH_STUB_OUTPUTS + OrfsInfo.lef/lib_pre_layout for synth

--- a/private/rules.bzl
+++ b/private/rules.bzl
@@ -744,8 +744,21 @@

CANON_OUTPUT = "1_1_yosys_canonicalize.rtlil"
SYNTH_OUTPUTS = ["1_2_yosys.v", "1_2_yosys.sdc", "1_synth.sdc", "mem.json"]
+# Synth-stage stub artifacts: port-only LEF + Liberty emitted by
+# synth_odb.tcl (when ORFS_WRITE_SYNTH_STUBS=1). Parents instantiating
+# this design as a macro consume them at synth_odb time so they don't
+# have to wait for our floorplan/place/cts to produce real LEF and
+# post-place Liberty. Augmented in place by lefin when the real LEF
+# is read at the parent's floorplan stage (load.tcl's
+# ORFS_AUGMENT_STUB_MASTERS path). Filename is per-design to avoid
+# lefin's `library (1_synth) already exists` error when a parent
+# reads multiple sibling stubs (lefin derives library name from LEF
+# basename).
SYNTH_REPORTS = ["synth_stat.txt", "synth_mocked_memories.txt"]

+def synth_stub_outputs(module_top):
+ return ["{}_1_synth.lef".format(module_top), "{}_1_synth.lib".format(module_top)]
+
def _yosys_parallel_synth(ctx, config, canon_output, synth_outputs, synth_logs, synth_reports, num_partitions, save_odb, all_arguments = {}):
"""Parallel synthesis: keep → kept-json → N partitions → merge.

@@ -1123,7 +1136,10 @@
deps_inputs(ctx),
],
),
- outputs = [synth_outputs["1_synth.odb"], synth_outputs["1_synth.sdc"]],
+ outputs = [
+ synth_outputs["1_synth.odb"],
+ synth_outputs["1_synth.sdc"],
+ ] + [synth_outputs[n] for n in synth_stub_outputs(ctx.attr.module_top)],
tools = yosys_and_flow_tools,
)

@@ -1199,8 +1215,9 @@

synth_logs = declare_artifacts(ctx, "logs", ["1_2_yosys.log", "1_2_yosys_metrics.log"] + (["1_synth.log"] if save_odb else []))

+ stub_outputs = synth_stub_outputs(ctx.attr.module_top) if save_odb else []
synth_outputs = {}
- for output in SYNTH_OUTPUTS + (["1_synth.odb"] if save_odb else []):
+ for output in SYNTH_OUTPUTS + (["1_synth.odb"] + stub_outputs if save_odb else []):
synth_outputs[output] = declare_artifact(ctx, "results", output)

synth_reports = declare_artifacts(ctx, "reports", SYNTH_REPORTS)
@@ -1415,9 +1432,17 @@
variant = ctx.attr.variant,
odb = synth_outputs.get("1_synth.odb"),
gds = None,
- lef = None,
+ # Port-only stub LEF + Liberty written by synth_odb.tcl
+ # under ORFS_WRITE_SYNTH_STUBS=1. A parent that targets
+ # this rule (instead of <name>_generate_abstract) for its
+ # synth-stage deps consumes these stubs and schedules
+ # without waiting for our floorplan/place/cts. lefin
+ # augments stub masters with full geometry when the real
+ # macro LEF is read at the parent's floorplan stage
+ # (load.tcl's ORFS_AUGMENT_STUB_MASTERS path).
+ lef = synth_outputs.get("{}_1_synth.lef".format(ctx.attr.module_top)),
lib = None,
- lib_pre_layout = None,
+ lib_pre_layout = synth_outputs.get("{}_1_synth.lib".format(ctx.attr.module_top)),
additional_gds = depset(
[dep[OrfsInfo].gds for dep in ctx.attr.deps if dep[OrfsInfo].gds],
),
Loading