-
Notifications
You must be signed in to change notification settings - Fork 970
dbSta: POCV / LVF parametric statistical-OCV timing (draft) #10853
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Changes from all commits
5d5d0ec
c2e1043
eb61024
7504a3e
4ac2fbb
4c70e71
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,52 @@ | ||
| // SPDX-License-Identifier: BSD-3-Clause | ||
| // Copyright (c) 2019-2025, The OpenROAD Authors | ||
|
|
||
| // Parametric statistical on-chip-variation (POCV / LVF) derate, first slice. | ||
| // | ||
| // This is an OpenROAD-side (dbSta) feature that re-uses public OpenSTA APIs to | ||
| // recompute a STATISTICAL (root-sum-square / quadrature) setup slack on | ||
| // already-found critical paths. It does NOT modify the OpenSTA forward search: | ||
| // parametric-POCV variation combines in quadrature, which does not compose with | ||
| // the additive arrival sum the search propagates, so a per-arc propagation hook | ||
| // (like AOCV) is not the right place for it. | ||
|
|
||
| #pragma once | ||
|
|
||
| #include <string> | ||
|
|
||
| #include "sta/Search.hh" // OpenROAD-fork: POCV -- Search::PocvSigma state holder | ||
|
|
||
| namespace sta { | ||
|
|
||
| class Sta; | ||
| class PathEnd; | ||
|
|
||
| // One row of the POCV slack report for a single path end. | ||
| struct PocvPathResult | ||
| { | ||
| std::string endpoint; | ||
| int logic_depth = 0; // number of combinational data-path stages | ||
| float flat_slack = 0.0f; // slack as graded by flat OCV (baseline) | ||
| float pocv_slack = 0.0f; // statistical (RSS) slack | ||
| float flat_sigma = 0.0f; // linear (fully-correlated) path sigma: k*sum(d_i) | ||
| float rss_sigma = 0.0f; // quadrature path sigma: k*sqrt(sum(d_i^2)) | ||
| float n_sigma = 0.0f; // sign-off sigma multiple used | ||
| }; | ||
|
|
||
| // Recompute the parametric-POCV statistical setup slack for one path end. | ||
| // | ||
| // For each combinational data-path stage i with raw (un-derated) delay d_i and | ||
| // per-stage fractional sigma k, the path variation combines in QUADRATURE: | ||
| // sigma_rss = k * sqrt( sum_i d_i^2 ) | ||
| // versus the fully-correlated linear sigma a flat derate implies: | ||
| // sigma_lin = k * sum_i d_i | ||
| // The reported POCV slack replaces the linear pessimism with N_sigma*sigma_rss: | ||
| // pocv_slack = flat_slack + N_sigma*(sigma_lin - sigma_rss) | ||
| // Because sigma_rss <= sigma_lin (equal only for a single stage), POCV slack is | ||
| // >= flat slack, and the gap GROWS with depth as sqrt(N) vs N. When the sigma | ||
| // state is inactive (default), pocv_slack == flat_slack exactly. | ||
| PocvPathResult pocvAdjustPathEnd(Sta* sta, | ||
| PathEnd* path_end, | ||
| const Search::PocvSigma& sigma); | ||
|
|
||
| } // namespace sta |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,93 @@ | ||
| // SPDX-License-Identifier: BSD-3-Clause | ||
| // Copyright (c) 2019-2025, The OpenROAD Authors | ||
|
|
||
| #include "db_sta/PocvDerate.hh" | ||
|
|
||
| #include <cmath> | ||
|
|
||
| #include "sta/Delay.hh" | ||
| #include "sta/Graph.hh" | ||
| #include "sta/MinMax.hh" | ||
| #include "sta/Network.hh" | ||
| #include "sta/Path.hh" | ||
| #include "sta/PathEnd.hh" | ||
| #include "sta/PathExpanded.hh" | ||
| #include "sta/Search.hh" | ||
| #include "sta/Sta.hh" | ||
| #include "sta/TimingArc.hh" | ||
| #include "sta/TimingRole.hh" | ||
|
|
||
| namespace sta { | ||
|
|
||
| PocvPathResult pocvAdjustPathEnd(Sta* sta, | ||
| PathEnd* path_end, | ||
| const Search::PocvSigma& sigma) | ||
| { | ||
| PocvPathResult result; | ||
| const Path* end_path = path_end->path(); | ||
| result.endpoint = sta->network()->pathName(end_path->pin(sta)); | ||
| result.flat_slack = delayAsFloat(path_end->slack(sta)); | ||
| result.pocv_slack = result.flat_slack; | ||
| result.n_sigma = sigma.n_sigma; | ||
|
|
||
| Graph* graph = sta->graph(); | ||
| PathExpanded expanded(end_path, sta); | ||
| const size_t size = expanded.size(); | ||
| // path(0) is the path root (startpoint side); path(size-1) is the endpoint. | ||
|
|
||
| // Walk combinational data-path stages, accumulating both the linear sum of | ||
| // raw stage delays and the sum of their squares (for the quadrature sigma). | ||
| int logic_depth = 0; | ||
| double sum_delay = 0.0; // sum_i d_i -> linear (correlated) sigma | ||
| double sum_delay_sq = 0.0; // sum_i d_i^2 -> RSS (independent) sigma | ||
| for (size_t i = 1; i < size; i++) { | ||
| const Path* p = expanded.path(i); | ||
| const Path* prev = p->prevPath(); | ||
| const TimingArc* arc = p->prevArc(sta); | ||
| const Edge* edge = p->prevEdge(sta); | ||
| if (arc == nullptr || edge == nullptr || prev == nullptr) { | ||
| continue; | ||
| } | ||
| // Only the combinational logic stages carry per-stage gate variation; clock | ||
| // / reg / latch / check arcs keep their flat treatment (matches AOCV | ||
| // slice). | ||
| if (arc->role() != TimingRole::combinational()) { | ||
| continue; | ||
| } | ||
| const DcalcAPIndex dcalc_ap = p->dcalcAnalysisPtIndex(sta); | ||
| // Raw (un-derated) arc delay: the nominal stage delay d_i. | ||
| const double d = delayAsFloat(graph->arcDelay(edge, arc, dcalc_ap)); | ||
| if (d <= 0.0) { | ||
| continue; // negative/zero arcs contribute no meaningful variation | ||
| } | ||
| logic_depth++; | ||
| sum_delay += d; | ||
| sum_delay_sq += d * d; | ||
| } | ||
| result.logic_depth = logic_depth; | ||
|
|
||
| // Per-stage fractional sigma k. Linear (fully-correlated) path sigma is the | ||
| // straight sum; quadrature (independent) path sigma is the root-sum-square. | ||
| const double k = sigma.per_stage; | ||
| result.flat_sigma = static_cast<float>(k * sum_delay); | ||
| result.rss_sigma = static_cast<float>(k * std::sqrt(sum_delay_sq)); | ||
|
|
||
| // Inactive (default) => POCV slack == flat slack exactly (baseline-safe). | ||
| if (!sigma.active() || logic_depth == 0) { | ||
| return result; | ||
| } | ||
|
|
||
| // The flat grading effectively charged N_sigma * sigma_lin of pessimism to | ||
| // the data path. Parametric POCV charges only N_sigma * sigma_rss. Since | ||
| // sigma_rss <= sigma_lin, recovering the difference improves setup slack: | ||
| // pocv_slack = flat_slack + N_sigma * (sigma_lin - sigma_rss) | ||
| // For min/hold paths the early variation subtracts from arrival, so reduced | ||
| // pessimism likewise relaxes the (required - arrival) hold check in the same | ||
| // slack-improving direction; the magnitude is identical, hence one formula. | ||
| const double recovered | ||
| = sigma.n_sigma * (result.flat_sigma - result.rss_sigma); | ||
| result.pocv_slack = static_cast<float>(result.flat_slack + recovered); | ||
| return result; | ||
| } | ||
|
|
||
| } // namespace sta |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -9,6 +9,11 @@ | |
| #include "odb/PtrSetMap.h" | ||
| #include "db_sta/dbSta.hh" | ||
| #include "db_sta/dbNetwork.hh" | ||
| #include "db_sta/PocvDerate.hh" | ||
| #include "sta/PocvMode.hh" // OpenROAD-fork: LVF -- PocvMode for propagation | ||
| #include "sta/TableModel.hh" // OpenROAD-fork: LVF-lib -- LVF sigma table detect | ||
| #include "sta/TimingArc.hh" // OpenROAD-fork: LVF-lib -- arc -> gate model | ||
| #include "sta/TimingRole.hh" // OpenROAD-fork: LVF-lib -- combinational filter | ||
| #include "db_sta/IpChecker.hh" | ||
| #include "db_sta/MakeDbSta.hh" | ||
| #include "ord/OpenRoad.hh" | ||
|
|
@@ -314,6 +319,229 @@ bool parasitics_annotated(Pin *pin, Scene *scene) { | |
| return parasitics->findParasiticNetwork(pin) != nullptr; | ||
| } | ||
|
|
||
| // Parametric statistical OCV (POCV / LVF) derate, first slice (report-only). | ||
| // Variation combines in QUADRATURE (root-sum-square) | ||
| // along the path, so this is intentionally a report-only recompute on already | ||
| // found paths, NOT a forward-search propagation hook. | ||
|
|
||
| // Process-global POCV sigma state, mirrored into Search::PocvSigma. Default | ||
| // (enabled=false, per_stage=0, n_sigma=0) == feature inactive == baseline. | ||
| static Search::PocvSigma & | ||
| pocvSigmaState() | ||
| { | ||
| static Search::PocvSigma sigma; | ||
| return sigma; | ||
| } | ||
|
|
||
| // OpenROAD-fork: POCV -- push the current sigma state onto Search. This only | ||
| // updates a default-OFF state holder that NO propagation-path code reads, so it | ||
| // never changes timing; it exists so the state lives with the timer and the | ||
| // report can read it back. No arrival invalidation is needed (search unchanged). | ||
| static void | ||
| pocvSyncState() | ||
| { | ||
| ord::OpenRoad *openroad = ord::getOpenRoad(); | ||
| sta::dbSta *sta = openroad->getSta(); | ||
| sta->search()->setPocvSigma(pocvSigmaState()); | ||
| } | ||
|
|
||
| // Set the per-stage fractional sigma (k) and the sign-off sigma multiple | ||
| // (n_sigma) and enable POCV. With k==0 or n_sigma==0 the feature stays inactive | ||
| // (POCV slack == flat slack). | ||
| void | ||
| pocv_sigma_set(float per_stage, | ||
| float n_sigma) | ||
| { | ||
| Search::PocvSigma &s = pocvSigmaState(); | ||
| s.enabled = true; | ||
| s.per_stage = per_stage; | ||
| s.n_sigma = n_sigma; | ||
| pocvSyncState(); | ||
| } | ||
|
|
||
| // OpenROAD-fork: LVF -- enable PROPAGATION-TIME POCV. In addition to the | ||
| // report-only state, this flips the timer into statistical (normal) delay-ops | ||
| // with the sign-off quantile = n_sigma, so the synthetic per-stage variance | ||
| // injected in Search::deratedDelayData accumulates in quadrature through the | ||
| // forward search and is read out as mean +/- n_sigma*sqrt(var) at the checks. | ||
| // Switching the mode invalidates arrivals (handled by Sta::setPocvMode). | ||
| void | ||
| pocv_sigma_set_propagate(float per_stage, | ||
| float n_sigma) | ||
| { | ||
| Search::PocvSigma &s = pocvSigmaState(); | ||
| s.enabled = true; | ||
| s.per_stage = per_stage; | ||
| s.n_sigma = n_sigma; | ||
| s.propagate = true; | ||
| pocvSyncState(); | ||
|
|
||
| ord::OpenRoad *openroad = ord::getOpenRoad(); | ||
| sta::dbSta *sta = openroad->getSta(); | ||
| // n_sigma is the sign-off quantile used by DelayOpsNormal::asFloat. | ||
| sta->setPocvQuantile(n_sigma); | ||
| // Statistical readout of the accumulated variance. | ||
| sta->setPocvMode(sta::PocvMode::normal); | ||
| } | ||
|
Comment on lines
+368
to
+385
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Add a null check for |
||
|
|
||
| // OpenROAD-fork: LVF-lib -- does ANY loaded liberty library carry real LVF | ||
| // (ocv_sigma_*) delay-sigma tables? Walks libraries -> cells -> combinational | ||
| // timing arcs and asks the GateTableModel for a delay sigma table. Used to warn | ||
| // the user when -from_liberty is requested but the libs have no LVF data (in | ||
| // which case library-driven POCV yields zero variance == baseline). | ||
| static bool | ||
| anyLibraryHasLvf() | ||
| { | ||
| ord::OpenRoad *openroad = ord::getOpenRoad(); | ||
| sta::dbSta *sta = openroad->getSta(); | ||
| LibertyLibraryIterator *lib_iter = sta->network()->libertyLibraryIterator(); | ||
| bool found = false; | ||
| while (lib_iter->hasNext() && !found) { | ||
| LibertyLibrary *lib = lib_iter->next(); | ||
| LibertyCellIterator cell_iter(lib); | ||
| while (cell_iter.hasNext() && !found) { | ||
| LibertyCell *cell = cell_iter.next(); | ||
| for (TimingArcSet *arc_set : cell->timingArcSets()) { | ||
| if (arc_set->role() != TimingRole::combinational()) | ||
| continue; | ||
| for (TimingArc *arc : arc_set->arcs()) { | ||
| const TimingModel *model = arc->model(); | ||
| const GateTableModel *gate | ||
| = dynamic_cast<const GateTableModel *>(model); | ||
| if (gate && gate->delayModels()) { | ||
| const TableModels *dm = gate->delayModels(); | ||
| if (dm->sigma(MinMax::max()) || dm->sigma(MinMax::min()) | ||
| || dm->stdDev()) { | ||
| found = true; | ||
| break; | ||
| } | ||
| } | ||
| } | ||
| if (found) | ||
| break; | ||
| } | ||
| } | ||
| } | ||
| delete lib_iter; | ||
| return found; | ||
| } | ||
|
Comment on lines
+392
to
+427
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Add comprehensive null checks for |
||
|
|
||
| // OpenROAD-fork: LVF-lib -- true if at least one loaded library has LVF tables. | ||
| bool | ||
| pocv_liberty_has_lvf() | ||
| { | ||
| return anyLibraryHasLvf(); | ||
| } | ||
|
|
||
| // OpenROAD-fork: LVF-lib -- enable LIBRARY-DRIVEN POCV. The per-stage delay | ||
| // sigma comes from the real Liberty LVF ocv_sigma_* tables via the NATIVE | ||
| // statistical delay calc (GateTableModel::gateDelayPocv under PocvMode::normal), | ||
| // so sigma varies per cell / per arc -- the sign-off-accurate source -- instead | ||
| // of one global hand-set number. The synthetic per-stage injection in | ||
| // Search::deratedDelayData is suppressed in this mode (PocvSigma::from_liberty) | ||
| // so it cannot overwrite the library-derived stdDev. n_sigma is the sign-off | ||
| // quantile. Switching the mode invalidates arrivals (Sta::setPocvMode). | ||
| void | ||
| pocv_sigma_set_from_liberty(float n_sigma) | ||
| { | ||
| Search::PocvSigma &s = pocvSigmaState(); | ||
| s.enabled = true; | ||
| s.per_stage = 0.0f; // unused in library mode (sigma is per-cell from LVF) | ||
| s.n_sigma = n_sigma; | ||
| s.propagate = true; // library variance still flows through propagation | ||
| s.from_liberty = true; // ... but via native LVF, not synthetic injection | ||
| pocvSyncState(); | ||
|
|
||
| ord::OpenRoad *openroad = ord::getOpenRoad(); | ||
| sta::dbSta *sta = openroad->getSta(); | ||
| sta->setPocvQuantile(n_sigma); | ||
| sta->setPocvMode(sta::PocvMode::normal); | ||
| } | ||
|
Comment on lines
+444
to
+459
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Add a null check for |
||
|
|
||
| void | ||
| pocv_sigma_clear() | ||
| { | ||
| const bool was_propagate = pocvSigmaState().propagate; | ||
| pocvSigmaState() = Search::PocvSigma(); // back to inactive default | ||
| pocvSyncState(); | ||
| // OpenROAD-fork: LVF -- if propagation mode had been enabled, return the | ||
| // timer to scalar delay-ops so timing is byte-identical to baseline again. | ||
| if (was_propagate) { | ||
| ord::OpenRoad *openroad = ord::getOpenRoad(); | ||
| sta::dbSta *sta = openroad->getSta(); | ||
| sta->setPocvMode(sta::PocvMode::scalar); | ||
| } | ||
| } | ||
|
Comment on lines
+461
to
+474
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Add a null check for |
||
|
|
||
| bool | ||
| pocv_sigma_active() | ||
| { | ||
| return pocvSigmaState().active(); | ||
| } | ||
|
|
||
| // Recompute the POCV statistical slack for one path end and return a Tcl list: | ||
| // {endpoint logic_depth flat_slack pocv_slack flat_sigma rss_sigma n_sigma} | ||
| // Slacks and sigmas are converted to the user time unit. | ||
| StringSeq | ||
| pocv_adjust_path_end(PathEnd *path_end) | ||
| { | ||
| ord::OpenRoad *openroad = ord::getOpenRoad(); | ||
| sta::dbSta *sta = openroad->getSta(); | ||
| PocvPathResult r = pocvAdjustPathEnd(sta, path_end, pocvSigmaState()); | ||
| sta::Unit *time_unit = sta->units()->timeUnit(); | ||
| auto fmt = [](double v) { | ||
| std::ostringstream ss; | ||
| ss.setf(std::ios::fixed); | ||
| ss << std::setprecision(9) << v; | ||
| return ss.str(); | ||
| }; | ||
| StringSeq out; | ||
| out.push_back(r.endpoint); | ||
| out.push_back(std::to_string(r.logic_depth)); | ||
| out.push_back(fmt(time_unit->staToUser(r.flat_slack))); | ||
| out.push_back(fmt(time_unit->staToUser(r.pocv_slack))); | ||
| out.push_back(fmt(time_unit->staToUser(r.flat_sigma))); | ||
| out.push_back(fmt(time_unit->staToUser(r.rss_sigma))); | ||
| out.push_back(fmt(r.n_sigma)); | ||
| return out; | ||
| } | ||
|
Comment on lines
+485
to
+507
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Add null checks for |
||
|
|
||
| // OpenROAD-fork: LVF -- read the PROPAGATION-TIME statistical slack of a path | ||
| // end. During propagation-mode POCV the path's arrival carries an accumulated | ||
| // variance (sum of (k*d_i)^2). The Tcl `slack`/`arrival` properties expose only | ||
| // the MEAN (delayAsFloat single-arg), so this accessor exposes the full | ||
| // statistical picture for testing/reporting. Returns a Tcl list: | ||
| // {mean_slack slack_std_dev stat_slack n_sigma} | ||
| // where stat_slack = mean_slack - n_sigma*slack_std_dev (worst-case readout: | ||
| // the slack std-dev combines arrival+required variance, and the statistical | ||
| // worst slack subtracts the sigma margin). With POCV inactive / propagate off, | ||
| // slack_std_dev == 0 so stat_slack == mean_slack (baseline-safe). | ||
| StringSeq | ||
| pocv_path_end_stat_slack(PathEnd *path_end) | ||
| { | ||
| ord::OpenRoad *openroad = ord::getOpenRoad(); | ||
| sta::dbSta *sta = openroad->getSta(); | ||
| const sta::Slack slack = path_end->slack(sta); | ||
| const float mean = slack.mean(); | ||
| const float std_dev = slack.stdDev(); | ||
| const float n_sigma = pocvSigmaState().n_sigma; | ||
| // Worst-case statistical slack: pull the slack down by n_sigma sigmas. | ||
| const float stat = mean - n_sigma * std_dev; | ||
| sta::Unit *time_unit = sta->units()->timeUnit(); | ||
| auto fmt = [](double v) { | ||
| std::ostringstream ss; | ||
| ss.setf(std::ios::fixed); | ||
| ss << std::setprecision(9) << v; | ||
| return ss.str(); | ||
| }; | ||
| StringSeq out; | ||
| out.push_back(fmt(time_unit->staToUser(mean))); | ||
| out.push_back(fmt(time_unit->staToUser(std_dev))); | ||
| out.push_back(fmt(time_unit->staToUser(stat))); | ||
| out.push_back(fmt(n_sigma)); | ||
| return out; | ||
| } | ||
|
Comment on lines
+519
to
+543
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Add null checks for |
||
|
|
||
| } // namespace sta | ||
|
|
||
| bool | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Add a null check for
staandsta->search()before callingsetPocvSigmato prevent a potential crash if STA is not fully initialized.