diff --git a/docs/contrib/3DIC.md b/docs/contrib/3DIC.md new file mode 100644 index 00000000000..c15a82294e0 --- /dev/null +++ b/docs/contrib/3DIC.md @@ -0,0 +1,498 @@ +# 3DIC STA Notes + +Living developer-facing doc for the 3DIC STA enablement work landing in +`src/dbSta` and `src/odb`. Captures the mental model + invariants that are +not obvious from reading the code alone. Update incrementally as new +stages land. + +**v1 scope.** This PR lands cross-chiplet STA for **single-level** +hierarchies (one top `dbChip` with direct `dbChipInst` children, no +nested chiplets) under a **zero-delay** bond model. Two known gaps — +multi-hierarchical 3DIC support and `dbChipConn` RC parasitics — +are out of scope here and documented as concrete follow-up work in +[`3DIC_TODO.md`](3DIC_TODO.md). The single-level assumption is +load-bearing in `dbNetwork::parent(Instance*)`, +`DbInstanceChildIterator`, and `dbNetwork::isLeaf(Instance*)`. + +## Mental model: how a chiplet bump becomes an STA Pin + +OpenSTA reasons in terms of `Network` abstractions: Instance, Pin, Net, +Port, Cell. The timing engine doesn't care what the underlying database +object is; it only needs the relationships (`instance(pin)`, `net(pin)`, +`port(pin)`, `term(pin)`, etc.) to walk the design. + +In a 2D / single-chip flow the mapping is: + +| STA concept | OpenDB object | +|-------------------|-------------------------------------------| +| Top instance | the chip's `dbBlock` (`Sta` synthesizes a top `Cell`) | +| Leaf instance | `dbInst` | +| Hierarchical inst | `dbModInst` (Verilog `-hier` flow) | +| Leaf-cell port | `dbMTerm` | +| Pin on leaf inst | `dbITerm` | +| Top-level port | `dbBTerm` | +| Hier-pin handoff | `dbModBTerm`/`dbModITerm` | +| Net | `dbNet` / `dbModNet` | + +For 3DIC (3DBlox), the same master/instance pattern repeats one level up: + +| 2D world | 3DIC world | +|-------------------|------------------------| +| `dbMaster` | `dbChip` | +| `dbInst` | `dbChipInst` | +| `dbMTerm` | `dbChipBump` | +| `dbITerm` | `dbChipBumpInst` | +| `dbNet` | `dbChipNet` | + +So a **bump on a placed chiplet** (`dbChipBumpInst`) plays the same role +a **terminal on a placed standard cell** (`dbITerm`) plays — it is "one +place where a net touches an instance." That is exactly STA's definition +of a `Pin`. The dbSta adapter reuses STA's existing `Pin*` abstraction +without inventing a parallel `dbChipITerm` / `dbChipMTerm` schema. + +The crucial bridge for cross-chiplet timing is `Network::term(Pin*)`. For +a chip-bump-inst Pin, `term()` returns the *master bump's `dbBTerm`*, +which is an ordinary boundary port on the chiplet's internal `dbBlock`. +STA's `visitConnectedPins` then walks Pin → Term → Net into the chiplet's +own netlist and continues the trace. No new traversal code is required — +the existing hierarchical Network machinery already handles it. + +## Pin pointer-tag scheme (and its alignment requirement) + +`dbNetwork` distinguishes the four kinds of `Pin*` it produces by reusing +the lower 3 bits of the pointer as a type tag: + +```cpp +enum class PinPointerTags : std::uintptr_t { + kNone = 0U, + kDbIterm = 1U, + kDbBterm = 2U, + kDbModIterm = 3U, + kDbChipBumpInst = 4U, // Stage 2+ +}; +``` + +Encoding is `(char*)raw_ptr + tag`; decoding strips the low 3 bits. The +scheme depends on **every base pointer being 8-byte aligned** so the tag +bits don't collide with real address bits. + +Heap allocations from `malloc` / `operator new` return 16-byte-aligned +addresses on x86_64 Linux. Inside an OpenDB `dbTable` page, objects +are packed with stride `sizeof(T)`. If `sizeof(T)` is not a multiple of +8, every other object lands on a 4-aligned address and the encoding +silently produces `kNone` for it. + +`_dbObject` is 8 bytes. `_dbChipBumpInst` had three `dbId` fields +(4 bytes each), giving `sizeof = 20`. Half the objects in a `dbTable` +page ended up 4-aligned. The Stage-4 symptom was a sporadic +`ORD-2018 "Pin is not ITerm or BTerm or modITerm."` for whichever bump +happened to fall on the bad stride. + +**Rule of thumb for any new dbObject that is the target of a pointer +tag**: ensure `sizeof(T) % 8 == 0`. Either size the class accordingly +when adding fields, or add an explicit 4-byte padding field. Field-by- +field serialization (`operator<<` / `operator>>`) is unaffected; only +the in-memory stride matters. + +## How chip-aware behavior is gated + +A single predicate, `dbNetwork::has3DicChip()`, returns `true` when the +top is a hierarchical `dbChip` (no own `dbBlock`, has child +`dbChipInst`s). Iterators and accessors check this **first**, before +falling through to the legacy hierarchical (Verilog `dbModInst`) or flat +paths. The three modes are independent: + +``` +has3DicChip() -> chip-hier iteration (Stage 3+) +hasHierarchy() -> Verilog module hier (existing) +neither -> flat single-block (existing) +``` + +These do not auto-toggle each other. `read_3dbx` does not set +`hasHierarchy`; Verilog hier read does not set `has3DicChip`. + +## Iterator hardening + +`DbInstanceChildIterator` and `DbInstancePinIterator` carry several +`dbSet<...>::iterator` members for the various iteration modes. A +default-constructed `dbSet` iterator has **undefined `!=` behavior** — +comparing two default-constructed iterators may not return true, leading +to a deref of an invalid object. + +Chip-aware branches in these iterators use explicit `chip_walk_` / +`chip_inst_` bool flags and **short-circuit** `hasNext` / `next` before +touching the unrelated iterators. Any future iterator that adds a new +mode must do the same — do not rely on default-constructed iterators +behaving as "empty." + +## dbChipNet ↔ STA Net encoding + +A `dbChipNet*` is encoded as `Net*` via plain `reinterpret_cast` (no +pointer tag). Decoded at runtime via `dbObject::getObjectType() == +dbChipNetObj`. Same scheme that distinguishes `dbNet` vs `dbModNet`. + +Reasons tag bits are unused for `dbChipNet`: +- No alignment hazard like Stage 4's `dbChipBumpInst` (we never add a + small offset to the pointer). +- A single `Net*` type-tag is enough for callers; the existing two-out- + param `staToDb(Net*, dbNet*&, dbModNet*&)` was extended with a silent + `dbChipNetObj` case so legacy callers see "neither" and skip. + +When extending: if you ever add an offset-style tag to `dbChipNet*`, +audit `_dbChipNet` size for the 8-byte stride rule (the same one that +bit `_dbChipBumpInst`). + +## Bump-inst ↔ chip-net reverse lookup (lazy) + +`net(Pin*)` for a chip-bump-inst pin needs the owning `dbChipNet`. +odb stores the forward relationship (`dbChipNet::getBumpInst(i, path)`); +the reverse lookup is built once in `dbNetwork::bump_to_chip_net_` +on first call via `ensureBumpToChipNetCache()`. Pattern: + +``` +if (bump_to_chip_net_.empty() && top_chip_ has chip-nets) { + walk getChipNets(); walk getBumpInst(i); fill map. +} +``` + +Reset in `clear()` because `top_chip_` resets to nullptr and the dbObject +table may be torn down. + +**Limitation:** if chip-nets are mutated after the first cache hit (e.g. +`addBumpInst` called later), the cache is stale and the new mapping +won't be picked up. In normal flows the chip-net table is built during +`read_3dbx` and immutable afterward, so this is acceptable. Stage 6+ may +add `dbBlockCallBackObj`-style notifications if mutation becomes a +supported flow. + +The `dbChipNet::getBumpInst(i, path)` API gives a hierarchical path +(`std::vector`) along with the bump-inst. The cache and the +`DbNetPinIterator` currently drop the path because v1 is single-level +hierarchy. Nested chip hierarchies (post-v1) will need to keep paths to +disambiguate identically-named bumps under different chip-inst trees. + +## STA VertexId storage for chip-bump pins (side-map) + +OpenSTA's `Graph::makePinVertices` calls `network_->setVertexId(pin, vid)` +so it can later resolve a `Pin*` back to its timing-graph `Vertex` via +`network_->vertexId(pin)`. For regular pins the id is stored on the odb +object itself: + +- `_dbITerm` has `sta_vertex_id_` (see `dbITerm.h`). +- `_dbBTerm` has the same. + +`_dbChipBumpInst` has **no such field**. Adding one would be an odb +schema change. Instead, `dbNetwork` keeps a side-map: + +```cpp +std::map chip_bump_vertex_ids_; +``` + +`setVertexId(chip_bump_pin, id)` writes there; `vertexId(chip_bump_pin)` +reads. Reset in `clear()` since rebuild may invalidate old ids. + +**Why this matters**: if either accessor is silent for a new pin type, +the consequence is non-obvious and downstream. `setVertexId` no-ops → +`vertexId` returns `object_id_null = 0` → `Graph::vertex(0)` returns +null → `bfs.enqueue(null)` in `ClkNetwork::findClkPins` segfaults far +from the actual bug. Pattern lesson: every new `Pin*` tag the network +yields must have both `setVertexId` and `vertexId` branches even if the +storage is just a `std::map`. + +## How a cross-chiplet timing path is built + +This is the full chain — useful when debugging why a path stops short. + +``` + chipA (uniquely-owned master flop_chip_a) + .---------------------------------------------. + | ff.CK <- clk | + | | | + | ff.Q -> n1 -> inv.A inv.ZN -> n2 -> | + | buf.A | + | buf.Z -> q ----+ + .---------------------------------------------.| + | bump_q (chip-bump-inst) + | direction OUTPUT + v + bridge (dbChipNet on top dbChip) + ^ + | bump_d (chip-bump-inst) + | direction INPUT + .----------------------------------------------+ + | d <- buf.A buf.Z -> n1 -> inv.A | + | inv.ZN -> n2 -> | + | ff.D | + | | + | ff.CK <- clk | + .----------------------------------------------. + chipB (uniquely-owned master flop_chip_b) +``` + +Five distinct layers of accessor work make this traceable: + +1. **Pin enumeration.** `DbInstancePinIterator(chip_inst)` yields `dbChipBumpInst` pins via the chip-inst's region-insts × bump-insts. `topInstance` childIterator flattens: each chip-inst's master block's dbInsts are also yielded as leaves so STA's `Graph::makePinVertices` runs for `ff/CK`, `inv/A`, `buf/Z`, etc. +2. **Net enumeration.** `DbInstanceNetIterator(top_instance)` yields top-level `dbChipNet`s. `DbNetPinIterator(chip_net)` walks `chip_net->getBumpInst(i, path)` and yields each bump-inst Pin. +3. **Forward bridge.** `term(chip_bump_pin)` returns the master bump's `dbBTerm` (chiplet inner BTerm) so `Network::visitConnectedPins(Pin*)` descends Pin → Term → inner-net → inner-ITerm-pins. +4. **Reverse bridge.** `pin(Term* = inner_bterm)` looks up `bterm->getChipBump()`, finds the bump-inst on the chip-inst owning the chiplet block (via `block_to_chip_inst_`), and returns it as a Pin. This lets `Network::visitConnectedPins(Net*)`'s termIterator walk ascend from a chiplet inner net up to the chip-net. +5. **Instance/parent attribution.** `instance(inner_iterm_pin)` returns the inner dbInst; `parent(inner_dbInst)` returns the chip-inst owning the block. `instance(inner_bterm_pin)` returns the chip-inst (NOT `top_instance_`) so STA's `isTopLevelPort` correctly classifies it as internal — without this, `Sdc::pinCaps` calls `port(pin)` on `top_cell_` and crashes when no matching Port exists. + +## block_to_chip_inst_ and the shared-master limitation + +`std::map block_to_chip_inst_` is built in +`setTopChip`. It only contains chiplet master blocks referenced by **exactly +one** chip-inst: + +``` +chip-inst A (master=flop_chip_a) -> flop_chip_a block: unique -> in map +chip-inst B (master=flop_chip_b) -> flop_chip_b block: unique -> in map +chip-inst C (master=SoC) \ +chip-inst D (master=SoC) / SoC block: refcount=2 -> NOT in map +``` + +Chip-insts whose master is in the map become "descendable": + +- `isLeaf(chip_inst)` returns true (chip-bump pins get Vertices), but the + `topInstance` childIterator flat-walk adds the master block's inner + dbInsts to the DFS frontier so they also get Vertices. +- `parent(inner_dbInst)` returns the owning chip-inst. + +Chip-insts whose master is NOT in the map (shared) stay as leaves with no +descent — STA sees them as opaque boxes with bump pins only. The current +implementation cannot timing-analyze the bodies of two chip-insts that +share a master `dbChip` because their inner dbInst pointers alias. +Supporting shared masters requires a virtual `(chip_inst, dbInst)` +instance encoding, deferred post-v1. + +## Closing the flop-to-flop loop — Stage 7 wiring (LANDED) + +A chip-bump and its chiplet-side inner BTerm form a single hierarchical +boundary. STA's `isDriver(pin) = (isLeaf && output) || (isTopInstance && +input)` requires the pin to be a driver to participate in wire-edge +formation. With a load-only direction on either side of the boundary, +`Graph::makeWireEdgesFromPin` skips the bump and no cross-chiplet edge +forms in one direction. + +**Fix (two parts):** + +1. `direction(chip_bump_pin) = PortDirection::bidirect()`. Bidirect is + both driver and load, so `makeWireEdgesFromPin` runs on every chip + bump regardless of the underlying BTerm's IoType. As a side effect, + `Graph::makePinVertices` allocates dual vertices (load + driver) for + each chip-bump. +2. `dbNetwork::visitConnectedPins(Net*)` chip-net branch: after + `visitor(bump_pin)`, recurse via `term(bump_pin) → net(term) → + visitConnectedPins(inner_net, ...)` so STA's **fat-net** wire-edge + model sees the chiplet's leaf loads through the boundary. Without + this, the visitor stops at the bump and the path terminates at + `chipB/d` (the inner BTerm). + +The fat-net model collapses the boundary: a wire edge goes directly from +`chipA/buf/Z` (inner driver) to `chipB/buf/A` (inner load across the +chip-net), skipping bump pins entirely. So an inner-LibertyCell-less +chip-bump never needs its own timing arc to carry the *data* path — the +edges form across it. + +After Stage 7 the test reports a constrained `chipA/ff → chipB/ff` +setup check with real Liberty delays. + +## Anchoring create_clock on chip-bump pins (LANDED) + +The natural anchor form + +```tcl +create_clock -name clk -period 1.0 [get_pins -of_objects [get_nets clk_top]] +``` + +now produces a constrained `Path Group: clk` setup check identical to +the inner-CK form. Two fixes were required on top of Stage 7: + +**Fix A — Synthesize a LibertyCell per chip-master with a self-arc per +chip-bump port.** `dbNetwork::makeTopCellForChip` builds the chip-master +Cell *as* a `LibertyCell` (via `LibertyBuilder`) on a private +`LibertyLibrary`. For each chip-bump bterm it creates a `LibertyPort` +and a combinational self-arc (`lc->makeTimingArcSet(lp, lp, ...)`). +`Graph::makeInstanceEdges` consumes the arc and creates: + +- `bump_load → bump_bidir_drvr` (forward combinational, traversable by + `SearchAdj`) +- `bump_bidir_drvr → bump_load` (flagged `isBidirectInstPath`, filtered + by default predicate but harmless) + +The forward arc closes the gap between the load and driver vertices of +the BIDIRECT chip-bump pin, so a clock arrival seeded on the load (or +the driver) fans out via the regular wire-edge model. Zero arc delay +preserves the inner-CK anchor's reported slack. + +**Fix B — Per-block discriminator in `getDbNwkObjectId`.** Each chiplet +`dbBlock` numbers its iterms/bterms/insts/nets from 1. Without +disambiguation, "clk" net (db_id=1) in chipA collides with "clk" net +(db_id=1) in chipB — `NetSet::contains()` (and `PinSet::contains()`) +sort by `id()` and treat them as equal. `visitConnectedPins`'s +`visited_nets` then dedupes them, so the second chiplet's inner clk +net is silently skipped during chip-net descent, and wire edges from +the clk_top bump driver only reach chipA-side loads. + +`dbNetwork::setTopChip` allocates a 1..N discriminator per chiplet +block in `block_disc_`. `blockDiscBits(obj, typ)` stamps the disc into +the upper 4 bits of the encoded `ObjectId` for iterm/bterm/inst/net +when `block_disc_` is non-empty. `id(Net*)` now routes through the +tagged encoder whenever `has3DicChip()` is true (the legacy path +bypassed the encoder in non-hierarchy mode and returned raw +`dnet->getId()`). + +Without both fixes the symptom is the same: `all_registers -clock_pins` +still finds both ff/CK pins via `ClkNetwork`'s static BFS, but +`report_clock_skew` reports "No launch/capture paths found" and +constrained `report_checks` reports "No paths found" because the +dynamic Search BFS can't reach the second chiplet's CK pin. + +## Wire-load model — fanout includes BIDIRECT chip-bump load vertices + +STA does **not** build a per-segment drvr → load chain. `Graph::makeWireEdgesFromPin(drvr_pin)` runs `visitConnectedPins(drvr_pin)` to aggregate **every** drvr and **every** load reachable across the fat net (chipnet + term-descended inner nets), then emits pairwise `drvr × load` wire edges via `makeWireEdge`. Implications for 3DIC: + +1. **Cross-chiplet data edge has no intermediate bump-pin hop.** The wire edge `chipA/buf/Z → chipB/buf/A` is created in one shot when `chipA/q_bump` (the BIDIRECT driver iterated from chipA's chip_inst pin walk) is processed. The visitor descends into chipA's inner q dbNet (yielding `chipA/buf/Z`, `chipA.q_bterm`) and chipB's inner d dbNet (yielding `chipB/buf/A`, `chipB.d_bterm`). `FindNetDrvrLoads` then classifies — `chipA/buf/Z` joins `drvrs`, `chipB/buf/A` joins `loads` — and the pairwise loop emits the cross-chip wire edge. + +2. **Every BIDIRECT chip-bump appears in BOTH drvrs and loads.** `isDriver` and `isLoad` both return true for `direction == bidirect`. So `chipA/q_bump` and `chipB/d_bump` show up as loads in the aggregated set too, and `chipA/buf/Z` gets extra outgoing wire edges to their **load-side vertices** in addition to the real load `chipB/buf/A`. These edges are harmless for path search — the bump load vertex's only outgoing arc is the synthesized `load → bidir_drvr` self-arc, which then re-enters the same chipnet, and `SearchPred` (forward search) only emits paths via the `bidir_drvr_vertex`. No spurious paths form. + +3. **Wire-delay calc sees fanout count.** STA's default wire-load lookup uses fanout count (or summed pin caps; chip-bump LibertyPort cap defaults to 0). Every distinct load on a fat net contributes to the count. If two loads dedup on identity (`PinSet`/`NetSet` sort by `id()` — see the per-block discriminator section above), the fanout under-counts and wire delay drops by one tier. + +The Stage 7 golden `slack 0.83` was produced under exactly that under-count: cross-block iterm/bterm/net id collisions in `visited_drvrs` / `visited_nets` silently dropped some chip-side loads. After the `block_disc_` fix the load set is correctly sized, fanout grows, and the wire edge `chipA/buf/Z → chipB/buf/A` picks up an extra +0.01 ns, landing at `slack 0.82`. The regen captures the physical-topology-correct value. + +## term(Pin*) history (Stage 4–6.5) + +`Network::visitConnectedPins(Pin*, visitor)` (sta base, **not virtual**) +calls `net(pin)` first then `term(pin)` and walks `net(term)` to descend. +For a chip-bump pin, `term()` returns the chiplet's inner `dbBTerm` +so STA can trace into the chiplet's internal net. + +This was the *cross-boundary bridge* added in Stage 4 but had to be +temporarily disabled in Stage 6 because returning the inner BTerm +caused STA to descend into chiplet bodies whose inner pins had no +Vertex yet — `bfs.enqueue(null)` segfaulted in `findClkPins`. + +Stage 6.5 closed the gap and restored `term()`: + +1. `topInstance` childIterator flat-walks each uniquely-owned chiplet + master block's inner dbInsts. Those become leaves so + `Graph::makePinVertices` allocates Vertices for `ff/CK`, `ff/Q`, + `inv/A`, `buf/Z`, etc. +2. `parent(inner_dbInst)` → owning chip-inst (via `block_to_chip_inst_`), + so `pathName` composes `chipA/ff/Q`. +3. `instance(inner_bterm_pin)` → owning chip-inst (NOT `top_instance_`), + so `isTopLevelPort` correctly returns false and downstream STA does + not try `port(pin)` on `top_cell_`. +4. `pin(Term* = inner_bterm)` reverse bridge — looks up the bterm's + chip-bump, walks to the bump-inst on the chip-inst owning this + block, returns it as a Pin so termIterator walks ascend. +5. `term(chip_bump_pin)` restored. + +After Stage 6.5 `report_checks -unconstrained` produces a real +cross-chiplet path: + +``` +chipA/ff/CK -> ff/Q -> inv/ZN -> buf/Z -> q -> bridge -> chipB/d +``` + +with Liberty delays from each cell. + +## Per-master chiplet Cell + +For each unique `dbChip` referenced by some `dbChipInst`, dbNetwork +synthesizes a `Cell` via `makeCell` and a `Port` per master `dbChipBump` +that has a backing `dbBTerm`. This is needed because: + +1. `ConcreteNetwork::libertyCell(Cell*)` does not null-check; returning + `nullptr` from `cell(chip_inst)` would crash inside STA's property + accessors. Having a non-null Cell with `libertyCell() == nullptr` is + the safe, expected state. +2. `name(Pin*)` → `pathName(Pin*)` → `portName(pin)` → `name(port(pin))` + requires `port(pin)` to return a real `Port*` to produce a name. + +The map `chip_master_cells_` is reset in `dbNetwork::clear()` since +`ConcreteNetwork::clear()` destroys the libraries that own those Cells. + +## Diagnostics + +`dbSta::postRead3Dbx` emits a one-line structural summary plus two +warning kinds so missing-data problems surface immediately on +`read_3dbx` rather than as silent "No paths found" later. + +| Msg | Level | When | +|---|---|---| +| STA-3000 | INFO | Always. `3DIC STA active: chiplets, top-level nets, 3D bond regions, bump pads.` Counts taken at end of `postRead3Dbx`. Tcl-created chip-nets that show up after `read_3dbx` will not be in this count. | +| STA-3001 | WARN | A `dbChipNet` has zero bump pads attached → orphan net. Single-bump nets are legitimate top IO and stay silent. | +| STA-3002 | WARN | One unique chiplet definition has one or more bumps with no chiplet-port mapping (5th column of `.bmap` is `-`). Aggregated per definition to avoid per-bump spam. Intentional in fixtures (e.g. `dbSta/test/3dic_get_cells.tcl`) — see test comment. | + +Tcl helper `report_3dic_summary` (in `dbSta.tcl`) prints the same +counts plus per-chiplet-instance reference names — useful as a +post-read sanity check or to dump the structural state mid-flow. + +## Fixture authoring notes + +Non-obvious gotchas when adding new 3DIC tests: + +1. **`.bmap` 5th column binds bump → chiplet port.** + Format per line: ` `. + `bterm_name = "-"` leaves the bump unbound — STA cannot cross that + boundary. Always set the 5th column to a real chiplet `dbBTerm` name + unless the test specifically exercises unmapped bumps. + +2. **`BUMP` macro center offset constrains `.bmap` XY range.** + `ThreeDBlox::createBump` (`src/odb/src/3dblox/3dblox.cpp:599-602`) + computes the bump `dbInst` origin as + `bmap.x * dbu_per_micron - bbox.xCenter() + chip.offset.x`. With + `fake_bumps.lef`'s `BUMP` macro at `SIZE 29 BY 29`, `bbox.xCenter() = + 29000 DBU`. So `.bmap` XY must be ≥ ~14.5µm to keep the origin + non-negative, and the chiplet region must be large enough to contain + the resulting origin — otherwise `Checker::checkBumpPhysicalAlignment` + fires ODB-0463. + +3. **`Connection:` block grounds chiplets.** + `Checker::checkLogicalConnectivity` requires every chiplet to be + transitively reachable from a "ground" node (PCB / package side). + Declare one virtual one-sided `Connection:` with `bot: ~` (or `top: + ~`) for the bottom-most chiplet: + ```yaml + Connection: + to_pkg: + top: chipA.regions.front_reg + bot: ~ + ``` + Without this, ODB-0206 (no ground group) + ODB-0151 (floating chip + sets) fire. The 3DBlox YAML parser rejects a Connection that + omits either `top` or `bot` outright — use `~` for the null side. + +4. **Physical bump alignment vs logical net assignment.** + When two chiplets stack with `orient: MZ` and share a bump XY, + `Checker::checkBumpPhysicalAlignment` requires both bumps at that XY + to belong to the same top-level `dbChipNet`. Mismatch → ODB-0208. + Verify by reading the top Verilog and the per-chiplet `.bmap` files + together: e.g. `chipA.q → bridge` must share its `.bmap` XY with + `chipB.d → bridge`, not with `chipB.q → out_top`. + +5. **Distinct `dbBlock` per chiplet definition is recommended.** + Two `dbChipInst`s of the same `dbChip` master share the master's + `dbBlock`. dbSta filters such shared-master blocks out of + `block_to_chip_inst_` (see "block_to_chip_inst_ and the + shared-master limitation"), so inner `dbInst`s of a shared master + become invisible to `leafInstanceIterator`. For path tests, give + each chiplet its own `ChipletDef:` with a distinct `.def`. + +## File map + +| Purpose | File | +|--------------------------------------|--------------------------------------------| +| Network adapter | `src/dbSta/src/dbNetwork.cc` | +| Adapter declarations | `src/dbSta/include/db_sta/dbNetwork.hh` | +| `postRead3Dbx` entry point | `src/dbSta/src/dbSta.cc` | +| odb 3DBlox schema | `src/odb/include/odb/db.h` (`dbChip*`) | +| odb 3DBlox impls | `src/odb/src/db/dbChip*.cpp` | +| Unfolded model (flat geometric view) | `src/odb/include/odb/unfoldedModel.h` | +| Tcl regression | `src/dbSta/test/3dic_get_cells.tcl` | + +## Stage tracking + +Stage-by-stage TODOs, decisions, and known unknowns live in the +plans-side document. The repo doc captures only the durable invariants +that future contributors need to understand the code. diff --git a/docs/contrib/3DIC_TODO.md b/docs/contrib/3DIC_TODO.md new file mode 100644 index 00000000000..b2c82ce7ff5 --- /dev/null +++ b/docs/contrib/3DIC_TODO.md @@ -0,0 +1,311 @@ +# 3DIC STA — Post-v1 TODOs + +v1 (Stages 0–8, see `3DIC.md`) lands cross-chiplet timing for **single-level** +hierarchies under a **zero-delay** bond model. Two known gaps remain. +This document outlines the concrete refactors needed to close each. + +--- + +## TODO 1 — Multi-hierarchical 3DIC support + +### Problem + +v1 stores per-chip-bump `VertexId` in a flat side-map keyed by +`dbChipBumpInst*`: + +```cpp +// dbNetwork.hh +std::map chip_bump_vertex_ids_; +``` + +The `Pin*` identity for a chip-bump is the tag-encoded `dbChipBumpInst*`. + +This works for **single-level** 3DIC (one top `dbChip` with direct +`dbChipInst` children). In **multi-hierarchical** 3DIC (chiplet of +chiplets, e.g. `top → mid [HIER] → leaf`), a single `mid` chiplet +master can be placed multiple times at top. Each placement reuses the +SAME inner `dbChipBumpInst*` pointers (they belong to the `mid` +schema, not to a per-placement copy). The unfolded model produces +distinct physical bumps per unfold path +`(top.mid_inst_X, leaf_inst_Y, bump)`, but they all hash back to one +`dbChipBumpInst*` → side-map collapses them to one `VertexId` → +graph topology wrong → cross-hierarchy paths break. + +### Concrete changes + +1. **Store `VertexId` on `UnfoldedBump`**, not in `dbNetwork`. + - File: `src/odb/include/odb/unfoldedModel.h`. Add + `VertexId sta_vertex_id_{object_id_null};` to `UnfoldedBump` + (or equivalent field). + - `UnfoldedBump` already carries `std::vector path`, + so it uniquely identifies each unfolded instance regardless of + nesting depth. + - Delete `chip_bump_vertex_ids_` from `dbNetwork.hh`. + +2. **Re-key the `Pin*` for chip-bumps from `dbChipBumpInst*` to + `UnfoldedBump*`.** + - File: `src/dbSta/src/dbNetwork.cc`. + - Change the pointer-tag scheme: + ```cpp + // current: kDbChipBumpInst = 4U (dbChipBumpInst* tagged) + // new: kDbChipBumpInst = 4U (UnfoldedBump* tagged) + ``` + `UnfoldedBump` already requires 8-byte alignment (contains a + `Point3D` etc.), so the lower-3-bits encoding works unchanged. + - Update `staToDbChipBumpInst(Pin*)` to instead return an + `UnfoldedBump*`. Rename to `staToUnfoldedBump`. + - Update every emitter of a chip-bump `Pin*` to encode the right + `UnfoldedBump*` (DbInstancePinIterator, DbNetPinIterator, + visitConnectedPins chip-net branch, term-descent inverse map + in `pin(Term*)`). + +3. **Update `vertexId(Pin*) / setVertexId(Pin*, id)`** to read/write + the `sta_vertex_id_` field on the `UnfoldedBump` directly, no + side-map. + +4. **`instance(Pin*)` for chip-bumps must walk the unfold path.** + - Currently: + ```cpp + return dbToSta(bump->getChipRegionInst()->getChipInst()); + ``` + This returns the leaf-level chip_inst. For multi-hier, the + `Instance*` returned must reflect the FULL path — + `UnfoldedBump::path` ends in the leaf chip_inst, but the + `Instance` in STA's hierarchy is the path-qualified instance, + not the raw `dbChipInst*` master. + - Implies: chip_inst `Instance*` encoding also needs path-aware + identity. Two options: + - (a) Encode `UnfoldedChip*` (already path-tagged in + UnfoldedModel) as Instance, replacing the current + `dbChipInst*` tag. + - (b) Compose a `(dbChipInst*, path_hash)` pair into a single + 64-bit Instance* by allocating shadow records — heavier. + - (a) is the clean path; mirrors the `Pin*` change. + +5. **Iterators must descend hierarchical chip_insts recursively.** + - `DbInstanceChildIterator` for a top `dbChip`: if a child + `dbChipInst` has a HIER master (no `dbBlock`, contains nested + `dbChipInst` children), recurse into its children. v1 short- + circuits with `staToDbChipInst(instance) != nullptr` returning + no children; this must change. + - `DbInstancePinIterator` for a non-leaf chip_inst: yield its + `UnfoldedBump`s at every level, not just leaf chip-bumps. + - `pinIterator(Net*)` / `DbNetPinIterator` for a `dbChipNet`: + resolve each `dbChipBumpInst` via `getBumpInst(i, path)` (already + path-aware in odb) but yield the `UnfoldedBump*`-keyed Pin + instead of the raw bump-inst pin. + +6. **`block_to_chip_inst_` becomes path-aware.** + - Current map collapses `dbBlock → dbChipInst*` (filters + shared-master case). For multi-hier with shared mid-chip masters, + a single `dbBlock` is reached by multiple unfold paths. + - Replace with `std::map` or a path + lookup via `UnfoldedModel::findUnfoldedChip(path)`. + +7. **`block_disc_` discriminator**: still keyed by `dbBlock`. Still + correct because cross-block id collisions are at the `dbBlock` + level, not the unfold-path level. No change needed. + +8. **Update `dbNetwork::parent(Instance*)`** to walk one step up the + `UnfoldedChip::path` (currently just returns `top_instance_` + for any chip_inst). + +### Test plan + +- Extend `dbSta/test/3dic_cross.tcl` with a second test fixture + (`3dic_cross_hier.tcl`?) that places `mid` chip twice at top, with + one leaf chiplet inside each `mid`. Verify constrained setup check + forms across the four leaf instances. +- Verify `report_3dic_summary` count of chiplets matches the number of + UNFOLDED instances, not just the top-level `dbChipInst` count. + +--- + +## TODO 2 — Parasitics on dbChipConn (RC bond model) + +### Problem + +v1 treats every cross-chiplet wire edge as zero-delay. STA sums only +the Liberty cell delays plus the (zero) wire delay → cross-chip +slack reflects ONLY cell logic + intra-chiplet wire load, not the +physical bond delay between chiplets. Real 3DIC bonds (hybrid bond, +TSV, microbump) contribute non-trivial RC. + +### Concrete changes + +1. **Define a parasitic-bearing field on `dbChipConn`.** + - File: `src/odb/src/codeGenerator/schema/chip/dbChipConn.json`. + - `dbChipConn` already has `thickness`. Add per-conn lumped R, C + (or a reference to a parasitic model). Suggested fields: + ``` + - name: resistance_, type: double, default: 0.0 + - name: capacitance_, type: double, default: 0.0 + ``` + Or a richer model id pointing to a library of bond profiles. + +2. **Bind parasitics in dbSta after read.** + - File: `src/dbSta/src/dbSta.cc::postRead3Dbx`. + - After `setTopChip` + `constructUnfoldedModel`, iterate + `chip->getChipNets()`. For each chip-net, locate the + `dbChipConn` that bonds the two bump endpoints (region-pair + match) and compute the lumped RC for that net. + - Use the existing OpenSTA hook: + ```cpp + sta_->parasitics()->makeParasiticNetwork( + net, /*reduced=*/false, parasitic_ap); + // attach lumped R + C nodes to the chip-bump pins + ``` + The `Net*` here is the `dbChipNet`-encoded `Net*`. + - Per-corner: use `ParasiticAnalysisPt*` from the active scene. + +3. **Optional: parasitic reduction.** + - Real 3DIC will want pi-model or detailed RC tree, not pure lumped. + OpenSTA's `ParasiticReduce` can collapse a detailed RC tree to a + pi-model. Wire the dbChipConn parameters into that pipeline. + +4. **Reporting hook.** + - Extend `report_3dic_summary` to list, per dbChipConn, the bound + parasitic R / C / wire delay (if available). + - New diagnostic: STA-3004 INFO at end of `postRead3Dbx`: + `" chip-bond regions carry parasitic R/C."` + - STA-3005 WARN if a `dbChipConn` has thickness but no R/C model + bound — flags users that the bond will be modeled as zero delay. + +5. **Test plan.** + - Extend `3dic_cross.tcl` with a corner where the `bridge` + chip-bond carries a non-zero RC. Compare slack against the + zero-delay baseline; verify delta matches the analytic + RC-delay calc. + +### Out of scope (defer to a later iteration) + +- Multi-conductor / coupling cap modeling (cross-bond crosstalk). +- Frequency-dependent / wideband bond models. +- Bond statistics for OCV / SSTA. + +--- + +## TODO 3 — ETM-bound chiplets (use real Liberty when available) + +### Problem + +v1 always synthesizes a stub `LibertyCell` per chiplet master with a +zero-delay self-arc per chip-bump port. That works when the chiplet +ships only as DEF + bump map. When the chiplet vendor instead ships an +**Extracted Timing Model (ETM)** — a real `.lib` whose `cell` matches +the chiplet name and whose ports match the chip-bump bterm names — +the stub is wrong: it hides the ETM's real clock-to-q, setup/hold, +and internal arcs. + +3DBlox already supports this via `external.liberty_file:` under +`ChipletDef:`. dbSta just doesn't consult it. + +### Concrete changes + +1. **Lookup before synthesis in `makeTopCellForChip`.** + - File: `src/dbSta/src/dbNetwork.cc`. Inside the per-master loop: + ```cpp + LibertyCell* etm = network_->findLibertyCell(master->getName()); + if (etm) { + chip_master_cells_[master] = reinterpret_cast(etm); + continue; // skip stub synthesis + } + // no ETM — fall through to LibertyBuilder stub. + ``` + - The ETM `LibertyCell`'s `LibertyPort`s must match the chip-bump + bterm names. Validate via a sanity pass: + ```cpp + for (each bump bterm) { + if (!etm->findLibertyPort(bterm->name)) warn(STA-3006, ...); + } + ``` + +2. **Suppress the BIDIRECT direction override when ETM is present.** + - `dbNetwork::direction(chip_bump_pin)` currently returns BIDIRECT + unconditionally so wire-edge formation runs on every bump. With an + ETM, port direction should come from the ETM's `LibertyPort` + (which encodes the real INPUT/OUTPUT/INOUT semantics). The ETM + model itself drives clock-edge propagation through real arcs, no + BIDIRECT trick needed. + +3. **Skip the `chip_bump_lib_` private LibertyLibrary entirely** when + every chiplet master has an ETM. + +4. **Diagnostic.** + - STA-3000 INFO line: append `, ETM-bound: / chiplets` so the + user sees how many chiplets are running on real Liberty vs stubs. + - STA-3006 WARN: ETM cell found but bump bterm name has no matching + LibertyPort. + +### Test plan + +- Generate a tiny ETM `.lib` for `flop_chip_a` (one cell, clock/d/q + ports with realistic delays). +- Extend `3dic_cross.tcl` to read the ETM and verify the slack number + includes the ETM's internal delay, not zero. + +### Why this matters + +ETM-based chiplet flows are how production 3DIC designs actually ship. +v1's stub synthesis is fine for early bring-up; downstream RTL-to-GDS +flows that integrate vendor chiplets will need this path. + +--- + +## TODO 4 — Callback-driven cache invalidation for `bump_to_chip_net_` + +### Problem + +`dbNetwork::refreshBumpToChipNetCache()` currently rebuilds the +bump-inst → chip-net map only when the odb chip-net **count** changes. +That catches `dbChipNet_create` between lookups (the common case in +Tcl test fixtures), but it MISSES in-place rewires: + +- `existing_chip_net->addBumpInst(bump, path)` on a chip-net that's + already in the cache → cache stays stale for the new bump. +- `existing_chip_net->removeBumpInst(bump)` similarly. + +A user who builds chip-nets dynamically (without creating new ones) +will silently get null `net(bump_pin)` for the rewired bumps. + +### Concrete change + +Wire `dbNetwork` to listen on a `dbBlockCallBackObj`-style hook that +fires on `dbChipNet` and `dbChipBumpInst` edits (create / destroy / +addBumpInst / removeBumpInst). On any such signal, invalidate the +cache (e.g. clear and reset the size counter). dbCbk currently emits +no chip-net / bump-inst signals — adding those is the prerequisite. + +### Files + +- `src/odb/include/odb/dbBlockCallBackObj.h` — add the new hook + methods. +- `src/odb/src/db/...` (the dbChipNet / dbChipBumpInst impl) — fire + the new callbacks. +- `src/dbSta/src/dbSta.cc::dbStaCbk` — implement the hooks to + invalidate `bump_to_chip_net_` and reset + `bump_to_chip_net_cache_size_` to 0. + +--- + +## File / module references + +- `src/dbSta/include/db_sta/dbNetwork.hh` — `chip_bump_vertex_ids_`, + `block_to_chip_inst_`, `block_disc_`. +- `src/dbSta/src/dbNetwork.cc` — pointer-tag scheme, iterator + short-circuits to refactor for multi-hier; ObjectId discriminator. +- `src/odb/include/odb/unfoldedModel.h` — `UnfoldedBump`, + `UnfoldedChip`, `UnfoldedModel::findUnfoldedChip`. +- `src/odb/src/codeGenerator/schema/chip/dbChipConn.json` — extend + for parasitic fields. +- `src/dbSta/src/dbSta.cc::postRead3Dbx` — parasitic binding entry. +- OpenSTA `Parasitics::makeParasiticNetwork` — existing hook. + +## v1 single-level constraint, where it lives in code + +- `dbNetwork::parent(Instance*)` line ≈1373 comment: "Single-level + chip hierarchy in v1; deeper nesting is post-v1." +- `DbInstanceChildIterator` line ≈338 comment: "Chip-insts + themselves are leaves (Stage 4 model)." +- `dbNetwork::isLeaf(Instance*)` line ≈1441: chip_insts treated as + leaves unconditionally. diff --git a/src/dbSta/include/db_sta/dbNetwork.hh b/src/dbSta/include/db_sta/dbNetwork.hh index 96aaeb4c865..c71438b86d7 100644 --- a/src/dbSta/include/db_sta/dbNetwork.hh +++ b/src/dbSta/include/db_sta/dbNetwork.hh @@ -3,6 +3,9 @@ #pragma once +#include +#include +#include #include #include #include @@ -79,6 +82,15 @@ class dbNetwork : public ConcreteNetwork void removeObserver(dbNetworkObserver* observer); odb::dbBlock* block() const { return block_; } + // Top dbChip when a 3Dbx design is active; null otherwise. + odb::dbChip* topChip() const { return top_chip_; } + void setTopChip(odb::dbChip* chip); + // Master block of a chiplet instance; null if the master chip is itself + // hierarchical (no own dbBlock). + odb::dbBlock* blockOf(odb::dbChipInst* chip_inst) const; + // True when chip_inst's master block is referenced by exactly one + // chip-inst (the caller). Used to gate descent into the master's body. + bool blockOwnedUniquelyBy(odb::dbChipInst* chip_inst) const; utl::Logger* getLogger() const { return logger_; } void makeLibrary(odb::dbLib* lib); void makeCell(Library* library, odb::dbMaster* master); @@ -152,6 +164,15 @@ class dbNetwork : public ConcreteNetwork Port* dbToSta(odb::dbModBTerm* modbterm) const; Term* dbToStaTerm(odb::dbModBTerm* modbterm) const; + // dbChipBumpInst uses the pointer-tag scheme (lower-3-bits tag); + // dbChipInst / dbChipNet are discriminated at decode via dbObject type id. + Pin* dbToSta(odb::dbChipBumpInst* bump_inst) const; + Instance* dbToSta(odb::dbChipInst* chip_inst) const; + Net* dbToSta(odb::dbChipNet* chip_net) const; + odb::dbChipBumpInst* staToDbChipBumpInst(const Pin* pin) const; + odb::dbChipInst* staToDbChipInst(const Instance* instance) const; + odb::dbChipNet* staToDbChipNet(const Net* net) const; + PortDirection* dbToSta(const odb::dbSigType& sig_type, const odb::dbIoType& io_type) const; @@ -408,6 +429,9 @@ class dbNetwork : public ConcreteNetwork void disableHierarchy() { db_->setHierarchy(false); } bool hasHierarchy() const { return db_->hasHierarchy(); } bool hasHierarchicalElements() const; + // 3DIC gate: true when top_chip_ is a hierarchical chip (no own dbBlock, + // owns dbChipInsts). Chip-aware iterators key off this. + bool has3DicChip() const; void reassociateHierFlatNet(odb::dbModNet* mod_net, odb::dbNet* new_flat_net, odb::dbNet* orig_flat_net); @@ -449,10 +473,12 @@ class dbNetwork : public ConcreteNetwork void readDbNetlistAfter(); void checkLibertyCellsWithoutLef() const; void makeTopCell(); + void makeTopCellForChip(odb::dbChip* chip); void findConstantNets(); void makeAccessHashes(); bool portMsbFirst(std::string_view port_name, std::string_view cell_name); ObjectId getDbNwkObjectId(const odb::dbObject* object) const; + ObjectId blockDiscBits(const odb::dbObject* obj, odb::dbObjectType typ) const; //////////////////////////////////////////////////////////////// // Debug functions @@ -461,6 +487,37 @@ class dbNetwork : public ConcreteNetwork odb::dbDatabase* db_ = nullptr; utl::Logger* logger_ = nullptr; odb::dbBlock* block_ = nullptr; + odb::dbChip* top_chip_ = nullptr; + // One synthetic Cell per dbChip master referenced by a chiplet inst. The + // Cell has no LibertyCell binding; STA property queries that go through + // libertyCell(Cell*) need a non-null Cell* to avoid a null deref. + std::map chip_master_cells_; + // Reverse lookup: bump-inst -> owning chip-net. ensureBumpToChipNet- + // CacheFresh() rebuilds it on chip-net-count change so net(chip_bump_ + // _pin) sees chip-nets created via the odb API post-setTopChip(). + // Caveat: in-place addBumpInst/removeBumpInst on an existing chip-net + // (no count change) is NOT detected — see 3DIC_TODO.md TODO 4. + mutable std::map bump_to_chip_net_; + mutable size_t bump_to_chip_net_cache_size_ = 0; + void ensureBumpToChipNetCacheFresh() const; + // STA vertex-id storage for chip-bump-inst pins. _dbChipBumpInst has no + // staVertexId field on the odb side; keep the mapping here. + std::map chip_bump_vertex_ids_; + // Reverse lookup: chiplet master dbBlock -> chip-inst that placed it. + // Built in setTopChip. Only contains blocks whose master is referenced + // by exactly one chip-inst — shared masters (e.g. two chip-insts of + // the same chiplet) are skipped, since their inner dbInsts would alias + // across chip-insts and break STA's per-pin Vertex assumption. + std::map block_to_chip_inst_; + // Per-block discriminator stamped into upper bits of getDbNwkObjectId + // so iterms/bterms/insts/nets from different chiplet blocks (each + // numbered from 1) don't collide in NetSet/PinSet keys. + std::map block_disc_; + // Synthetic LibertyLibrary for chip-master Cells. Each carries a + // self-arc per chip-bump port so Graph::makeInstanceEdges builds the + // internal load<->bidir_drvr edges that let create_clock anchors on + // chip-bump pins propagate into the chiplet body. + LibertyLibrary* chip_bump_lib_ = nullptr; Instance* top_instance_; Cell* top_cell_ = nullptr; std::set observers_; @@ -475,6 +532,9 @@ class dbNetwork : public ConcreteNetwork static constexpr unsigned DBMODINST_ID = 0x6; static constexpr unsigned DBMODNET_ID = 0x7; static constexpr unsigned DBMODULE_ID = 0x8; + static constexpr unsigned DBCHIPINST_ID = 0x9; + static constexpr unsigned DBCHIPBUMP_INST_ID = 0xA; + static constexpr unsigned DBCHIPNET_ID = 0xB; static constexpr unsigned CONCRETE_OBJECT_ID = 0xF; // Number of lower bits used static constexpr unsigned DBIDTAG_WIDTH = 0x4; diff --git a/src/dbSta/src/CMakeLists.txt b/src/dbSta/src/CMakeLists.txt index ad52352fb96..68b3ba98c0e 100644 --- a/src/dbSta/src/CMakeLists.txt +++ b/src/dbSta/src/CMakeLists.txt @@ -18,6 +18,10 @@ target_include_directories(dbSta_lib ../include ${PROJECT_SOURCE_DIR}/include PRIVATE + # OpenSTA's PUBLIC exports only ${OPENSTA_HOME}/include. dbNetwork.cc + # needs liberty/LibertyBuilder.hh (synthetic chip-bump arc creation) + # which lives under ${OPENSTA_HOME}, and that header includes + # "MinMax.hh" without an sta/ prefix, requiring ${OPENSTA_HOME}/include/sta. # Needed for search/Levelize.hh and the unprefixed transitive # OpenSTA headers it includes (e.g. Graph.hh) which are not part of # OpenSTA's public include. diff --git a/src/dbSta/src/dbNetwork.cc b/src/dbSta/src/dbNetwork.cc index 30add983146..9a52d2a83c3 100644 --- a/src/dbSta/src/dbNetwork.cc +++ b/src/dbSta/src/dbNetwork.cc @@ -61,6 +61,7 @@ Recommended conclusion: use map for concrete cells. They are invariant. #include #include "dbEditHierarchy.hh" +#include "liberty/LibertyBuilder.hh" #include "odb/db.h" #include "odb/dbObject.h" #include "odb/dbSet.h" @@ -78,6 +79,8 @@ Recommended conclusion: use map for concrete cells. They are invariant. #include "sta/PortDirection.hh" #include "sta/Search.hh" #include "sta/StringUtil.hh" +#include "sta/TimingArc.hh" +#include "sta/TimingRole.hh" #include "sta/VertexId.hh" #include "utl/Logger.h" #include "utl/algorithms.h" @@ -92,6 +95,12 @@ using odb::dbBTerm; using odb::dbBTermObj; using odb::dbBusPort; using odb::dbChip; +using odb::dbChipBumpInst; +using odb::dbChipBumpInstObj; +using odb::dbChipInst; +using odb::dbChipInstObj; +using odb::dbChipNet; +using odb::dbChipNetObj; using odb::dbDatabase; using odb::dbInst; using odb::dbInstObj; @@ -187,26 +196,76 @@ PinInfo getPinInfo(const dbNetwork* network, const Pin* pin) // lower 4 bits used to encode type // +// 3DIC chiplets each own a private dbBlock that numbers its +// iterms/bterms/insts/nets from 1, so two "clk" nets (one per chiplet) +// hash to the same id and collide in NetSet/PinSet. Steal the top +// kBlockTagWidth bits for a per-block discriminator allocated in +// setTopChip(). 8 bits = 255 unique chiplet blocks (0 reserved for +// non-3DIC); leaves 20 bits for the per-block db_id (~1M objects per +// block, ample for typical chiplet sizes). +static constexpr unsigned kBlockTagWidth = 8; +static constexpr ObjectId kBlockTagMask = (1U << kBlockTagWidth) - 1; +static constexpr unsigned kBlockTagShift = 32 - kBlockTagWidth; + +ObjectId dbNetwork::blockDiscBits(const dbObject* obj, dbObjectType typ) const +{ + if (block_disc_.empty()) { + return 0; + } + odb::dbBlock* blk = nullptr; + switch (typ) { + case dbITermObj: + blk = static_cast(obj)->getBlock(); + break; + case dbBTermObj: + blk = static_cast(obj)->getBlock(); + break; + case dbInstObj: + blk = static_cast(obj)->getBlock(); + break; + case dbNetObj: + blk = static_cast(obj)->getBlock(); + break; + default: + return 0; + } + if (blk == nullptr) { + return 0; + } + auto it = block_disc_.find(blk); + if (it == block_disc_.end()) { + return 0; + } + return (it->second & kBlockTagMask) << kBlockTagShift; +} + ObjectId dbNetwork::getDbNwkObjectId(const dbObject* object) const { const dbObjectType typ = object->getObjectType(); const ObjectId db_id = object->getId(); - if (db_id > (std::numeric_limits::max() >> DBIDTAG_WIDTH)) { + // In 3DIC mode the top kBlockTagWidth bits encode block_disc, leaving + // 32 - DBIDTAG_WIDTH - kBlockTagWidth bits for the per-block db_id. + // In non-3DIC mode block_disc is always 0, so the full + // (32 - DBIDTAG_WIDTH) bits are available. + const unsigned db_id_bits = has3DicChip() + ? (32 - DBIDTAG_WIDTH - kBlockTagWidth) + : (32 - DBIDTAG_WIDTH); + if (db_id > ((1ULL << db_id_bits) - 1)) { logger_->error(ORD, 2019, "Database id exceeds capacity"); } switch (typ) { case dbITermObj: { - return ((db_id << DBIDTAG_WIDTH) | DBITERM_ID); + return blockDiscBits(object, typ) | (db_id << DBIDTAG_WIDTH) | DBITERM_ID; } break; case dbBTermObj: { - return ((db_id << DBIDTAG_WIDTH) | DBBTERM_ID); + return blockDiscBits(object, typ) | (db_id << DBIDTAG_WIDTH) | DBBTERM_ID; } break; case dbInstObj: { - return ((db_id << DBIDTAG_WIDTH) | DBINST_ID); + return blockDiscBits(object, typ) | (db_id << DBIDTAG_WIDTH) | DBINST_ID; } break; case dbNetObj: { - return ((db_id << DBIDTAG_WIDTH) | DBNET_ID); + return blockDiscBits(object, typ) | (db_id << DBIDTAG_WIDTH) | DBNET_ID; } break; case dbModITermObj: { return ((db_id << DBIDTAG_WIDTH) | DBMODITERM_ID); @@ -223,6 +282,15 @@ ObjectId dbNetwork::getDbNwkObjectId(const dbObject* object) const case dbModuleObj: { return ((db_id << DBIDTAG_WIDTH) | DBMODULE_ID); } break; + case dbChipInstObj: { + return ((db_id << DBIDTAG_WIDTH) | DBCHIPINST_ID); + } break; + case dbChipBumpInstObj: { + return ((db_id << DBIDTAG_WIDTH) | DBCHIPBUMP_INST_ID); + } break; + case dbChipNetObj: { + return ((db_id << DBIDTAG_WIDTH) | DBCHIPNET_ID); + } break; default: logger_->error( ORD, 2017, "Unknown database type passed into unique id generation"); @@ -248,6 +316,7 @@ enum class PinPointerTags : std::uintptr_t kDbIterm = 1U, kDbBterm = 2U, kDbModIterm = 3U, + kDbChipBumpInst = 4U, }; // @@ -267,10 +336,19 @@ class DbInstanceChildIterator : public InstanceChildIterator private: const dbNetwork* network_; bool top_; + // When true, only chipinst_iter_ is valid. The other iterators are + // default-constructed and unsafe to compare/deref. + bool chip_walk_ = false; dbSet::iterator dbinst_iter_; dbSet::iterator dbinst_end_; dbSet::iterator modinst_iter_; dbSet::iterator modinst_end_; + dbSet::iterator chipinst_iter_; + dbSet::iterator chipinst_end_; + // Flat list of inner dbInsts collected at construction when the + // topInstance child walk needs to surface chiplet bodies. + std::vector flat_inner_insts_; + size_t flat_inner_idx_ = 0; }; DbInstanceChildIterator::DbInstanceChildIterator(const Instance* instance, @@ -279,6 +357,36 @@ DbInstanceChildIterator::DbInstanceChildIterator(const Instance* instance, { dbBlock* block = network->block(); + // Chip-hierarchy gate runs before block/module gates so the chip-inst + // path is independent of hasHierarchy(). On the top instance the + // iterator FLATTENS: yields every chip-inst AND, for each uniquely + // owned chiplet master block, that block's inner dbInsts. STA's + // leafInstanceIterator then sees both populations as leaves and + // Graph::makePinVertices creates Vertices for chip-bump pins and inner + // ITerm/BTerm pins both. + if (network->has3DicChip() && instance == network->topInstance()) { + top_ = true; + chip_walk_ = true; + dbSet chip_insts = network->topChip()->getChipInsts(); + chipinst_iter_ = chip_insts.begin(); + chipinst_end_ = chip_insts.end(); + for (odb::dbChipInst* chip_inst : chip_insts) { + if (network->blockOwnedUniquelyBy(chip_inst)) { + if (dbBlock* mb = chip_inst->getMasterChip()->getBlock()) { + for (dbInst* inst : mb->getInsts()) { + flat_inner_insts_.push_back(inst); + } + } + } + } + return; + } + // Chip-insts themselves are leaves (Stage 4 model). Their bodies are + // surfaced via the topInstance flat walk above. + if (network->staToDbChipInst(instance) != nullptr) { + return; + } + // original code for non hierarchy if (!network->hasHierarchy()) { if (instance == network->topInstance() && block) { @@ -315,11 +423,26 @@ DbInstanceChildIterator::DbInstanceChildIterator(const Instance* instance, bool DbInstanceChildIterator::hasNext() { + if (chip_walk_) { + return chipinst_iter_ != chipinst_end_ + || flat_inner_idx_ < flat_inner_insts_.size(); + } return !((dbinst_iter_ == dbinst_end_) && (modinst_iter_ == modinst_end_)); } Instance* DbInstanceChildIterator::next() { + if (chip_walk_) { + if (chipinst_iter_ != chipinst_end_) { + odb::dbChipInst* child = *chipinst_iter_; + chipinst_iter_++; + return network_->dbToSta(child); + } + if (flat_inner_idx_ < flat_inner_insts_.size()) { + return network_->dbToSta(flat_inner_insts_[flat_inner_idx_++]); + } + return nullptr; + } Instance* ret = nullptr; if (dbinst_iter_ != dbinst_end_) { dbInst* child = *dbinst_iter_; @@ -348,12 +471,26 @@ class DbInstanceNetIterator : public InstanceNetIterator dbSet::iterator mod_net_end_; std::vector flat_nets_vec_; size_t flat_net_idx_ = 0; + dbSet::iterator chip_net_iter_; + dbSet::iterator chip_net_end_; + // Short-circuit flag — chip-net iterators are only valid in this path; + // the other iterators stay default-constructed (UB to compare). + bool chip_walk_ = false; }; DbInstanceNetIterator::DbInstanceNetIterator(const Instance* instance, const dbNetwork* network) : network_(network) { + if (network_->has3DicChip()) { + if (instance == network_->topInstance()) { + dbSet nets = network_->topChip()->getChipNets(); + chip_walk_ = true; + chip_net_iter_ = nets.begin(); + chip_net_end_ = nets.end(); + } + return; + } if (network_->hasHierarchy()) { // // In hierarchical flow, the net iterator collects both hierarchical @@ -434,6 +571,9 @@ DbInstanceNetIterator::DbInstanceNetIterator(const Instance* instance, bool DbInstanceNetIterator::hasNext() { + if (chip_walk_) { + return chip_net_iter_ != chip_net_end_; + } if (network_->hasHierarchy()) { if (mod_net_iter_ != mod_net_end_) { return true; @@ -445,6 +585,14 @@ bool DbInstanceNetIterator::hasNext() Net* DbInstanceNetIterator::next() { + if (chip_walk_) { + if (chip_net_iter_ != chip_net_end_) { + odb::dbChipNet* net = *chip_net_iter_; + chip_net_iter_++; + return network_->dbToSta(net); + } + return nullptr; + } if (network_->hasHierarchy()) { if (mod_net_iter_ != mod_net_end_) { odb::dbModNet* net = *mod_net_iter_; @@ -482,6 +630,13 @@ class DbInstancePinIterator : public InstancePinIterator Pin* next_ = nullptr; dbInst* db_inst_; odb::dbModInst* mod_inst_; + // Flattened bump-inst list for a chip-inst pin walk (collected at + // construction; per-region nesting is hidden from callers). + std::vector chip_bumps_; + size_t chip_bumps_idx_ = 0; + // When true, iitr_/mi_itr_ are default-constructed and unsafe to compare; + // hasNext must short-circuit through the chip_bumps_ path only. + bool chip_inst_ = false; }; DbInstancePinIterator::DbInstancePinIterator(const Instance* inst, @@ -493,6 +648,14 @@ DbInstancePinIterator::DbInstancePinIterator(const Instance* inst, mod_inst_ = nullptr; if (top_) { + // 3DIC top has no block. Route through the empty-chip_bumps_ path so + // hasNext short-circuits without touching default-constructed BTerm + // iterators. Stage 5+ may expose chip-net bumps as top pins. + if (network->has3DicChip()) { + top_ = false; + chip_inst_ = true; + return; + } dbBlock* block = network->block(); // it is possible that a block might not have been created if no design // has been read in. @@ -500,19 +663,30 @@ DbInstancePinIterator::DbInstancePinIterator(const Instance* inst, bitr_ = block->getBTerms().begin(); bitr_end_ = block->getBTerms().end(); } - } else { - dbInst* db_inst; - odb::dbModInst* mod_inst; - network_->staToDb(inst, db_inst, mod_inst); - if (db_inst) { - iitr_ = db_inst->getITerms().begin(); - iitr_end_ = db_inst->getITerms().end(); - } else if (mod_inst) { - if (network_->hasHierarchy()) { - mi_itr_ = mod_inst->getModITerms().begin(); - mi_itr_end_ = mod_inst->getModITerms().end(); + return; + } + + if (dbChipInst* chip_inst = network_->staToDbChipInst(inst)) { + chip_inst_ = true; + for (odb::dbChipRegionInst* region : chip_inst->getRegions()) { + for (odb::dbChipBumpInst* bump : region->getChipBumpInsts()) { + chip_bumps_.push_back(bump); } } + return; + } + + dbInst* db_inst; + odb::dbModInst* mod_inst; + network_->staToDb(inst, db_inst, mod_inst); + if (db_inst) { + iitr_ = db_inst->getITerms().begin(); + iitr_end_ = db_inst->getITerms().end(); + } else if (mod_inst) { + if (network_->hasHierarchy()) { + mi_itr_ = mod_inst->getModITerms().begin(); + mi_itr_end_ = mod_inst->getModITerms().end(); + } } } @@ -531,6 +705,14 @@ bool DbInstancePinIterator::hasNext() return false; } + if (chip_inst_) { + if (chip_bumps_idx_ < chip_bumps_.size()) { + next_ = network_->dbToSta(chip_bumps_[chip_bumps_idx_++]); + return true; + } + return false; + } + while (iitr_ != iitr_end_) { dbITerm* iterm = *iitr_; if (!network_->isPGSupply(iterm)) { @@ -571,15 +753,33 @@ class DbNetPinIterator : public NetPinIterator dbSet::iterator mitr_end_; Pin* next_; const dbNetwork* network_; + // Chip-net mode: bump-inst pins are pre-collected; legacy iter members + // stay default-constructed (UB to compare). + std::vector chip_bumps_; + size_t chip_bumps_idx_ = 0; + bool chip_walk_ = false; }; DbNetPinIterator::DbNetPinIterator(const Net* net, const dbNetwork* network) { + network_ = network; + next_ = nullptr; + + if (odb::dbChipNet* chip_net = network_->staToDbChipNet(net)) { + chip_walk_ = true; + const uint32_t n = chip_net->getNumBumpInsts(); + std::vector path; + for (uint32_t i = 0; i < n; ++i) { + if (odb::dbChipBumpInst* bump = chip_net->getBumpInst(i, path)) { + chip_bumps_.push_back(bump); + } + } + return; + } + dbNet* dnet = nullptr; odb::dbModNet* modnet = nullptr; - network_ = network; network->staToDb(net, dnet, modnet); - next_ = nullptr; if (dnet) { iitr_ = dnet->getITerms().begin(); iitr_end_ = dnet->getITerms().end(); @@ -594,6 +794,13 @@ DbNetPinIterator::DbNetPinIterator(const Net* net, const dbNetwork* network) bool DbNetPinIterator::hasNext() { + if (chip_walk_) { + if (chip_bumps_idx_ < chip_bumps_.size()) { + next_ = network_->dbToSta(chip_bumps_[chip_bumps_idx_++]); + return true; + } + return false; + } while (iitr_ != iitr_end_) { dbITerm* iterm = *iitr_; if (!network_->isPGSupply(iterm)) { @@ -699,10 +906,190 @@ void dbNetwork::setBlock(dbBlock* block) readDbNetlistAfter(); } +void dbNetwork::setTopChip(dbChip* chip) +{ + top_chip_ = chip; + bump_to_chip_net_.clear(); + bump_to_chip_net_cache_size_ = 0; + block_to_chip_inst_.clear(); + block_disc_.clear(); + if (chip == nullptr) { + return; + } + // chiplet block -> chip-inst, but only when the master block is + // referenced by exactly one chip-inst. Shared-master chip-insts can't + // descend into the same dbBlock (inner dbInst pointers would alias) + // and stay treated as leaves. + std::map block_refcount; + uint32_t next_disc = 1; + // block_disc values are masked to kBlockTagWidth bits when stamped + // into the ObjectId. Beyond the mask, distinct chiplet blocks alias + // to one another in NetSet/PinSet keys, silently merging pins/nets + // across chiplets. Error out so wrong timing isn't produced quietly. + constexpr uint32_t kMaxBlockDisc = (1U << kBlockTagWidth) - 1; + for (odb::dbChipInst* chip_inst : chip->getChipInsts()) { + if (odb::dbBlock* mb = chip_inst->getMasterChip()->getBlock()) { + block_refcount[mb]++; + block_to_chip_inst_[mb] = chip_inst; + if (block_disc_.emplace(mb, next_disc).second) { + if (next_disc > kMaxBlockDisc) { + logger_->error(utl::STA, + 3004, + "3DIC design has more than {} unique chiplet " + "blocks; per-block ObjectId discriminator " + "({} bits) overflows. Widen kBlockTagWidth in " + "dbNetwork.cc or reduce unique chiplet " + "definitions.", + kMaxBlockDisc, + kBlockTagWidth); + } + ++next_disc; + } + } + } + for (auto& [block, cnt] : block_refcount) { + if (cnt > 1) { + block_to_chip_inst_.erase(block); + } + } + // Leaf chip with own dbBlock: route through the existing flat path. + if (chip->getBlock() != nullptr) { + if (block_ == nullptr) { + block_ = chip->getBlock(); + } + return; + } + // Hierarchical chip (no own block): synthesize a top cell so STA can + // see the chip-inst children via topInstance(). + if (!chip->getChipInsts().empty()) { + makeTopCellForChip(chip); + } +} + +void dbNetwork::ensureBumpToChipNetCacheFresh() const +{ + if (top_chip_ == nullptr) { + return; + } + // Detect chip-nets added between cache builds by tracking the odb + // chip-net count. Rebuild on any size mismatch — covers tcl/C++ + // callers that dbChipNet_create + addBumpInst after read_3dbx. + // + // Caveat: this still misses in-place bump-inst rewires on an EXISTING + // chip-net (addBumpInst without creating a new net first). dbCbk + // currently emits no chip-net / bump-inst signals, so callback-driven + // invalidation isn't possible yet. See 3DIC_TODO.md. + const dbSet chip_nets = top_chip_->getChipNets(); + if (bump_to_chip_net_cache_size_ == chip_nets.size() + && !bump_to_chip_net_.empty()) { + return; + } + bump_to_chip_net_.clear(); + for (odb::dbChipNet* chip_net : chip_nets) { + const uint32_t n = chip_net->getNumBumpInsts(); + std::vector path; + for (uint32_t i = 0; i < n; ++i) { + odb::dbChipBumpInst* bump_inst = chip_net->getBumpInst(i, path); + if (bump_inst != nullptr) { + bump_to_chip_net_[bump_inst] = chip_net; + } + } + } + bump_to_chip_net_cache_size_ = chip_nets.size(); +} + +void dbNetwork::makeTopCellForChip(dbChip* chip) +{ + if (top_cell_) { + Library* old_lib = library(top_cell_); + deleteLibrary(old_lib); + } + if (chip_bump_lib_) { + deleteLibrary(reinterpret_cast(chip_bump_lib_)); + chip_bump_lib_ = nullptr; + } + chip_master_cells_.clear(); + const char* design_name = chip->getName(); + Library* top_lib = makeLibrary(design_name, ""); + top_cell_ = makeCell(top_lib, design_name, false, ""); + // Per-master LibertyCell with a self-arc per chip-bump port. Without + // the self-arc Graph::makeInstanceEdges builds no load<->bidir_drvr + // edge for the BIDIRECT bump pin, blocking create_clock propagation + // through the chip-bump. + chip_bump_lib_ = makeLibertyLibrary("3dic_chip_bump_lib", ""); + LibertyBuilder builder(debug_, report_); + for (dbChipInst* chip_inst : chip->getChipInsts()) { + dbChip* master = chip_inst->getMasterChip(); + if (master == nullptr || chip_master_cells_.contains(master)) { + continue; + } + LibertyCell* lc + = builder.makeCell(chip_bump_lib_, master->getName(), "3dic-synth"); + Cell* master_cell = reinterpret_cast(lc); + chip_master_cells_[master] = master_cell; + for (odb::dbChipRegion* region : master->getChipRegions()) { + for (odb::dbChipBump* bump : region->getChipBumps()) { + odb::dbBTerm* bterm = bump->getBTerm(); + if (bterm == nullptr) { + continue; + } + LibertyPort* lp = builder.makePort(lc, bterm->getConstName()); + lp->setDirection(dbToSta(bterm->getSigType(), bterm->getIoType())); + registerConcretePort(reinterpret_cast(lp)); + auto attrs = std::make_shared(); + attrs->setTimingType(TimingType::combinational); + lc->makeTimingArcSet( + lp, lp, nullptr, TimingRole::combinational(), attrs); + } + } + lc->finish(false, report_, debug_); + } +} + +dbBlock* dbNetwork::blockOf(dbChipInst* chip_inst) const +{ + if (chip_inst == nullptr) { + return nullptr; + } + dbChip* master = chip_inst->getMasterChip(); + return master ? master->getBlock() : nullptr; +} + +bool dbNetwork::blockOwnedUniquelyBy(dbChipInst* chip_inst) const +{ + if (chip_inst == nullptr) { + return false; + } + dbBlock* mb = blockOf(chip_inst); + if (mb == nullptr) { + return false; + } + auto it = block_to_chip_inst_.find(mb); + return it != block_to_chip_inst_.end() && it->second == chip_inst; +} + +bool dbNetwork::has3DicChip() const +{ + if (top_chip_ == nullptr || top_chip_->getBlock() != nullptr) { + return false; + } + return !top_chip_->getChipInsts().empty(); +} + void dbNetwork::clear() { ConcreteNetwork::clear(); db_ = nullptr; + top_chip_ = nullptr; + // Owned by the libraries destroyed in ConcreteNetwork::clear(); drop + // dangling pointers. + chip_master_cells_.clear(); + bump_to_chip_net_.clear(); + bump_to_chip_net_cache_size_ = 0; + chip_bump_vertex_ids_.clear(); + block_to_chip_inst_.clear(); + block_disc_.clear(); + chip_bump_lib_ = nullptr; } Instance* dbNetwork::topInstance() const @@ -778,6 +1165,9 @@ ObjectId dbNetwork::id(const Instance* instance) const if (instance == top_instance_) { return 0; } + if (dbChipInst* chip_inst = staToDbChipInst(instance)) { + return getDbNwkObjectId(chip_inst); + } if (hasHierarchy()) { const dbObject* obj = reinterpret_cast(instance); return getDbNwkObjectId(obj); @@ -851,9 +1241,16 @@ std::string dbNetwork::busName(const Port* port) const std::string dbNetwork::name(const Instance* instance) const { if (instance == top_instance_) { + if (has3DicChip()) { + return top_chip_->getName(); + } return block_->getConstName(); } + if (dbChipInst* chip_inst = staToDbChipInst(instance)) { + return chip_inst->getName(); + } + dbInst* db_inst; odb::dbModInst* mod_inst; staToDb(instance, db_inst, mod_inst); @@ -1011,6 +1408,10 @@ Cell* dbNetwork::cell(const Instance* instance) const if (instance == top_instance_) { return reinterpret_cast(top_cell_); } + if (dbChipInst* chip_inst = staToDbChipInst(instance)) { + auto it = chip_master_cells_.find(chip_inst->getMasterChip()); + return it != chip_master_cells_.end() ? it->second : nullptr; + } dbInst* db_inst; odb::dbModInst* mod_inst; staToDb(instance, db_inst, mod_inst); @@ -1030,6 +1431,23 @@ Instance* dbNetwork::parent(const Instance* instance) const if (instance == top_instance_) { return nullptr; } + if (staToDbChipInst(instance) != nullptr) { + // Single-level chip hierarchy in v1; deeper nesting is post-v1. + return top_instance_; + } + // Inner dbInst living in a chiplet's block: parent is the chip-inst + // that placed that chiplet (resolved via block_to_chip_inst_). + if (has3DicChip()) { + dbInst* maybe_inner = nullptr; + odb::dbModInst* maybe_mi = nullptr; + staToDb(instance, maybe_inner, maybe_mi); + if (maybe_inner) { + auto it = block_to_chip_inst_.find(maybe_inner->getBlock()); + if (it != block_to_chip_inst_.end()) { + return dbToSta(it->second); + } + } + } dbInst* db_inst; odb::dbModInst* mod_inst; staToDb(instance, db_inst, mod_inst); @@ -1089,6 +1507,12 @@ bool dbNetwork::isLeaf(const Instance* instance) const if (instance == top_instance_) { return false; } + // Chip-insts are reported as leaves so STA's Graph::makeVerticesAndEdges + // creates Vertices for their chip-bump pins. Their inner dbInsts get + // surfaced separately by the topInstance childIterator's flat walk. + if (staToDbChipInst(instance) != nullptr) { + return true; + } if (hasHierarchy()) { dbMaster* db_master; dbModule* db_module; @@ -1102,6 +1526,28 @@ bool dbNetwork::isLeaf(const Instance* instance) const Instance* dbNetwork::findInstance(std::string_view path_name) const { std::string path_name_str(path_name); + if (has3DicChip()) { + // Top-level chip-inst lookup (O(1) via odb's name-indexed map). + if (dbChipInst* chip_inst = top_chip_->findChipInst(path_name_str)) { + return dbToSta(chip_inst); + } + // "/" — escape-aware split at the last divider. + std::string chip_name, inner_name; + pathNameLast(path_name_str, chip_name, inner_name); + if (chip_name.empty()) { + return nullptr; + } + dbChipInst* chip_inst = top_chip_->findChipInst(chip_name); + if (chip_inst == nullptr || !blockOwnedUniquelyBy(chip_inst)) { + return nullptr; + } + if (dbBlock* mb = chip_inst->getMasterChip()->getBlock()) { + if (dbInst* inner = mb->findInst(inner_name.c_str())) { + return dbToSta(inner); + } + } + return nullptr; + } if (hasHierarchy()) { // are we in hierarchical mode ? // find a hierarchical module instance first odb::dbModInst* mod_inst = block()->findModInst(path_name_str.c_str()); @@ -1149,6 +1595,14 @@ Instance* dbNetwork::findChild(const Instance* parent, std::string name_str(name); const char* name_cstr = name_str.c_str(); if (parent == top_instance_) { + if (has3DicChip()) { + for (dbChipInst* chip_inst : top_chip_->getChipInsts()) { + if (chip_inst->getName() == name_str) { + return dbToSta(chip_inst); + } + } + return nullptr; + } dbInst* inst = block_->findInst(name_cstr); if (!inst) { dbModule* top_module = block_->getTopModule(); @@ -1182,9 +1636,23 @@ Pin* dbNetwork::findPin(const Instance* instance, std::string port_name_str(port_name); const char* port_name_cstr = port_name_str.c_str(); if (instance == top_instance_) { + if (has3DicChip()) { + return nullptr; + } dbBTerm* bterm = block_->findBTerm(port_name_cstr); return dbToSta(bterm); } + if (dbChipInst* chip_inst = staToDbChipInst(instance)) { + for (odb::dbChipRegionInst* region : chip_inst->getRegions()) { + for (odb::dbChipBumpInst* bump : region->getChipBumpInsts()) { + odb::dbBTerm* bterm = bump->getChipBump()->getBTerm(); + if (bterm && port_name_str == bterm->getName()) { + return dbToSta(bump); + } + } + } + return nullptr; + } dbInst* db_inst; odb::dbModInst* mod_inst; staToDb(instance, db_inst, mod_inst); @@ -1218,6 +1686,15 @@ Pin* dbNetwork::findPin(const Instance* instance, const Port* port) const // Net* dbNetwork::findNetAllScopes(std::string_view net_name) const { + if (has3DicChip()) { + const std::string name_str(net_name); + for (odb::dbChipNet* chip_net : top_chip_->getChipNets()) { + if (chip_net->getName() == name_str) { + return dbToSta(chip_net); + } + } + return nullptr; + } std::string net_sname(net_name); const char* net_cname = net_sname.c_str(); for (dbModule* dbm : block_->getModules()) { @@ -1236,6 +1713,13 @@ Net* dbNetwork::findNetAllScopes(std::string_view net_name) const Net* dbNetwork::findNet(const Instance* instance, std::string_view net_name) const { + if (has3DicChip()) { + if (instance != top_instance_) { + return nullptr; + } + return findNetAllScopes(net_name); + } + dbModule* scope = nullptr; std::string net_sname(net_name); @@ -1279,6 +1763,23 @@ void dbNetwork::findInstNetsMatching(const Instance* instance, const PatternMatch* pattern, NetSeq& nets) const { + if (has3DicChip()) { + if (instance != top_instance_) { + return; + } + if (pattern->hasWildcards()) { + for (odb::dbChipNet* chip_net : top_chip_->getChipNets()) { + if (pattern->match(chip_net->getName())) { + nets.push_back(dbToSta(chip_net)); + } + } + } else { + if (Net* net = findNetAllScopes(pattern->pattern())) { + nets.push_back(net); + } + } + return; + } if (instance == top_instance_) { if (pattern->hasWildcards()) { for (dbNet* dnet : block_->getNets()) { @@ -1363,6 +1864,10 @@ void dbNetwork::setAttribute(Instance* instance, ObjectId dbNetwork::id(const Pin* pin) const { + if (odb::dbChipBumpInst* bump = staToDbChipBumpInst(pin)) { + return getDbNwkObjectId(bump); + } + dbITerm* iterm = nullptr; dbBTerm* bterm = nullptr; dbModITerm* moditerm = nullptr; @@ -1391,6 +1896,10 @@ ObjectId dbNetwork::id(const Pin* pin) const Instance* dbNetwork::instance(const Pin* pin) const { + if (odb::dbChipBumpInst* bump = staToDbChipBumpInst(pin)) { + return dbToSta(bump->getChipRegionInst()->getChipInst()); + } + dbITerm* iterm = nullptr; dbBTerm* bterm = nullptr; dbModITerm* moditerm = nullptr; @@ -1401,6 +1910,16 @@ Instance* dbNetwork::instance(const Pin* pin) const return dbToSta(dinst); } if (bterm) { + // dbBTerm belonging to a chiplet's inner block: owner is the + // chip-inst, not the synthetic top. Without this STA treats the + // inner BTerm pin as a top-level port and crashes downstream when + // it can't find a matching Port on top_cell_. + if (has3DicChip()) { + auto it = block_to_chip_inst_.find(bterm->getBlock()); + if (it != block_to_chip_inst_.end()) { + return dbToSta(it->second); + } + } return top_instance_; } if (moditerm) { @@ -1412,6 +1931,12 @@ Instance* dbNetwork::instance(const Pin* pin) const Net* dbNetwork::net(const Pin* pin) const { + if (odb::dbChipBumpInst* bump = staToDbChipBumpInst(pin)) { + ensureBumpToChipNetCacheFresh(); + auto it = bump_to_chip_net_.find(bump); + return it != bump_to_chip_net_.end() ? dbToSta(it->second) : nullptr; + } + dbITerm* iterm = nullptr; dbBTerm* bterm = nullptr; dbModITerm* moditerm = nullptr; @@ -1507,6 +2032,13 @@ void dbNetwork::net(const Pin* pin, Term* dbNetwork::term(const Pin* pin) const { + if (odb::dbChipBumpInst* bump = staToDbChipBumpInst(pin)) { + if (odb::dbBTerm* inner = bump->getChipBump()->getBTerm()) { + return dbToStaTerm(inner); + } + return nullptr; + } + dbITerm* iterm = nullptr; dbBTerm* bterm = nullptr; dbModITerm* moditerm = nullptr; @@ -1529,6 +2061,19 @@ Term* dbNetwork::term(const Pin* pin) const Port* dbNetwork::port(const Pin* pin) const { + if (odb::dbChipBumpInst* bump = staToDbChipBumpInst(pin)) { + odb::dbBTerm* bterm = bump->getChipBump()->getBTerm(); + if (bterm == nullptr) { + return nullptr; + } + dbChip* master = bump->getChipRegionInst()->getChipInst()->getMasterChip(); + auto it = chip_master_cells_.find(master); + if (it == chip_master_cells_.end()) { + return nullptr; + } + return findPort(it->second, bterm->getConstName()); + } + dbITerm* iterm; dbBTerm* bterm; dbModITerm* moditerm; @@ -1583,6 +2128,18 @@ PortDirection* dbNetwork::direction(const Pin* pin) const return lib_port->direction(); } + if (odb::dbChipBumpInst* bump = staToDbChipBumpInst(pin)) { + // Report BIDIRECT so Graph::makeWireEdgesFromPin runs on every bump + // regardless of the underlying bterm's IoType. Without this an INPUT + // bump (e.g. clk) has no outgoing wire edges and a clock anchored on + // it never reaches the chiplet's internal loads. + odb::dbChipBump* chip_bump = bump->getChipBump(); + if (chip_bump != nullptr && chip_bump->getBTerm() != nullptr) { + return PortDirection::bidirect(); + } + return nullptr; + } + dbITerm* iterm; dbBTerm* bterm; dbModITerm* moditerm; @@ -1619,6 +2176,10 @@ PortDirection* dbNetwork::direction(const Pin* pin) const VertexId dbNetwork::vertexId(const Pin* pin) const { + if (odb::dbChipBumpInst* bump = staToDbChipBumpInst(pin)) { + auto it = chip_bump_vertex_ids_.find(bump); + return it != chip_bump_vertex_ids_.end() ? it->second : object_id_null; + } dbITerm* iterm = nullptr; dbBTerm* bterm = nullptr; dbModITerm* miterm = nullptr; @@ -1634,6 +2195,10 @@ VertexId dbNetwork::vertexId(const Pin* pin) const void dbNetwork::setVertexId(Pin* pin, VertexId id) { + if (odb::dbChipBumpInst* bump = staToDbChipBumpInst(pin)) { + chip_bump_vertex_ids_[bump] = id; + return; + } dbITerm* iterm = nullptr; dbBTerm* bterm = nullptr; dbModITerm* moditerm = nullptr; @@ -1754,10 +2319,19 @@ bool dbNetwork::isPlaced(const Pin* pin) const ObjectId dbNetwork::id(const Net* net) const { + if (staToDbChipNet(net) != nullptr) { + const dbObject* obj = reinterpret_cast(net); + return getDbNwkObjectId(obj); + } odb::dbModNet* modnet = nullptr; dbNet* dnet = nullptr; staToDb(net, dnet, modnet); - if (hasHierarchy()) { + // In 3DIC mode every chiplet block numbers its own nets from 1, so + // raw dnet->getId() collides across blocks. Route through the tagged + // encoder (which mixes the block id in) to keep NetSet/visited_nets + // dedup working when visitConnectedPins descends into multiple + // chiplet inner nets. + if (hasHierarchy() || has3DicChip()) { const dbObject* obj = reinterpret_cast(net); return getDbNwkObjectId(obj); } @@ -1783,6 +2357,10 @@ std::string dbNetwork::pathName(const Net* net) const // its full name, ditto hierarchical mode. // For a modnet in hierarchy mode things are a bit more interesting. + if (odb::dbChipNet* chip_net = staToDbChipNet(net)) { + return chip_net->getName(); + } + odb::dbModNet* modnet = nullptr; dbNet* dnet = nullptr; @@ -1825,6 +2403,10 @@ std::string dbNetwork::pathName(const Net* net) const std::string dbNetwork::name(const Net* net) const { + if (odb::dbChipNet* chip_net = staToDbChipNet(net)) { + return chip_net->getName(); + } + odb::dbModNet* modnet = nullptr; dbNet* dnet = nullptr; staToDb(net, dnet, modnet); @@ -1950,6 +2532,33 @@ void dbNetwork::visitConnectedPins(const Net* net, } visited_nets.insert(net); + + // Cross-chiplet net: visit each bump-inst Pin AND descend into the + // chiplet's inner net (via term()) so the visitor sees driver/load pins + // inside every chiplet sharing this chip-net. STA's wire-edge model is a + // fat net — direct drvr → load edges across hierarchy — so descending + // here lets Graph::makeWireEdgesFromPin form cross-chiplet wire edges + // (e.g. chipA/buf/Z → chipB/buf/A) without needing dual vertices on the + // chip-bump pin itself. + if (odb::dbChipNet* chip_net = staToDbChipNet(net)) { + const uint32_t n = chip_net->getNumBumpInsts(); + std::vector path; + for (uint32_t i = 0; i < n; ++i) { + odb::dbChipBumpInst* bump = chip_net->getBumpInst(i, path); + if (bump == nullptr) { + continue; + } + Pin* bump_pin = dbToSta(bump); + visitor(bump_pin); + if (Term* inner_term = term(bump_pin)) { + if (Net* inner_net = this->net(inner_term)) { + visitConnectedPins(inner_net, visitor, visited_nets); + } + } + } + return; + } + staToDb(net, db_net, mod_net); if (mod_net) { @@ -2046,6 +2655,26 @@ Pin* dbNetwork::pin(const Term* term) const dbITerm* iterm = nullptr; staToDb(term, iterm, bterm, modbterm); if (bterm) { + // Chiplet boundary: an inner BTerm with a backing dbChipBump maps + // upward to the chip-bump-inst on the chip-inst that placed this + // chiplet. Returning the chip-bump-inst Pin lets STA walk up across + // the chiplet boundary. + if (has3DicChip()) { + if (odb::dbChipBump* bump = bterm->getChipBump()) { + if (auto it = block_to_chip_inst_.find(bterm->getBlock()); + it != block_to_chip_inst_.end()) { + odb::dbChipInst* chip_inst = it->second; + if (odb::dbChipRegionInst* region_inst + = chip_inst->findChipRegionInst(bump->getChipRegion())) { + for (odb::dbChipBumpInst* bi : region_inst->getChipBumpInsts()) { + if (bi->getChipBump() == bump) { + return dbToSta(bi); + } + } + } + } + } + } return dbToSta(bterm); } if (modbterm) { @@ -2984,6 +3613,11 @@ void dbNetwork::staToDb(const Instance* instance, } else if (type == dbModInstObj) { db_inst = nullptr; mod_inst = static_cast(obj); + } else if (type == dbChipInstObj) { + // Decoded via staToDbChipInst(); explicit nulls — out-params are not + // default-initialized by callers and would otherwise carry garbage. + db_inst = nullptr; + mod_inst = nullptr; } else { logger_->error(ORD, 2016, "Instance is not dbInst or odb::dbModInst"); } @@ -2995,9 +3629,14 @@ void dbNetwork::staToDb(const Instance* instance, dbNet* dbNetwork::staToDb(const Net* net) const { - dbNet* db_net = reinterpret_cast(const_cast(net)); - assert(!db_net || db_net->getObjectType() == odb::dbNetObj); - return db_net; + if (net == nullptr) { + return nullptr; + } + dbObject* obj = reinterpret_cast(const_cast(net)); + if (obj->getObjectType() != odb::dbNetObj) { + return nullptr; + } + return static_cast(obj); } dbNet* dbNetwork::flatNet(const Net* net) const @@ -3025,6 +3664,8 @@ void dbNetwork::staToDb(const Net* net, dnet = static_cast(obj); } else if (type == odb::dbModNetObj) { modnet = static_cast(obj); + } else if (type == dbChipNetObj) { + // Decoded via staToDbChipNet(); both out-params remain null. } else { logger_->error(ORD, 2034, "Net is not dbNet or odb::dbModNet"); } @@ -3124,6 +3765,10 @@ void dbNetwork::staToDb(const Pin* pin, case PinPointerTags::kDbModIterm: moditerm = reinterpret_cast(pointer_without_tag); break; + case PinPointerTags::kDbChipBumpInst: + // Decoded via staToDbChipBumpInst(); leave iterm/bterm/moditerm null + // so callers see "no match" rather than misclassify the pin. + break; case PinPointerTags::kNone: logger_->error(ORD, 2018, "Pin is not ITerm or BTerm or modITerm."); break; @@ -3382,6 +4027,68 @@ Pin* dbNetwork::dbToSta(dbITerm* iterm) const + static_cast(PinPointerTags::kDbIterm)); } +Pin* dbNetwork::dbToSta(dbChipBumpInst* bump_inst) const +{ + if (bump_inst == nullptr) { + return nullptr; + } + char* unaligned_pointer = reinterpret_cast(bump_inst); + return reinterpret_cast( + unaligned_pointer + + static_cast(PinPointerTags::kDbChipBumpInst)); +} + +Instance* dbNetwork::dbToSta(dbChipInst* chip_inst) const +{ + return reinterpret_cast(chip_inst); +} + +Net* dbNetwork::dbToSta(dbChipNet* chip_net) const +{ + return reinterpret_cast(chip_net); +} + +dbChipBumpInst* dbNetwork::staToDbChipBumpInst(const Pin* pin) const +{ + if (pin == nullptr) { + return nullptr; + } + std::uintptr_t pointer_with_tag = reinterpret_cast(pin); + PinPointerTags tag_value + = static_cast(pointer_with_tag & kPointerTagMask); + if (tag_value != PinPointerTags::kDbChipBumpInst) { + return nullptr; + } + const char* char_pointer_pin = reinterpret_cast(pin); + char* pointer_without_tag = const_cast( + char_pointer_pin - static_cast(tag_value)); + return reinterpret_cast(pointer_without_tag); +} + +dbChipInst* dbNetwork::staToDbChipInst(const Instance* instance) const +{ + if (instance == nullptr || instance == top_instance_) { + return nullptr; + } + dbObject* obj = reinterpret_cast(const_cast(instance)); + if (obj->getObjectType() != dbChipInstObj) { + return nullptr; + } + return static_cast(obj); +} + +dbChipNet* dbNetwork::staToDbChipNet(const Net* net) const +{ + if (net == nullptr) { + return nullptr; + } + dbObject* obj = reinterpret_cast(const_cast(net)); + if (obj->getObjectType() != dbChipNetObj) { + return nullptr; + } + return static_cast(obj); +} + Pin* dbNetwork::dbToSta(dbObject* term_obj) const { if (term_obj == nullptr) { diff --git a/src/dbSta/src/dbSta.cc b/src/dbSta/src/dbSta.cc index e1fdd5bddc0..ceaf303ad25 100644 --- a/src/dbSta/src/dbSta.cc +++ b/src/dbSta/src/dbSta.cc @@ -378,7 +378,85 @@ void dbSta::postReadDef(odb::dbBlock* block) void dbSta::postRead3Dbx(odb::dbChip* chip) { - // TODO: we are not ready to do timing on chiplets yet + debugPrint(logger_, + utl::STA, + "3dic", + 1, + "3Dbx callback received for chip {}.", + chip ? chip->getName() : ""); + if (chip == nullptr) { + return; + } + db_network_->setTopChip(chip); + if (db_->getUnfoldedModel() == nullptr) { + db_->constructUnfoldedModel(); + } + // Per-chiplet edit callbacks fire on each chiplet's dbBlock; chip-level + // dbChipCallBackObj carries no chip-inst/bump/net signals today. + for (odb::dbChipInst* chip_inst : chip->getChipInsts()) { + if (odb::dbBlock* chiplet_block = db_network_->blockOf(chip_inst)) { + db_cbk_->addOwner(chiplet_block); + } + } + db_cbk_->setNetwork(db_network_); + + // Diagnostics: surface chip-inst / chip-net / chip-conn counts and flag + // structural issues that would silently block cross-chiplet paths. + const size_t inst_count = chip->getChipInsts().size(); + const size_t net_count = chip->getChipNets().size(); + const size_t conn_count = chip->getChipConns().size(); + size_t total_bump_insts = 0; + for (odb::dbChipNet* chip_net : chip->getChipNets()) { + const uint32_t n = chip_net->getNumBumpInsts(); + total_bump_insts += n; + // Single-bump chip-nets are legitimate (top-level IO that connects a + // chiplet bump to the top boundary); only flag truly orphan nets. + if (n == 0) { + logger_->warn(utl::STA, + 3001, + "3DIC top-level net '{}' has no bump pads attached; " + "net is orphan and carries no STA pin.", + chip_net->getName()); + } + } + // Aggregate unbound chip-bumps per master into one summary warning so + // designs that intentionally leave non-signal bumps unbound (e.g. PG + // bumps) don't spam the log. + std::set seen_masters; + for (odb::dbChipInst* chip_inst : chip->getChipInsts()) { + odb::dbChip* master = chip_inst->getMasterChip(); + if (master == nullptr || !seen_masters.insert(master).second) { + continue; + } + size_t bumps_total = 0; + size_t bumps_unbound = 0; + for (odb::dbChipRegion* region : master->getChipRegions()) { + for (odb::dbChipBump* bump : region->getChipBumps()) { + ++bumps_total; + if (bump->getBTerm() == nullptr) { + ++bumps_unbound; + } + } + } + if (bumps_unbound > 0) { + logger_->warn(utl::STA, + 3002, + "3DIC chiplet '{}': {}/{} bump pads not mapped to " + "a chiplet port (missing name in .bmap col 5). " + "Paths through them drop.", + master->getName(), + bumps_unbound, + bumps_total); + } + } + logger_->info(utl::STA, + 3000, + "3DIC STA active: {} chiplets, {} top-level nets, " + "{} 3D bond regions, {} bump pads.", + inst_count, + net_count, + conn_count, + total_bump_insts); } void dbSta::postReadDb(odb::dbDatabase* db) diff --git a/src/dbSta/src/dbSta.tcl b/src/dbSta/src/dbSta.tcl index e73123f1c64..7127637d7af 100644 --- a/src/dbSta/src/dbSta.tcl +++ b/src/dbSta/src/dbSta.tcl @@ -197,5 +197,39 @@ proc check_ip { args } { return [sta::check_ip_cmd $master_name $check_all $max_polygons $verbose] } +define_cmd_args "report_3dic_summary" {} + +proc report_3dic_summary { args } { + set db [ord::get_db] + set chip [$db getChip] + if { $chip == "NULL" } { + utl::warn STA 3003 "No chip loaded; nothing to report." + return + } + set chip_insts [$chip getChipInsts] + set chip_nets [$chip getChipNets] + set chip_conns [$chip getChipConns] + set inst_count [llength $chip_insts] + set net_count [llength $chip_nets] + set conn_count [llength $chip_conns] + set bump_inst_count 0 + foreach n $chip_nets { + incr bump_inst_count [$n getNumBumpInsts] + } + utl::report "3DIC summary for chip [$chip getName]:" + utl::report " chiplets : $inst_count" + utl::report " top-level nets : $net_count" + utl::report " 3D bond regions : $conn_count" + utl::report " bump pads : $bump_inst_count" + if { $inst_count > 0 } { + utl::report " chiplet instances:" + foreach ci $chip_insts { + set ref [$ci getMasterChip] + set ref_name [expr { $ref == "NULL" ? "" : [$ref getName] }] + utl::report " [$ci getName] (reference: $ref_name)" + } + } +} + # namespace } diff --git a/src/dbSta/test/3dic_cross.3dbx b/src/dbSta/test/3dic_cross.3dbx new file mode 100644 index 00000000000..e8911632ee9 --- /dev/null +++ b/src/dbSta/test/3dic_cross.3dbx @@ -0,0 +1,35 @@ +Header: + version: "1.0" + unit: "micron" + precision: 2000 + include: + - 3dic_cross_flop.3dbv + +Design: + name: "top" + external: + verilog_file: "3dic_cross_top.v" + +ChipletInst: + chipA: + reference: flop_chip_a + chipB: + reference: flop_chip_b + +Stack: + chipA: + loc: [0.0, 0.0] + z: 0.0 + orient: R0 + chipB: + loc: [0.0, 0.0] + z: 300.0 + orient: MZ + +Connection: + bond_a_to_b: + top: chipB.regions.front_reg + bot: chipA.regions.front_reg + to_pkg: + top: chipA.regions.front_reg + bot: ~ diff --git a/src/dbSta/test/3dic_cross.ok b/src/dbSta/test/3dic_cross.ok new file mode 100644 index 00000000000..6f1cf77ae78 --- /dev/null +++ b/src/dbSta/test/3dic_cross.ok @@ -0,0 +1,54 @@ +[INFO ODB-0227] LEF file: ../../odb/test/Nangate45/Nangate45_tech.lef, created 22 layers, 27 vias +[INFO ODB-0227] LEF file: ../../odb/test/Nangate45/Nangate45_stdcell.lef, created 135 library cells +[INFO ODB-0227] LEF file: ../../odb/test/Nangate45/fake_bumps.lef, created 1 library cells +[INFO ODB-0128] Design: flop_chip_a +[INFO ODB-0130] Created 3 pins. +[INFO ODB-0131] Created 3 components and 14 component-terminals. +[INFO ODB-0133] Created 5 nets and 7 connections. +[WARNING STA-1140] ../../odb/test/Nangate45/Nangate45_typ.lib line 37, library NangateOpenCellLibrary already exists. +[INFO ODB-0128] Design: flop_chip_b +[INFO ODB-0130] Created 3 pins. +[INFO ODB-0131] Created 3 components and 14 component-terminals. +[INFO ODB-0133] Created 5 nets and 7 connections. +[INFO STA-3000] 3DIC STA active: 2 chiplets, 4 top-level nets, 2 3D bond regions, 6 bump pads. +3DIC summary for chip top: + chiplets : 2 + top-level nets : 4 + 3D bond regions : 2 + bump pads : 6 + chiplet instances: + chipB (reference: flop_chip_b) + chipA (reference: flop_chip_a) +Startpoint: chipA/ff (rising edge-triggered flip-flop clocked by clk) +Endpoint: chipB/ff (rising edge-triggered flip-flop clocked by clk) +Path Group: clk +Path Type: max + + Delay Time Description +--------------------------------------------------------- + 0.00 0.00 clock clk (rise edge) + 0.00 0.00 clock network delay (ideal) + 0.00 0.00 ^ chipA/ff/CK (DFF_X1) + 0.09 0.09 ^ chipA/ff/Q (DFF_X1) + 0.01 0.09 v chipA/inv/ZN (INV_X1) + 0.02 0.11 v chipA/buf/Z (BUF_X1) + 0.02 0.14 v chipB/buf/Z (BUF_X1) + 0.01 0.15 ^ chipB/inv/ZN (INV_X1) + 0.00 0.15 ^ chipB/ff/D (DFF_X1) + 0.15 data arrival time + + 1.00 1.00 clock clk (rise edge) + 0.00 1.00 clock network delay (ideal) + 0.00 1.00 clock reconvergence pessimism + 1.00 ^ chipB/ff/CK (DFF_X1) + -0.03 0.97 library setup time + 0.97 data required time +--------------------------------------------------------- + 0.97 data required time + -0.15 data arrival time +--------------------------------------------------------- + 0.82 slack (MET) + + +Summary 2 / 2 (100% pass) +pass diff --git a/src/dbSta/test/3dic_cross.tcl b/src/dbSta/test/3dic_cross.tcl new file mode 100644 index 00000000000..00151c2d988 --- /dev/null +++ b/src/dbSta/test/3dic_cross.tcl @@ -0,0 +1,41 @@ +source "helpers.tcl" + +# Cross-chiplet timing path. Two distinct chiplet masters (flop_chip_a, +# flop_chip_b), each with DFF + INV + BUF, wired top-side so chipA.q -> +# bridge -> chipB.d. buildChipNetsFromVerilog reads 3dic_cross_top.v and +# creates a dbChipNet per top-level wire. STA traverses via term() +# (Stage 4 bridge) + DbNetPinIterator (Stage 5) + visitConnectedPins +# chip-net descent (Stage 7) so wire edges form across the boundary. +set_thread_count 1 + +read_3dbx 3dic_cross.3dbx + +# Stage 8: structural summary helper. Exercises the dbChip/dbChipNet/ +# dbChipConn iteration paths and surfaces user-facing counts in 3DBlox +# terminology. +report_3dic_summary + +proc chip_net_names { } { + set names {} + foreach n [get_nets *] { + lappend names [get_full_name $n] + } + return [lsort $names] +} +check "chip-net set" { chip_net_names } {bridge clk_top in_top out_top} +check "bridge spans both chiplets" { + set insts {} + foreach pin [get_pins -of_objects [get_nets bridge]] { + lappend insts [get_full_name [$pin instance]] + } + lsort -unique $insts +} {chipA chipB} + +# Anchor the clock on the chip-bump pins of clk_top — the natural form +# users will write. Propagation through the BIDIRECT bump relies on the +# synthesized LibertyCell self-arc built in makeTopCellForChip. +create_clock -name clk -period 1.0 \ + [get_pins -of_objects [get_nets clk_top]] +report_checks -path_delay max -group_path_count 4 + +exit_summary diff --git a/src/dbSta/test/3dic_cross_flop.3dbv b/src/dbSta/test/3dic_cross_flop.3dbv new file mode 100644 index 00000000000..f7ef8ef74d4 --- /dev/null +++ b/src/dbSta/test/3dic_cross_flop.3dbv @@ -0,0 +1,52 @@ +#!define NG45_PATH ../../odb/test/Nangate45 + +Header: + version: 2.5 + unit: micron + precision: 2000 + +ChipletDef: + flop_chip_a: + type: die + design_area: [20, 20] + shrink: 1 + tsv: false + thickness: 300 + offset: [0, 0] + regions: + front_reg: + side: front + coords: + - [0, 0] + - [20, 0] + - [20, 20] + - [0, 20] + bmap: 3dic_cross_flop_a.bmap + layer: metal1 + external: + APR_tech_file: [NG45_PATH/Nangate45_tech.lef] + liberty_file: [NG45_PATH/Nangate45_typ.lib] + LEF_file: [NG45_PATH/Nangate45_stdcell.lef, NG45_PATH/fake_bumps.lef] + DEF_file: 3dic_cross_flop_a.def + flop_chip_b: + type: die + design_area: [20, 20] + shrink: 1 + tsv: false + thickness: 300 + offset: [0, 0] + regions: + front_reg: + side: front + coords: + - [0, 0] + - [20, 0] + - [20, 20] + - [0, 20] + bmap: 3dic_cross_flop_b.bmap + layer: metal1 + external: + APR_tech_file: [NG45_PATH/Nangate45_tech.lef] + liberty_file: [NG45_PATH/Nangate45_typ.lib] + LEF_file: [NG45_PATH/Nangate45_stdcell.lef, NG45_PATH/fake_bumps.lef] + DEF_file: 3dic_cross_flop_b.def diff --git a/src/dbSta/test/3dic_cross_flop_a.bmap b/src/dbSta/test/3dic_cross_flop_a.bmap new file mode 100644 index 00000000000..c0b3bdf85d5 --- /dev/null +++ b/src/dbSta/test/3dic_cross_flop_a.bmap @@ -0,0 +1,3 @@ +bump_clk BUMP 15.0 17.0 clk - +bump_d BUMP 15.0 19.0 d - +bump_q BUMP 19.0 19.0 q - diff --git a/src/dbSta/test/3dic_cross_flop_a.def b/src/dbSta/test/3dic_cross_flop_a.def new file mode 100644 index 00000000000..a067c9a253c --- /dev/null +++ b/src/dbSta/test/3dic_cross_flop_a.def @@ -0,0 +1,31 @@ +VERSION 5.8 ; +DIVIDERCHAR "/" ; +BUSBITCHARS "[]" ; +DESIGN flop_chip_a ; +UNITS DISTANCE MICRONS 2000 ; +DIEAREA ( 0 0 ) ( 40000 40000 ) ; + +COMPONENTS 3 ; + - ff DFF_X1 + PLACED ( 5000 10000 ) N ; + - inv INV_X1 + PLACED ( 15000 10000 ) N ; + - buf BUF_X1 + PLACED ( 25000 10000 ) N ; +END COMPONENTS + +PINS 3 ; + - clk + NET clk + DIRECTION INPUT + USE SIGNAL + + LAYER metal1 ( 0 0 ) ( 200 200 ) + PLACED ( 0 5000 ) N ; + - d + NET d + DIRECTION INPUT + USE SIGNAL + + LAYER metal1 ( 0 0 ) ( 200 200 ) + PLACED ( 0 15000 ) N ; + - q + NET q + DIRECTION OUTPUT + USE SIGNAL + + LAYER metal1 ( 0 0 ) ( 200 200 ) + PLACED ( 40000 15000 ) N ; +END PINS + +NETS 5 ; + - clk ( PIN clk ) ( ff CK ) + USE SIGNAL ; + - d ( PIN d ) ( ff D ) + USE SIGNAL ; + - n1 ( ff Q ) ( inv A ) + USE SIGNAL ; + - n2 ( inv ZN ) ( buf A ) + USE SIGNAL ; + - q ( PIN q ) ( buf Z ) + USE SIGNAL ; +END NETS + +END DESIGN diff --git a/src/dbSta/test/3dic_cross_flop_b.bmap b/src/dbSta/test/3dic_cross_flop_b.bmap new file mode 100644 index 00000000000..c689dfaea9f --- /dev/null +++ b/src/dbSta/test/3dic_cross_flop_b.bmap @@ -0,0 +1,3 @@ +bump_clk BUMP 15.0 17.0 clk - +bump_d BUMP 19.0 19.0 d - +bump_q BUMP 19.0 17.0 q - diff --git a/src/dbSta/test/3dic_cross_flop_b.def b/src/dbSta/test/3dic_cross_flop_b.def new file mode 100644 index 00000000000..be59fb13312 --- /dev/null +++ b/src/dbSta/test/3dic_cross_flop_b.def @@ -0,0 +1,31 @@ +VERSION 5.8 ; +DIVIDERCHAR "/" ; +BUSBITCHARS "[]" ; +DESIGN flop_chip_b ; +UNITS DISTANCE MICRONS 2000 ; +DIEAREA ( 0 0 ) ( 40000 40000 ) ; + +COMPONENTS 3 ; + - buf BUF_X1 + PLACED ( 5000 10000 ) N ; + - inv INV_X1 + PLACED ( 15000 10000 ) N ; + - ff DFF_X1 + PLACED ( 25000 10000 ) N ; +END COMPONENTS + +PINS 3 ; + - clk + NET clk + DIRECTION INPUT + USE SIGNAL + + LAYER metal1 ( 0 0 ) ( 200 200 ) + PLACED ( 0 5000 ) N ; + - d + NET d + DIRECTION INPUT + USE SIGNAL + + LAYER metal1 ( 0 0 ) ( 200 200 ) + PLACED ( 0 15000 ) N ; + - q + NET q + DIRECTION OUTPUT + USE SIGNAL + + LAYER metal1 ( 0 0 ) ( 200 200 ) + PLACED ( 40000 15000 ) N ; +END PINS + +NETS 5 ; + - clk ( PIN clk ) ( ff CK ) + USE SIGNAL ; + - d ( PIN d ) ( buf A ) + USE SIGNAL ; + - n1 ( buf Z ) ( inv A ) + USE SIGNAL ; + - n2 ( inv ZN ) ( ff D ) + USE SIGNAL ; + - q ( PIN q ) ( ff Q ) + USE SIGNAL ; +END NETS + +END DESIGN diff --git a/src/dbSta/test/3dic_cross_top.v b/src/dbSta/test/3dic_cross_top.v new file mode 100644 index 00000000000..d80ecf0e671 --- /dev/null +++ b/src/dbSta/test/3dic_cross_top.v @@ -0,0 +1,23 @@ +// Stub flop_chip_a / flop_chip_b modules so STA's verilog reader doesn't +// black-box them when buildChipNetsFromVerilog parses this file. The real +// bodies are loaded separately from each chiplet's DEF. +module flop_chip_a (clk, d, q); + input clk; + input d; + output q; +endmodule + +module flop_chip_b (clk, d, q); + input clk; + input d; + output q; +endmodule + +module top (clk_top, in_top, out_top); + input clk_top; + input in_top; + output out_top; + wire bridge; + flop_chip_a chipA (.clk(clk_top), .d(in_top), .q(bridge)); + flop_chip_b chipB (.clk(clk_top), .d(bridge), .q(out_top)); +endmodule diff --git a/src/dbSta/test/3dic_get_cells.ok b/src/dbSta/test/3dic_get_cells.ok new file mode 100644 index 00000000000..5fcf9fd832b --- /dev/null +++ b/src/dbSta/test/3dic_get_cells.ok @@ -0,0 +1,11 @@ +[INFO ODB-0227] LEF file: ../../odb/test/data/../Nangate45/Nangate45_tech.lef, created 22 layers, 27 vias +[INFO ODB-0227] LEF file: ../../odb/test/data/../Nangate45/fake_macros.lef, created 10 library cells +[INFO ODB-0227] LEF file: ../../odb/test/data/../Nangate45/fake_bumps.lef, created 1 library cells +[WARNING STA-1171] ../../odb/test/data/../Nangate45/fake_macros.lib line 32, default_max_transition is 0.0. +[INFO ODB-0128] Design: fake_macros +[INFO ODB-0131] Created 10 components and 32 component-terminals. +[INFO ODB-0133] Created 12 nets and 24 connections. +[WARNING STA-3002] 3DIC chiplet 'SoC': 2/2 bump pads not mapped to a chiplet port (missing name in .bmap col 5). Paths through them drop. +[INFO STA-3000] 3DIC STA active: 2 chiplets, 0 top-level nets, 2 3D bond regions, 0 bump pads. +Summary 7 / 7 (100% pass) +pass diff --git a/src/dbSta/test/3dic_get_cells.tcl b/src/dbSta/test/3dic_get_cells.tcl new file mode 100644 index 00000000000..05afd99bb13 --- /dev/null +++ b/src/dbSta/test/3dic_get_cells.tcl @@ -0,0 +1,71 @@ +source "helpers.tcl" + +# Hierarchical top dbChip must expose its dbChipInst children via +# dbNetwork::DbInstanceChildIterator, so `get_cells *` returns the chiplet +# instance names. +# +# example.bmap leaves both bumps unmapped (col-4 = "-"), so STA-3002 +# fires on read_3dbx. That is intentional here — this fixture exercises +# the structural iteration APIs (get_cells / get_pins / get_nets), not +# signal traversal. The expected warning is part of the golden .ok. +read_3dbx ../../odb/test/data/example.3dbx + +proc cell_names { } { + set names {} + foreach c [get_cells *] { + lappend names [get_full_name $c] + } + return [lsort $names] +} + +check "chiplet count" { llength [get_cells *] } 2 +check "chiplet names" { cell_names } {soc_inst soc_inst_duplicate} + +# Stage 4: chip-inst pin iteration must yield one Pin per bump-inst. +# example.bmap defines 2 bumps (bump1, bump2) per SoC chiplet master. +check "soc_inst pin count" { llength [get_pins -of_objects [get_cells soc_inst]] } 2 +check "soc_inst_dup pin count" { + llength [get_pins -of_objects [get_cells soc_inst_duplicate]] +} 2 + +# Stage 5: dbChipNet iteration. example.3dbx ships no chip-nets (its +# top.v reference is unresolved at parse time), so build one +# programmatically: tie soc_inst's first bump to soc_inst_duplicate's +# first bump as a synthetic cross-chiplet net. +set db [ord::get_db] +set top_chip [$db getChip] +set chip_inst_a [$top_chip findChipInst soc_inst] +set chip_inst_b [$top_chip findChipInst soc_inst_duplicate] +proc first_bump_inst { chip_inst } { + foreach region [$chip_inst getRegions] { + foreach bump [$region getChipBumpInsts] { + return $bump + } + } + return "" +} +set bump_a [first_bump_inst $chip_inst_a] +set bump_b [first_bump_inst $chip_inst_b] +set chip_net [odb::dbChipNet_create $top_chip "bridge"] +$chip_net addBumpInst $bump_a [list $chip_inst_a] +$chip_net addBumpInst $bump_b [list $chip_inst_b] + +# DbInstanceNetIterator picks up the newly created chip-net. +check "chip-net count" { llength [get_nets *] } 1 +# DbNetPinIterator yields one chip-bump-inst Pin per bump on the net. +check "bridge pin count" { + llength [get_pins -of_objects [get_nets bridge]] +} 2 +# Each pin's owning instance is a distinct chiplet — proves the net +# spans BOTH chiplets, not just lists two bumps on one chiplet. +proc bridge_pin_instances { } { + set names {} + foreach pin [get_pins -of_objects [get_nets bridge]] { + lappend names [get_full_name [$pin instance]] + } + return [lsort $names] +} +check "bridge spans both chiplets" { bridge_pin_instances } \ + {soc_inst soc_inst_duplicate} + +exit_summary diff --git a/src/dbSta/test/BUILD b/src/dbSta/test/BUILD index 8980dd8a95a..d133d13771f 100644 --- a/src/dbSta/test/BUILD +++ b/src/dbSta/test/BUILD @@ -11,6 +11,8 @@ package(features = ["layering_check"]) # From CMakeLists.txt or_integration_tests(TESTS ALL_TESTS = [ + "3dic_cross", + "3dic_get_cells", "block_sta1", "check_axioms", "clock_pin", @@ -105,6 +107,18 @@ filegroup( test_name + ".*", ], ) + { + "3dic_cross": [ + "//src/odb/test:regression_resources", + "3dic_cross_flop.3dbv", + "3dic_cross_flop_a.bmap", + "3dic_cross_flop_a.def", + "3dic_cross_flop_b.bmap", + "3dic_cross_flop_b.def", + "3dic_cross_top.v", + ], + "3dic_get_cells": [ + "//src/odb/test:regression_resources", + ], "block_sta1": [ "example1_typ.lib", "example1.def", diff --git a/src/dbSta/test/CMakeLists.txt b/src/dbSta/test/CMakeLists.txt index 98d185a4512..d6971ecb59a 100644 --- a/src/dbSta/test/CMakeLists.txt +++ b/src/dbSta/test/CMakeLists.txt @@ -1,6 +1,8 @@ or_integration_tests( "dbSta" TESTS + 3dic_cross + 3dic_get_cells block_sta1 check_axioms clock_pin diff --git a/src/dbSta/test/cpp/TestDbSta.cc b/src/dbSta/test/cpp/TestDbSta.cc index 1825ff0ad3e..e747e11b6dc 100644 --- a/src/dbSta/test/cpp/TestDbSta.cc +++ b/src/dbSta/test/cpp/TestDbSta.cc @@ -2,6 +2,7 @@ // Copyright (c) 2023-2025, The OpenROAD Authors #include +#include #include #include @@ -314,4 +315,50 @@ TEST_F(TestDbSta, StalePrevPath) << pre_pin_name << " after=" << post_pin_name; } +TEST_F(TestDbSta, Chip3DicEncodeDecodeRoundTrip) +{ + std::string test_name = "TestDbSta_0"; + readVerilogAndSetup(test_name + ".v"); + + EXPECT_EQ(db_network_->dbToSta(static_cast(nullptr)), + nullptr); + EXPECT_EQ(db_network_->dbToSta(static_cast(nullptr)), + nullptr); + EXPECT_EQ(db_network_->dbToSta(static_cast(nullptr)), + nullptr); + EXPECT_EQ(db_network_->staToDbChipBumpInst(nullptr), nullptr); + EXPECT_EQ(db_network_->staToDbChipInst(nullptr), nullptr); + EXPECT_EQ(db_network_->staToDbChipNet(nullptr), nullptr); + + // Round-trip uses fake aligned storage; encode/decode are pure pointer + // arithmetic with no dereference of the bump-inst pointer. + alignas(16) char fake_storage[sizeof(void*) * 4] = {}; + auto* fake_bump = reinterpret_cast(&fake_storage[0]); + + Pin* pin = db_network_->dbToSta(fake_bump); + ASSERT_NE(pin, nullptr); + EXPECT_EQ(reinterpret_cast(pin) & 0b111U, 4U); + + odb::dbChipBumpInst* decoded = db_network_->staToDbChipBumpInst(pin); + EXPECT_EQ(decoded, fake_bump); + + odb::dbITerm* iterm = nullptr; + odb::dbBTerm* bterm = nullptr; + odb::dbModITerm* moditerm = nullptr; + db_network_->staToDb(pin, iterm, bterm, moditerm); + EXPECT_EQ(iterm, nullptr); + EXPECT_EQ(bterm, nullptr); + EXPECT_EQ(moditerm, nullptr); + + odb::dbInst* any_inst = nullptr; + for (odb::dbInst* i : block_->getInsts()) { + any_inst = i; + break; + } + ASSERT_NE(any_inst, nullptr); + odb::dbITerm* real_iterm = *any_inst->getITerms().begin(); + Pin* iterm_pin = db_network_->dbToSta(real_iterm); + EXPECT_EQ(db_network_->staToDbChipBumpInst(iterm_pin), nullptr); +} + } // namespace sta diff --git a/src/mpl/BUILD b/src/mpl/BUILD index 6b5a4be1b98..dc9b6eeecf8 100644 --- a/src/mpl/BUILD +++ b/src/mpl/BUILD @@ -42,7 +42,7 @@ cc_library( "src/shapes.h", ], deps = [ - "//src/odb", + "//src/odb/src/db", "//src/utl", "@boost.random", ], diff --git a/src/mpl/test/BUILD b/src/mpl/test/BUILD index d9e8c7cd86a..c1e2eda5562 100644 --- a/src/mpl/test/BUILD +++ b/src/mpl/test/BUILD @@ -288,7 +288,7 @@ cc_test( ], deps = [ "//src/mpl:pusher", - "//src/odb", + "//src/odb/src/db", "//src/tst", "//src/utl", "@googletest//:gtest", diff --git a/src/odb/src/codeGenerator/gen.py b/src/odb/src/codeGenerator/gen.py index 82242905967..d1d17080544 100755 --- a/src/odb/src/codeGenerator/gen.py +++ b/src/odb/src/codeGenerator/gen.py @@ -120,7 +120,16 @@ def add_field_attributes( hdr = STD_TYPE_HDR.get(field.type) if hdr: klass.h_sys_includes.append(hdr) - klass.cpp_sys_includes.append(hdr) + # Only include in cpp when at least one body emission references the + # type by name (cmp, serial, or get). A fully-suppressed field (e.g. + # an alignment pad) needs the type only in the header. + cpp_emits_type = not ( + "no-cmp" in field.flags + and "no-serial" in field.flags + and "no-get" in field.flags + ) + if cpp_emits_type: + klass.cpp_sys_includes.append(hdr) if field.type.startswith("std::"): for t in re.findall(r"std::(\w+)", field.type): diff --git a/src/odb/src/codeGenerator/schema/chip/dbChipBumpInst.json b/src/odb/src/codeGenerator/schema/chip/dbChipBumpInst.json index 540d383e93d..16cc0e30501 100644 --- a/src/odb/src/codeGenerator/schema/chip/dbChipBumpInst.json +++ b/src/odb/src/codeGenerator/schema/chip/dbChipBumpInst.json @@ -4,8 +4,10 @@ "fields": [ { "name": "chip_bump_", "type":"dbId<_dbChipBump>", "flags": ["private"] }, { "name": "chip_region_inst_", "type":"dbId<_dbChipRegionInst>", "flags": ["private"] }, - { "name": "region_next_", "type":"dbId<_dbChipBumpInst>", "flags": ["private"] } + { "name": "region_next_", "type":"dbId<_dbChipBumpInst>", "flags": ["private"] }, + { "name": "pad_for_pointer_tag_alignment_", "type": "uint32_t", "default": 0, + "flags": ["private", "no-serial", "no-cmp", "no-get"] } ], "h_includes": ["odb/dbId.h"], "cpp_includes": ["dbInst.h", "dbChip.h"] -} \ No newline at end of file +} diff --git a/src/odb/src/db/dbChipBumpInst.cpp b/src/odb/src/db/dbChipBumpInst.cpp index aa7d1134713..82a24a46f08 100644 --- a/src/odb/src/db/dbChipBumpInst.cpp +++ b/src/odb/src/db/dbChipBumpInst.cpp @@ -42,6 +42,7 @@ bool _dbChipBumpInst::operator<(const _dbChipBumpInst& rhs) const _dbChipBumpInst::_dbChipBumpInst(_dbDatabase* db) { + pad_for_pointer_tag_alignment_ = 0; } dbIStream& operator>>(dbIStream& stream, _dbChipBumpInst& obj) diff --git a/src/odb/src/db/dbChipBumpInst.h b/src/odb/src/db/dbChipBumpInst.h index fee15017c0c..07b082424a4 100644 --- a/src/odb/src/db/dbChipBumpInst.h +++ b/src/odb/src/db/dbChipBumpInst.h @@ -29,6 +29,7 @@ class _dbChipBumpInst : public _dbObject dbId<_dbChipBump> chip_bump_; dbId<_dbChipRegionInst> chip_region_inst_; dbId<_dbChipBumpInst> region_next_; + uint32_t pad_for_pointer_tag_alignment_; }; dbIStream& operator>>(dbIStream& stream, _dbChipBumpInst& obj); dbOStream& operator<<(dbOStream& stream, const _dbChipBumpInst& obj); diff --git a/src/odb/test/BUILD b/src/odb/test/BUILD index 18365fb291d..41e30b0fd5e 100644 --- a/src/odb/test/BUILD +++ b/src/odb/test/BUILD @@ -337,7 +337,7 @@ filegroup( "sky130hd/sky130hd_vt.tlef", "sky130hd/work_around_yosys/formal_pdk.v", ], - visibility = ["//src/odb/test/cpp:__subpackages__"], + visibility = ["//visibility:public"], ) filegroup( diff --git a/src/odb/test/check_3dblox.ok b/src/odb/test/check_3dblox.ok index 293e7ef8a78..0949f5de04a 100644 --- a/src/odb/test/check_3dblox.ok +++ b/src/odb/test/check_3dblox.ok @@ -5,6 +5,8 @@ [INFO ODB-0128] Design: fake_macros [INFO ODB-0131] Created 10 components and 32 component-terminals. [INFO ODB-0133] Created 12 nets and 24 connections. +[WARNING STA-3002] 3DIC chiplet 'SoC': 2/2 bump pads not mapped to a chiplet port (missing name in .bmap col 5). Paths through them drop. +[INFO STA-3000] 3DIC STA active: 2 chiplets, 0 top-level nets, 2 3D bond regions, 0 bump pads. [WARNING ODB-0151] Found 1 floating chip sets [WARNING ODB-0207] Invalid connection soc_to_soc: soc_inst_duplicate/front_reg (faces BOTTOM) to soc_inst/front_reg (faces TOP) [WARNING ODB-0273] Found 1 invalid connections diff --git a/src/odb/test/read_3dbx.ok b/src/odb/test/read_3dbx.ok index 32f444aa410..99c4203eb13 100644 --- a/src/odb/test/read_3dbx.ok +++ b/src/odb/test/read_3dbx.ok @@ -5,4 +5,6 @@ [INFO ODB-0128] Design: fake_macros [INFO ODB-0131] Created 10 components and 32 component-terminals. [INFO ODB-0133] Created 12 nets and 24 connections. +[WARNING STA-3002] 3DIC chiplet 'SoC': 2/2 bump pads not mapped to a chiplet port (missing name in .bmap col 5). Paths through them drop. +[INFO STA-3000] 3DIC STA active: 2 chiplets, 0 top-level nets, 2 3D bond regions, 0 bump pads. pass diff --git a/src/odb/test/write_3dbv.ok b/src/odb/test/write_3dbv.ok index 03e6732b831..a4497d3b95b 100644 --- a/src/odb/test/write_3dbv.ok +++ b/src/odb/test/write_3dbv.ok @@ -5,6 +5,8 @@ [INFO ODB-0128] Design: fake_macros [INFO ODB-0131] Created 10 components and 32 component-terminals. [INFO ODB-0133] Created 12 nets and 24 connections. +[WARNING STA-3002] 3DIC chiplet 'SoC': 2/2 bump pads not mapped to a chiplet port (missing name in .bmap col 5). Paths through them drop. +[INFO STA-3000] 3DIC STA active: 2 chiplets, 0 top-level nets, 2 3D bond regions, 0 bump pads. [INFO ODB-0541] More than one lib exists, multiple files will be written. No differences found. pass diff --git a/src/odb/test/write_3dbx.ok b/src/odb/test/write_3dbx.ok index 03e6732b831..a4497d3b95b 100644 --- a/src/odb/test/write_3dbx.ok +++ b/src/odb/test/write_3dbx.ok @@ -5,6 +5,8 @@ [INFO ODB-0128] Design: fake_macros [INFO ODB-0131] Created 10 components and 32 component-terminals. [INFO ODB-0133] Created 12 nets and 24 connections. +[WARNING STA-3002] 3DIC chiplet 'SoC': 2/2 bump pads not mapped to a chiplet port (missing name in .bmap col 5). Paths through them drop. +[INFO STA-3000] 3DIC STA active: 2 chiplets, 0 top-level nets, 2 3D bond regions, 0 bump pads. [INFO ODB-0541] More than one lib exists, multiple files will be written. No differences found. pass diff --git a/src/sta b/src/sta index 76c4d6df353..1acaf86a203 160000 --- a/src/sta +++ b/src/sta @@ -1 +1 @@ -Subproject commit 76c4d6df3537ccce331b5caa812196c3330ba7c4 +Subproject commit 1acaf86a203f735cb9c4d6f137b049fc81b8d12f