Skip to content

rsz: clamp buffer locations to core area during insertion#10141

Open
openroad-ci wants to merge 13 commits intoThe-OpenROAD-Project:masterfrom
The-OpenROAD-Project-staging:secure-rsz-instance-outside-core
Open

rsz: clamp buffer locations to core area during insertion#10141
openroad-ci wants to merge 13 commits intoThe-OpenROAD-Project:masterfrom
The-OpenROAD-Project-staging:secure-rsz-instance-outside-core

Conversation

@openroad-ci
Copy link
Copy Markdown
Collaborator

Summary

RSZ-inserted buffers could land outside the core area, in the die-core gap where no placement rows exist, causing downstream DPL legalization failures. Clamp buffer coordinates to the core bounding box at the point of insertion.

Root Cause

The buffers in question are wire repeaters produced by repair_design (and similarly by repair_setup / repair_hold rebuffering). The insertion mechanism:

  1. RSZ builds a Steiner tree over the driver pin + all load pins of a net (SteinerTree::findSteinerPoints).
  2. isTopLevelPort(load_pin) returns true for BTerms, so top-level ports become Steiner terminals. Their coordinates come from dbBTerm::getFirstPinLocation() — which returns the die-boundary pin location, not a core-side coordinate.
  3. When a wire segment from a core-side point to a die-edge BTerm exceeds max_wire_length, RepairDesign::repairNet inserts a repeater by interpolating a point along the segment:
    buf_x = to_x + d * dx    // d ∈ (0, 1]
    buf_y = to_y + d * dy
    
    If the segment endpoint is at the die edge, the interpolated point lands inside the die-core gap — the band between die boundary and core row area.
  4. setLocation(buf_inst, {buf_x, buf_y}) was called without clamping — buffer placed outside core.
  5. DPL cannot legalize: no placement row exists at those coordinates.

Verification

Reproduced on a customer design — see The-OpenROAD-Project-private/OpenROAD-flow-scripts#1622 for details. Wire repeaters inserted during the place-resize stage were observed outside the core, all traced to die-edge BTerms feeding the Steiner-point interpolation.

Fix

clampLocToCore() (Resizer.cc, Resizer.hh):

  • Returns (x, y) clamped into [core.xMin, core.xMax - cell.width] × [core.yMin, core.yMax - cell.height]
  • std::max guard for the upper bound handles the edge case where the core is smaller than the cell (so std::clamp is safe — requires hi >= lo)

setLocation() applies the clamp and emits [INFO RSZ-0077] {instance_name} clamped to core ({orig_x}, {orig_y}) -> ({new_x}, {new_y}) whenever a buffer is moved — per-buffer detail, always on, instance name lets you grep the DEF directly.

Two insertion paths are both covered:

  1. setLocation() path (repair_hold, repair_setup, make_buffer) — clamp inside setLocation()
  2. ODB insertBuffer* path (repair_design wire repeaters) — insertBufferPostProcess(buffer_inst, loc) calls setLocation after ODB places the buffer, re-placing with the clamped coordinate if needed

Why RSZ-layer (not ODB-layer) clamping

Tempting alternative: clamp inside dbInsertBuffer::placeBufferAtLocation() (single ODB site). Rejected because:

  • "Core area" is a placement/floorplan concept; ODB is the geometry DB layer. Adding core-awareness to ODB is a layering violation.
  • dbInsertBuffer is used by non-RSZ callers (e.g., swap_pins_dont_touch) that don't necessarily want core-clamping.
  • RSZ-layer clamping keeps the policy co-located with the decision-maker and still hits every path through one helper.

Testing

New test: repair_design_outside_core

  • 200×200 μm die with tight core (10, 10)–(90, 90) μm
  • Driver at core center, 10 loads deliberately placed outside core at x=195000 DBU
  • set_max_fanout 5 + repair_design -max_wire_length 100 triggers wire-repeater insertion
  • Asserts all TIMING-source instances remain inside the core post-repair
  • Log confirms per-buffer clamp actions

Updated golden files (11 tests) — all exercise paths that now emit per-buffer RSZ-0077 info messages:

Test Why it hits the clamp
repair_hold2 / repair_hold8 / repair_hold11 / repair_hold12 Loads at die-bottom pins (y=0) → hold-buffer insertion lands below core
repair_hold14 Load placement outside core → hold buffer clamped
repair_slew4 / repair_slew12 Driver cell larger than core on one axis → clamp fires
repair_wire11 Long wires to off-core loads → repeaters clamped
repair_tie5 Tie-cell insertion near die edge
upf_aes Timing-driven global_placement inserts a repair_design buffer whose top edge extends past core yMax
repair_design_outside_core New test (see above)

