dbSta: POCV / LVF parametric statistical-OCV timing (draft)#10853
dbSta: POCV / LVF parametric statistical-OCV timing (draft)#10853saurav-fermions wants to merge 6 commits into
Conversation
Add a flag-gated, default-OFF POCV/LVF first slice that recomputes a statistical (root-sum-square / quadrature) setup slack on already-found critical paths and reports flat-OCV slack vs POCV slack vs recovered pessimism. POCV models each stage delay as a distribution (per-stage fractional sigma k). Independent stage variations combine in QUADRATURE: sigma_path = k*sqrt(sum d_i^2), which grows as sqrt(N) for an N-stage path instead of the linear N of a flat derate -- the de-pessimism that is the whole point of POCV. The reported statistical slack is flat_slack + N_sigma*(sigma_linear - sigma_rss). Because quadrature does not compose with the additive arrival sum the OpenSTA forward search propagates, this is intentionally report-only and does NOT modify the search (unlike the AOCV propagation hook). Worst_slack/WNS/TNS are therefore byte-identical whether POCV is on or off (proven in test/pocv_baseline.tcl). With no sigma set, POCV slack == flat slack exactly. Commands: set_pocv_sigma [-sigma k] [-n_sigma N] [-reset]; report_checks_pocv [-path_count n] [-digits d] [find_timing_paths opts]. Bumps src/sta to the POCV fork commit (header-only Search::PocvSigma holder). Mirrors the existing AOCV slice's integration style. See POCV_INVESTIGATION.md and AGENT_REPORT.md. Tests: dbSta.pocv_derate (inactive==flat, sqrt-of-N direction, closed-form N=100 ratio==10), dbSta.pocv_baseline (worst_slack unchanged on/off). Full dbSta ctest suite green (75/75). Signed-off-by: Saurav Singh <saurav.singh@fermions.co>
…ult off) Add a propagation-time mode to the parametric-POCV (LVF) feature: with 'set_pocv_sigma -propagate', the synthetic per-stage sigma (k) is injected as per-arc variance (k*d_i)^2 into combinational data-path arcs during the OpenSTA forward search. The native statistical delay-ops accumulate it in quadrature and the checks read out arrival +/- n_sigma*sqrt(variance), giving real sqrt(N) de-pessimism during propagation (not just a post-hoc report). Reuses OpenSTA's existing statistical Delay/DelayOpsNormal machinery rather than adding a new Path/Tag accumulator (see LVF_INVESTIGATION.md). Default OFF: with no -propagate the timer stays in scalar mode and timing is byte-identical to baseline (proven by a full report_checks/wns/tns diff after a propagate+reset cycle). - src/sta @ 2d376f5a: PocvSigma.propagate + variance injection in deratedDelayData (marked OpenROAD-fork: LVF). - dbSta: set_pocv_sigma -propagate wires PocvMode::normal + quantile=n_sigma; pocv_path_end_stat_slack exposes the propagated statistical slack. - test: pocv_propagate (CMake + Bazel); report-only path unchanged. ctest: dbSta 79/79, OpenSTA search/sdc 115/115, dcalc/liberty 33/33. Signed-off-by: Saurav Singh <saurav.singh@fermions.co>
…-Liberty) Source the per-stage POCV delay sigma from the real Liberty LVF ocv_sigma_* tables of the loaded libraries (per cell / per arc) instead of one global hand-set -sigma. set_pocv_sigma -from_liberty enables PocvMode::normal + setPocvQuantile(n_sigma) so OpenSTA's native statistical delay calc (GateTableModel::gateDelayPocv) reads the library sigma and the forward search accumulates it in quadrature, reporting mean - n_sigma*sqrt(var). - src/sta (fork 86dc6647): PocvSigma::from_liberty gate suppresses the synthetic global k*d injection in library mode so it cannot overwrite the library stdDev. Default from_liberty=false => byte-identical baseline. - dbSta.i: pocv_liberty_has_lvf() (detect LVF tables) and pocv_sigma_set_from_liberty(). dbSta.tcl: -from_liberty flag, mutually exclusive with -sigma (STA-8011), warns if no library has LVF (STA-8012). Tests (src/dbSta/test, CMake-registered): - lvf_flagoff: The-OpenROAD-Project#1 gate -- report_checks byte-identical to baseline with the feature on (no-LVF lib => inert) and after -reset (gcd setup). - lvf_sigma: synthetic LVF lib (Nangate45 has none) with HI/LO sigma buffers sharing identical mean tables; asserts equal means, per-cell quadrature sigma accumulation (10x ratio), and HI path less optimistic than LO path. ctest -R 'pocv|dbSta': 81/81 pass. dbSta.i is a SWIG .i file, not clang-formatted per AGENTS.md. Scope: synthetic LVF lib, delay-sigma only; documented in AGENT_REPORT.md. Signed-off-by: Saurav Singh <saurav.singh@fermions.co>
…m_liberty POCV
The prior LVF-from-Liberty slice proved per-CELL sigma sourcing but used
SCALAR (axis-less) ocv_sigma tables, so it could not demonstrate that the
library-driven POCV path reads sigma as a TABLE LOOKUP over the same slew x
load axes as the nominal delay table.
OpenSTA in the pinned fork (86dc6647) already parses and consumes 2-D LVF
ocv_sigma_cell_{rise,fall} tables natively (LibertyReader::readLvfModels ->
TableModels::setSigma; GateTableModel::gateDelayPocv -> findValue ->
setStdDev). No src/sta change is needed and none is made.
This slice adds a regression that exercises the table-indexed property:
- lvf_lib.lib: new snl_bufvar cell with 2-D ocv_sigma_cell_rise/fall on the
drive1_rise/fall (slew x load) templates; sigma grows with output load.
- lvf_lib.lef: snl_bufvar macro.
- lvf_table.v / lvf_table.tcl: same cell at small vs large output load;
asserts the large-load arc accumulates strictly larger per-arc sigma read
from the 2-D table (measured ~8.8x), is pulled further below its mean, and
-reset restores baseline mean slack.
- lvf_table.ok: golden output.
- lvf_sigma.ok: LEF cell-count line updated 3 -> 4 (shared LEF gained a macro);
all assertion lines unchanged.
- CMakeLists.txt / BUILD: register lvf_table in both build systems.
Gates: lvf_flagoff (byte-identical baseline, no LVF), lvf_sigma + lvf_table
(larger lib sigma => more path variation, driven from the .lib). ctest dbSta
82/82 pass; ctest lvf 3/3 pass. Flag-off path unchanged.
Signed-off-by: Saurav Singh <saurav.singh@fermions.co>
Comment-only cleanup: remove dangling references to internal POCV design notes (POCV_INVESTIGATION.md). No functional change. Signed-off-by: Saurav Singh <saurav.singh@fermions.co>
There was a problem hiding this comment.
Code Review
This pull request introduces parametric statistical on-chip-variation (POCV / LVF) derating to dbSta, adding C++ implementations for statistical slack adjustment, SWIG bindings for configuration and propagation, Tcl commands, and comprehensive integration tests. The review feedback focuses on improving robustness by adding null pointer checks across several SWIG/C++ functions in dbSta.i to prevent potential crashes, as well as checking for empty lists in the Tcl reporting loop to avoid runtime errors.
| static void | ||
| pocvSyncState() | ||
| { | ||
| ord::OpenRoad *openroad = ord::getOpenRoad(); | ||
| sta::dbSta *sta = openroad->getSta(); | ||
| sta->search()->setPocvSigma(pocvSigmaState()); | ||
| } |
There was a problem hiding this comment.
Add a null check for sta and sta->search() before calling setPocvSigma to prevent a potential crash if STA is not fully initialized.
static void
pocvSyncState()
{
ord::OpenRoad *openroad = ord::getOpenRoad();
sta::dbSta *sta = openroad->getSta();
if (sta != nullptr && sta->search() != nullptr) {
sta->search()->setPocvSigma(pocvSigmaState());
}
}
| 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); | ||
| } |
There was a problem hiding this comment.
Add a null check for sta before calling setPocvQuantile and setPocvMode to prevent a potential crash.
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();
if (sta != nullptr) {
// 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);
}
}
| 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; | ||
| } |
There was a problem hiding this comment.
Add comprehensive null checks for sta, sta->network(), lib_iter, lib, cell, arc_set, and arc to prevent potential null pointer dereferences and crashes when iterating over loaded libraries.
static bool
anyLibraryHasLvf()
{
ord::OpenRoad *openroad = ord::getOpenRoad();
sta::dbSta *sta = openroad->getSta();
if (sta == nullptr || sta->network() == nullptr) {
return false;
}
LibertyLibraryIterator *lib_iter = sta->network()->libertyLibraryIterator();
if (lib_iter == nullptr) {
return false;
}
bool found = false;
while (lib_iter->hasNext() && !found) {
LibertyLibrary *lib = lib_iter->next();
if (lib == nullptr) {
continue;
}
LibertyCellIterator cell_iter(lib);
while (cell_iter.hasNext() && !found) {
LibertyCell *cell = cell_iter.next();
if (cell == nullptr) {
continue;
}
for (TimingArcSet *arc_set : cell->timingArcSets()) {
if (arc_set == nullptr) {
continue;
}
if (arc_set->role() != TimingRole::combinational())
continue;
for (TimingArc *arc : arc_set->arcs()) {
if (arc == nullptr) {
continue;
}
const TimingModel *model = arc->model();
const GateTableModel *gate
= dynamic_cast<const GateTableModel *>(model);
if (gate && gate->delayModels()) {
const TableModels *dm = gate->delayModels();
if (dm && (dm->sigma(MinMax::max()) || dm->sigma(MinMax::min())
|| dm->stdDev())) {
found = true;
break;
}
}
}
if (found)
break;
}
}
}
delete lib_iter;
return found;
}
| 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); | ||
| } |
There was a problem hiding this comment.
Add a null check for sta before calling setPocvQuantile and setPocvMode to prevent a potential crash.
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();
if (sta != nullptr) {
sta->setPocvQuantile(n_sigma);
sta->setPocvMode(sta::PocvMode::normal);
}
}
| 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); | ||
| } | ||
| } |
There was a problem hiding this comment.
Add a null check for sta before calling setPocvMode to prevent a potential crash.
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();
if (sta != nullptr) {
sta->setPocvMode(sta::PocvMode::scalar);
}
}
}
| 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; | ||
| } |
There was a problem hiding this comment.
Add null checks for sta, path_end, and time_unit to prevent potential null pointer dereferences and crashes.
StringSeq
pocv_adjust_path_end(PathEnd *path_end)
{
ord::OpenRoad *openroad = ord::getOpenRoad();
sta::dbSta *sta = openroad->getSta();
if (sta == nullptr || path_end == nullptr) {
return StringSeq();
}
PocvPathResult r = pocvAdjustPathEnd(sta, path_end, pocvSigmaState());
sta::Unit *time_unit = sta->units()->timeUnit();
if (time_unit == nullptr) {
return StringSeq();
}
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;
}
| 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; | ||
| } |
There was a problem hiding this comment.
Add null checks for sta, path_end, and time_unit to prevent potential null pointer dereferences and crashes.
StringSeq
pocv_path_end_stat_slack(PathEnd *path_end)
{
ord::OpenRoad *openroad = ord::getOpenRoad();
sta::dbSta *sta = openroad->getSta();
if (sta == nullptr || path_end == nullptr) {
return StringSeq();
}
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();
if (time_unit == nullptr) {
return StringSeq();
}
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;
}
| foreach path_end $path_ends { | ||
| set row [sta::pocv_adjust_path_end $path_end] | ||
| lassign $row endpoint depth flat pocv lin_sigma rss_sigma n_sigma |
There was a problem hiding this comment.
Check if row is empty before assigning variables and calculating recovered to prevent a potential Tcl runtime error if pocv_adjust_path_end returns an empty list.
foreach path_end $path_ends {
set row [sta::pocv_adjust_path_end $path_end]
if { $row == {} } {
continue
}
lassign $row endpoint depth flat pocv lin_sigma rss_sigma n_sigma
7582ccf to
a5fa662
Compare
Skip a path end when pocv_adjust_path_end returns an empty list, avoiding a Tcl lassign/expr error (addresses review feedback). Signed-off-by: Saurav Singh <saurav.singh@fermions.co>
a5fa662 to
4c70e71
Compare
|
Thanks for the review. Note this PR is a draft — it depends on the OpenSTA POCV/LVF hooks in The-OpenROAD-Project/OpenSTA#385 (and reuses the AOCV test fixtures from #10846), so CI can't go green until those land.
|
Summary
Adds the dbSta POCV/LVF family — flag-gated, default-OFF parametric statistical-OCV commands. Statistical OCV models each stage delay as a distribution and combines independent per-stage variations in quadrature (
sigma_path = k*sqrt(sum d_i^2)), which grows assqrt(N)instead of the linearNof a flat derate. Every path is additive; default/flag-off is byte-identical to baseline.set_pocv_sigma [-sigma k] [-n_sigma N] [-reset],report_checks_pocv; recomputes a quadrature (RSS) setup slack on already-found critical paths (flat-OCV vs POCV vs recovered pessimism). Does not modify the forward search.set_pocv_sigma -propagateinjects per-stage variance(k*d_i)^2into combinational data-path arcs during the forward search; native statistical delay-ops accumulate in quadrature (arrival +/- n_sigma*sqrt(var)).set_pocv_sigma -from_libertysources per-stage sigma from real Liberty LVFocv_sigma_*tables (per cell/arc); mutually exclusive with-sigma.Search::PocvSigma, variance injection inderatedDelayData,PocvSigma::from_liberty). CI here fails until repair_tie_fanout multi-output tie cells #385 is merged and this repo'ssrc/stais bumped. This PR carries nosrc/stagitlink change. (repair_tie_fanout multi-output tie cells #385 in turn stacks on search/parasitics: AOCV depth-based derate hook + SI-window coupling-cap setter OpenSTA#383.)aocv_derate.def/aocv_derate.sdcfixtures introduced by the dbSta timing-accuracy PR dbSta: timing-accuracy report commands (AOCV/SI/PBA/CPPR/MCMM/crosstalk/noise/incremental-STA) #10846. Until that lands, those two fixture files must be present for the tests to run.Testing
Built
openroadagainstsrc/stacarrying the #385 hooks, with the AOCV fixtures present: all 6 POCV/LVF regressions pass and the full dbSta suite is green (76/76), including the flag-off byte-identical baseline checks.Notes
src/stahooks (repair_tie_fanout multi-output tie cells #385) and the shared AOCV fixtures (dbSta: timing-accuracy report commands (AOCV/SI/PBA/CPPR/MCMM/crosstalk/noise/incremental-STA) #10846).