Skip to content

pdn: enforce effective enclosure validation for generated vias - #11539

Merged
maliberty merged 11 commits into
The-OpenROAD-Project:masterfrom
MahmoudNedalSubLaban:pdn-generated-via-enclosure
Nov 2, 2026
Merged

maliberty merged 11 commits into
The-OpenROAD-Project:masterfrom
MahmoudNedalSubLaban:pdn-generated-via-enclosure

Conversation

@MahmoudNedalSubLaban

@MahmoudNedalSubLaban MahmoudNedalSubLaban commented Sep 27, 2026 •

Copy link
Copy Markdown
Contributor

Summary

Generated vias can bypass enclosure validation when no applicable cut-layer
rule replaces the VIARULE default. One reproducer has a 140 nm cut and a
20 nm landing: −60 nm enclosure on each side.

Retain the default independently for each landing without an applicable
override. Preserve negative margins through half-margin calculation and
manufacturing-grid snapping. Applicable overrides remain alternatives;
legal landing-extension attempts remain available. The only production file
changed is src/pdn/src/via.cpp.

Fixes #11538.

Current validation - 24 October

Head: 0eaf08712f2bee52fbbd2493ed67cd9f53d559e6.
Merged upstream: 6f3f28cc85407d71ba64a550cca194d8a6a01c1c.

The enclosure fix remains the only production change relative to upstream.
This update adds native C++ regressions in the existing PDN test targets.
It adds no Tcl tests and makes no additional expected-output changes.

  • 289/289 Bazel targets pass in three fresh executions each: PDN, PSM,
    RAM, and repair_setup_reroute1. Test-result reuse was disabled.
  • The same via-test source on unpatched upstream gives 205/211 passing:
    six defect tests fail in all three runs. With the fix, 211/211 pass.
  • The same connection-test source gives 74/77 passing on unpatched
    upstream: three enclosure-rejection tests fail in all three runs.
    With the fix, 77/77 pass.
  • Positive controls cover valid overrides, locked/pinned containment,
    tapered stacks, legal landing extension, obstruction ripup, and
    permitted/forbidden nonpreferred-direction widening.
  • clang-format --dry-run --Werror and git diff --check pass.

Five recently added connection fixtures expected successful construction
using insufficient enclosure. An independent check of their actual OpenDB
rectangles found margins of 0, 5, or -5 DBU against 10-DBU requirements.
The positive fixtures now use adequate landings, and the original undersized
locked/pinned, outer-stack, and widening cases are rejection regressions.
The rectangle checks do not call the generator's enclosure predicate.

The previously noted multiport RAM local mismatch does not occur in this
Bazel run. Its existing expected output was not changed for this update.
These are local results; CI and maintainer review of the new head are pending.

Current reproduction commands and rule precedence.

Earlier validation - 11 October

Head: 1ec9fa04d61aaba2bfb0672c100b3bc0676617df.
Upstream base: b931a33de66ae1956279d47eda0cc26df25dedb7.

Fresh local CMake/GCC checks:

  • 216/216 PDN tests pass (201 Tcl, 15 Python; 32.11 s).
  • repair_setup_reroute1 and make_7x7_nangate45 pass.
  • Both geometry-defect assertions fail before the fix and pass after it.
    The valid control's DEF and geometry tables are byte-identical.
  • git diff --check passes. All contributor commits carry DCO sign-off.

The additional multiport RAM shell-runner check still has a local route-order
mismatch with its golden. Fresh unpatched and patched outputs differ only
in the previously reviewed generated-via definition; their signal routes
match each other. That unrelated golden is unchanged. This is not a claim
that every local regression passes or that a full local Bazel build was run.

Public reproduction commands and rule precedence.
Existing override tests, including stacked_via_merged_enclosure and the
IHP cases, remain unchanged. New-head CI and maintainer review are pending.

Merge #12 follow-up

Merge #12
had two regression failures as well as infrastructure errors.

  • Resizer: upstream commit
    53b91d5
    fixes its expected log. The complete failing log matches that correction.
    The current upstream merge includes it; this PR adds no resizer change.
  • Blanket-obstruction pad fixture: added after this PR's original base.
    Its old expected DEF includes 374 generated vias with 195 nm metal9
    enclosure against a 200 nm requirement
    . An independent OpenDB rectangle
    audit finds 374 invalid instances before and zero after the fix
    (756 → 382 generated instances; 187 rejected on each supply).
    Metal shapes, instance poses and net-terminal records are unchanged.

