Skip to content

dbSta: POCV / LVF parametric statistical-OCV timing (draft)#10853

Draft
saurav-fermions wants to merge 6 commits into
The-OpenROAD-Project:masterfrom
Fermions-ASI:feat/lvf-pocv
Draft

dbSta: POCV / LVF parametric statistical-OCV timing (draft)#10853
saurav-fermions wants to merge 6 commits into
The-OpenROAD-Project:masterfrom
Fermions-ASI:feat/lvf-pocv

Conversation

@saurav-fermions

Copy link
Copy Markdown
Contributor

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 as sqrt(N) instead of the linear N of a flat derate. Every path is additive; default/flag-off is byte-identical to baseline.

  1. POCV report slice (report-only)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.
  2. Propagation-time derateset_pocv_sigma -propagate injects per-stage variance (k*d_i)^2 into combinational data-path arcs during the forward search; native statistical delay-ops accumulate in quadrature (arrival +/- n_sigma*sqrt(var)).
  3. Library-driven POCVset_pocv_sigma -from_liberty sources per-stage sigma from real Liberty LVF ocv_sigma_* tables (per cell/arc); mutually exclusive with -sigma.
  4. Table-indexed LVF test — proves the library path reads sigma as a 2-D table lookup over the same slew x load axes as the nominal delay table.

⚠️ Dependencies (please read)

Testing

Built openroad against src/sta carrying 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

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>

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Comment thread src/dbSta/src/dbSta.i
Comment on lines +340 to +346
static void
pocvSyncState()
{
ord::OpenRoad *openroad = ord::getOpenRoad();
sta::dbSta *sta = openroad->getSta();
sta->search()->setPocvSigma(pocvSigmaState());
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

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());
  }
}

Comment thread src/dbSta/src/dbSta.i
Comment on lines +368 to +385
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);
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

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);
  }
}

Comment thread src/dbSta/src/dbSta.i
Comment on lines +392 to +427
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;
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

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;
}

Comment thread src/dbSta/src/dbSta.i
Comment on lines +444 to +459
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);
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

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);
  }
}

Comment thread src/dbSta/src/dbSta.i
Comment on lines +461 to +474
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);
}
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

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);
    }
  }
}

Comment thread src/dbSta/src/dbSta.i
Comment on lines +485 to +507
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;
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

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;
}

Comment thread src/dbSta/src/dbSta.i
Comment on lines +519 to +543
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;
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

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;
}

Comment thread src/dbSta/src/dbSta.tcl
Comment on lines +323 to +325
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

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

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

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>
@saurav-fermions

Copy link
Copy Markdown
Contributor Author

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.

  • Empty path-end guard (report_checks_pocv): added — pocv_adjust_path_end returning an empty list is now skipped instead of hitting a lassign/expr error.

  • The sta/network/iterator null-checks flagged on the SWIG entry points: these commands follow the existing dbSta command pattern — ord::getOpenRoad()->getSta() is always valid once the app is initialized, and ensureLinked()/ensureGraph() guard the timing state before use, exactly like the sibling report_checks_* commands. Adding per-call null-checks there would be dead code inconsistent with the rest of dbSta.i, so I've left those as-is; happy to reconsider if the team wants defensive guards added uniformly.

  • Also removed an accidental src/sta submodule bump.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant