Skip to content

sync - #2

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

sync#2
nanocoh wants to merge 2336 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]

jhkim-pii and others added 25 commits July 18, 2026 14:40
Treat larger intrinsic delay as weaker when drive resistance ties so both comparator keys use descending order. Add an equal-drive Liberty fixture and regression coverage because the Nangate45 buffer family cannot isolate the intrinsic-delay tie.

Signed-off-by: Jaehyun Kim <jhkim@precisioninno.com>
dpl: remove negotiation post-optimization
odb: make 3DBlox connection region check direction-aware
Permit legacy size-up to select an equal-drive replacement when its estimated stage delay improves. Extend the MT drive filter to equal resistance while retaining the strict delay-improvement check in candidate estimation and excluding the current cell.

Signed-off-by: Jaehyun Kim <jhkim@precisioninno.com>
Remove the weaker-to-stronger ordering from the multi-threaded size-up generator. The policy estimates every candidate and selects the highest score, so sorting only added unnecessary work and an implicit equal-score tie break.

Signed-off-by: Jaehyun Kim <jhkim@precisioninno.com>
Rename the current-cell drive resistance variable to drive_r in both size-up generators. This makes the resistance comparison direction explicit and avoids reading the value as drive strength.

Signed-off-by: Jaehyun Kim <jhkim@precisioninno.com>
Stop the legacy setup repair loop before starting another endpoint once the configured global iteration limit is reached.

The endpoint-local check allowed one extra iteration for each remaining endpoint, so a limit of 20 could report 65 iterations.

Reuse reachedIterationLimit in the outer endpoint loop and rebase the affected regression outputs.

Signed-off-by: Jaehyun Kim <jhkim@precisioninno.com>
rcx: fix deadcode coverity issue
Assisted-by: Claude (various models)
Signed-off-by: Martin Povišer <povik@cutebit.org>
Signed-off-by: Martin Povišer <povik@cutebit.org>
Signed-off-by: Martin Povišer <povik@cutebit.org>
Signed-off-by: Martin Povišer <povik@cutebit.org>
Assisted-by: Claude (various models)
Signed-off-by: Martin Povišer <povik@cutebit.org>
Assisted-by: Claude (various models)
Signed-off-by: Martin Povišer <povik@cutebit.org>
Signed-off-by: Martin Povišer <povik@cutebit.org>
Signed-off-by: LucasYuki <lucasyuki@yahoo.com.br>
Signed-off-by: LucasYuki <lucasyuki@yahoo.com.br>
Signed-off-by: LucasYuki <lucasyuki@yahoo.com.br>
Signed-off-by: LucasYuki <lucasyuki@yahoo.com.br>
Signed-off-by: LucasYuki <lucasyuki@yahoo.com.br>
Signed-off-by: LucasYuki <lucasyuki@yahoo.com.br>
A candidate whose displacement + congestion cost exactly ties the
incumbent cannot win unless its scan rank is smaller: the DRC term only
adds cost, so a nonzero count disqualifies it and a zero count leaves a
tie the rank comparison rejects. Return before countDRCViolations - the
most expensive term - in that case. Exact pre-DRC ties are common in
uncontended regions (free sites at equal displacement over uniform
history), and wavefront visit order equals rank order, so every tie
after the first in a wavefront now skips its DRC evaluation.

Addresses review feedback on the pull request.

Signed-off-by: Matt Liberty <mliberty@precisioninno.com>
The man page cat build emitted several classes of troff/groff warnings:

- 'cannot select font C/V': Pandoc renders code/verbatim text in the
  Courier font family, which the nroff/tty device lacks. Add
  src/man_fontfix.tmac (injected via --include-in-header) to remap those
  fonts to available ones on devices without Courier.
- 'tbl preprocessor failed': the OPTIONS tables need tbl; add -t.
- 'character with input code N not defined' / mojibake: non-ASCII README
  content (e.g. accented author names) was decoded as Latin-1. Run the
  build in a UTF-8 locale and render with 'groff -Kutf8 ... -Tutf8'.
- 'table wider than line length': Pandoc sizes tables at nearly full
  width while the man body is indented; narrow the indent with -rIN=4n.

Register man_fontfix.tmac as a Bazel input for //docs:man_pages.

Signed-off-by: Matt Liberty <mliberty@precisioninno.com>
For the module docs that render as man pages (cgt, est, ram, syn, tap,
web), Pandoc derives roff table column widths from the relative number
of dashes in the Markdown separator row. The uniform '| ----- | ----- |'
separators forced a 50/50 split, leaving the short 'Switch Name' column
half-empty while squeezing the long descriptions. Size column 1 to the
longest option name, capped so the description keeps the majority of the
width. Modules excluded from man-page generation are left unchanged --
their dash ratios have no effect on any rendered output.

