Skip to content

flow: delete the stage-number parasitics rules and the .spef file-exists test - #4691

Open
oharboe wants to merge 6 commits into
The-OpenROAD-Project:masterfrom
oharboe:estimate-parasitics-auto
Open

oharboe wants to merge 6 commits into
The-OpenROAD-Project:masterfrom
oharboe:estimate-parasitics-auto

Conversation

@oharboe

@oharboe oharboe commented Oct 16, 2026 •

Copy link
Copy Markdown
Collaborator

The flow decides which parasitics to time with in four places: open.tcl, generate_abstract.tcl, write_endpoint_slack and final_outputs.tcl. Each decides from the stage number, grt::have_routes and whether a .spef file exists. Those rules are easy to get wrong. #4688 fixes one copy: an abstract written from global route was timed with placement parasitics, 6.9 ns instead of 1.8 ns on one arc.

Gemini Code Assist's review of #4688 is a concrete example. It suggested reading $stem.spef from stage 5 on whenever the file exists (comment). It reads as a sensible improvement, and it's wrong:

  • Only the final stage writes a SPEF, so before final that branch can only match a leftover file.
  • Re-running a flow from place or global route leaves the previous final's SPEF in RESULTS_DIR. The re-run stage's timing would silently use parasitics from a different netlist, and nothing in the log would show it.

The rules are subtle enough that an AI reviewer, given the code, proposed a change that turns a loud failure into a silent wrong number. A reviewer reading only the diff, human or bot, couldn't have caught it either: the decision is spread over four scripts and depends on which files a previous run left behind. So the rules belong somewhere they can't be gotten wrong.

Whether a file is on disk is not a valid test, and a stage number is a second copy of a fact OpenROAD already has. So:

  • OpenROAD decides what: a flagless estimate_parasitics uses global routes if they exist, otherwise placement. It fails loudly on an unplaced design, zero wire RC, or a congested route that wasn't accepted, and does nothing once read_spef has run.
  • The flow decides when: log_cmd estimate_parasitics wherever it wants to pay for an estimate (the log shows the time), and an unconditional read_spef exactly where its configuration says a SPEF was made. final_extracts_parasitics says so from RCX_RULES and SKIP_DETAILED_ROUTE. A missing SPEF fails loudly, and a stale one is never read.

On gcd (asap7 and nangate45), the flow with this change and the OpenROAD one is byte-identical to today's at every stage: the .odb files from place through final, the abstract's .lib and .lef, and 6_final.spef apart from its *DATE line. What changes is what happens to a SPEF that is missing or doesn't belong to the design:

generate_abstract on gcd's results today with this change
6_final, SPEF missing succeeds without a word, estimates fails: cannot read file .../6_final.spef
6_final, detailed route skipped, leftover SPEF reads the leftover estimates from the global routes
6_final, extracted SPEF reads it reads it

Changes in behaviour:

  • With RCX configured, a missing 6_final.spef fails open and generate_abstract instead of falling back to an estimate.
  • A global route that is congested and wasn't accepted fails estimate_parasitics instead of being timed without parasitics. To look at one, open it with GUI_TIMING=0. A failed route written with GENERATE_ARTIFACTS_ON_FAILURE gets no endpoint-slack snapshot.
  • generate_abstract from global route or later is timed with global-route parasitics instead of placement parasitics. This supersedes generate_abstract: time a global-route abstract with the route's parasitics #4688's change.
  • The endpoint-slack snapshot records where OpenROAD's parasitics came from: 6_final says spef instead of rcx, and 3_1_place_gp_skip_io with unplaced pins says none instead of placement.

SKIP_DETAILED_ROUTE now reaches generate_abstract, which needs it to know whether 6_final has a SPEF.

Integration test: The-OpenROAD-Project/bazel-orfs#1284, which runs ORFS's generate_abstract.tcl on copies of gcd's results (missing, leftover and extracted SPEF, and an abstract from global route) on asap7 and nangate45.

🤖 Generated with Claude Code

@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 simplifies and standardizes parasitics estimation across various flow scripts by replacing explicit estimation flags (such as -placement or -global_routing) with a bare estimate_parasitics call, allowing OpenROAD to automatically determine the correct source based on the design's state. It also introduces the final_extracts_parasitics helper function, updates documentation, and extends the SKIP_DETAILED_ROUTE variable to the generate_abstract stage. There are no review comments, so I have no feedback to provide.