Full rsz regression: 195/195 pass.

Type of Change

  • Bug fix

Impact

  • All RSZ-inserted buffers now land inside the core. DPL legalization no longer fails on wire repeaters created from die-edge BTerms.
  • Designs that previously produced out-of-core buffers will see slightly different timing results (wire lengths / parasitics reflect the new, legal locations).
  • New per-buffer RSZ-0077 info messages replace the prior summary warning — instance name + coordinates, more useful for diagnosis, no debug flag needed.

Verification

  • Local build passes (./etc/Build.sh).
  • Relevant rsz regression (195/195) passes.
  • clang-format, tclfmt, tclint clean.
  • Commits signed (DCO).

Related Issues

Fixes The-OpenROAD-Project-private/OpenROAD-flow-scripts#1622

Buffers inserted by RSZ (repair_design, repair_hold, repair_setup,
recover_power) could be placed outside the core area when Steiner
points fall beyond the core boundary. This causes legalization failures
and DRC violations.

Add clampLocToCore() that clamps buffer coordinates to the core
bounding box, accounting for cell width/height. Apply it in both
the makeBuffer (setLocation) and insertBuffer (ODB) paths.

Emit RSZ-0077 warning when any buffer was moved, and a debug-level
message with per-buffer coordinates (set_debug_level RSZ buffer_move 1).

Add repair_design_outside_core regression test that verifies all
inserted buffers stay within core.

Fixes The-OpenROAD-Project-private/OpenROAD-flow-scripts#1622

Signed-off-by: minjukim55 <mkim@precisioninno.com>
Signed-off-by: minjukim55 <mkim@precisioninno.com>
Signed-off-by: minjukim55 <mkim@precisioninno.com>
RepairHold was missing the flag reset and warnBufferMovedIntoCore()
call that all other repair passes (RepairDesign, RepairSetup,
RecoverPower) already had. This meant hold repair could silently
place buffers outside core without warning.

Signed-off-by: minjukim55 <mkim@precisioninno.com>
Buffer locations clamped to core area change wire lengths, causing
~1% regression in DRT max_slew_slack and max_capacitance_slack.
This is expected QoR impact from correct buffer placement.

Signed-off-by: minjukim55 <mkim@precisioninno.com>
The previous RSZ-0077 warning ("some buffers were moved inside the core")
required enabling a debug level to see which buffers were clamped and
where. Replace this with a direct info log from clampLocToCore() that
always reports the master cell, original location, and clamped
location — useful for debugging without needing to rerun with
set_debug_level RSZ buffer_move 1.

Remove the now-unused buffer_moved_into_core_ flag and
warnBufferMovedIntoCore() helper from Resizer, and strip the reset/warn
calls that were sprinkled through RepairDesign, RepairSetup, RepairHold,
RecoverPower, and bufferInputs/bufferOutputs.

Update golden files for the 10 regression tests that previously emitted
the summary warning to instead match the new per-buffer INFO messages.

Signed-off-by: minjukim55 <mkim@precisioninno.com>
Previously the three insertBuffer* wrappers each duplicated a
clamped_loc/effective_loc dance before calling the ODB layer, and the
clamp log message showed the library cell name (e.g. CLKBUF_X1) which
is not actionable when tracing a specific misplaced buffer.

Fold the clamp into setLocation(), which now runs after the ODB insert
returns the instance. This lets the log use the instance name
(e.g. hold5, wire1) and lets clampLocToCore return by value with no
out-parameter.

- clampLocToCore: value-returning, const, no logging
- setLocation: calls clampLocToCore, logs on clamp with instance name
- insertBuffer{AfterDriver,BeforeLoad,BeforeLoads}: drop pre-clamp,
  call setLocation(buffer_inst, *loc) after ODB insert succeeds
- Update 10 .ok files: log now reports instance names

Signed-off-by: minjukim55 <mkim@precisioninno.com>
The three insertBuffer* wrappers each repeated the same tail:

  if (loc) {
    setLocation(buffer_inst, *loc);
  }
  insertBufferPostProcess(buffer_inst);