The follow-up changes only that fixture's expected DEF and log: one invalid
via definition and its 374 instances disappear, and all 374 rejection warnings
remain visible. No production code, test assertion or technology rule is
changed in this follow-up.

Both supply checks already fail in this fixture. After rejection, native
unconnected-shape messages increase 468 → 1,001 and unconnected-instance
messages 200 → 266. These are message counts, not a sign-off metric.

Consequences and limits

Rejecting an illegal via does not guarantee a replacement connection.
In the earlier, already-disconnected pads_black_parrot_macro_grid fixture,
the retained audit reports disconnected-shape markers increasing from
1,595 to 9,349 for VDD and 1,035 to 9,133 for VSS.

This does not solve DFT68's separate PDN-0179 construction problem or
establish full-chip PPA, IR-drop, electromigration or universal PDN repair.
Local results are not upstream CI or maintainer acceptance.

Historical validation and tested executable identities

The September 28 local PDN result was 207/207. The earlier RAM correction
(a69d08ebb) changed one via-definition line per expected DEF: the existing
legal Via1Array-1 replaces the insufficiently enclosed Via1Array-0.
The retained OpenDB audits removed 36/54 invalid generated instances from
the two RAM fixtures, with supply checks passing before and after.

Jenkins head #3
and merge #3
passed on September 28 (4,467 passing, four skipped; respectively 10 and
1,768 fresh executions). These historical runs do not certify the current head.

Fresh October 11 executable SHA-256 values:

Arm SHA-256
Current upstream production source d4ea7743b5c232cc3583831e6918a644347b21be54660d832690b83ede23defc
Current merged production fix eba45f730c88e5adad31ead83cdc9ecff0ecdb02c4493993dbec67dad651165b

The builds use identical dependencies. Their production-source difference
is only via.cpp. The corrected executable was built from 3396a194ae;
the subsequent 1ec9fa04d6 changes only the two reviewed expected-output
files, used by the final regression run.

Review of the eleven existing PDN expected-output changes

These counts come from the retained original-versus-corrected OpenDB review,
not a recount by the latest regression run. They count generated-array
instances, not individual cuts or independent designs. Existing test scripts
and assertions are unchanged, and macro/cell poses are preserved.

Existing test Invalid generated arrays removed Physical reason / other consequence
macros_cells_via_failure 1 Reject the malformed stack; two companion vias at the same stack location also disappear. Metal unchanged; existing native failure remains.
pads_black_parrot 374 Default enclosure rejects the arrays. Metal unchanged; disconnected-marker counts remain 377/377 for VDD/VSS.
pads_black_parrot_flipchip 208 Same rejection; metal unchanged; marker counts remain 311/307.
pads_black_parrot_flipchip_connect_bumps 165 Same rejection; metal unchanged; existing native failure remains.
pads_black_parrot_flipchip_connect_overpads 0 final instances Final PG geometry is identical. Three unused malformed via definitions disappear; 25 cleanup warnings become earlier rejection warnings.
pads_black_parrot_grid_define 374 Same rejection; metal unchanged; markers remain 377/377.
pads_black_parrot_macro_grid 1,779 Illegal links disappear in an already-disconnected network. Markers increase as above; 30 metal rectangles disappear and one shortened rectangle appears, consistent with downstream cleanup. The pad-boundary assertion still reports zero violations.
pads_black_parrot_offset 374 Default enclosure rejects the arrays. Metal unchanged; markers remain 377/377.
pads_black_parrot_straps 263 Same rejection; metal unchanged; markers remain 377/377.
polygon_cut_corners_pads 85 Same rejection; metal unchanged; existing native failure remains.
polygon_two_macros 2 Same rejection; metal unchanged; existing native failure remains.

The metal-cleanup explanation is an inference from geometry differences, not a
per-operation trace. These expected outputs document rejection consequences;
they do not certify these fixtures as fully connected or physically signed off.

Related earlier work: #1828, #11193 and #11212. Their functionality and
attribution are preserved.

