Merge pull request #386 from The-OpenROAD-Project-staging/sta_fix_coverity
coverity and tag and scene doc
This commit is contained in:
commit
a536a9e526
|
|
@ -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` |
|
||||||
|
|
@ -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));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue