From 14e7f1d052ce680f0bc3787f6d1a18de7c71966e Mon Sep 17 00:00:00 2001 From: Saurav Singh Date: Tue, 23 Jun 2026 03:15:43 +0000 Subject: [PATCH 1/7] OpenROAD-fork: AOCV depth-based derate during STA propagation Add a flag-gated hook (AocvDepthDerate interface + Search::deratedDelayData) so the OCV derate applied to data-path combinational arcs during forward arrival propagation can be selected by the path's accumulated combinational depth instead of the flat SDC factor. When no hook is installed (the default) deratedDelayData forwards verbatim to deratedDelay, so timing is byte-identical to upstream. Only data-path combinational cell arcs are depth-derated; clock/reg/latch/check arcs keep the flat derate. Depth is the count of combinational arcs on the live from_path prev chain + 1. All edits marked // OpenROAD-fork: AOCV. clang-format not run on src/sta/*. Signed-off-by: Saurav Singh --- include/sta/Search.hh | 44 +++++++++++++++++++++++++++++ search/Search.cc | 66 +++++++++++++++++++++++++++++++++++++++++-- 2 files changed, 108 insertions(+), 2 deletions(-) diff --git a/include/sta/Search.hh b/include/sta/Search.hh index 539c9895..0629e3af 100644 --- a/include/sta/Search.hh +++ b/include/sta/Search.hh @@ -72,6 +72,27 @@ using WorstSlacksSeq = std::vector; using DelayDblSeq = std::vector; using ExceptionPathSeq = std::vector; +// OpenROAD-fork: AOCV +// Abstract depth->derate hook for AOCV-style depth-dependent OCV derating +// during forward arrival propagation. OpenSTA core does not depend on the +// concrete dbSta AocvDerateTable; the implementation lives in OpenROAD +// (src/dbSta) and is installed on Search via setAocvDepthDerate(). When no hook +// is installed (the default), timing propagation is byte-identical to upstream. +class AocvDepthDerate +{ + public: + virtual ~AocvDepthDerate() = default; + // depth is the number of combinational data-path stages traversed so far + // (including the arc being derated). + virtual float lateDerate(int depth) const = 0; + virtual float earlyDerate(int depth) const = 0; + // When false for a side, deratedDelayData keeps the flat SDC derate for that + // side instead of overriding it (e.g. a late-only table must not silently + // zero out a user's set_timing_derate -early on the min path). + virtual bool lateActive() const = 0; + virtual bool earlyActive() const = 0; +}; + class Search : public StaState { public: @@ -349,6 +370,24 @@ public: const MinMax *min_max, DcalcAPIndex dcalc_ap, const Sdc *sdc); + // OpenROAD-fork: AOCV + // Data-path arc delay that, when an AocvDepthDerate hook is installed, applies + // a depth-dependent derate (selected by the combinational depth of from_path) + // instead of the flat SDC derate. With no hook installed this forwards + // verbatim to deratedDelay(...) so the result is byte-identical to upstream. + ArcDelay deratedDelayData(const Path *from_path, + const Vertex *from_vertex, + const TimingArc *arc, + const Edge *edge, + const MinMax *min_max, + DcalcAPIndex dcalc_ap, + const Sdc *sdc); + // Install (or clear, with nullptr) the AOCV depth-derate hook. Not owned. + void setAocvDepthDerate(const AocvDepthDerate *derate); + const AocvDepthDerate *aocvDepthDerate() const { return aocv_depth_derate_; } + // Combinational depth of the data arrival reaching from_path's vertex, + // including the arc currently being applied (so the first data stage == 1). + int aocvDataDepth(const Path *from_path) const; TagGroup *tagGroup(const Vertex *vertex) const; TagGroup *tagGroup(TagGroupIndex index) const; @@ -670,6 +709,11 @@ protected: VisitPathEnds *visit_path_ends_; GatedClk *gated_clk_; CheckCrpr *check_crpr_; + + // OpenROAD-fork: AOCV + // Optional depth-dependent OCV derate hook. nullptr (the default) means the + // feature is OFF and propagation is byte-identical to upstream. Not owned. + const AocvDepthDerate *aocv_depth_derate_{nullptr}; }; // Eval across latch D->Q edges. diff --git a/search/Search.cc b/search/Search.cc index 879c1247..64abab9a 100644 --- a/search/Search.cc +++ b/search/Search.cc @@ -2216,8 +2216,10 @@ PathVisitor::visitFromPath(const Pin *from_pin, else { if (!(sdc->isPathDelayInternalFromBreak(to_pin) || sdc->isPathDelayInternalToBreak(from_pin))) { - arc_delay = search_->deratedDelay(from_vertex, arc, edge, false, - min_max, dcalc_ap, sdc); + // OpenROAD-fork: AOCV -- depth-dependent derate when a hook is installed; + // byte-identical to deratedDelay(... false ...) when it is not. + arc_delay = search_->deratedDelayData(from_path, from_vertex, arc, edge, + min_max, dcalc_ap, sdc); if (!delayInf(arc_delay, this)) { to_arrival = delaySum(from_arrival, arc_delay, this); to_tag = search_->thruTag(from_tag, edge, to_rf, tag_cache_); @@ -3024,6 +3026,66 @@ Search::deratedDelay(const Vertex *from_vertex, return delayProduct(delay, derate, this);; } +// OpenROAD-fork: AOCV +void +Search::setAocvDepthDerate(const AocvDepthDerate *derate) +{ + aocv_depth_derate_ = derate; +} + +// OpenROAD-fork: AOCV +// Count combinational data-path stages on from_path's prev chain, plus 1 for +// the arc currently being applied. The prev chain is complete here because the +// forward BFS commits prev-path pointers in topological level order before a +// vertex's own paths are extended. +int +Search::aocvDataDepth(const Path *from_path) const +{ + int depth = 1; // the arc about to be derated is the next stage + const Path *p = from_path; + while (p) { + const TimingArc *arc = p->prevArc(this); + if (arc && arc->role() == TimingRole::combinational()) + depth++; + p = p->prevPath(); + } + return depth; +} + +// OpenROAD-fork: AOCV +ArcDelay +Search::deratedDelayData(const Path *from_path, + const Vertex *from_vertex, + const TimingArc *arc, + const Edge *edge, + const MinMax *min_max, + DcalcAPIndex dcalc_ap, + const Sdc *sdc) +{ + // Feature OFF: identical to the original data-path derate call. + if (aocv_depth_derate_ == nullptr) + return deratedDelay(from_vertex, arc, edge, false, min_max, dcalc_ap, sdc); + + // Only depth-derate combinational cell arcs; everything else keeps the flat + // SDC derate (matches the report-only slice and avoids perturbing clock / + // CRPR / check arcs). + if (edge->role() != TimingRole::combinational()) + return deratedDelay(from_vertex, arc, edge, false, min_max, dcalc_ap, sdc); + + // If the table has no entries for this side (late/early), keep the flat SDC + // derate for that side rather than silently overriding it with 1.0. + const bool is_late = (min_max == MinMax::max()); + if (is_late ? !aocv_depth_derate_->lateActive() + : !aocv_depth_derate_->earlyActive()) + return deratedDelay(from_vertex, arc, edge, false, min_max, dcalc_ap, sdc); + + const int depth = aocvDataDepth(from_path); + const float derate = is_late ? aocv_depth_derate_->lateDerate(depth) + : aocv_depth_derate_->earlyDerate(depth); + const ArcDelay &delay = graph_->arcDelay(edge, arc, dcalc_ap); + return delayProduct(delay, derate, this); +} + float Search::timingDerate(const Vertex *from_vertex, const TimingArc *arc, From cd8ba0ae6379e441e7f57a947d9d830c573ada35 Mon Sep 17 00:00:00 2001 From: Saurav Singh Date: Tue, 23 Jun 2026 03:23:04 +0000 Subject: [PATCH 2/7] OpenROAD-fork: SI-window per-coupling-capacitor value setter Add Parasitics::setCapacitorValue (default no-op) and a ConcreteParasitics override / ConcreteParasiticDevice::setValue so the OpenROAD timing-window aware coupling derate can scale individual coupling caps. Only invoked from the OpenROAD window path; stock OpenSTA behavior is unchanged. Signed-off-by: Saurav Singh --- include/sta/Parasitics.hh | 6 ++++++ parasitics/ConcreteParasitics.cc | 10 ++++++++++ parasitics/ConcreteParasitics.hh | 2 ++ parasitics/ConcreteParasiticsPvt.hh | 3 +++ 4 files changed, 21 insertions(+) diff --git a/include/sta/Parasitics.hh b/include/sta/Parasitics.hh index c6706f90..973c55dd 100644 --- a/include/sta/Parasitics.hh +++ b/include/sta/Parasitics.hh @@ -201,6 +201,12 @@ public: ParasiticNode *node2) = 0; virtual uint32_t id(const ParasiticCapacitor *capacitor) const = 0; virtual float value(const ParasiticCapacitor *capacitor) const = 0; + // OpenROAD-fork: SI-window -- set a single (coupling) capacitor's value. + // Used by the OpenROAD timing-window aware coupling derate to scale the + // coupling caps of non-overlapping aggressors. Default no-op preserves + // behavior for any Parasitics implementation that does not support it. + virtual void setCapacitorValue(ParasiticCapacitor * /* capacitor */, + float /* value */) {} virtual ParasiticNode *node1(const ParasiticCapacitor *capacitor) const = 0; virtual ParasiticNode *node2(const ParasiticCapacitor *capacitor) const = 0; virtual ParasiticNode *otherNode(const ParasiticCapacitor *capacitor, diff --git a/parasitics/ConcreteParasitics.cc b/parasitics/ConcreteParasitics.cc index 6ab2bcbb..8c4fef64 100644 --- a/parasitics/ConcreteParasitics.cc +++ b/parasitics/ConcreteParasitics.cc @@ -1414,6 +1414,16 @@ ConcreteParasitics::value(const ParasiticCapacitor *capacitor) const return ccapacitor->value(); } +// OpenROAD-fork: SI-window +void +ConcreteParasitics::setCapacitorValue(ParasiticCapacitor *capacitor, + float value) +{ + ConcreteParasiticCapacitor *ccapacitor = + static_cast(capacitor); + ccapacitor->setValue(value); +} + ParasiticNode * ConcreteParasitics::node1(const ParasiticCapacitor *capacitor) const { diff --git a/parasitics/ConcreteParasitics.hh b/parasitics/ConcreteParasitics.hh index 936609c1..c75465f1 100644 --- a/parasitics/ConcreteParasitics.hh +++ b/parasitics/ConcreteParasitics.hh @@ -170,6 +170,8 @@ public: ParasiticNode *node2) override; uint32_t id(const ParasiticCapacitor *capacitor) const override; float value(const ParasiticCapacitor *capacitor) const override; + // OpenROAD-fork: SI-window + void setCapacitorValue(ParasiticCapacitor *capacitor, float value) override; ParasiticNode *node1(const ParasiticCapacitor *capacitor) const override; ParasiticNode *node2(const ParasiticCapacitor *capacitor) const override; diff --git a/parasitics/ConcreteParasiticsPvt.hh b/parasitics/ConcreteParasiticsPvt.hh index 8b36ff4a..7121a564 100644 --- a/parasitics/ConcreteParasiticsPvt.hh +++ b/parasitics/ConcreteParasiticsPvt.hh @@ -303,6 +303,9 @@ public: ConcreteParasiticNode *node2); uint32_t id() const { return id_; } float value() const { return value_; } + // OpenROAD-fork: SI-window -- per-device value override used by the + // OpenROAD window-aware coupling derate. Not used by stock OpenSTA. + void setValue(float value) { value_ = value; } ConcreteParasiticNode *node1() const { return node1_; } ConcreteParasiticNode *node2() const { return node2_; } void replaceNode(ConcreteParasiticNode *from_node, From 592a910e562ff9ceeb7a42e7cbfa907db130c933 Mon Sep 17 00:00:00 2001 From: Saurav Singh Date: Thu, 9 Jul 2026 23:17:23 +0000 Subject: [PATCH 3/7] parasitics: null-guard ConcreteParasitics::setCapacitorValue (address review) Return early on a null ParasiticCapacitor instead of dereferencing it. Signed-off-by: Saurav Singh --- parasitics/ConcreteParasitics.cc | 2 ++ 1 file changed, 2 insertions(+) diff --git a/parasitics/ConcreteParasitics.cc b/parasitics/ConcreteParasitics.cc index 8c4fef64..6efc7c53 100644 --- a/parasitics/ConcreteParasitics.cc +++ b/parasitics/ConcreteParasitics.cc @@ -1419,6 +1419,8 @@ void ConcreteParasitics::setCapacitorValue(ParasiticCapacitor *capacitor, float value) { + if (capacitor == nullptr) + return; ConcreteParasiticCapacitor *ccapacitor = static_cast(capacitor); ccapacitor->setValue(value); From da63c49c7db8c8ce680d621103dd7e7c6de4f5af Mon Sep 17 00:00:00 2001 From: Saurav Singh Date: Tue, 23 Jun 2026 05:01:04 +0000 Subject: [PATCH 4/7] OpenROAD-fork: POCV per-stage sigma state holder on Search Add a default-OFF Search::PocvSigma state holder (enabled, per-stage sigma fraction, n_sigma, active()) plus inline pocvSigma()/setPocvSigma() accessors and a pocv_sigma_ member. This is read ONLY by the OpenROAD-side report-only POCV command (report_checks_pocv / pocvAdjustPathEnd in src/dbSta); no forward-search / arrival-propagation code reads it, so with POCV inactive (the default) timing is byte-identical to upstream. Parametric POCV variation combines in quadrature (root-sum-square), which does not compose with the additive arrival sum the search propagates, so POCV is intentionally NOT a propagation hook (unlike AOCV). See POCV_INVESTIGATION.md. Header-only edit, marked // OpenROAD-fork: POCV. clang-format not run on src/sta/*. Signed-off-by: Saurav Singh --- include/sta/Search.hh | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/include/sta/Search.hh b/include/sta/Search.hh index 0629e3af..458f99f9 100644 --- a/include/sta/Search.hh +++ b/include/sta/Search.hh @@ -389,6 +389,27 @@ public: // including the arc currently being applied (so the first data stage == 1). int aocvDataDepth(const Path *from_path) const; + // OpenROAD-fork: POCV + // Parametric on-chip-variation (POCV/LVF) parameters, first slice. These are + // PURELY a default-OFF state holder read by the OpenROAD-side report-only + // command (report_checks_pocv / pocvAdjustPathEnd in src/dbSta). No code on + // the forward-search / arrival-propagation path reads them, so when POCV is + // inactive (the default) timing is byte-identical to upstream. POCV uses + // QUADRATURE (root-sum-square) accumulation of per-stage variation, which does + // not compose with the additive arrival sum the search propagates; hence it is + // intentionally NOT a propagation hook. + struct PocvSigma + { + bool enabled = false; // master flag; false => feature OFF (default) + float per_stage = 0.0f; // fractional per-stage delay sigma (k); 0 => off + float n_sigma = 0.0f; // sign-off sigma multiple (e.g. 3 for 3-sigma) + // Active only when explicitly enabled with non-zero coefficients. With this + // false the POCV slack equals the flat slack exactly (baseline). + bool active() const { return enabled && per_stage != 0.0f && n_sigma != 0.0f; } + }; + const PocvSigma &pocvSigma() const { return pocv_sigma_; } + void setPocvSigma(const PocvSigma &sigma) { pocv_sigma_ = sigma; } + TagGroup *tagGroup(const Vertex *vertex) const; TagGroup *tagGroup(TagGroupIndex index) const; void reportArrivals(Vertex *vertex, @@ -714,6 +735,12 @@ protected: // Optional depth-dependent OCV derate hook. nullptr (the default) means the // feature is OFF and propagation is byte-identical to upstream. Not owned. const AocvDepthDerate *aocv_depth_derate_{nullptr}; + + // OpenROAD-fork: POCV + // Parametric OCV (POCV/LVF) parameters. Default-constructed => inactive, so + // the feature is OFF and nothing on the propagation path reads it. Only the + // OpenROAD-side report-only command consumes this (see pocvSigma()). + PocvSigma pocv_sigma_; }; // Eval across latch D->Q edges. From c3e5c126f64fed4ce35775c3b3a142289d714bd9 Mon Sep 17 00:00:00 2001 From: Saurav Singh Date: Wed, 24 Jun 2026 04:12:41 +0000 Subject: [PATCH 5/7] OpenROAD-fork: LVF -- propagation-time POCV statistical-derate variance injection Inject the synthetic per-stage POCV sigma (k*d_i)^2 into combinational data-path arc delays inside deratedDelayData when PocvSigma is in -propagate mode. The native statistical delay-ops (DelayOpsNormal) then accumulate the variance in quadrature through delaySum during the forward search, and report arrival +/- n_sigma*sqrt(variance) at the checks. - Search.hh: add PocvSigma::propagate flag + propagateActive() gate. - Search.cc: factor the scalar (mean) data-path derate into deratedDelayDataMean; deratedDelayData layers the synthetic variance on top without changing the mean. Default OFF (propagate=false) and scalar PocvMode => zero variance => timing is byte-identical to baseline. Signed-off-by: Saurav Singh --- include/sta/Search.hh | 18 ++++++++++++++++++ search/Search.cc | 43 +++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 61 insertions(+) diff --git a/include/sta/Search.hh b/include/sta/Search.hh index 458f99f9..552ace91 100644 --- a/include/sta/Search.hh +++ b/include/sta/Search.hh @@ -382,6 +382,16 @@ public: const MinMax *min_max, DcalcAPIndex dcalc_ap, const Sdc *sdc); + // OpenROAD-fork: LVF -- scalar (mean) component of deratedDelayData (flat or + // AOCV-depth derate). Factored out so the LVF synthetic POCV variance can be + // layered on top without altering the mean. + ArcDelay deratedDelayDataMean(const Path *from_path, + const Vertex *from_vertex, + const TimingArc *arc, + const Edge *edge, + const MinMax *min_max, + DcalcAPIndex dcalc_ap, + const Sdc *sdc); // Install (or clear, with nullptr) the AOCV depth-derate hook. Not owned. void setAocvDepthDerate(const AocvDepthDerate *derate); const AocvDepthDerate *aocvDepthDerate() const { return aocv_depth_derate_; } @@ -403,9 +413,17 @@ public: bool enabled = false; // master flag; false => feature OFF (default) float per_stage = 0.0f; // fractional per-stage delay sigma (k); 0 => off float n_sigma = 0.0f; // sign-off sigma multiple (e.g. 3 for 3-sigma) + // OpenROAD-fork: LVF -- when true AND active(), the synthetic per-stage + // variance (k*d_i)^2 is injected into the data-path arc delay's stdDev2 in + // deratedDelayData so the native statistical delay-ops accumulate it in + // quadrature during the forward search (propagation-time POCV). Default + // false => purely the report-only slice => byte-identical baseline timing. + bool propagate = false; // Active only when explicitly enabled with non-zero coefficients. With this // false the POCV slack equals the flat slack exactly (baseline). bool active() const { return enabled && per_stage != 0.0f && n_sigma != 0.0f; } + // OpenROAD-fork: LVF -- propagation-time injection gate. + bool propagateActive() const { return propagate && active(); } }; const PocvSigma &pocvSigma() const { return pocv_sigma_; } void setPocvSigma(const PocvSigma &sigma) { pocv_sigma_ = sigma; } diff --git a/search/Search.cc b/search/Search.cc index 64abab9a..b83dd1e1 100644 --- a/search/Search.cc +++ b/search/Search.cc @@ -3061,6 +3061,49 @@ Search::deratedDelayData(const Path *from_path, const MinMax *min_max, DcalcAPIndex dcalc_ap, const Sdc *sdc) +{ + // First compute the scalar (flat or AOCV-depth) data-path delay. This block + // is byte-identical to the AOCV slice; nothing here changes the mean delay. + ArcDelay delay = deratedDelayDataMean(from_path, from_vertex, arc, edge, + min_max, dcalc_ap, sdc); + + // OpenROAD-fork: LVF -- propagation-time POCV variance injection. + // When the synthetic per-stage sigma model is in -propagate mode, attach a + // statistical variance (k*d_i)^2 to this stage's delay so the native + // DelayOpsNormal accumulates it in quadrature through delaySum during the + // forward search. The mean is left untouched (above), so with the feature + // off this is a no-op and timing is byte-identical. Only combinational cell + // arcs carry per-stage variation (matches the report-only slice; clock / + // CRPR / check arcs keep their flat scalar treatment). + if (pocv_sigma_.propagateActive() + && edge->role() == TimingRole::combinational()) { + const float k = pocv_sigma_.per_stage; + const float d = delayAsFloat(delay); // nominal stage delay mean + if (d > 0.0f) { + const float stage_sigma = k * d; // per-stage sigma = k * d_i + // makeDelay(mean, std_dev) stores std_dev^2 == (k*d_i)^2 as the variance, + // which is exactly the per-stage contribution the path accumulates in + // quadrature. The mean is preserved so the nominal arrival is unchanged. + // Any prior arc variance (e.g. from LVF liberty) is replaced by the + // synthetic model on purpose -- they are mutually exclusive configs. + delay = makeDelay(delayAsFloat(delay), stage_sigma); + } + } + return delay; +} + +// OpenROAD-fork: AOCV -- scalar (mean) data-path delay: flat SDC derate, or the +// AOCV depth-derate when that hook is installed. Factored out of +// deratedDelayData so the LVF variance injection can layer on top without +// changing the mean. Byte-identical to the original AOCV body. +ArcDelay +Search::deratedDelayDataMean(const Path *from_path, + const Vertex *from_vertex, + const TimingArc *arc, + const Edge *edge, + const MinMax *min_max, + DcalcAPIndex dcalc_ap, + const Sdc *sdc) { // Feature OFF: identical to the original data-path derate call. if (aocv_depth_derate_ == nullptr) From ed33f344cb2b0f9ac3cce641b5b6818207c1ddad Mon Sep 17 00:00:00 2001 From: Saurav Singh Date: Wed, 24 Jun 2026 10:05:16 +0000 Subject: [PATCH 6/7] OpenROAD-fork: LVF-lib -- library-driven POCV gate on PocvSigma Add PocvSigma::from_liberty so set_pocv_sigma -from_liberty sources the per-stage delay sigma from the real Liberty LVF ocv_sigma_* tables via the native statistical delay calc (GateTableModel::gateDelayPocv under PocvMode::normal) instead of the synthetic global per_stage. In this mode propagateActive() returns false so the synthetic (k*d) injection in Search::deratedDelayData is suppressed and cannot overwrite the library-derived stdDev. Default from_liberty=false keeps the existing -propagate/-sigma behaviour and a byte-identical flag-off baseline. Signed-off-by: Saurav Singh --- include/sta/Search.hh | 23 ++++++++++++++++++++--- 1 file changed, 20 insertions(+), 3 deletions(-) diff --git a/include/sta/Search.hh b/include/sta/Search.hh index 552ace91..35d44628 100644 --- a/include/sta/Search.hh +++ b/include/sta/Search.hh @@ -419,11 +419,28 @@ public: // quadrature during the forward search (propagation-time POCV). Default // false => purely the report-only slice => byte-identical baseline timing. bool propagate = false; + // OpenROAD-fork: LVF-lib -- library-driven POCV. When true the per-stage + // sigma comes from the real Liberty LVF ocv_sigma_* tables via the NATIVE + // delay calc (GateTableModel::gateDelayPocv under PocvMode::normal), NOT + // from the synthetic global per_stage. In this mode the synthetic variance + // injection in deratedDelayData is suppressed so it cannot overwrite the + // library-derived stdDev. Default false => byte-identical baseline. + bool from_liberty = false; // Active only when explicitly enabled with non-zero coefficients. With this - // false the POCV slack equals the flat slack exactly (baseline). + // false the POCV slack equals the flat slack exactly (baseline). Unchanged: + // this governs only the global synthetic-sigma model (per_stage based). bool active() const { return enabled && per_stage != 0.0f && n_sigma != 0.0f; } - // OpenROAD-fork: LVF -- propagation-time injection gate. - bool propagateActive() const { return propagate && active(); } + // OpenROAD-fork: LVF-lib -- library-driven mode gate. Independent of the + // synthetic per_stage; needs only enabled + a sign-off quantile (n_sigma). + bool libertyActive() const { return enabled && from_liberty && n_sigma != 0.0f; } + // OpenROAD-fork: LVF -- propagation-time SYNTHETIC injection gate. Fires + // only for the global-sigma model; in library-driven mode the variance + // comes from the native LVF delay calc, so the synthetic injection is + // suppressed (from_liberty => false) to avoid overwriting the library stdDev. + bool propagateActive() const + { + return propagate && active() && !from_liberty; // OpenROAD-fork: LVF-lib + } }; const PocvSigma &pocvSigma() const { return pocv_sigma_; } void setPocvSigma(const PocvSigma &sigma) { pocv_sigma_ = sigma; } From 61875ee7ed5f91c2a16b64d5d78951a09a2ce530 Mon Sep 17 00:00:00 2001 From: Saurav Singh Date: Fri, 10 Jul 2026 18:46:03 +0000 Subject: [PATCH 7/7] search: address POCV/LVF review nits - PocvSigma::active()/libertyActive(): require per_stage/n_sigma > 0.0f (reject negative/invalid values, not just != 0). - Variance injection: guard against non-finite stage delay (std::isfinite) and reuse the already-computed mean 'd' instead of recomputing delayAsFloat. Signed-off-by: Saurav Singh --- include/sta/Search.hh | 4 ++-- search/Search.cc | 5 +++-- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/include/sta/Search.hh b/include/sta/Search.hh index 35d44628..c3c4e424 100644 --- a/include/sta/Search.hh +++ b/include/sta/Search.hh @@ -429,10 +429,10 @@ public: // Active only when explicitly enabled with non-zero coefficients. With this // false the POCV slack equals the flat slack exactly (baseline). Unchanged: // this governs only the global synthetic-sigma model (per_stage based). - bool active() const { return enabled && per_stage != 0.0f && n_sigma != 0.0f; } + bool active() const { return enabled && per_stage > 0.0f && n_sigma > 0.0f; } // OpenROAD-fork: LVF-lib -- library-driven mode gate. Independent of the // synthetic per_stage; needs only enabled + a sign-off quantile (n_sigma). - bool libertyActive() const { return enabled && from_liberty && n_sigma != 0.0f; } + bool libertyActive() const { return enabled && from_liberty && n_sigma > 0.0f; } // OpenROAD-fork: LVF -- propagation-time SYNTHETIC injection gate. Fires // only for the global-sigma model; in library-driven mode the variance // comes from the native LVF delay calc, so the synthetic injection is diff --git a/search/Search.cc b/search/Search.cc index b83dd1e1..720a7840 100644 --- a/search/Search.cc +++ b/search/Search.cc @@ -25,6 +25,7 @@ #include "Search.hh" #include +#include #include #include @@ -3079,14 +3080,14 @@ Search::deratedDelayData(const Path *from_path, && edge->role() == TimingRole::combinational()) { const float k = pocv_sigma_.per_stage; const float d = delayAsFloat(delay); // nominal stage delay mean - if (d > 0.0f) { + if (d > 0.0f && std::isfinite(d)) { const float stage_sigma = k * d; // per-stage sigma = k * d_i // makeDelay(mean, std_dev) stores std_dev^2 == (k*d_i)^2 as the variance, // which is exactly the per-stage contribution the path accumulates in // quadrature. The mean is preserved so the nominal arrival is unchanged. // Any prior arc variance (e.g. from LVF liberty) is replaced by the // synthetic model on purpose -- they are mutually exclusive configs. - delay = makeDelay(delayAsFloat(delay), stage_sigma); + delay = makeDelay(d, stage_sigma); } } return delay;