Retain each generated-rule default during validation when no applicable
cut-layer enclosure rule replaces it. Preserve signed deficits through
half-margin calculation and manufacturing-grid snapping.

Add actual-geometry regressions and retain valid override behavior.
Update reviewed expected outputs to expose rejected malformed connections.

Signed-off-by: Mahmoud Sub Laban <137659892+MahmoudNedalSubLaban@users.noreply.github.com>

@github-actions github-actions 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.

Welcome to OpenROAD! Thanks for opening your first PR.
Before we review:

Please ensure:

  • CI passes
  • Code is properly formatted
  • Tests are included where applicable
    A maintainer will review shortly!

@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 improves the handling of generated-via enclosures in the PDN generator. It updates snapping logic to prevent rounding enclosure deficits up to zero, changes double enclosure division to use floor division instead of truncating integer division, and ensures default VIARULE enclosures are retained when no cut-layer overrides apply. Additionally, several regression tests are introduced. The feedback suggests optimizing the floor division by replacing the floating-point std::floor conversions with highly efficient arithmetic right shifts (>> 1) on signed integers.

Comment thread src/pdn/src/via.cpp
Comment on lines +2101 to +2124
bottom_enclosure_->setX(determine_enclosure(
use_bottom_min_enclosure,
true,
bottom_min_enclosure.getX(),
static_cast<int>(std::floor(double_enc_x / 2.0)),
lower_constraint_));
bottom_enclosure_->setY(determine_enclosure(
use_bottom_min_enclosure,
false,
bottom_min_enclosure.getY(),
static_cast<int>(std::floor(double_enc_y / 2.0)),
lower_constraint_));
top_enclosure_->setX(determine_enclosure(
use_top_min_enclosure,
true,
top_min_enclosure.getX(),
static_cast<int>(std::floor(double_enc_x / 2.0)),
upper_constraint_));
top_enclosure_->setY(determine_enclosure(
use_top_min_enclosure,
false,
top_min_enclosure.getY(),
static_cast<int>(std::floor(double_enc_y / 2.0)),
upper_constraint_));

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

Using floating-point division double_enc_x / 2.0, std::floor, and casting back to int introduces unnecessary overhead. Since double_enc_x and double_enc_y are signed integers, we can perform highly efficient integer-only floor division by 2 using the arithmetic right shift operator >> 1 (which is guaranteed to be arithmetic and perform floor division in C++20, and is universally supported as such in C++17). This avoids floating-point conversions entirely.

          bottom_enclosure_->setX(determine_enclosure(
              use_bottom_min_enclosure,
              true,
              bottom_min_enclosure.getX(),
              double_enc_x >> 1,
              lower_constraint_));
          bottom_enclosure_->setY(determine_enclosure(
              use_bottom_min_enclosure,
              false,
              bottom_min_enclosure.getY(),
              double_enc_y >> 1,
              lower_constraint_));
          top_enclosure_->setX(determine_enclosure(
              use_top_min_enclosure,
              true,
              top_min_enclosure.getX(),
              double_enc_x >> 1,
              upper_constraint_));
          top_enclosure_->setY(determine_enclosure(
              use_top_min_enclosure,
              false,
              top_min_enclosure.getY(),
              double_enc_y >> 1,
              upper_constraint_));

Comment thread src/pdn/src/via.cpp
Comment on lines +2155 to +2178
bottom_enclosure_->setX(
determine_enclosure(use_bottom_min_enclosure,
true,
bottom_min_enclosure.getX(),
static_cast<int>(std::floor(double_enc_x / 2.0)),
lower_constraint_));
bottom_enclosure_->setY(
determine_enclosure(use_bottom_min_enclosure,
false,
bottom_min_enclosure.getY(),
static_cast<int>(std::floor(double_enc_y / 2.0)),
lower_constraint_));
top_enclosure_->setX(
determine_enclosure(use_top_min_enclosure,
true,
top_min_enclosure.getX(),
static_cast<int>(std::floor(double_enc_x / 2.0)),
upper_constraint_));
top_enclosure_->setY(
determine_enclosure(use_top_min_enclosure,
false,
top_min_enclosure.getY(),
static_cast<int>(std::floor(double_enc_y / 2.0)),
upper_constraint_));

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

Using floating-point division double_enc_x / 2.0, std::floor, and casting back to int introduces unnecessary overhead. Since double_enc_x and double_enc_y are signed integers, we can perform highly efficient integer-only floor division by 2 using the arithmetic right shift operator >> 1 (which is guaranteed to be arithmetic and perform floor division in C++20, and is universally supported as such in C++17). This avoids floating-point conversions entirely.

      bottom_enclosure_->setX(
          determine_enclosure(use_bottom_min_enclosure,
                              true,
                              bottom_min_enclosure.getX(),
                              double_enc_x >> 1,
                              lower_constraint_));
      bottom_enclosure_->setY(
          determine_enclosure(use_bottom_min_enclosure,
                              false,
                              bottom_min_enclosure.getY(),
                              double_enc_y >> 1,
                              lower_constraint_));
      top_enclosure_->setX(
          determine_enclosure(use_top_min_enclosure,
                              true,
                              top_min_enclosure.getX(),
                              double_enc_x >> 1,
                              upper_constraint_));
      top_enclosure_->setY(
          determine_enclosure(use_top_min_enclosure,
                              false,
                              top_min_enclosure.getY(),
                              double_enc_y >> 1,
                              upper_constraint_));

Apply the repository Tcl formatter and split long diagnostic output lines.
Preserve geometry assertions and byte-identical reproduction outputs.

Signed-off-by: Mahmoud Sub Laban <137659892+MahmoudNedalSubLaban@users.noreply.github.com>
@maliberty
maliberty requested a review from gadfort September 27, 2026 14:22
@maliberty

Copy link
Copy Markdown
Member

@gadfort would you review this? The via code is involved as I recall

The enclosure guard selects Via1Array-1 in the two Nangate45 RAM
tests. Via1Array-0 requires 35 nm on both axes, but the metal1
landing provides only 5 nm on one axis.

Update only the generated-via definition in each expected DEF.
Keep signal-routing, placement and Verilog expectations unchanged.
Independent OpenDB checks reduce invalid instances from 36/54 to
zero while preserving both supply connectivity checks.

Signed-off-by: Mahmoud Sub Laban <137659892+MahmoudNedalSubLaban@users.noreply.github.com>
@MahmoudNedalSubLaban
MahmoudNedalSubLaban requested a review from a team as a code owner September 28, 2026 12:51
@MahmoudNedalSubLaban

Copy link
Copy Markdown
Contributor Author

I’ve pushed a69d08e to address the two RAM regression failures. Both tests now pass, and the Jenkins head and merge builds are successful. The expected-output changes were checked against actual via geometry and supply connectivity; the PR description includes the results and limitations.

Could a maintainer approve the pending GitHub Actions workflows when convenient?
Thank you for reviewing.

@maliberty
maliberty removed their request for review September 29, 2026 03:22
@MahmoudNedalSubLaban

Copy link
Copy Markdown
Contributor Author

The head build remains successful. Merge build #8 failed while fetching build dependencies with HTTP 403 errors; merge build
#7 passed. Could a maintainer approve the pending GitHub Actions workflows and rerun merge CI when convenient? @gadfort, I would appreciate your review when available.

Signed-off-by: Mahmoud Sub Laban <137659892+MahmoudNedalSubLaban@users.noreply.github.com>
Signed-off-by: Mahmoud Sub Laban <137659892+MahmoudNedalSubLaban@users.noreply.github.com>

Copy link
Copy Markdown
Contributor Author

Pushed 1ec9fa0 to address merge #12.

The resizer failure is the upstream golden-log issue fixed by 53b91d5, now included by merging current master. The new pad fixture's expected DEF retained 374 vias with 195 nm enclosure where metal9 requires 200 nm. The rectangle audit confirms their rejection, with metal shapes and instance poses unchanged. Only that fixture's expected DEF and log change; all rejection warnings remain.

Fresh local checks: 216/216 PDN tests, the resizer test and single-port RAM pass. Defect controls fail before/pass after; the valid control is unchanged. The separate local multiport RAM route-order mismatch occurs with both executables, so its golden remains unchanged.

The pad fixture still fails both supply checks and reports more disconnected shapes after illegal connections are removed. This is not a connectivity repair. The PR description includes the geometry, source/executable bindings and limitations. New-head CI and review remain pending.

Signed-off-by: Mahmoud Sub Laban <137659892+MahmoudNedalSubLaban@users.noreply.github.com>
Exercise per-landing defaults, applicable enclosure alternatives, signed rounding, and rejection of insufficiently enclosed cuts with the native via fixture. Six defect tests fail consistently on unpatched upstream; the applicable-override control passes.

Document the native Bazel target and retain the existing integration geometry checks.

Signed-off-by: Mahmoud Sub Laban <137659892+MahmoudNedalSubLaban@users.noreply.github.com>
Use legal landings for containment, tapered-stack, extension and ripup controls. Check emitted rectangles and retain the undersized landings as rejection regressions.

Signed-off-by: Mahmoud Sub Laban <137659892+MahmoudNedalSubLaban@users.noreply.github.com>
@github-actions github-actions Bot added size/L and removed size/M labels Oct 24, 2026

Copy link
Copy Markdown
Contributor Author

I've updated this PR to current master (6f3f28cc85) and added native C++ tests in TestPDNVia.cpp and TestPDNConnect.cpp.

The same tests reproduce six enclosure defects and three caller-level rejection failures on unpatched upstream, consistently across three runs. They all pass with the fix. The complete PDN, PSM and RAM suites, plus repair_setup_reroute1, also pass: 289 targets, each executed three times with test caching disabled.

The merge exposed five connection fixtures that relied on under-enclosed vias. I kept their containment, extension and ripup coverage using valid landings, added checks against the emitted rectangles, and retained the undersized cases as rejection tests. There are no additional production changes or expected-output updates.

@gadfort, could you review the per-landing default fallback and signed rounding when you have time? The updated description and reproduction notes include the results and remaining limits. Rejecting an illegal via still does not guarantee a replacement connection.

@gadfort

gadfort commented Oct 24, 2026

Copy link
Copy Markdown
Contributor

@MahmoudNedalSubLaban I'll take a look this weekend. Thanks for updating the tests.

Use variant so the content scan does not mistake a shell variable for a restricted technology name. Commands and output paths are unchanged.

Signed-off-by: Mahmoud Sub Laban <137659892+MahmoudNedalSubLaban@users.noreply.github.com>

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

Overall looks good, if the added tcl tests are not checking anything more than the CPP test, then I would suggest removing them. If they do, then they should mirror the other tests so its a little easier to manage.

Keep one integration test for rejection reporting and supply connectivity, using the standard PDN test helpers. Remove the redundant Tcl cases and custom runner; enclosure assertions remain in the C++ tests.

Signed-off-by: Mahmoud Sub Laban <137659892+MahmoudNedalSubLaban@users.noreply.github.com>

Copy link
Copy Markdown
Contributor Author

Thanks for reviewing, @gadfort. Pushed b5d6449.

I removed the two redundant Tcl cases and the custom runner and audit scripts. I kept one integration test because it checks the rejected-connection report and VDD/VSS connectivity after pdngen, which the C++ tests do not cover together. It now uses the standard PDN test helpers and file comparisons.

No production changes. All 287 affected test targets passed three fresh runs locally. Could you take another look?

@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

Resolve the sole modify/delete conflict by accepting upstream removal of src/pdn/test/CMakeLists.txt. Preserve the reviewed enclosure fix, regression fixtures, expected outputs, and Bazel test registration.

Signed-off-by: Mahmoud Sub Laban <137659892+MahmoudNedalSubLaban@users.noreply.github.com>
@MahmoudNedalSubLaban

Copy link
Copy Markdown
Contributor Author

Thank you for the review and approval, @gadfort. I merged current master in 26f0193 and resolved the conflict by keeping upstream's CMake removal. The enclosure fix and regression tests are unchanged, with Bazel registration preserved.

@gadfort

gadfort commented Nov 2, 2026

Copy link
Copy Markdown
Contributor

@maliberty can you approve for the maintainers?

@maliberty
maliberty merged commit ef9b293 into The-OpenROAD-Project:master Nov 2, 2026
20 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.

pdn: generated vias can bypass required enclosure validation on narrow overlaps

3 participants