Also drop the 'Check out GitHub discussion' FAQ footers, whose long
inline URLs were not usable as links in man/cat output.

Signed-off-by: Matt Liberty <mliberty@precisioninno.com>
- Define GROFF ?= groff and use it in the cat recipes so the man renderer
  can be overridden (e.g. gnroff/macOS); drop the now-unused NROFF variable.
- Detect an installed UTF-8 locale instead of hard-coding C.UTF-8, which is
  absent on some hosts (e.g. stock macOS). Fall back to the user's locale if
  none is found rather than exporting an invalid LC_ALL.

Signed-off-by: Matt Liberty <mliberty@precisioninno.com>
mikesinouye and others added 30 commits July 31, 2026 17:30
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: Jonas Gava <jfgava@precisioninno.com>
Signed-off-by: Mike Inouye <mikeinouye@google.com>
Signed-off-by: Mike Inouye <mikeinouye@google.com>
Signed-off-by: Jonas Gava <jfgava@precisioninno.com>
…antennas

grt: fix antenna check based on CUGR guides
Signed-off-by: bnmfw <bernardoborgessandoval@gmail.com>
…render-perf

web: remove per-tile ODB traversal from tile rendering
Signed-off-by: Eder Monteiro <emrmonteiro@precisioninno.com>
Signed-off-by: Eder Monteiro <emrmonteiro@precisioninno.com>
Signed-off-by: Eder Monteiro <emrmonteiro@precisioninno.com>
rsz: check for dont touch before applying CloneMove
The viewer created one Leaflet pane per (tech layer x chiplet instance).
On a 3DBlox multi-die design that is 97 panes, each holding a full grid
of ~24 tiles, each tile decoding to (256*dpr)^2 * 4 bytes: 582 MB of
decoded images at dpr 1 in a 1240x999 window.  Past roughly 458 MB on
the machine profiled, Chrome discards decoded images, and the discarded
regions paint white and stay white until something forces a full
invalidation -- a window resize, or opening DevTools.  A single-die
design shows ~20 panes and ~120 MB, which is why only multi-die designs
hit it.

Measured by replacing tile images with a 1x1 pixel, which removes the
bytes while leaving the pane count, the DOM and the composited-layer
structure untouched.  The symptom is monotonic in bytes across seven
points and indifferent to how the bytes are distributed: at a matched
516 vs 517 MB, 86 and 97 content-bearing panes fail identically.  That
rules out pane count and stack depth; it is the bytes.  docs/tile-memory.md
records the full elimination, including tile delivery, decode failure,
positioning, clipping, GPU rasterization (it reproduces under
--disable-gpu), compositor raster memory and JS heap.

Routing layers are now partitioned into N groups, each rasterized into
one canvas per tile position, with N derived from a memory budget
(default 350 MB) and the viewport's tile count -- which matters, since
97 panes at 2560x1440 would be ~1.9 GB and any fixed N would fail there.
Decodes go through createImageBitmap so they can be close()d immediately;
an <img> gives no control over when its bitmap is released, which is the
whole problem.  _instances, _pins and _modules keep their own panes.

Three invariants, each with tests:
  - Groups are contiguous runs of the z-order.  Each becomes one image,
    and {1,3},{2,4} has no stacking that reproduces [1,2,3,4].
  - The merged pane's opacity is 1.  Leaflet applies layer opacity to
    the container, so per-layer opacity now lives inside the canvas; a
    translucent pane would apply it twice and wash the view out.
  - The client and server agree on dpr to the pixel, since the canvas
    backing store is sized from it.

The server also stops snapping dpr to a five-step ladder and honours the
ratio the client reports, rounded to two decimals to bound the tile-cache
key space.  1.75 and 2.5 are ordinary Windows scale factors and both were
off the ladder, so those displays received tiles 17% and 25% too small
and had the browser rescale them -- the resampling the tile pipeline
exists to avoid.

Selecting a layer no longer adds or removes a map layer: it flips a flag
in the owning pane's draw list and refreshes that pane, so the pane count
never moves.  refreshTiles() keeps the same contract the per-layer panes
had, so the design-changed refresh push, pattern changes and visibility
changes all continue to arrive through redrawAllLayers().

  ?mergetiles=0      one pane per layer (previous behaviour)
  ?tilebudget=<MB>   override the budget
  ?mergegroups=<N>   pin N directly

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Signed-off-by: Peter Gadfort <gadfort@zeroasic.com>

web: remove memory

Signed-off-by: Peter Gadfort <gadfort@zeroasic.com>

web: keep tile merging enabled when the map is not laid out yet

Two ways the group count could be computed against a viewport that is not
the one the tiles will actually be drawn in, both of which silently
defeat the merging.

A container that is hidden or not yet laid out reports clientWidth 0.
estimateTilesPerPane then returns 1, so computeGroupCount divides the
whole budget by a single tile and comes back with groupCount ==
paneCount: one pane per layer, no merging at all, while the startup log
reports "merged 94 layers into 94 panes".  The failure the merging
exists to prevent then arrives as soon as the panel is shown.  Measure
the viewport through measureViewport(), which falls back to the window
and then to 1024x768.  Over-estimating is the safe direction, since more
tiles per pane means fewer panes.

The grouping was also computed once at startup and never revisited, so
enlarging the window raised the per-pane cost until a grouping that fit
the budget no longer did -- the same failure, arriving later.  Recompute
on map resize and redo the grouping when it has to shrink.  Growing N
back when the window shrinks is deliberately not done: it would churn
panes on every splitter drag for no safety benefit, and leaves the merge
coarser than necessary, which is the harmless direction.

Reported by Peter Gadfort.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Signed-off-by: Peter Gadfort <gadfort@zeroasic.com>
…repair_antennas

grt: repair antennas with diodes when using CUGR
…toggles

The group header is a <label> wrapping the visibility checkbox, so a click
that misses the expand triangle activates the label and toggles visibility
instead. The arrow's hit box was only its own 10px line box (font-size 9px)
inside a 25px row, so 15 of 25 pixel rows in the triangle's column landed on
the label.

Stretch the arrow to the full row height and extend it over the header's
vertical padding with negative margins. Rendering is byte-identical; the
14px column width and all checkbox positions are unchanged.

Signed-off-by: Matt Liberty <mliberty@precisioninno.com>
… checkbox

A <label> wrapping the visibility checkbox activates that checkbox for a
click anywhere in the row, so any click that missed a checkbox -- the expand
triangle, the group name, the empty space to the right -- toggled the whole
category's visibility. Widening the triangle's hit box only shrank the
window for that misfire.

Make the header a <div> and give the whole row minus its checkboxes the
expand/collapse action, so a stray click can no longer change visibility.
Clicking a group's name now expands it, matching Qt's displayControls tree
where the name never affects visibility. Shared makeGroupHeader() and
attachGroupCollapse() helpers in ui-utils.js replace the four hand-rolled
headers (vis-tree groups, layer groups, Chiplets, Heat Maps).

This supersedes the arrow hit-box CSS from the previous commit, which is
reverted: every part of the header now performs the same action, so
stretching the arrow no longer changes anything.

Rendering is byte-identical (same PNG md5 at 3x device scale). A real-Chrome
sweep clicking all 25 pixel rows of a header's triangle column, plus the
name and the empty space, expands/collapses every time with visibility
untouched.

Signed-off-by: Matt Liberty <mliberty@precisioninno.com>
The blocklist guard (skip inputs, selects, buttons) fails toward a wrong
action: a control added to the header later that is not in the list both
acts and collapses the group. Test the two targets that should collapse
instead -- the triangle, and the header itself, which is what a click on
the group name or the empty space targets. A new child then cannot double
fire; the worst it can do is not respond, which is immediately visible.

Also covers a <label>-wrapped checkbox in a header: it toggles its own
checkbox and leaves the group alone, without the guard having to name
<label> and thereby turn any label in a header into a dead zone.

Signed-off-by: Matt Liberty <mliberty@precisioninno.com>
…ay-controls-arrow-hitbox

gui: fix display-controls expand arrow stealing clicks as visibility toggles
Refresh only the panes whose visibility changed.  The layer tree's
onChange walks every node and asserts the desired state on each one, so
marking the owning pane dirty unconditionally meant a single checkbox
click re-requested every tile in every pane -- roughly 2300 requests on
the 97-layer design this was written for, each with a new server cache
key because visible_layers had changed.  The per-layer panes never had
this problem, since Leaflet's addTo() is a no-op for a layer already on
the map.  setItemVisible() now reports whether the state actually moved
and only a real change dirties a pane.