Move the if-guard into insertBufferPostProcess, which now takes the
optional location alongside the instance.  This removes three copies
of identical glue and keeps "place, then post-process" atomic so a
future contributor cannot inject code between the two steps.

Signed-off-by: minjukim55 <mkim@precisioninno.com>
The timing-driven global_placement invoked in upf_aes.tcl inserts a
repair_design buffer at y=966219 whose top edge would extend 619 DBU
past core yMax=970000.  The new RSZ-0077 info message now records
the clamp; sync the golden log.

Signed-off-by: minjukim55 <mkim@precisioninno.com>
Lost in the fold-into-postProcess refactor; restore it above each
insertBufferPostProcess() call site for readability.

Signed-off-by: minjukim55 <mkim@precisioninno.com>
Copy link
Copy Markdown
Contributor

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

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 replaces the manual buffer-moved-into-core warning with an automatic clamping mechanism using a new clampLocToCore method, and adds a corresponding test case. Feedback highlights that the modified signature of insertBufferPostProcess causes compilation errors at several call sites where references are passed instead of pointers, and misses other sites like makeBuffer. To resolve these errors and improve API usability, it is recommended to revert the signature change and have the method retrieve the buffer's location internally, ensuring consistent clamping for all inserted buffers.

Comment thread src/rsz/include/rsz/Resizer.hh Outdated
const odb::Point& loc);

void insertBufferPostProcess(odb::dbInst* buffer_inst);
void insertBufferPostProcess(odb::dbInst* buffer_inst, const odb::Point* loc);
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

The change to the signature of insertBufferPostProcess to take a pointer const odb::Point* loc introduces several issues:

  1. Compilation Errors: The call sites updated in Resizer.cc (e.g., line 4910) pass a reference loc to this pointer parameter, which will not compile in C++.
  2. Incomplete Updates: Other call sites not included in this diff (such as makeBuffer) will also fail to compile as they now lack the required second argument.
  3. Clamping Policy: Making the location optional via a pointer risks missing clamping for some buffers if nullptr is passed, which contradicts the goal of ensuring all RSZ-inserted buffers land inside the core.

Instead of changing the signature, it is recommended to keep the original void insertBufferPostProcess(odb::dbInst* buffer_inst) and internally call setLocation(buffer_inst, buffer_inst->getLocation()). This ensures all inserted buffers are clamped using their current location without needing to modify any call sites, prioritizing API usability.

Suggested change
void insertBufferPostProcess(odb::dbInst* buffer_inst, const odb::Point* loc);
void insertBufferPostProcess(odb::dbInst* buffer_inst);
References
  1. API usability can be prioritized over strict const-correctness.

Comment thread src/rsz/src/Resizer.cc Outdated

// Legalize the buffer position and update the design area
insertBufferPostProcess(buffer_inst);
insertBufferPostProcess(buffer_inst, loc);
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

This call will fail to compile because loc is a reference (const odb::Point&) but the new signature of insertBufferPostProcess expects a pointer. Reverting the signature change as suggested in the header file would resolve this and simplify the call sites.

  insertBufferPostProcess(buffer_inst);

Comment thread src/rsz/src/Resizer.cc Outdated

// Legalize the buffer position and update the design area
insertBufferPostProcess(buffer_inst);
insertBufferPostProcess(buffer_inst, loc);
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

This call will fail to compile because loc is a reference (const odb::Point&) but the new signature of insertBufferPostProcess expects a pointer.

  insertBufferPostProcess(buffer_inst);

Comment thread src/rsz/src/Resizer.cc Outdated

// Legalize the buffer position and update the design area
insertBufferPostProcess(buffer_inst);
insertBufferPostProcess(buffer_inst, loc);
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

This call will fail to compile because loc is a reference (const odb::Point&) but the new signature of insertBufferPostProcess expects a pointer.

  insertBufferPostProcess(buffer_inst);

