coverity and tag and scene doc

Signed-off-by: dsengupta0628 <dsengupta@precisioninno.com>
This commit is contained in:
dsengupta0628 2026-07-08 20:02:44 +00:00
parent 4a837e70c6
commit 3115b1a2e5
2 changed files with 904 additions and 1 deletions

901
doc/TagsAndScenes.md Normal file
View File

@ -0,0 +1,901 @@
# Tags, Scenes, and Timing Propagation in OpenSTA (Scene-based fork)
This document describes how `Tag`/`ClkInfo`/`TagGroup` distinguish and store multiple
timing paths per vertex, how arrival and required times propagate through the graph,
how delay calculation is indexed, and how multiple clocks and multiple scenes
(MCMM) are carried through all of it.
It is written as a handoff reference: each section starts with a plain-language
explanation before diving into the code. Section 0 is a glossary of terms used
throughout.
It reflects this fork, in which the upstream `Corner`/`PathAnalysisPt`/
`DcalcAnalysisPt` machinery has been replaced by the `Scene`/`Mode` model.
All file references are relative to the repo root.
---
## 0. Glossary — plain-language definitions
**Timing graph.** The circuit reduced to a graph: every pin is a *vertex*, every
way a signal can get from one pin to another (through a gate, or along a wire)
is an *edge*. A gate edge can contain several *timing arcs* (e.g. rising input
→ falling output).
**Arrival time.** The latest (for setup) or earliest (for hold) time a signal
transition can reach a pin, measured from the launching clock edge. Computed by
walking the graph forward from startpoints, adding delays.
**Required time.** The time a signal *must* arrive at a pin for the design to
work — derived from the capturing flop's clock edge, its setup/hold value, and
constraints. Computed by walking the graph backward from endpoints.
**Slack.** Required minus arrival (for setup). Positive = timing met, negative
= violation. Hold is the mirror image (arrival minus required).
**Startpoint / endpoint.** Startpoints: input ports and register clock pins
(where paths launch). Endpoints: register data pins with setup/hold checks and
output ports (where paths are captured/checked).
**Min/max (early/late).** Every analysis is done twice: with the largest
delays (*max*, used for setup checks) and the smallest delays (*min*, used for
hold checks).
**Rise/fall (rf).** Whether the signal at this pin is transitioning low→high
or high→low. Delays differ per transition, so paths are tracked per rise/fall.
**Clock edge.** A specific edge (rise or fall) of a specific clock, e.g.
"clk1 rising". A path is launched by one clock edge and captured by another;
the pair determines the timing check window.
**Ideal vs propagated clock.** An *ideal* clock arrives everywhere at a fixed
modeled latency (`set_clock_latency`). A *propagated* clock is actually traced
through the clock tree buffers, so each flop sees a real, calculated delay
(*insertion delay* = delay from clock source into the tree).
**Clock uncertainty.** Extra margin (`set_clock_uncertainty`) subtracted from
the check window to model jitter/skew not otherwise captured.
**CRPR (clock reconvergence pessimism removal).** When launch and capture
clock paths share a common stretch of clock tree, using max delay for one and
min delay for the other double-counts variation on the shared part. CRPR
computes that shared pessimism and adds it back. To do this, the analysis must
remember *which physical clock-tree branch* launched each path — that is what
the "crpr clock path" stored in `ClkInfo` is for.
**Timing exception.** An SDC command that overrides default single-cycle
timing: `set_false_path` (ignore these paths), `set_multicycle_path` (allow N
cycles), `set_max_delay`/`set_min_delay` (fixed limit). Exceptions can have
`-from/-thru/-to` points; a path only matches once it has passed the right
sequence of points. The *exception state* in a tag records "how far along the
-thru list this path has gotten so far."
**Tag.** The label that distinguishes one class of paths from another at the
same pin. If two signals reach a pin but were launched by different clocks, or
belong to different scenes, or matched different exceptions, they must be kept
as separate arrival numbers — the tag is the key that keeps them separate.
"One tag = one arrival/required slot on the vertex."
**Interning (a.k.a. hash-consing).** "Store one shared copy, reuse it
everywhere." Before creating a new Tag (or ClkInfo, or TagGroup), the code
looks it up in a global hash table. If an equal one already exists, that
existing object is reused; only if not is a new one created and added to the
table. Same idea as string interning in Java/Python. Three payoffs: (1) two
equal tags are literally the same object, so "same tag?" is a pointer compare;
(2) memory stays bounded — a million paths tagged identically share one Tag;
(3) each object gets a small stable index number (`TagIndex`), so a `Path`
can store a 28-bit index instead of a pointer. `Search::findTag`,
`findClkInfo`, and `findTagGroup` all follow this pattern. Where this document
says "interned" or "shared table", this is all it means.
**Scene.** One complete analysis view: which liberty libraries (corner), which
parasitics (SPEF), and which mode/SDC to use. Roughly what other tools call an
"analysis view" in MCMM (multi-corner multi-mode) analysis. Defined via the
`define_scene` Tcl command.
**Mode.** One set of constraints: an `Sdc` plus everything derived from it
(clock network, constant propagation, generated clocks). Several scenes can
share one mode (same SDC, different corners).
**Levelized BFS.** The propagation order: vertices are processed level by
level (level = max logic depth from a startpoint), so by the time a vertex is
visited, all its fanin already has final values. Backward for requireds.
**Generated clock (genclk).** A clock defined by `create_generated_clock`,
derived from a master clock through logic. Before normal analysis, the tool
runs a special search from the master source to the genclk source pin to
measure the genclk's insertion delay; tags on that search are marked
"genclk source path".
**Pulse clock.** A clock arc producing a pulse rather than a level transition;
needs its sense tracked through the clock tree.
**Segment start.** `set_input_delay` placed on an *internal* pin creates a
"path segment" starting there; the tag marks it so the pin behaves like a
startpoint.
**Derating.** `set_timing_derate` scale factors applied to delays (OCV
margining) when a delay is used in a check.
---
## 1. The Scene/Mode data model
*Plain language: a Scene answers "which libraries, which parasitics, which
constraints do I analyze with?" A Mode is the constraint half of that. The
whole MCMM setup is just a list of Scenes, and every path in the analysis
knows which Scene it belongs to.*
### Scene (`include/sta/Scene.hh`, `search/Scene.cc`)
A `Scene` is one analysis view — the fork's replacement for the upstream
(corner, min/max path analysis point) pair. It bundles:
| Member | Meaning |
|---|---|
| `name_`, `index_` | Unique name and dense index (0..N-1) |
| `mode_` | The `Mode` (and therefore `Sdc`) this scene analyzes under |
| `liberty_[min/max]` | Liberty libraries per min/max (`std::array<LibertySeq, 2>`) |
| `parasitics_[min/max]` | `Parasitics*` per min/max (`std::array<Parasitics*, 2>`) |
The scene collection lives on `StaState` as `SceneSeq scenes_`
(`include/sta/StaState.hh:132`), created by `Sta::makeScenes` (`search/Sta.cc`).
There is no `Scenes` collection class (`friend class Scenes;` in `Scene.hh` is a
leftover). Max scene count is `scene_count_max = 128`
(`include/sta/SearchClass.hh:137`).
### Mode (`include/sta/Mode.hh`, `search/Mode.cc`)
A `Mode` is "Sdc and dependent state". Each Mode owns:
- one `Sdc` (constraints)
- one `Sim` (constant propagation — which pins are tied 0/1 and what that disables)
- one `ClkNetwork` (which pins are in clock trees)
- one `Genclks` (generated clock machinery)
- `PathGroups` (report grouping)
and a `SceneSeq scenes_` — the scenes that reference it.
**Relationship: many Scenes → one Mode → one Sdc.** `Scene::sdc()` forwards to
`mode_->sdc()`. This is how one set of constraints (a mode) can be analyzed
across several corners (scenes), and how multiple modes with different SDCs can
coexist in one run.
### Index formulas (the glue between scenes and flat arrays)
*Plain language: delays, slews, and slack tables are stored in flat C arrays.
The slot number for "scene S, min or max" is always `S*2 + (0 for min, 1 for
max)`. Three differently-named accessors exist because they index three
different kinds of arrays, but they all compute the same number.*
```cpp
Scene::pathIndex(min_max) = index_ * MinMax::index_count + min_max->index();
Scene::dcalcAnalysisPtIndex(min_max) = index_ * MinMax::index_count + min_max->index();
Scene::libertyIndex(min_max) = index_ * MinMax::index_count + min_max->index();
```
- `dcalcAnalysisPtIndex` — indexes graph arc-delay and slew slots (§5).
- `pathIndex` (`PathAPIndex`) — indexes per-analysis-point arrays such as
worst-slack / TNS tables (`search/Search.cc:3686,3699`, `search/WorstSlack.cc`)
and `Path::pathAnalysisPtIndex` (`search/Path.cc:334`).
- `libertyIndex` — indexes scene-specific liberty cells/arcs (§5.2).
Total analysis point count: `StaState::dcalcAnalysisPtCount() =
MinMax::index_count * scenes_.size()` (`search/StaState.cc:166`).
---
## 2. Tags: the path discriminator
*Plain language: a pin does not have "an arrival time" — it has one arrival
time per distinguishable path class through it. The Tag is the composite key
that defines those classes. Everything in this section is about what goes into
that key, and when two keys count as "the same".*
### 2.1 Tag (`search/Tag.hh`, `search/Tag.cc`)
| Field | Meaning |
|---|---|
| `scene_` | **The Scene this path belongs to.** Replaces upstream `PathAnalysisPt*`. Immutable along a path; compared first in every tag comparison, so paths from different scenes never merge. |
| `rf_index_` | Rise/fall of the signal at this vertex |
| `min_max_index_` | Min (hold/early) or max (setup/late) analysis |
| `clk_info_` | Pointer to a shared `ClkInfo` — the launching clock context (§2.2) |
| `is_clk_` | True while the path is still inside the clock network. Clock paths get separate tags from data paths because a clock pin can also have input arrivals wrt other clocks. |
| `input_delay_` | `set_input_delay` that seeded the path (startpoint only; never propagated forward — see `thruTag`) |
| `is_segment_start_` | Path segment start (`set_input_delay` on an internal pin) |
| `states_` | `ExceptionStateSet*` — progress through `-thru` points of timing exceptions (false path, multicycle, path delay, filter). This is how exception matching is incremental instead of re-searching. |
| `is_loop_`, `is_filter_` | Derived from `states_` at construction (`Tag.cc:62-71`). "Filter" = the internal exception used by `report_timing -from/-thru/-to` to trace only matching paths. "Loop" = tag on a combinational loop breaking exception. |
| `index_` | Dense index into `Search::tags_` table; `Path` stores only this 28-bit index |
Tags live in one global table: `Search::findTag` (`search/Search.cc:2875`)
looks the tag up in `tag_set_` and creates it only if new (see "interning" in
the glossary). Equal tags are therefore the same object — pointer compares
work — and each tag gets a `TagIndex` (28 bits, `tag_index_bit_count` in
`SearchClass.hh`). Rise/fall
sibling tags are created together so `mutateTag` can cheaply re-index a tag for
the opposite transition via `tags_[tagsTableRfIndex(index, to_rf)]`
(`Search.cc:2634`).
### 2.2 ClkInfo (`search/ClkInfo.hh`, `search/ClkInfo.cc`)
*Plain language: ClkInfo answers "which clock launched this path, from where,
and with what latency/uncertainty?" It is split out of Tag because thousands
of tags share the same clock context — keeping one shared copy (in
`clk_info_set_`, same lookup-or-create pattern as tags) saves memory and makes
comparisons cheap.*
| Field | Meaning |
|---|---|
| `scene_` | Scene (again — ClkInfo is per-scene too) |
| `clk_edge_` | The launching `ClockEdge` (clock + rise/fall). Null = unclocked. **This is the "which clock" dimension of a tag.** |
| `clk_src_` | Clock source pin the path came from (a clock created on multiple pins → distinct ClkInfos per pin) |
| `gen_clk_src_` | Generated-clock source pin, for genclk source paths |
| `is_gen_clk_src_path_` | Tag is on a generated-clock *source* path (the pre-pass that measures genclk insertion delay) |
| `is_propagated_` | Propagated vs ideal clock |
| `insertion_` | Source latency (`set_clock_latency -source` or computed genclk insertion) |
| `latency_` | Ideal clock network latency (`set_clock_latency`) |
| `uncertainties_` | `set_clock_uncertainty` on clock/pin |
| `is_pulse_clk_`, `pulse_clk_sense_` | Pulse clock handling |
| `crpr_clk_path_` | **CRPR anchor**: the clock path (a `Path` value) at the last clock driver pin. Only set when the clock is propagated and CRPR is enabled. Lets paths launched from different min/max clock-tree branches stay distinct so `CheckCrpr` can subtract common pessimism. |
| `min_max_index_` | Min/max |
### 2.3 Tag comparison: equal vs match
*Plain language: there are two different questions. (1) "Is this exactly the
same tag?" — used when checking whether a tag already exists in the global
table. (2) "Should these
two paths share one storage slot, keeping only the worst?" — used when merging
arrivals at a vertex. The second is deliberately looser: paths that differ only
in clock latency numbers still land in one slot, because only the worst one
can ever matter for slack.*
Two comparison regimes (`search/Tag.cc`):
- **`Tag::cmp`/`equal`** — full identity, used by `findTag` to decide reuse
vs create. Compares
scene index, full `ClkInfo::cmp` (edge, src pin, insertion, latency,
uncertainties, crpr path, ...), rf, min/max, is_clk, input_delay,
segment_start, exception states.
- **`Tag::matchCmp`/`match`** — *merging* criterion, used to decide when two
paths share one arrival slot. Compares scene index, rf, min/max,
**clock edge only** (not the full ClkInfo!), is_clk, is_gen_clk_src,
segment_start, exception states, and optionally the CRPR clock vertex id.
Consequence: paths that differ only in clock latency/insertion/uncertainty
merge into one slot (worst one wins), but paths from different clock *edges*,
different scenes, or (with CRPR active) different last-clock-driver vertices
stay separate.
Variants: `matchNoCrpr` (ignore crpr pin; used by required propagation when the
exact tag was pruned), `matchNoPathAp` / `matchCrpr` (ignore min/max; used to
find the opposite-min/max path for CRPR), `stateEqualCrpr` (only loop states).
Hashes: `Tag::findHash` folds scene index, rf, min/max, is_clk, segment_start,
state hashes, and `clk_info_->hash()`; `hash(match_crpr_clk_pin)` optionally
adds the crpr clock vertex id.
### 2.4 TagGroup: per-vertex tag→slot map (`search/TagGroup.hh/.cc`)
*Plain language: a vertex may hold ten paths, so it needs a little directory
saying "the tag for scene0/max/rise/clk1 lives in slot 3". That directory is
the TagGroup. Because many vertices (e.g. all pins in one cone) end up with
identical directories, one shared copy of each distinct directory is kept:
the vertex stores just a group number plus its own array of Path slots.*
- `PathIndexMap = VectorMap<Tag*, size_t, TagMatchLess>`
(`SearchClass.hh:113`) maps tag → index into the vertex's `Path[]` array.
Because the map is ordered by `TagMatchLess`, tags that *match* collapse to
one slot.
- TagGroups use the same lookup-or-create table pattern, in
`Search::tag_group_set_`
(`Search::findTagGroup`, `Search.cc:2642`): vertices with the same tag set
share one `TagGroup` object; the vertex stores just a `tag_group_index_`
(`Graph.hh:322`).
- Group-level flags: `has_clk_tag_`, `has_genclk_src_tag_`, `has_filter_tag_`,
`has_loop_tag_` — cheap per-vertex queries without iterating tags.
- `TagGroupBldr` is the mutable builder used while (re)computing a vertex's
arrivals: `tagMatchPath` finds the matching slot, `setMatchPath`/`insertPath`
write it. `default_path_count_ = #scenes × 2(rf) × 2(min/max)` — the
expected common case of one tag per (scene, rf, min/max).
### 2.5 Path (`include/sta/Path.hh`, `search/Path.cc`)
*Plain language: a Path is one storage slot — one (arrival, required) pair
plus a pointer to the previous Path so the worst path can be traced back for
reporting. It is intentionally tiny; everything descriptive lives in the tag.*
```cpp
Path *prev_path_; // previous path in the worst-path tree
Arrival arrival_;
Required required_; // required time lives here too (no separate array)
union { VertexId vertex_id_; EdgeId prev_edge_id_; };
TagIndex tag_index_ : 28;
bool is_enum_ : 1;
unsigned prev_arc_idx_ : 2;
```
Everything else is derived from the tag: `Path::scene()` = `tag->scene()`,
`Path::dcalcAnalysisPtIndex()` = `scene->dcalcAnalysisPtIndex(minMax())`, etc.
Arrivals for a vertex live in `Vertex::paths_` (flat `Path[]`,
`Graph.hh:267-268,318`), indexed by `TagGroup::pathIndex(tag)`;
`Path::pathIndex()` recovers its slot by pointer arithmetic against
`vertex->paths()`.
---
## 3. Arrival propagation
*Plain language: start at clock pins and input ports, plant initial tags and
times there, then sweep forward through the graph in level order. At each pin,
pull every path from every fanin pin, add the edge delay, work out what the
tag becomes on this side of the edge, and keep the worst time per tag slot.*
Forward levelized BFS (`BfsFwdIterator arrival_iter_`) driven by
`Search::findArrivals``ArrivalVisitor::visit(Vertex*)` (`Search.cc:1136`).
Each visit *re-derives all* arrivals of a vertex from its fanin, builds a
`TagGroupBldr`, and commits.
### 3.1 Seeding (startpoints)
`Search::seedArrivals` (`Search.cc:1442`) enqueues clock roots, graph roots,
and input drivers. When visited, `ArrivalVisitor::seedArrivals`
(`Search.cc:1221`) loops over modes and dispatches:
- **Clock source pins → `seedClkArrivals`** (`Search.cc:1493`). The
multiplicative fan-out of tags is explicit here:
```cpp
for (const Clock *clk : *clks) // per clock on the pin
for (Scene *scene : mode->scenes()) // per scene of the mode
for (const MinMax *min_max : MinMax::range()) // min & max
for (const RiseFall *rf : RiseFall::range())// rise & fall
seedClkArrival(pin, rf, clk, clk->edge(rf), min_max, insertion, scene, ...);
```
One physical clock pin ⇒ `#clocks × #scenes × 2 × 2` tags. Each
`seedClkArrival` builds a `ClkInfo` (propagated?, latency, insertion,
uncertainties), collects false-path `-from clk` exception states, and calls
`findTag(scene, rf, min_max, clk_info, is_clk=true, ...)`. Arrival =
insertion + `clk_edge->time()`.
- **Input arrivals (`set_input_delay`) → `seedInputArrival`**
(`Search.cc:1697-1950`): one loop per `InputDelay` on the pin (this is how a
port carries arrivals wrt *multiple different clocks* — each
`set_input_delay -clock X` is a separate `InputDelay` object and produces
its own tags), nested over scene, min/max, rise/fall. `inputDelayTag` builds
the ClkInfo for the launch clock, makes a non-clock tag with `input_delay`
set, then runs `mutateTag` to pick up `-thru` exceptions starting at that pin.
- **Unconstrained inputs / register clock pins / `set_min|max_delay -from`
internal startpoints → `makeUnclkedPaths`** (`Search.cc:1615`), also looping
scene × min/max × rf, tag via `fromUnclkedInputTag` (null clk edge).
### 3.2 Propagating a path across an edge
`PathVisitor::visitEdge` (`Search.cc:2015`) iterates every `Path` on the from
vertex (all scenes' paths interleaved), gates each by the search predicate
*for that path's mode* (`pred_->searchThru(edge, mode)` — so a disable or
constant in one mode does not block another mode's paths), and calls
`visitFromPath` (`Search.cc:2072`) — the dispatcher that decides how the
"to tag" is derived from the "from tag" by edge role:
| Edge situation | Tag builder |
|---|---|
| Generated-clock source path (`from_clk_info->isGenClkSrcPath()`) | `thruClkTag` |
| Register CLK→Q | `fromRegClkTag` then `thruTag` (§3.3) |
| Latch D→Q | `latchOutArrival` + `thruTag` |
| Still in clock network (`from_tag->isClock()`) | `thruClkTag` |
| Ordinary data edge | `thruTag` |
`to_arrival = from_arrival + arc_delay`, where `arc_delay` comes from
`Search::deratedDelay(..., dcalc_ap, sdc)` with
`dcalc_ap = from_path->dcalcAnalysisPtIndex()` — i.e. **the tag's scene picks
which delay slot is read** (§5.3). For clock paths under OCV, the insertion
uses early/late opposite conventions, and check-clock slews may read the
opposite min/max slot (`scene->dcalcAnalysisPtIndex(min_max->opposite())`,
`Search.cc:2116,2204`).
**`Search::mutateTag`** (`Search.cc:2536`) is the single function that changes
tag identity along an edge. *Plain language: as a path crosses an edge, its
tag usually stays the same shape — but if the edge passes a `-thru` point of
some exception, the exception state advances; if a false path completes, the
path dies; if the path exits the clock tree, is_clk flips. mutateTag applies
all of those rules and returns the resulting "to" tag from the global table
(or null to kill the path).*
1. Advances each `ExceptionState` in `from_tag->states()` whose next `-thru`
matches (from_pin, to_pin, to_rf, min_max); adds states for new exceptions
whose first `-thru` begins at to_pin (`sdc->exceptionThruStates`).
2. Kills the path (returns null tag) when a false path completes, or a loop
exception completes, or a path-delay `-to` pin is passed.
3. If nothing changed (same ClkInfo, same is_clk, same segment_start/input
handling), fast-path: re-index the existing tag for the new rise/fall.
4. Otherwise `findTag(scene, to_rf, min_max, to_clk_info, to_is_clk, ...)`.
**Scene and input_delay never change across an edge; is_clk, ClkInfo, rf, and
exception states do.**
### 3.3 Clock tag → data tag at register CLK→Q
*Plain language: at a flop's CLK→Q arc, the path stops being "a clock" and
becomes "data launched by that clock". Right at that boundary the analysis
snapshots which clock-tree branch it came from (for CRPR) and picks up any
`-from <clock>` exceptions.*
At a `regClkToQ` arc (`visitFromPath`, `Search.cc:2131-2173`):
1. `clkInfoWithCrprClkPath(from_clk_info, from_path)` (`Search.cc:2356`)
rebuilds the ClkInfo with `crpr_clk_path_ = from_path` (when CRPR active) —
the launched data path remembers *which* clock-tree branch launched it.
2. `fromRegClkTag` (`Search.cc:2332`) makes a fresh tag with `is_clk=false`,
pulling in `-from <clock>` exception states.
3. Launch arrival is normalized by `clkPathArrival` (ideal clocks re-add
insertion + edge time + latency), then the arc delay is added.
Inside the clock tree, `thruClkInfo` (`Search.cc:2424`) rebuilds ClkInfo only
when something changes: propagated flag turns on at a pin, genclk src pin
recorded, **CRPR clock path captured when leaving the clock network or
reaching a reg clk pin**, pulse-clock sense, per-pin `set_clock_latency`
override, uncertainty override. `to_is_clk` goes false when clock propagation
stops (non-wire/combinational edge or `clkStopPropagation`).
### 3.4 Merging and pruning
*Plain language: when several fanin paths land on the same tag slot, only the
worst survives (latest for max, earliest for min). CRPR pruning goes further:
if a path can't possibly become the worst even after the best-case pessimism
credit, drop it now instead of dragging it through the rest of the design.*
- `ArrivalVisitor::visitFromToPath` (`Search.cc:1330`) merges: look up
matching tag in the builder (`tagMatchPath`); keep the incoming arrival only
if no match exists or `delayGreater(to_arrival, match->arrival(), min_max)`
i.e. worst arrival per (matching) tag per min/max.
- CRPR pruning (`pruneCrprArrivals`, `Search.cc:1377`): among tags that differ
only in crpr clock pin, drop those that cannot be worst even after removing
the maximum possible common pessimism (`Crpr::maxCrpr`). A second
no-crpr-matching builder (`tag_bldr_no_crpr_`) tracks the crpr-blind worst
arrival for this comparison.
- `Search::setVertexArrivals` (`Search.cc:2678`) finds-or-creates the shared
TagGroup and
writes the `Path[]`; `arrivalsChanged` decides whether fanout must be
re-enqueued (incremental timing).
---
## 4. Required propagation
*Plain language: requireds start at the endpoints. For each path (tag) ending
at a flop's D pin, the required time is "capture clock edge + its network
delay setup value uncertainty + CRPR credit" (mirror-image for hold), with
multicycle/max-delay exceptions overriding the default one-cycle window. Those
numbers then walk backward: the required at a pin is the tightest requirement
imposed by any of its fanout, minus the edge delay to get there.*
Backward levelized BFS (`BfsBkwdIterator required_iter_`) driven by
`Search::findRequireds` (`Search.cc:3143`).
### 4.1 Seeding at endpoints
`seedRequireds``seedRequired(vertex)` (`Search.cc:3328`) runs
`VisitPathEnds::visitPathEnds` with a `FindEndRequiredVisitor`:
- `VisitPathEnds::visitClkedPathEnds` (`search/VisitPathEnds.cc:84`) iterates
**every Path (= every tag) on the endpoint vertex**, filters by scene
membership in the mode and min/max, then builds `PathEnd` objects per
constraint. A `PathEnd` pairs a data path with the specific check that
constrains it: `PathEndCheck` (setup/hold from in-edge check arcs whose
`check_role->pathMinMax()` matches the tag's min/max), `PathEndLatchCheck`,
`PathEndOutputDelay` (`set_output_delay`), `PathEndPathDelay`
(`set_max/min_delay` override), `PathEndGatedClock`, `PathEndDataCheck`
(`set_data_check`), `PathEndUnconstrained`.
- For each constrained end: `required = path_end->requiredTime(sta)`, merged
into the tag's slot with `RequiredCmp::requiredSet` (worst over ends).
Required-time math (`search/PathEnd.cc`):
```
requiredTime = targetClkArrivalNoCrpr ± margin + crpr
targetClkArrivalNoCrpr = tgt_clk_delay (latency/insertion of capture clock path)
+ cycleAccting(src_edge, tgt_edge)->requiredTime(role)
+ clock uncertainty
+ multicycle adjustment
```
setup subtracts the check margin, hold adds it. `CycleAccting` is the object
that works out, for a given (launch edge, capture edge) pair — possibly of
different clocks with different periods — which capture edge closes the check
window and what the default single-cycle relationship is. CRPR
(`CheckCrpr::checkCrpr(path, tgt_clk_path)`) uses the crpr clock paths recorded
in both tags' ClkInfos. The governing exception (multicycle, path delay, false
path) is the highest-priority match found via `Search::exceptionTo` against the
states carried in the data path's tag.
### 4.2 Backward propagation
`RequiredVisitor::visit(vertex)` (`Search.cc:3441`):
1. Init a scratch `requireds_` array sized `tag_group->pathCount()` — one slot
per tag, initialized to +∞/−∞ per the tag's min/max opposite.
2. `visitFanoutPaths`: for each out-edge and each from-path, compute the tag
the forward search *would* produce at the to-vertex (same `thruTag`/
`mutateTag` machinery — this guarantees the backward walk pairs up with the
exact forward tags), then in `RequiredVisitor::visitFromToPath`
(`Search.cc:3461`):
- exact lookup: `to_tag_group->hasTag(to_tag)` → `from_required =
to_required arc_delay`;
- if the exact tag was CRPR-pruned at the to-vertex: fall back to any
to-path matching `Tag::matchNoCrpr` (same scene, clock edge, rf, min/max,
states — ignoring crpr pin).
- `RequiredCmp::requiredSet` keeps the worst (min over fanout for max
paths, max for min paths).
3. If the vertex is also an endpoint, seed its own path-end requireds too.
4. `requiredsSave` writes each slot into `path->setRequired(...)` — requireds
live inline in the same `Path` objects as arrivals, same tag slot, no
separate array.
5. If anything changed, enqueue fanin (BFS proceeds toward startpoints).
Slack: `Path::slack = required arrival` (max) / `arrival required` (min).
WNS (worst negative slack) / TNS (total negative slack, sum over endpoints)
tables are per `PathAPIndex = scene->pathIndex(min_max)`; reduction across
scenes happens only at reporting (`totalNegativeSlack`, `worstSlack` loop over
`scenes_`).
---
## 5. Delay calculation and scene indexing
*Plain language: before the search runs, every timing arc in the graph gets
its delay computed once per (scene, min/max) and stored in a flat array on the
edge. The search never recomputes delays — it just reads the slot its tag's
scene points at. Slews (transition times) are stored the same way per vertex.*
### 5.1 No more DcalcAnalysisPt
`DcalcAnalysisPt` is gone; `DcalcAPIndex` is a plain `int`
(`include/sta/GraphClass.hh:65`) computed as
`scene->dcalcAnalysisPtIndex(min_max) = scene_index*2 + mm_index`.
Graph storage (`graph/Graph.cc`):
- Arc delays: flat per-edge array, slot = `arc->index() * ap_count_ + ap_index`
where `ap_count_ = 2 * #scenes` (`Graph::arcDelay`, `Graph.cc:743`;
`initArcDelays`, `:901`). Annotation bits (marking SDF-annotated delays)
parallel this layout.
- Slews: per-vertex flat array, slot = `ap_index * RiseFall::index_count +
rf->index()` (`Graph.cc:648-691`).
### 5.2 GraphDelayCalc iteration
`GraphDelayCalc::findDelays` (`dcalc/GraphDelayCalc.cc:338`) runs a parallel
forward BFS over drivers. Per driver edge (`findDriverEdgeDelays`,
`GraphDelayCalc.cc:994`):
```cpp
for (Scene *scene : scenes_) {
const Mode *mode = scene->mode();
if (search_pred_->searchFrom(from_vertex, mode)
&& search_pred_->searchThru(edge, mode)) {
for (const MinMax *min_max : MinMax::range())
for (const TimingArc *arc : arc_set->arcs())
findDriverArcDelays(drvr_vertex, ..., arc, scene, min_max, ...);
}
}
```
Per (scene, min/max) the calculation selects:
- **Liberty model**: `arc->gateModel(scene, min_max)`
`TimingArc::sceneArc(scene->libertyIndex(min_max))`
(`liberty/TimingArc.cc:614`). When the same cell exists in several corner
libraries, each `LibertyCell` keeps its per-scene variants in a
`scene_cells_` array (`LibertyCell::sceneCell`, `liberty/Liberty.cc:1519`);
the scene's liberty index picks the right variant. Op-cond scaling comes
from `scene->sdc()->operatingConditions(min_max)`.
- **Parasitics**: `scene->parasitics(min_max)`
(`GraphDelayCalc::parasiticLoad`, `GraphDelayCalc.cc:1405`) — this is where
the per-scene SPEF (from `define_scene -spef`) is consumed.
Results are annotated with `ap_index = scene->dcalcAnalysisPtIndex(min_max)`
(`annotateDelaysSlews` / `annotateLoadDelays`).
### 5.3 How search consumes delays
During arrival propagation, `visitFromPath` reads
`dcalc_ap = from_path->dcalcAnalysisPtIndex(this)` — which is
`from_tag->scene()->dcalcAnalysisPtIndex(from_tag->minMax())` — and
`Search::deratedDelay` (`Search.cc:3013`) fetches
`graph_->arcDelay(edge, arc, dcalc_ap)` plus timing derates from the mode's
Sdc. **The tag is thus the single source of truth: its scene picks the delay/
slew slots, its mode picks the Sdc, its min/max picks the analysis side.**
Timing checks read the clock-side slew from
`scene->checkClkSlewIndex(min_max)` = `dcalcAnalysisPtIndex(checkClkSlewMinMax)`,
where `checkClkSlewMinMax` depends on the analysis type (`search/Scene.cc:102`):
`single` → min, `bc_wc` → same min/max, `ocv` → opposite min/max. (*Plain
language: in OCV analysis, for a setup check the data path uses max delays but
the capture clock should be as fast as possible — so the check reads the
clock slew from the opposite min/max slot.*)
---
## 6. How multiple clocks and multiple scenes coexist in a TagGroup
*Plain language: the engine does not run once per scene or once per clock. It
runs one propagation, and the tags do the bookkeeping: every combination of
scene × clock edge × min/max × rise/fall × exception context is its own tag,
its own slot, its own numbers. Isolation falls out of the comparison rules —
tags from different scenes or clocks simply never compare equal, so they never
merge or interfere.*
- **Multiple clocks**: each clock (and each of its edges) on a startpoint
seeds its own `ClkInfo`/tag (§3.1). A pin clocked by N clock edges holds ≥N
tags; `Tag::matchCmp` compares `clkEdge()`, so paths from different clock
edges never merge. Different *source pins* of the same clock, different
genclk source paths, and (with CRPR) different last-clock-driver vertices
also stay distinct via ClkInfo/crpr-pin comparison.
- **Multiple scenes**: seeding loops `for (Scene *scene : mode->scenes())`
create one tag per scene; every tag comparison orders by scene index first,
so per-scene paths flow through one shared BFS but never interact. A
vertex's `TagGroup` therefore holds the union of all scenes' × clocks' ×
rf × min/max × exception-state tags, each with its own arrival/required slot
in `Vertex::paths_`.
- **Mode isolation**: the search predicate is evaluated per path using the
tag's mode (`from_path->mode()`), so disables/constants from one mode's
Sdc/Sim don't block another mode's paths on the same edge.
- Cross-scene reduction (worst slack, TNS, reporting) happens only after
search, indexed by `scene->pathIndex(min_max)`.
`VertexPathIterator` (`Path.hh:187`) is the standard way to walk a vertex's
paths filtered by (scene, min/max, rf).
---
## 7. Quick reference: what makes two paths "different"
A vertex holds separate arrival/required slots for paths that differ in any of:
1. Scene
2. Min/max (setup vs hold analysis)
3. Rise/fall at the vertex
4. Launching clock edge (clock + edge) — including "unclocked"
5. Clock vs data (`is_clk_`) — a clock pin's clock path vs input-arrival paths
6. Generated-clock source path flag
7. Exception state progress (false path / multicycle / path delay / filter `-thru` chains)
8. Segment start flag
9. Input delay object (full tag identity only; merges under match rules)
10. CRPR last-clock-driver vertex (only when CRPR enabled and clocks propagated)
Everything else about the clock context (latency, insertion, uncertainty,
pulse sense) lives in ClkInfo and affects tag *identity* (whether a separate
Tag object exists in the table) but not tag *matching* — such paths share a
slot and the worst one wins.
---
## 8. Worked example (plain language)
Design: input port `in1` with `set_input_delay -clock clk1 2.0`, flop `r1`
clocked by propagated `clk1`, two scenes `fast` (mode M, FF libs) and `slow`
(mode M, SS libs).
At `r1/CLK` the vertex holds clock tags:
`{fast,slow} × {min,max} × {rise,fall}` for `clk1` — 8 tags, each `is_clk`,
each ClkInfo carrying the accumulated insertion for its scene/min-max.
At `r1/Q` the CLK→Q arc converts each max clock tag into a data tag: `is_clk`
false, clock edge `clk1 rise` (say), ClkInfo now carrying the crpr clock path
snapshot at `r1/CLK`. Meanwhile any combinational path from `in1` reaching the
same downstream pins carries tags with `input_delay` set and clock edge
`clk1 rise` — different tags (different exception/input context), so both
arrivals coexist at every shared pin.
At a downstream flop `r2/D`, `visitPathEnds` builds a `PathEndCheck` per data
tag against `r2`'s capture clock path, computes required with setup margin,
uncertainty, and CRPR (comparing the launch crpr path via `r1/CLK` with the
capture crpr path via `r2/CLK`), and requireds flow backward per tag. Slack
per scene is then just `required arrival` in each slot; `report_checks`
reduces across scenes at the end.
---
## 9. Debugging tags: commands and techniques
*Plain language: everything below answers "what paths does this pin actually
hold, and why?" The commands live in the Tcl layer; most are intentionally
undocumented/hidden debug commands in the `sta::` namespace. Run them from any
OpenSTA shell. Arrivals must exist first — run `report_checks` (or anything
that triggers `findArrivals`) or a `find_timing` before inspecting, otherwise
vertices report "no arrivals".*
### 9.1 Report all tags + arrivals/requireds on a pin: `report_tag_arrivals`
The workhorse. Internal debug proc (`search/Search.tcl:808`, marked "Internal
debugging command", call as `sta::report_tag_arrivals`):
```tcl
sta::report_tag_arrivals [-digits N] pin_name
```
Calls `Search::reportArrivals(vertex, report_tag_index=1, digits)`
(`search/Search.cc:2748` via `report_tag_arrivals_cmd`, `search/Search.i:285`).
Output — one line per Path slot on the vertex, sorted:
```
Vertex r1/Q (fall)
Group 17
r max 1.234 / 4.567 23 scene1 clk1 r clk_src ck1buf/Y crpr_pin r1/CLK max ...
^ ^ ^ ^ ^ ^ ^
rf mm arrival required tag_index scene <rest = Tag::to_string, see 9.4>
```
Format string: `" {rf} {min_max} {arrival} / {required} {tag}"`. So this one
command answers both "what tags does this pin have" and "what arrival/required
is stored per tag". `Group N` is the vertex's TagGroup number in the shared
group table.
### 9.2 Report the worst (or every) path through a pin: `report_path`
User-visible command with two hidden flags `-all` and `-tags`
(`search/Search.tcl:614`, comment: "Note that -all and -tags are intentionally
hidden"):
```tcl
report_path [-min|-max] [-all] [-tags] [-format ...] pin r|f
```
- default: reports the worst arrival path ending at the pin for that rf/min-max.
- `-all`: iterates every Path on the vertex (`$vertex path_iterator $rf $min_max`)
and reports each one — one full path report per tag. Also prints
`Tag group: <index>`.
- `-tags`: prefixes each report with `Tag: <Tag::to_string>` — this is "see
details of a particular tag of a pin" combined with the full path that
carries it.
So `report_path -max -all -tags r1/D f` = every fall/max path class into
`r1/D`, each labeled with its tag.
### 9.3 Arrival/required/slack per clock edge: `report_arrival` / `report_required` / `report_slack`
User-level, tag-aware but summarized per launching clock edge
(`search/Search.tcl:741-802` → `Sta::reportArrivalWrtClks` etc.,
`search/Sta.cc:3311`):
```tcl
report_arrival [-scene scene] [-digits N] pin
report_required [-scene scene] [-digits N] pin
report_slack [-scene scene] [-digits N] pin
```
These walk the vertex's paths and print min/max rise/fall values grouped by
clock edge (the tag's `clkEdge()`), optionally restricted to one scene. Use
these when you want "what are the arrivals wrt each clock" without raw tag
dumps.
### 9.4 Reading a tag dump: anatomy of `Tag::to_string`
(`search/Tag.cc:86-174`; ClkInfo part `search/ClkInfo.cc:145`.) A line like
```
23 scene1 rmax clk1 r (clock prop) clk_src ck1buf/Y crpr_pin ck2buf/Y max -false_path -from ... (next thru ...)
```
decodes as:
| Piece | Meaning |
|---|---|
| `23` | TagIndex (slot in the global tag table; stable within a run) |
| `scene1` | Scene name |
| `rmax` | rise/fall + min/max of the tag |
| `clk1 r` / `unclocked` | launching clock edge |
| `(clock prop)` / `(clock ideal)` / `(genclk)` | is_clk flag; propagated vs ideal; genclk source path |
| `clk_src <pin>` | clock source pin from ClkInfo |
| `crpr_pin <pin> <min/max>` / `crpr_pin null` | CRPR anchor: last clock driver path recorded in ClkInfo |
| `input <pin>` | seeding `set_input_delay` pin |
| `segment_start` | segment start flag |
| trailing exception text | each `ExceptionState`: the exception plus `(next thru ...)` = which `-thru` point the path must hit next, or `(thrus complete)` |
`ClkInfo::to_string` (printed by `report_clk_infos`) additionally shows
`insert <delay>` (source insertion) and `uncertain <min>:<max>`.
### 9.5 Global table dumps and statistics
All in `sta::` namespace (`search/Search.i:273-330`):
| Command | What it does |
|---|---|
| `sta::report_tags` | Dump every Tag in the global table `Search::tags_` (`Search::reportTags`, `Search.cc:2938`), plus longest hash-bucket stat |
| `sta::report_clk_infos` | Dump every ClkInfo in the global table, sorted (`Search.cc:2955`) — inspect all distinct clock contexts (insertions, crpr pins) in the run |
| `sta::report_tag_groups` | Dump every TagGroup: index, hash, and the tag→slot map per group (`Search.cc:2818`) |
| `sta::report_path_count_histogram` | Histogram: N paths-per-vertex vs vertex count (`Search.cc:2838`) — first stop when tag explosion is suspected |
| `sta::tag_count` | Number of distinct tags |
| `sta::clk_info_count` | Number of distinct ClkInfos |
| `sta::tag_group_count` | Number of distinct TagGroups |
| `sta::path_count` | Total Path slots over all vertices |
Watching `tag_count`/`path_count` before and after a constraint change is the
quick way to measure tag-explosion cost of exceptions.
### 9.6 Scriptable access from Tcl (vertex/path objects)
For ad-hoc debugging scripts (`graph/Graph.i`, `search/Search.i:1212-1285`):
```tcl
set pin [get_pins r1/Q]
foreach vertex [$pin vertices] { ;# bidirect pins have 2 vertices
puts "group [$vertex tag_group_index]" ;# TagGroup number
set iter [$vertex path_iterator "rise" "max"] ;# rf + min/max filter
while {[$iter has_next]} {
set path [$iter next]
puts "[$path tag]" ;# Tag::to_string
puts "arrival [$path arrival]"
puts "required [$path required]"
puts "slack [$path slack]"
puts "pins [$path pins]" ;# walk prev_path chain back to startpoint
}
$iter finish
}
```
`$path pins` returns the whole worst-path pin trace (follows `prev_path_`);
`$path start_path` gives the startpoint path. `PathEnd` objects (from
`find_timing_paths`) expose `path`, `slack`, `margin`, `data_required_time`,
etc.
### 9.7 `set_debug` trace categories
`sta::set_debug <category> <level>` (kept out of the global namespace on
purpose — `tcl/Util.tcl:288`). Use `set_thread_count 1` first; parallel BFS
interleaves output. Tag-relevant categories:
| Category | What it traces |
|---|---|
| `search` 1 | Pass-level: "find arrivals pass N", counts, invalidations (`Search.cc:655,807`) |
| `search` 2 | Per-vertex: "find arrivals \<vertex\>", seed decisions, invalid arrival/required marks (`Search.cc:847,1148,1245`) |
| `search` 3 | **Per-edge tag propagation** — the money level: prints from-vertex, `from tag:` / `to tag :` (both `Tag::to_string`), and the merge decision `from_arrival + arc_delay = to_arrival >/< stored` (`ArrivalVisitor::visitFromToPath`, `Search.cc:1345-1361`). Answers "why did/didn't this tag reach this pin". |
| `search` 4 | Path deletes, "arrivals changed" per vertex |
| `crpr` 2 | CRPR resolution: crpr pin, src/tgt deltas (`search/Crpr.cc:239-297`) |
| `genclk` | Generated clock source-path search (tags with `is_gen_clk_src_path`) |
| `bfs` | BFS queue mechanics |
| `levelize`, `endpoint`, `tns` | Supporting machinery |
Typical session for "why is this path missing / where does this tag die":
```tcl
set_thread_count 1
sta::set_debug search 3
report_checks -through [get_pins u1/Y] ;# or find_timing
# grep the log for the pin name; watch the from/to tag lines and the
# point where mutateTag kills the tag (false path complete, etc.)
```
### 9.8 C++-level hooks (for gdb / temporary instrumentation)
- `Search::reportArrivals(vertex, true, digits)` — callable from gdb on any
vertex.
- `Tag::to_string(sta)`, `ClkInfo::to_string(sta)`, `Path::to_string(sta)`,
`TagGroup::report(sta)`, `TagGroup::reportArrivalMap(sta)`,
`TagGroupBldr::reportArrivalEntries()` — every level of the machinery has a
printer.
- `Vertex::tagGroupIndex()` + `Search::tagGroup(index)` to go from vertex to
its group in a debugger.
- `debugPrint(debug_, "search", 3, ...)` is the pattern to add temporary
traces (per `doc/CodingGuidelines.txt` / repo conventions: `debugPrint` for
debug, `report_->report(...)` for always-on prints; never leave logging in
hot paths).
---
## 10. Key file index
| Area | Files |
|---|---|
| Tag definition, hashing, matching | `search/Tag.hh`, `search/Tag.cc` |
| Clock context | `search/ClkInfo.hh`, `search/ClkInfo.cc` |
| Per-vertex tag set / slot map | `search/TagGroup.hh`, `search/TagGroup.cc` |
| Path (arrival+required storage) | `include/sta/Path.hh`, `search/Path.cc` |
| Seeding, propagation, tag mutation, requireds | `search/Search.cc`, `include/sta/Search.hh` |
| Path end enumeration / required math | `search/VisitPathEnds.cc`, `search/PathEnd.cc` |
| CRPR | `search/Crpr.cc` |
| Scene / Mode | `include/sta/Scene.hh`, `search/Scene.cc`, `include/sta/Mode.hh`, `search/Mode.cc` |
| Delay calc | `dcalc/GraphDelayCalc.cc`, `graph/Graph.cc` |
| BFS iterators | `search/Bfs.cc` |

View File

@ -1322,7 +1322,9 @@ TEST_F(StaDesignTest, SearchPredNonReg2SearchThru) {
VertexInEdgeIterator edge_iter(v, sta_->graph()); VertexInEdgeIterator edge_iter(v, sta_->graph());
if (edge_iter.hasNext()) { if (edge_iter.hasNext()) {
Edge *edge = edge_iter.next(); Edge *edge = edge_iter.next();
pred.searchThru(edge); bool search_thru = pred.searchThru(edge);
// searchThru is a pure predicate; result must be stable across calls.
EXPECT_EQ(search_thru, pred.searchThru(edge));
} }
} }