The flow decided which parasitics to time with in four places, open.tcl,
generate_abstract.tcl, write_endpoint_slack and final_outputs.tcl, from
the stage number, grt::have_routes and whether a .spef file exists. A
file on disk says nothing about whether it belongs to the design being
timed: re-running a flow from place or global route leaves the previous
final's SPEF in the results directory.

estimate_parasitics without a flag now picks the source itself: global
routes when there are any, else placement, nothing after read_spef. The
stage scripts call it bare, under log_cmd, where they want to pay for an
estimate. 6_final is timed with 6_final.spef when the final stage
extracted it, which final_extracts_parasitics says from RCX_RULES and
SKIP_DETAILED_ROUTE; read_spef fails if the file is missing. A placed
design is timed with parasitics, a design before placement without.

Changes in behavior:

- With RCX configured, a missing 6_final.spef fails open and
  generate_abstract instead of falling back to an estimate.
- A global route that is congested and was not accepted fails
  estimate_parasitics instead of timing without parasitics; open with
  GUI_TIMING=0 to look at it. A failed route written with
  GENERATE_ARTIFACTS_ON_FAILURE gets no endpoint slack snapshot.
- generate_abstract from global route or later is timed with global
  route parasitics instead of placement parasitics.
- The endpoint slack snapshot records where OpenROAD's parasitics came
  from: 6_final says spef instead of rcx, and 3_1_place_gp_skip_io with
  unplaced pins says none instead of placement.

SKIP_DETAILED_ROUTE reaches generate_abstract, which needs it to know
whether 6_final has a SPEF.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Øyvind Harboe <oyvind.harboe@zylin.com>
@oharboe
oharboe force-pushed the estimate-parasitics-auto branch from 410dfb2 to 2447d20 Compare October 16, 2026 12:33
@openroad-ci

openroad-ci commented Oct 16, 2026 •

Copy link
Copy Markdown
Member

🔍 QoR check

Metrics reflect the PR merge build — i.e. what will land on the target branch.

Commit 28e4347 · Jenkins build #13 · Baseline: build · View build on dashboard

62 design(s) checked — 0 with regression(s), 1 without a comparable baseline.

@oharboe
oharboe marked this pull request as ready for review October 20, 2026 22:04
generate_abstract.tcl conflicted with The-OpenROAD-Project#4688 (695436e), which this
change supersedes: the flagless estimate_parasitics times an abstract
from global route on with the route's parasitics, and the SPEF is read
only where final_extracts_parasitics says one was made. Kept this
branch's side.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Øyvind Harboe <oyvind.harboe@zylin.com>
@oharboe
oharboe requested a review from eder-matheus October 20, 2026 22:21
@oharboe oharboe changed the title flow: OpenROAD picks the parasitics, the flow says when flow: simplifications/deletions now that estimate_parasitics is smart Oct 22, 2026
@oharboe

oharboe commented Oct 26, 2026

Copy link
Copy Markdown
Collaborator Author

@eder-matheus 🥺 ?

tools/OpenROAD must be bumped, which I will let someone else do.

If you approve + automerge, it will go in when pr-merge goes green.

@oharboe oharboe changed the title flow: simplifications/deletions now that estimate_parasitics is smart flow: delete the stage-number parasitics rules and the .spef file-exists test Oct 29, 2026
@oharboe

oharboe commented Nov 1, 2026

Copy link
Copy Markdown
Collaborator Author

@maliberty @eder-matheus pr-merge is red, but that's just a QoR problem that will fix itself when any ORFS pr is merged. So... this PR is as good as any.

Merge?

Comment thread flow/scripts/generate_abstract.tcl Outdated
Comment on lines 16 to 21
if { $stem == "6_final" && [final_extracts_parasitics] } {
log_cmd read_spef $::env(RESULTS_DIR)/6_final.spef
}
if { [grt::design_is_placed] } {
log_cmd estimate_parasitics
}

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

why not an elseif - is there a case where you want to both read_spef and estimate parasitics?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

No case wants both. It is an elseif now, in orfs_load_parasitics in util.tcl (92bda58); the two ifs only worked because estimate_parasitics does nothing after read_spef.