Comment thread src/rsz/src/Resizer.cc Outdated
Comment on lines +5158 to +5166
void Resizer::insertBufferPostProcess(dbInst* buffer_inst,
const odb::Point* loc)
{
// Clamp to core if the caller provided a target location. ODB's
// insertBuffer* already placed the instance at *loc; setLocation will
// re-place it if clamping changed the point.
if (loc) {
setLocation(buffer_inst, *loc);
}
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

By reverting the signature change and querying the instance's current location, you ensure that clamping is always applied to any buffer undergoing post-processing, regardless of how it was instantiated. This also fixes the compilation errors at the call sites and ensures that methods like makeBuffer also benefit from the clamping logic. This approach prioritizes API usability over the proposed signature change.

void Resizer::insertBufferPostProcess(dbInst* buffer_inst)
{
  // Clamp to core. ODB's insertBuffer* already placed the instance;
  // setLocation will re-place it if clamping is required.
  setLocation(buffer_inst, buffer_inst->getLocation());
}
References
  1. API usability can be prioritized over strict const-correctness.

@github-actions
Copy link
Copy Markdown
Contributor

clang-tidy review says "All clean, LGTM! 👍"

Add a comment explaining why bufferInputs / bufferOutputs pass
loc=nullptr on purpose: port buffers are placed near their BTerm,
which can sit at the die edge outside the core, and that is by
design -- they should stay adjacent to the port.

Makes the `if (loc)` guard self-documenting so future readers do
not wonder whether the nullptr case is a bug.

Comment-only change; no behavioral diff.

Signed-off-by: minjukim55 <mkim@precisioninno.com>
@minjukim55
Copy link
Copy Markdown
Contributor

Scope clarification — the branch now includes commit afb03b614a adding a code comment in insertBufferPostProcess to document the port buffer exception. The PR description below should also be read with this clarification:

Port buffer exception

bufferInputs / bufferOutputs pass loc=nullptr into insertBufferAfterDriver / insertBufferBeforeLoad; insertBufferPostProcess skips the clamp when loc == nullptr. Port buffers are placed by ODB near their BTerm location, which can sit at the die edge outside the core area, and that is by design — a port buffer should stay adjacent to the port it buffers. This matches the pre-existing behavior.

The three paths through insertBufferPostProcess:

  1. setLocation() path (repair_hold, repair_setup, make_buffer) — clamp inside setLocation()
  2. ODB insertBuffer* path with explicit loc (repair_design wire repeaters) — clamp via insertBufferPostProcess(buffer_inst, loc)
  3. Port buffer path (bufferInputs / bufferOutputs, loc=nullptr) — not clamped (by design, see above)

The "Impact" section of the description should be read as: wire repeaters and other loc-aware inserts now land inside the core; port buffers remain at BTerm location (unchanged from prior behavior).

@github-actions
Copy link
Copy Markdown
Contributor

clang-tidy review says "All clean, LGTM! 👍"

Make insertBufferPostProcess unconditionally clamp every buffer
(including port buffers from bufferInputs/bufferOutputs) to the
core area.  Previously port buffers passed loc=nullptr and skipped
the clamp; they can now land in the die-core gap on designs where
die != core, causing the same DPL legalization failures as wire
repeaters.

Demote the per-buffer RSZ-0077 clamp message from info to
debugPrint (category "buffer_clamp", level 1) so it only appears
when explicitly enabled with -set_debug rsz buffer_clamp 1.
This keeps logs clean while still allowing diagnosis when needed.

Updated 18 golden files: removed RSZ-0077 info lines from 12 .ok
files and updated 4 .defok files where port buffers now land
inside the core.

Signed-off-by: minjukim55 <mkim@precisioninno.com>
Enable set_debug_level RSZ buffer_clamp 1 in the test so the
per-buffer clamp debug messages appear in the log and get validated
against the golden file.

Signed-off-by: minjukim55 <mkim@precisioninno.com>
@github-actions
Copy link
Copy Markdown
Contributor

clang-tidy review says "All clean, LGTM! 👍"

1 similar comment
@github-actions
Copy link
Copy Markdown
Contributor

clang-tidy review says "All clean, LGTM! 👍"

Comment thread src/rsz/src/Resizer.cc
// ODB's insertBuffer* already placed the instance; re-run through
// setLocation so clampLocToCore can pull it back inside the core if
// the insertion point landed in the die-core gap.
setLocation(buffer_inst, buffer_inst->getLocation());
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.

How about replacing the setLocation() call in the following API?

void dbInsertBuffer::placeBufferAtLocation(dbInst* buffer_inst,
                                           const Point& loc,
                                           const char* reason)
{
  buffer_inst->setLocation(loc.getX(), loc.getY());
  buffer_inst->setPlacementStatus(dbPlacementStatus::PLACED);
  dlogPlacedBuffer(buffer_inst, loc, reason);
}

@maliberty
Copy link
Copy Markdown
Member

I have no further comments beyond @jhkim-pii

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.

4 participants