Skip to content

psm: error early when a report writer has no solution, and skip unpowered nodes - #11103

Merged
maliberty merged 3 commits into
The-OpenROAD-Project:masterfrom
shivarammysore:psm-write-guards
Aug 8, 2026
Merged

psm: error early when a report writer has no solution, and skip unpowered nodes#11103
maliberty merged 3 commits into
The-OpenROAD-Project:masterfrom
shivarammysore:psm-write-guards

Conversation

@shivarammysore

Copy link
Copy Markdown
Contributor

Fixes #11098, doing the three things @gadfort asked for.

Error before the file is created

writeSpiceFile, writeInstanceVoltageFile and writeEMFile all reach into currents_/voltages_ with at(), so calling one before a solution exists threw a bare std::out_of_range with nothing to act on. Each now calls a small ensureSolution() first — before opening its stream — reporting the net and corner and naming analyze_power_grid:

[ERROR PSM-0092] No solution available for VDD on corner default. Run analyze_power_grid first.

This matters most for write_pg_spice, which threw after writing the resistive network: it left a syntactically valid deck with no sources and no sinks — a floating network a simulator solves to nonsense rather than rejects.

find() for the sparse current map

writeSpiceFile looked up every ITermNode in the per-node current map with at(). That map is sparse by construction — generateCurrentMap() inserts only nodes of instances returned by getInstancePower(), so a filler, tap or decap has no entry. solve() reads the same map with find() and a zero default (ir_solver.cpp:932-937), twenty lines of context away, so the sparse case was already handled correctly there. The lookup now matches, and skips a node with no current the way the neighbouring kSpiceFileMinCurrent guard already skips a negligible one.

Test

report_writers_require_solution covers write_pg_spice before a solution exists — asserting the error and that no file is left behind — then analyze_power_grid -voltage_file -enable_em -em_outfile followed by write_pg_spice, so all three writers are exercised on the success path too.

Two things worth flagging

I could not run the suite locally, so the .ok was composed rather than captured: the ODB banner from missing_resistance.ok, the report block from gcd_em_test_vdd.ok, and write_pg_spice's own PSM-0015 line from gcd_write_sp_test_vdd.ok — all on the same design and invocation. It should be exact, but please have CI confirm it rather than taking my word.

The sparse-map path is not covered by this test, and I would rather say so than imply it is. Nangate45's FILLCELL_X1 has no pins and appears in no net, so gcd creates no unpowered ITermNode and at() never throws there — which is why gcd_write_sp_test_vdd passes today. Reproducing it needs a design whose unpowered instances connect to the power net, as sky130-style flows do via ( * VPWR ); I hit it on a 72k-instance sky130 block where 57,142 of the instances carry no power. If you would like that covered, sky130hd_data/gcd_sky130hd_floorplan.def looks like the right existing fixture and I am happy to add it in a follow-up.

…ered nodes

The three writers derive their output from a solution and all reached into
currents_/voltages_ with at(), so calling one before analyze_power_grid threw a
bare std::out_of_range with nothing to act on. write_pg_spice threw after it had
already written the resistive network, leaving a syntactically valid spice deck
with no sources and no sinks -- a floating network a simulator solves to nonsense
rather than rejects.

Each writer now calls ensureSolution() before creating its file, which reports
the net and corner and names analyze_power_grid.

writeSpiceFile additionally looked up every ITermNode in the per-node current
map with at(). That map is sparse by construction: generateCurrentMap() inserts
only nodes of instances returned by getInstancePower(), so a filler, tap or decap
has no entry. solve() reads the same map with find() and a zero default, so the
sparse case was already handled twenty lines of context away. The lookup now
matches, and skips a node with no current the way the neighbouring
kSpiceFileMinCurrent guard already skips a negligible one.

Adds report_writers_require_solution, covering write_pg_spice before a solution
exists -- including that no file is left behind -- and all three writers after
one.

Fixes The-OpenROAD-Project#11098

Signed-off-by: Shivaram Mysore <shivaram.mysore@gmail.com>
@shivarammysore
shivarammysore requested a review from a team as a code owner August 7, 2026 22:54
@github-actions github-actions Bot added the size/S label Aug 7, 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 safety checks in the IRSolver class to ensure a power grid solution exists before generating reports (voltage, EM, and SPICE files). Specifically, a new ensureSolution method is added and called in the report-writing functions to prevent out-of-range map lookups and output clear error messages. Additionally, writeSpiceFile is updated to safely handle instances that do not draw power by using find instead of at on the currents map. A new integration test has been added to verify this behavior. There are no review comments, so I have no feedback to provide.