Comment thread flow/scripts/open.tcl Outdated
} elseif { $design_stage >= 3 } {
log_cmd estimate_parasitics -placement
}
if { [grt::design_is_placed] } {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

same elseif question

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

No case wants both. It is an elseif now, in orfs_load_parasitics in util.tcl (92bda58); the two ifs only worked because estimate_parasitics does nothing after read_spef.

@maliberty

Copy link
Copy Markdown
Member

Codex (trimmed)

The other findings:

  • Undeclared input: generate_abstract.tcl now always reads 6_final.spef when RCX applies, but the generate_abstract target in the Makefile doesn't declare that file. In bazel-orfs, which only provides declared inputs, the step would fail.
  • Copied logic: the same "read SPEF, otherwise estimate if placed" block appears in open.tcl, generate_abstract.tcl and util.tcl. TclContract.md says logic needed in two places becomes a proc in util.tcl.
  • Speed:
    • The final stage on platforms without RCX estimates twice in a row with identical results.
    • Each guarded estimate walks the full netlist about four times.
  • Wrong label in reports: util.tcl:379 reports the parasitics source from the last estimate run, which can be out of date when the estimate is skipped.
  • Doc mismatch: in EndpointSlackTracking.md, the 6_final row leaves out the SKIP_DETAILED_ROUTE=1 fallback.

@maliberty

Copy link
Copy Markdown
Member

needs a merge of master to pick up a later OR version with: That commit includes OR #11788 (merge commit 99bd6268d8, "est-parasitics-auto").

oharboe and others added 4 commits November 3, 2026 08:35
The endpoint-slack table left out the SKIP_DETAILED_ROUTE=1 fallback
that final_extracts_parasitics takes.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Øyvind Harboe <oyvind.harboe@zylin.com>
generate_abstract.tcl reads 6_final.spef when final extracts
parasitics, so the target lists it with the other 6_final files.
The file is a result of 6_report like 6_final.def; without RCX_RULES,
or with detailed route skipped, no SPEF is written and make goes on
without it, so the graph stays the same for every design.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Øyvind Harboe <oyvind.harboe@zylin.com>
open.tcl, generate_abstract.tcl and write_endpoint_slack each had the
same two tests: read 6_final.spef when final extracts parasitics, and
estimate when the design is placed. orfs_load_parasitics in util.tcl
now holds them, as one if/elseif: a design timed with the extracted
SPEF is never also estimated.

orfs_load_parasitics returns where the parasitics came from, and the
endpoint-slack snapshot records that, so the label always describes
the parasitics the snapshot was timed with. est::parasitics_source on
its own reported the last estimate in the process, which is stale for
a step that does not estimate.

final_outputs.tcl loads its parasitics itself, by extraction or by an
estimate, and now tells write_endpoint_slack not to load them again.
Without RCX_RULES, 6_final was estimated twice in a row.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Øyvind Harboe <oyvind.harboe@zylin.com>
@oharboe

oharboe commented Nov 3, 2026

Copy link
Copy Markdown
Collaborator Author

Merged master, which brings OpenROAD 98c6045c, which includes #11788. New commits, one per finding:

  • Undeclared input: e8398b5. generate_abstract lists $(RESULTS_DIR)/6_final.spef, a result of 6_report like 6_final.def. Without RCX the file is not written and make goes on without it, so the graph is the same for every design. (bazel-orfs already declares 6_final.spef as a final-stage result; the abstract tests in bazel-orfs ran with it.)

  • Copied logic: 92bda58. orfs_load_parasitics <step> in util.tcl is the one copy, as an if/elseif; open.tcl, generate_abstract.tcl and write_endpoint_slack call it.

  • Speed, final estimates twice: 92bda58. final_outputs.tcl loads its parasitics itself and calls write_endpoint_slack 6_final 0.

  • Speed, netlist walks: one of the four is the flow's (grt::design_is_placed), now once per call; the other three are inside estimate_parasitics (designParasiticsSrc, haveRoutes, haveCongestedRoutes). Measured at global route on asap7, three runs each:

    design instances grt::design_is_placed estimate_parasitics
    aes_cipher_top 12,446 2.0–2.5 ms 77–89 ms
    jpeg_encoder 58,595 10–12 ms 299–339 ms
  • Wrong label: 92bda58. The snapshot records what orfs_load_parasitics returns: none when the design is not placed, else est::parasitics_source right after the read_spef or estimate it ran.

  • Doc mismatch: 3da44b6.

gcd asap7 with this change against without it: 2_floorplan, 3_place, 4_cts, 5_1_grt, 5_route and 6_final .odb, the abstract's .lib and .lef byte-identical, 6_final.spef apart from *DATE. The asap7 cases of the bazel-orfs integration test pass (extracted SPEF read, missing SPEF fails, leftover SPEF unread, abstract from global route). The nangate45 cases were not rerun: gcd on nangate45 fails at global route in bazel-orfs (DPL-0033 after repair at 98.2% utilization) with and without this change.

@oharboe
oharboe requested a review from maliberty November 3, 2026 08:31
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.

4 participants