Charge the unmerged panes against the budget.  _instances, _pins and the
always-on highlight overlay each hold a full tile grid and are never
merged.  At dpr 1 that is ~6 MB each and immaterial, but at dpr 3 a tile
is 2.25 MB, so a pane is ~54 MB and the three together are ~162 MB --
enough that the budget reported a comfortable fit while the real total
sat above the measured ceiling.  reserveForUnmergedPanes() subtracts
them first, and the logged estimate now covers the whole viewer rather
than only the part the grouping controls.

Resize the canvas backing store when the device pixel ratio changes.
createTile fixed it at the ratio in force at creation, but
devicePixelRatio changes when the window moves between monitors or the
browser zoom changes, and a refresh then requests images at the new
ratio.  The old backing store scaled every incoming tile, leaving
existing tiles blurry and inconsistent with any created afterwards.
Sizing is now re-applied on every render, and skipped when unchanged
because assigning width clears the canvas.

Reported by codex on #11045.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Signed-off-by: Peter Gadfort <gadfort@zeroasic.com>
A merged tile waited for every layer in its group before drawing
anything, so first paint was gated by the slowest of its ~6 subtiles and
a request that never settled left the tile blank indefinitely.  The
per-layer panes showed each layer the moment it landed; grouping turned
one stuck request from one blank layer into a blank group.

The canvas is now recomposited from scratch on every arrival, with slots
that have not arrived drawn as nothing.  That is exact rather than an
approximation: transparent is the identity for src-over, so compositing
the arrived subset in list order gives precisely the pixels the full
composite would have where those layers are absent.  Because every paint
walks the whole item list in order, the stacking cannot depend on which
response happens to land first -- there is no prefix pointer or gap
handling to get wrong, which was the alternative.

done() is reported on the first paint rather than on completion.
Leaflet keeps a tile hidden until then (.leaflet-tile { visibility:
hidden }), so deferring it would have made every incremental paint
invisible and the tile would still have appeared only once its slowest
layer had landed.

Decodes are held until the tile is complete rather than released per
paint: a late arrival belongs underneath layers already drawn, so the
recomposite needs every source still available.  Peak memory is the same
K bitmaps the batch version already held.

A tile whose every layer fails now paints an all-blank composite instead
of never painting, since a tile that never calls done() stays invisible
rather than merely empty.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Signed-off-by: Peter Gadfort <gadfort@zeroasic.com>
The HTTP handler matched the raw request target against the embedded
asset table, so "/?mergetiles=0" was looked up as a filename, missed,
and returned "Resource not found." for the whole page.  Every viewer
option is a query parameter, which made all of them unusable -- including
the ?mergetiles=0 escape hatch back to the pre-merge rendering path,
documented in the previous commits and never reachable.

assetPathFromTarget() drops the query and fragment and maps "/" onto the
index document.  It lives in request_handler.h rather than staying a
static in web.cpp so it can be tested; nothing covered this path, which
is how the options shipped broken.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Signed-off-by: Peter Gadfort <gadfort@zeroasic.com>
WebSocketManager.cancel() settled the promise only for a request still in
the queue.  For one already on the wire it dropped the handler from
`pending` and returned, and the eventual reply then found no handler and
was discarded, so the caller's promise never settled at all.

That was harmless while a tile was an <img>.  A merged tile releases its
decoded ImageBitmaps in renderMergedTile's finally, and a finally does
not run while an await is still outstanding.  Every tile pruned by a pan
and every tile re-requested by refreshTiles cancels its in-flight
requests, so the sibling layers that had already decoded were stranded --
explicit close() is the entire reason this path uses ImageBitmap over
<img>, and unlike an <img> decode an ImageBitmap is not discardable by
the browser's image cache, which makes leaving it to GC weaker than what
the per-layer panes had.  The sent branch now rejects like the queued
one.  The wire accounting is unchanged: the slot still frees only when
the (still ignored) reply lands, so cancellation churn cannot re-flood
the socket.

done() also moves off the render and onto the tile.  refreshTiles
re-renders in place without a done callback, so a refresh landing before
the first paint left the handshake with the render it superseded;
Leaflet keeps a tile hidden until done() marks it loaded, so the
replacement render painted a canvas that stayed invisible until the tile
was pruned and recreated.  A superseded render now declines to report it
-- it painted nothing, so reporting there would reveal whatever the
canvas held before the refresh instead of what replaced it.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Signed-off-by: Matt Liberty <mliberty@precisioninno.com>
Signed-off-by: Matt Liberty <mliberty@precisioninno.com>
web: bound tile memory by compositing layers into N panes
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.