The ensureSolution comment ran to 81 columns against the 80-column limit.
Verified with the same clang-format CI uses, Ubuntu 18.1.3, via --dry-run
-Werror on both changed files.

Signed-off-by: Shivaram Mysore <shivaram.mysore@gmail.com>
@maliberty

Copy link
Copy Markdown
Member

@codex review

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 4b8b59189e

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

insert_decap_with_padding1
missing_resistance
pad_connected_by_abutment
report_writers_require_solution

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Register the regression test with Bazel

When PSM tests are run through Bazel, this CMake-only addition never creates a report_writers_require_solution target because src/psm/test/BUILD generates targets exclusively from ALL_TESTS, where the new name is absent. Consequently, Bazel CI cannot execute this regression; add it to the Bazel test list as well.

AGENTS.md reference: AGENTS.md:L30-L30

Useful? React with 👍 / 👎.

Comment thread src/psm/src/ir_solver.cpp Outdated
Comment on lines +1353 to +1354
if (hasSolution(corner)) {
return;

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Accept solved grids with an empty current map

When a grid is solved without any powered instances—for example, a DEF-only or early-floorplanning analysis using set_pdnsim_net_voltage and no Liberty-backed loads—solve() still populates the voltage solution, but currents_[corner] remains empty, so hasSolution() returns false here. This newly makes analyze_power_grid -voltage_file ..., EM output, and write_pg_spice report “Run analyze_power_grid first” immediately after a successful solve; the writer guard should check the data each writer actually requires rather than treating an empty load map as no solution.

Useful? React with 👍 / 👎.

@maliberty
maliberty requested a review from gadfort August 8, 2026 05:00
Addresses both review comments on The-OpenROAD-Project#11103.

hasSolution() requires the current map to be non-empty, so a grid solved with
no powered instances -- a DEF-only or floorplanning analysis, where the solve
populates voltages and generateCurrentMap() inserts nothing -- would have been
rejected by the guard immediately after a successful solve. Voltages are still
reportable in that case.

Each writer now tests the map it actually reads: the voltage and EM writers
require node voltages, and the spice writer requires the current map, whose
emptiness is already handled by the find() lookup over ITerm nodes. The helper
is renamed reportNoSolution() since it no longer decides, only reports.

Also registers report_writers_require_solution in src/psm/test/BUILD. It was
added to the CMake list only, so Bazel generated no target for it and Bazel CI
could not run the regression. AGENTS.md asks for dual CMake+Bazel registration.

Signed-off-by: Shivaram Mysore <shivaram.mysore@gmail.com>
@shivarammysore

Copy link
Copy Markdown
Contributor Author

Both addressed in bdbcc5c. Thanks — the first one was a real regression I would have shipped.

Accept solved grids with an empty current map (P2). Correct, and the reasoning is right: hasSolution() requires the current map to be non-empty, so a grid solved with no powered instances — a DEF-only or floorplanning analysis where solve() fills the voltages and generateCurrentMap() inserts nothing — would have been rejected by the guard immediately after a successful solve. Voltages are perfectly reportable in that case.

Each writer now tests the map it actually reads rather than a global notion of "solved":

  • writeInstanceVoltageFile → node voltages
  • writeEMFile → node voltages, since generateCurrentMap() derives per-connection current from them
  • writeSpiceFile → the current map, whose emptiness is already handled by the find() lookup over ITerm nodes, so only a missing corner is an error

The helper is renamed reportNoSolution(), since it no longer decides anything — it only reports. hasSolution() is untouched, as getIRDrop() still wants exactly its current meaning.

Register with Bazel (P1). Also correct — report_writers_require_solution was in the CMake list only, so ALL_TESTS generated no target and Bazel CI could not have run it. Added to src/psm/test/BUILD. AGENTS.md does ask for dual registration; I missed it.

One thing to flag rather than leave implicit: I could not run clang-format on this revision — the machine I used for the 18.1.3 check on the previous push is offline. I verified no added line exceeds the column limit, but that is weaker than running the formatter, so please let CI be the judge and I will fix promptly if it objects.

@shivarammysore

Copy link
Copy Markdown
Contributor Author

Follow-up on my formatting caveat: the machine came back, and I have now run the same clang-format CI uses — Ubuntu 18.1.3 — over both changed files with --dry-run -Werror. Clean, no diff. Please disregard that paragraph.

@gadfort gadfort 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.

LGTM, Thanks.

@maliberty
maliberty merged commit c73a960 into The-OpenROAD-Project:master Aug 8, 2026
16 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

psm: write_pg_spice throws map::at on any design with unpowered instances (fillers/taps/decaps)

3 participants