Skip to content

sync - #2

Open
nanocoh wants to merge 2621 commits into
keplertech:masterfrom
The-OpenROAD-Project:master
Open

sync#2
nanocoh wants to merge 2621 commits into
keplertech:masterfrom
The-OpenROAD-Project:master

Conversation

@nanocoh

@nanocoh nanocoh commented May 23, 2026

Copy link
Copy Markdown

Summary

[Describe your changes here]

Type of Change

  • Bug fix
  • New feature
  • Breaking change
  • Refactoring
  • Documentation update

Impact

[How does this change the tool's behavior?]

Verification

  • I have verified that the local build succeeds (./etc/Build.sh).
  • I have run the relevant tests and they pass.
  • My code follows the repository's formatting guidelines.
  • I have included tests to prevent regressions.
  • I have signed my commits (DCO).

Related Issues

[Link issues here]

eder-matheus and others added 25 commits July 30, 2026 18:33
…rtical

Signed-off-by: Eder Monteiro <emrmonteiro@precisioninno.com>
Signed-off-by: Eder Monteiro <emrmonteiro@precisioninno.com>
Signed-off-by: Arthur Koucher <arthurkoucher@precisioninno.com>
Boost's rtree invokes its indexable getter on every element a query
visits, and BBoxIndexableGetter derived each element's box from ODB
(inst->getBBox()->getBox(), sbox->getBox()) — a chain of dbTable lookups
per visit.  The MinSize/MinHeight predicates then derived it a second
time.  At zoom-out the query box spans the whole design, so nothing
prunes and the getter runs once per element per tile per layer; on a
676k-instance design that was ~40% of tile-render CPU, and 91% of what
remained once every visibility flag was switched off.

Store the Rect in the tree instead: insts, blockages, obstructions and
both special-net shape maps become RtreeRect, whose value is the
std::pair<Rect, payload> boost indexes on directly.  The predicates
collapse to one pair overload that reads the stored box.  Public range
types are unchanged — the queries unpack .second when collecting — so no
caller outside this file is affected.  Costs 16 bytes per entry.

Rendering is bit-identical: verified over 3744 tiles (51 layers x 8
zoom/position combos x 9 visibility variants) plus a dpr 1/1.5/2/2.5/3
sweep, comparing PNG hashes against the unpatched binary.

Signed-off-by: Matt Liberty <mliberty@precisioninno.com>
The per-instance and special-net-via passes each walked all of a
master's geometry and called dbBox::getTechLayer() on every box to
filter for the layer being drawn.  getTechLayer() resolves through
getOwner -> block/lib -> chip -> tech -> layer_tbl, so it is several
dependent pointer chases, and the web viewer pays it once per layer per
tile — 49 times per view where the Qt viewer draws every layer into one
image and pays it once.  Together the master and via sides were ~54% of
tile-render CPU on a 676k-instance design with a 30k-net power grid.

Bucket both by layer up front, in a GeomCache built once and published
as an immutable snapshot:

  master_geom: layer -> master -> {OBS polys/boxes, pin polys/boxes}
  via_boxes:   layer -> via master -> boxes in via-local coordinates

The master half mirrors gui::LayoutViewer::boxesByLayer; the via half
has no Qt counterpart because dbBox::getViaLayerBoxes() carries the same
per-box getTechLayer() walk internally.  Nesting is layer-major rather
than master-major because the render loop has the layer fixed and
iterates many instances and sboxes: one lock per tile to take the
snapshot, then lock-free lookups.  Renders run on N I/O threads, unlike
Qt's single render thread, and the shared_ptr also keeps an in-flight
render's snapshot alive across an invalidation.

A layer absent from a map has nothing any instance or via could draw
there, which also lets the searchInsts and searchSNetViaShapes queries be
skipped outright on implant/marker layers and upper metals rather than
scanning the design and discarding the result.

Pin boxes stay grouped by dbMTerm in declaration order so the ITerm
label pass keeps labelling the first drawable box per pin.  The
no-layer-filter path (a layer name a chiplet's tech does not have, which
draws every master shape in the fallback color) keeps its original
unfiltered loops: a layer-keyed cache cannot reproduce its cross-layer
draw and label ordering, and only multi-tech designs reach it.

Also give drawFilledRect and fillPolygon the optional dim parameter
setPixel and blendPixel already had.  Both called bufferDim() —
lround(sqrt(size/4)) — once per drawn shape to recover a buffer side the
render loop already knows, which was another 14% once the traversal cost
above was gone.

Measured on that design, per-tile CPU at fit-to-view: 7940 -> 1576 ms
(5.0x), with M1 89x and M2 51x; a zoomed viewport is 563 -> 424 ms.
Every ODB accessor and the libm lround leave the profile, which is now
dominated by rasterization.  Costs 0.28 GB resident.

Rendering is bit-identical: verified over 3744 tiles (51 layers x 8
zoom/position combos x 9 visibility variants) plus a dpr 1/1.5/2/2.5/3
sweep, comparing PNG hashes against the unpatched binary.

Signed-off-by: Matt Liberty <mliberty@precisioninno.com>
Bring secure-replace-bracket up to the current private master before further development. Preserve the existing namespace-collision fixes while incorporating upstream changes through a non-rewriting merge.

Signed-off-by: Jaehyun Kim <jhkim@precisioninno.com>
The geometry cache was dropped from TileGenerator::onDesignChanged, which
Search reaches only through announceModified:

    const bool prev_flag = flag.exchange(false);
    if (prev_flag) { notifyModified(); }

That debounce exists so a batch of edits does not flood connected clients
with redraws, and each index has its own flag — so an edit clearing an
index that is already invalid is silent.  Reachable whenever a render has
touched some indices but not others: draw an _instances tile (insts_init
true, shapes_init still false), then add an SWire, and the callback never
fires.  The shape R-tree rebuilds correctly but the cache keeps a
pre-edit snapshot, so findViaBoxes returns nothing for a via master the
edit introduced and those shapes silently stop drawing.  Nothing could go
stale this way before the cache existed, since layer membership was read
live from ODB per box.

Give Search an undebounced revision counter — bumped by every edit it
hears about, whether or not the callback fires — and have geomCache()
rebuild when it moves.  This separates "did anything change" (cheap,
pull-based) from "tell the browser" (debounced, push-based), so the
redraw debounce is untouched.  The revision is read before the rebuild,
so an edit landing mid-build leaves the recorded value behind and the
next call rebuilds again.

eagerInit keeps dropping the cache outright: a reload's fresh dbMaster
and dbVia objects can be allocated where the old ones were, so a
revision bump alone would not prove the keys still mean the same thing —
the same address-reuse hazard the layer-color cache is cleared for.

GeomCacheRebuiltAfterDebouncedEdit reproduces the silent path and fails
without this fix.  Also covers the layer bucketing (a Nangate45 cell's
metal1 pins must not appear on a metal2 tile) and snapshot reuse across
tiles with no intervening edit.

Verified on a 676k-instance design: rendering is still bit-identical over
the same 3744-tile sweep, and fit-to-view wall clock is unchanged at
1169 ms, confirming the poll does not rebuild per tile.

Signed-off-by: Matt Liberty <mliberty@precisioninno.com>
Signed-off-by: dsengupta0628 <dsengupta@precisioninno.com>
…rmat

The CI container (26Q3-945-g499b4440a0) binary produces:
- CUGR congestion report via GRT-0130 (not unified GRT-0096)
- Float formatting: 0.00 and 1.50 (not 0 and 1.5)

Update golden files to match the expected CI binary output.

Signed-off-by: Sparsh Karna <sparsh2005karna@gmail.com>
Revert tests 1 and 2 to GRT-0096 format (0/1.5 float formatting).
Fix test 3 congestion table demand values to match actual
post-merge routing result (metal3: 16, total: 18).

Signed-off-by: Sparsh Karna <sparsh2005karna@gmail.com>
Signed-off-by: Eder Monteiro <emrmonteiro@precisioninno.com>
…oft-NDR demotion

Signed-off-by: Jonas Gava <jfgava@precisioninno.com>
Signed-off-by: Mike Inouye <mikeinouye@google.com>
Two gaps in buildGeomCache(), both on the chiplet side.

chiplets() reports one node per dbChipInst, so every instance of a shared
master chip reports the same dbBlock.  The via-master loop visited that
block once per instance and appended each dbVia's boxes again every time,
so the render pass redrew every box once per instance of the chiplet —
invisible in the output, since the fills are opaque, but quadratic in the
repeat count and it multiplies the cache's memory by it as well.  Track
the blocks already visited.

Separately, geomCache() keyed only on Search::revision(), which cannot
move when a dbChipInst is created: dbBlockCallBackObj has no chip-instance
hook.  Every other source of new geometry reaches revision() transitively
— a dbVia or dbMaster only becomes drawable once an sbox or instance
references it, and those do notify — but a chip instance makes an
already-populated block's vias newly reachable with no block edit at all.
chiplets() notices, via its own root-pointer + dbChipInst-count
fingerprint, so the render loop starts drawing the new chiplet while the
cache keeps a snapshot built without that block's dbVia masters:
findViaBoxes returns nothing and the chiplet's special-net vias silently
do not draw until an unrelated block edit or eagerInit() rebuilds.

Give chiplets() a generation counter, bumped whenever it rebuilds, and
add it to the geometry cache's key.  Read before the build like the
revision, so a hierarchy edit landing mid-build leaves the recorded value
behind the live one and the next call rebuilds.

GeomCacheVisitsASharedChipletBlockOnce and
GeomCacheRebuiltAfterChipletInstCreated fail without these fixes (3
duplicate boxes instead of 1; a snapshot missing the new chiplet's via).

Signed-off-by: Matt Liberty <mliberty@precisioninno.com>
Signed-off-by: dsengupta0628 <dsengupta@precisioninno.com>
Signed-off-by: Jonas Gava <jfgava@precisioninno.com>
Signed-off-by: Mike Inouye <mikeinouye@google.com>
Signed-off-by: Mike Inouye <mikeinouye@google.com>
hist_gen_ is reset to 0 by initFromDb() on every legalize() call and is
incremented at most once per negotiation iteration, bounded by
kMaxIterNeg + kMaxIterNeg2 = 1400. Wrapping the uint32_t stamp would
take 4.29e9 increments, so the guard was dead code. Document the
invariant instead.

Signed-off-by: Augusto Berndt <augusto.berndt@precisioninno.com>
Signed-off-by: Jonas Gava <jfgava@precisioninno.com>
dsengupta0628 and others added 30 commits August 12, 2026 00:32
Signed-off-by: dsengupta0628 <dsengupta@precisioninno.com>
…nst_net

update sta ptr to fix const nets
FastRouteCore::routeMonotonic pre-sized every tree edge's route.grids to
x_range_ + y_range_ -- the Manhattan diameter of the whole routing grid --
and then shrank it with resize(cnt) once the route was walked. resize()
downward keeps the capacity, and route.grids lives in sttrees_ for the
whole global route, so every edge held a worst-case buffer for the entire
run while the routes themselves are a handful of points.

On a large die that is many GPoint3D (6 bytes each) per edge, landing in
a large size class: many live blocks, many GB of which essentially all is
unused capacity.

Size the buffer from the mid point the cost search just picked instead.
walkSegment appends exactly |dx| + |dy| points per L-segment whichever
branch it takes, plus the trailing end point, so the new size is exact.
The trailing resize(cnt) is therefore a no-op and is replaced by assert()
checks that the walk doesn't write out-of-bounds and wrote exactly the
points the size was computed for.

Note the size is not |x2-x1| + |y2-y1| + 1: xmin/xmax and ymin/ymax grow
the search window by 'enlarge' (10, +5 per iteration) beyond the edge
bounding box, so bestp1 can fall outside it and the route is not always
monotonic. Bounding by the bounding box would overflow the buffer.

Co-authored-by: Olle Fredriksson <olle@ascenium.com>
Signed-off-by: Øyvind Harboe <oyvind@ascenium.com>
Signed-off-by: dsengupta0628 <dsengupta@precisioninno.com>
Signed-off-by: Eder Monteiro <emrmonteiro@precisioninno.com>
grt: size monotonic route buffer to the actual route length
…rting point

Signed-off-by: Arthur Koucher <arthurkoucher@precisioninno.com>
…ngestion

GRT: Fix FastRoute blocked tracks computation
Signed-off-by: dsengupta0628 <dsengupta@precisioninno.com>
dpl: check edge spacing DRC on opt_mirror
…ff-register-latch-mask

gpl: Separate registers and latches in MBFF masks
Signed-off-by: Mike Inouye <mikeinouye@google.com>
Signed-off-by: Mike Inouye <mikeinouye@google.com>
…place-bracket

odb: Avoid Verilog namespace collisions
mbff: dont count Enable input pin as data input
odb: temporary fix for wire ordering of input bump nets
…ROAD into ant_order_independent_merge

Signed-off-by: Eder Monteiro <emrmonteiro@precisioninno.com>
Signed-off-by: Eder Monteiro <emrmonteiro@precisioninno.com>
Signed-off-by: Øyvind Harboe <oyvind.harboe@zylin.com>
docs: Extract and expand Design Space Exploration guide
Signed-off-by: Eder Monteiro <emrmonteiro@precisioninno.com>
grt/cugr: skip zero length segments during buildNetRoute
bazel: expose fix_lint and lint_test targets with public visibility
…zel/soupsieve-2.8.4

build(deps): bump soupsieve from 2.8.3 to 2.8.4 in /bazel
ant: make per-gate layer merge order-independent in calculateAreas
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.