Skip to content
6 changes: 6 additions & 0 deletions include/sta/Parasitics.hh
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
106 changes: 106 additions & 0 deletions include/sta/Search.hh
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,27 @@ using WorstSlacksSeq = std::vector<WorstSlacks>;
using DelayDblSeq = std::vector<DelayDbl>;
using ExceptionPathSeq = std::vector<ExceptionPath*>;

// 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:
Expand Down Expand Up @@ -349,6 +370,80 @@ 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);
// 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_; }
// 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;

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

TagGroup *tagGroup(const Vertex *vertex) const;
TagGroup *tagGroup(TagGroupIndex index) const;
Expand Down Expand Up @@ -670,6 +765,17 @@ 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};

// 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.
Expand Down
12 changes: 12 additions & 0 deletions parasitics/ConcreteParasitics.cc
Original file line number Diff line number Diff line change
Expand Up @@ -1414,6 +1414,18 @@ ConcreteParasitics::value(const ParasiticCapacitor *capacitor) const
return ccapacitor->value();
}

// OpenROAD-fork: SI-window
void
ConcreteParasitics::setCapacitorValue(ParasiticCapacitor *capacitor,
float value)
{
if (capacitor == nullptr)
return;
ConcreteParasiticCapacitor *ccapacitor =
static_cast<ConcreteParasiticCapacitor*>(capacitor);
ccapacitor->setValue(value);
}

ParasiticNode *
ConcreteParasitics::node1(const ParasiticCapacitor *capacitor) const
{
Expand Down
2 changes: 2 additions & 0 deletions parasitics/ConcreteParasitics.hh
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down
3 changes: 3 additions & 0 deletions parasitics/ConcreteParasiticsPvt.hh
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
110 changes: 108 additions & 2 deletions search/Search.cc
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
#include "Search.hh"

#include <algorithm>
#include <cmath>
#include <cstddef>
#include <vector>

Expand Down Expand Up @@ -2216,8 +2217,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_);
Expand Down Expand Up @@ -3024,6 +3027,109 @@ 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)
{
// 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.
Comment on lines +3068 to +3071

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

medium

If the computed mean delay is infinite (e.g., representing an unconstrained or blocked path), performing float operations on it or passing it to makeDelay is unnecessary and could potentially lead to NaN or other undefined behavior. It is safer to check if the delay is infinite using delayInf and return early.

  ArcDelay delay = deratedDelayDataMean(from_path, from_vertex, arc, edge,
                                        min_max, dcalc_ap, sdc);

  if (delayInf(delay, this))
    return delay;

  // 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 && 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(d, 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)
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,
Expand Down