Merge pull request #379 from The-OpenROAD-Project-staging/sta_latest_0629

Latest STA changes 06/29
This commit is contained in:
Matt Liberty 2026-07-06 16:11:43 +00:00 committed by GitHub
commit 4533a67a26
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
56 changed files with 2449 additions and 2029 deletions

View File

@ -34,6 +34,13 @@ alwaysApply: true
- Prefer `std::string` over `char*` for string members. - Prefer `std::string` over `char*` for string members.
- Prefer pass-by-value and move for sink parameters (parameters that get stored). - Prefer pass-by-value and move for sink parameters (parameters that get stored).
## Control flow
- Prefer positive `if` conditions that wrap the main logic instead of early
`continue` or `return` to skip work.
- Example: use `if (!visited.contains(vertex)) { ... }` rather than
`if (visited.contains(vertex)) continue;`.
## File Extensions ## File Extensions
- C++ source: `.cc` - C++ source: `.cc`

View File

@ -10,7 +10,7 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- name: Checkout - name: Checkout
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
with: with:
submodules: true submodules: true

View File

@ -31,6 +31,7 @@
// slew voltage is matched instead of y20 in eqn 12. // slew voltage is matched instead of y20 in eqn 12.
#include "DmpCeff.hh" #include "DmpCeff.hh"
#include <Eigen/Dense>
#include <algorithm> #include <algorithm>
#include <array> #include <array>
@ -137,20 +138,23 @@ DmpAlg::init(const LibertyLibrary *drvr_library,
void void
DmpAlg::findDriverParams(double ceff) DmpAlg::findDriverParams(double ceff)
{ {
Eigen::Vector3d x = Eigen::Vector3d::Zero();
if (nr_order_ == 3) if (nr_order_ == 3)
x_[DmpParam::ceff] = ceff; x[DmpParam::ceff] = ceff;
auto [t_vth, t_vl, slew] = gateDelays(ceff); auto [t_vth, t_vl, slew] = gateDelays(ceff);
// Scale slew to 0-100% // Scale slew to 0-100%
double dt = slew / (vh_ - vl_); double dt = slew / (vh_ - vl_);
double t0 = t_vth + std::log(1.0 - vth_) * rd_ * ceff - vth_ * dt; double t0 = t_vth + std::log(1.0 - vth_) * rd_ * ceff - vth_ * dt;
x_[DmpParam::dt] = dt; x[DmpParam::dt] = dt;
x_[DmpParam::t0] = t0; x[DmpParam::t0] = t0;
newtonRaphson(); newtonRaphson(x);
t0_ = x_[DmpParam::t0]; t0_ = x[DmpParam::t0];
dt_ = x_[DmpParam::dt]; dt_ = x[DmpParam::dt];
if (nr_order_ == 3)
ceff_ = x[DmpParam::ceff];
debugPrint(debug_, "dmp_ceff", 3, " t0 = {} dt = {} ceff = {}", debugPrint(debug_, "dmp_ceff", 3, " t0 = {} dt = {} ceff = {}",
units_->timeUnit()->asString(t0_), units_->timeUnit()->asString(dt_), units_->timeUnit()->asString(t0_), units_->timeUnit()->asString(dt_),
units_->capacitanceUnit()->asString(x_[DmpParam::ceff])); units_->capacitanceUnit()->asString(ceff_));
if (debug_->check("dmp_ceff", 4)) if (debug_->check("dmp_ceff", 4))
showVo(); showVo();
} }
@ -241,21 +245,21 @@ DmpAlg::y0dcl(double t,
} }
void void
DmpAlg::showX() DmpAlg::showX(const Eigen::Vector3d &x)
{ {
for (int i = 0; i < nr_order_; i++) for (int i = 0; i < nr_order_; i++)
report_->report("{:4} {:12.3e}", dmp_param_index_strings[i], x_[i]); report_->report("{:4} {:12.3e}", dmp_param_index_strings[i], x[i]);
} }
void void
DmpAlg::showFvec() DmpAlg::showFvec(const Eigen::Vector3d &fvec)
{ {
for (int i = 0; i < nr_order_; i++) for (int i = 0; i < nr_order_; i++)
report_->report("{:4} {:12.3e}", dmp_func_index_strings[i], fvec_[i]); report_->report("{:4} {:12.3e}", dmp_func_index_strings[i], fvec[i]);
} }
void void
DmpAlg::showJacobian() DmpAlg::showJacobian(const Eigen::Matrix3d &fjac)
{ {
std::string line = " "; std::string line = " ";
for (int j = 0; j < nr_order_; j++) for (int j = 0; j < nr_order_; j++)
@ -265,7 +269,7 @@ DmpAlg::showJacobian()
line.clear(); line.clear();
line += sta::format("{:4} ", dmp_func_index_strings[i]); line += sta::format("{:4} ", dmp_func_index_strings[i]);
for (int j = 0; j < nr_order_; j++) for (int j = 0; j < nr_order_; j++)
line += sta::format("{:12.3e} ", fjac_[i][j]); line += sta::format("{:12.3e} ", fjac(i, j));
report_->reportLine(line); report_->reportLine(line);
} }
} }
@ -505,7 +509,9 @@ DmpCap::loadDelaySlew(const Pin *,
} }
void void
DmpCap::evalDmpEqns() DmpCap::evalDmpEqns(Eigen::Vector3d &,
Eigen::Vector3d &,
Eigen::Matrix3d &)
{ {
} }
@ -584,7 +590,6 @@ DmpPi::gateDelaySlew()
double slew = 0.0; double slew = 0.0;
try { try {
findDriverParamsPi(); findDriverParamsPi();
ceff_ = x_[DmpParam::ceff];
auto [table_delay, table_slew] = gateCapDelaySlew(ceff_); auto [table_delay, table_slew] = gateCapDelaySlew(ceff_);
delay = table_delay; delay = table_delay;
// slew = table_slew; // slew = table_slew;
@ -623,60 +628,85 @@ DmpPi::findDriverParamsPi()
// Given x_ as a vector of input parameters, fill fvec_ with the // Given x_ as a vector of input parameters, fill fvec_ with the
// equations evaluated at x_ and fjac_ with the jacobian evaluated at x_. // equations evaluated at x_ and fjac_ with the jacobian evaluated at x_.
void void
DmpPi::evalDmpEqns() DmpPi::evalDmpEqns(Eigen::Vector3d &x,
Eigen::Vector3d &fvec,
Eigen::Matrix3d &fjac)
{ {
double t0 = x_[DmpParam::t0]; const double t0 = x[DmpParam::t0];
double dt = x_[DmpParam::dt]; const double dt = x[DmpParam::dt];
double ceff = x_[DmpParam::ceff]; const double ceff = x[DmpParam::ceff];
if (ceff < 0.0) // Validate bounds to prevent mathematical domain errors.
if (ceff < 0.0) {
throw DmpError("eqn eval failed: ceff < 0"); throw DmpError("eqn eval failed: ceff < 0");
if (ceff > (c1_ + c2_)) }
if (ceff > (c1_ + c2_)) {
throw DmpError("eqn eval failed: ceff > c2 + c1"); throw DmpError("eqn eval failed: ceff > c2 + c1");
}
if (dt <= 0.0) {
throw DmpError("eqn eval failed: dt < 0");
}
auto [t_vth, t_vl, slew] = gateDelays(ceff); auto [t_vth, t_vl, slew] = gateDelays(ceff);
if (slew == 0.0) if (slew == 0.0) {
throw DmpError("eqn eval failed: slew = 0"); throw DmpError("eqn eval failed: slew = 0");
}
double ceff_time = slew / (vh_ - vl_); // ceff_time is bounded by 1.4 * dt.
ceff_time = std::min(ceff_time, 1.4 * dt); const double ceff_time = std::min(slew / (vh_ - vl_), 1.4 * dt);
if (dt <= 0.0) // Pre-calculate exponential terms to avoid redundant calls to
throw DmpError("eqn eval failed: dt < 0"); // transcendental functions.
const double exp_p1_dt = exp2(-p1_ * dt);
const double exp_p2_dt = exp2(-p2_ * dt);
const double exp_dt_rd_ceff = exp2(-dt / (rd_ * ceff));
double exp_p1_dt = exp2(-p1_ * dt); // Evaluate function values (residuals).
double exp_p2_dt = exp2(-p2_ * dt); const double y50 = y(t_vth, t0, dt, ceff).first;
double exp_dt_rd_ceff = exp2(-dt / (rd_ * ceff)); const double y20 = y(t_vl, t0, dt, ceff).first;
double y50 = y(t_vth, t0, dt, ceff).first; fvec[DmpFunc::ipi] = ipiIceff(t0, dt, ceff_time, ceff);
// Match Vl. fvec[DmpFunc::y50] = y50 - vth_;
double y20 = y(t_vl, t0, dt, ceff).first; fvec[DmpFunc::y20] = y20 - vl_;
fvec_[DmpFunc::ipi] = ipiIceff(t0, dt, ceff_time, ceff);
fvec_[DmpFunc::y50] = y50 - vth_;
fvec_[DmpFunc::y20] = y20 - vl_;
fjac_[DmpFunc::ipi][DmpParam::t0] = 0.0;
fjac_[DmpFunc::ipi][DmpParam::dt] =
(-A_ * dt + B_ * dt * exp_p1_dt - (2 * B_ / p1_) * (1.0 - exp_p1_dt)
+ D_ * dt * exp_p2_dt - (2 * D_ / p2_) * (1.0 - exp_p2_dt)
+ rd_ * ceff
* (dt + dt * exp_dt_rd_ceff - 2 * rd_ * ceff * (1.0 - exp_dt_rd_ceff)))
/ (rd_ * dt * dt * dt);
fjac_[DmpFunc::ipi][DmpParam::ceff] =
(2 * rd_ * ceff - dt - (2 * rd_ * ceff + dt) * exp2(-dt / (rd_ * ceff)))
/ (dt * dt);
std::tie(fjac_[DmpFunc::y20][DmpParam::t0], // Pre-calculate common sub-expressions for the Jacobian derivatives.
fjac_[DmpFunc::y20][DmpParam::dt], const double b_div_p1 = B_ / p1_;
fjac_[DmpFunc::y20][DmpParam::ceff]) = dy(t_vl, t0, dt, ceff); const double d_div_p2 = D_ / p2_;
const double rd_ceff = rd_ * ceff;
std::tie(fjac_[DmpFunc::y50][DmpParam::t0], // Row 1 (Ipi derivatives).
fjac_[DmpFunc::y50][DmpParam::dt], fjac(DmpFunc::ipi, DmpParam::t0) = 0.0;
fjac_[DmpFunc::y50][DmpParam::ceff]) = dy(t_vth, t0, dt, ceff);
// Derivative w.r.t dt (broken down into physical terms).
const double term_a = -A_ * dt;
const double term_b =
B_ * dt * exp_p1_dt - 2.0 * b_div_p1 * (1.0 - exp_p1_dt);
const double term_d =
D_ * dt * exp_p2_dt - 2.0 * d_div_p2 * (1.0 - exp_p2_dt);
const double term_rd = rd_ceff
* (dt + dt * exp_dt_rd_ceff - 2.0 * rd_ceff * (1.0 - exp_dt_rd_ceff));
fjac(DmpFunc::ipi, DmpParam::dt) =
(term_a + term_b + term_d + term_rd) / (rd_ * dt * dt * dt);
// Derivative w.r.t ceff (reusing exp_dt_rd_ceff).
const double two_rd_ceff = 2.0 * rd_ceff;
fjac(DmpFunc::ipi, DmpParam::ceff) =
(two_rd_ceff - dt - (two_rd_ceff + dt) * exp_dt_rd_ceff) / (dt * dt);
// Rows 2 & 3 (y20 and y50 derivatives).
std::tie(fjac(DmpFunc::y20, DmpParam::t0),
fjac(DmpFunc::y20, DmpParam::dt),
fjac(DmpFunc::y20, DmpParam::ceff)) = dy(t_vl, t0, dt, ceff);
std::tie(fjac(DmpFunc::y50, DmpParam::t0),
fjac(DmpFunc::y50, DmpParam::dt),
fjac(DmpFunc::y50, DmpParam::ceff)) = dy(t_vth, t0, dt, ceff);
if (debug_->check("dmp_ceff", 4)) { if (debug_->check("dmp_ceff", 4)) {
showX(); showX(x);
showFvec(); showFvec(fvec);
showJacobian(); showJacobian(fjac);
report_->report("................."); report_->report(".................");
} }
} }
@ -741,35 +771,37 @@ DmpOnePole::DmpOnePole(StaState *sta) :
} }
void void
DmpOnePole::evalDmpEqns() DmpOnePole::evalDmpEqns(Eigen::Vector3d &x,
Eigen::Vector3d &fvec,
Eigen::Matrix3d &fjac)
{ {
double t0 = x_[DmpParam::t0]; double t0 = x[DmpParam::t0];
double dt = x_[DmpParam::dt]; double dt = x[DmpParam::dt];
auto [t_vth, t_vl, ignore1] = gateDelays(ceff_); auto [t_vth, t_vl, ignore1] = gateDelays(ceff_);
double ignore2; double ignore2;
if (dt <= 0.0) if (dt <= 0.0)
dt = x_[DmpParam::dt] = (t_vl - t_vth) / 100; dt = x[DmpParam::dt] = (t_vl - t_vth) / 100;
fvec_[DmpFunc::y50] = y(t_vth, t0, dt, ceff_).first - vth_; fvec[DmpFunc::y50] = y(t_vth, t0, dt, ceff_).first - vth_;
fvec_[DmpFunc::y20] = y(t_vl, t0, dt, ceff_).first - vl_; fvec[DmpFunc::y20] = y(t_vl, t0, dt, ceff_).first - vl_;
if (debug_->check("dmp_ceff", 4)) { if (debug_->check("dmp_ceff", 4)) {
showX(); showX(x);
showFvec(); showFvec(fvec);
} }
std::tie(fjac_[DmpFunc::y20][DmpParam::t0], std::tie(fjac(DmpFunc::y20, DmpParam::t0),
fjac_[DmpFunc::y20][DmpParam::dt], fjac(DmpFunc::y20, DmpParam::dt),
ignore2) = dy(t_vl, t0, dt, ceff_); ignore2) = dy(t_vl, t0, dt, ceff_);
std::tie(fjac_[DmpFunc::y50][DmpParam::t0], std::tie(fjac(DmpFunc::y50, DmpParam::t0),
fjac_[DmpFunc::y50][DmpParam::dt], fjac(DmpFunc::y50, DmpParam::dt),
ignore2) = dy(t_vth, t0, dt, ceff_); ignore2) = dy(t_vth, t0, dt, ceff_);
if (debug_->check("dmp_ceff", 4)) { if (debug_->check("dmp_ceff", 4)) {
showJacobian(); showJacobian(fjac);
report_->report("................."); report_->report(".................");
} }
} }
@ -870,136 +902,86 @@ DmpZeroC2::voCrossingUpperBound()
// driver_param_tol_ is the scale that all changes in x must be under (1.0 = 100%). // driver_param_tol_ is the scale that all changes in x must be under (1.0 = 100%).
// evalDmpEqns() fills fvec_ and fjac_. // evalDmpEqns() fills fvec_ and fjac_.
void void
DmpAlg::newtonRaphson() DmpAlg::newtonRaphson(Eigen::Vector3d &x)
{ {
for (int k = 0; k < newton_raphson_max_iter_; k++) { Eigen::Vector3d fvec = Eigen::Vector3d::Zero();
evalDmpEqns(); Eigen::Matrix3d fjac = Eigen::Matrix3d::Zero();
for (int i = 0; i < nr_order_; i++) Eigen::Vector3d p = Eigen::Vector3d::Zero();
// Right-hand side of linear equations.
p_[i] = -fvec_[i]; for (int k = 0; k < newton_raphson_max_iter_; k++) {
luDecomp(); evalDmpEqns(x, fvec, fjac);
luSolve();
p = solveNewtonStep(fjac, fvec);
// Note: 'auto' on Eigen expressions captures the expression template
// and doesn't form a temporary vector/matrix, avoiding extra
// allocations.
auto p_abs = p.head(nr_order_).array().abs();
auto x_tol = x.head(nr_order_).array().abs() * driver_param_tol_;
bool all_under_x_tol = (p_abs <= x_tol).all();
x.head(nr_order_) += p.head(nr_order_);
bool all_under_x_tol = true;
for (int i = 0; i < nr_order_; i++) {
if (std::abs(p_[i]) > std::abs(x_[i]) * driver_param_tol_)
all_under_x_tol = false;
x_[i] += p_[i];
}
if (all_under_x_tol) { if (all_under_x_tol) {
evalDmpEqns();
return; return;
} }
} }
throw DmpError("Newton-Raphson max iterations exceeded"); throw DmpError("Newton-Raphson max iterations exceeded");
} }
// luDecomp, luSolve based on MatClass from C. R. Birchenhall, // Solves the linear system J * p = -f (Jacobian * step = -residuals) for the Newton step.
// University of Manchester
// ftp://ftp.mcc.ac.uk/pub/matclass/libmat.tar.Z
// Crout's Method of LU decomposition of square matrix, with implicit
// partial pivoting. fjac_ is overwritten. U is explicit in the upper
// triangle and L is in multiplier form in the subdiagionals i.e. subdiag
// a[i,j] is the multiplier used to eliminate the [i,j] term.
// //
// Replaces fjac_[0..nr_order_-1][*] by the LU decomposition. // This implementation uses a "Determinant Guarded" solver:
// index_[0..nr_order_-1] is an output vector of the row permutations. // 1. Manually computes/checks the determinant of the Jacobian (safety guard).
void // 2. If the determinant is dangerously close to zero (< 1e-12), throws a DmpError.
DmpAlg::luDecomp() // 3. Otherwise, uses Eigen's highly optimized analytical inverse (fast path).
//
// Performance Note:
// Analytical solvers are extremely fast for 2x2 and 3x3 matrices because they
// contain no loops or branching, allowing the compiler to unroll them and use
// SIMD instructions. This yields a ~23% speedup over LU decomposition in optimized builds.
//
// Numerical Stability Note:
// If this analytical approach ever causes numerical issues (e.g., in extremely
// ill-conditioned systems where the determinant is > 1e-12 but still causes loss
// of precision), it can be TRIVIALLY swapped back to a robust LU decomposition
// with partial pivoting by replacing the body of this function with:
//
// Eigen::Vector3d p = Eigen::Vector3d::Zero();
// if (nr_order_ == 2) {
// auto lu = fjac.topLeftCorner<2, 2>().partialPivLu();
// if (std::abs(lu.matrixLU().diagonal().prod()) < 1e-12) {
// throw DmpError("Jacobian is singular (order 2)");
// }
// p.head<2>() = lu.solve(-fvec.head<2>());
// return p;
// }
// auto lu = fjac.partialPivLu();
// if (std::abs(lu.matrixLU().diagonal().prod()) < 1e-12) {
// throw DmpError("Jacobian is singular (order 3)");
// }
// p = lu.solve(-fvec);
// return p;
//
Eigen::Vector3d
DmpAlg::solveNewtonStep(const Eigen::Matrix3d &fjac,
const Eigen::Vector3d &fvec)
{ {
const int size = nr_order_; Eigen::Vector3d p = Eigen::Vector3d::Zero();
if (nr_order_ == 2) {
double det = fjac.topLeftCorner<2, 2>().determinant();
if (std::abs(det) < 1e-12) {
throw DmpError("Jacobian is singular (order 2)");
}
p.head<2>() = fjac.topLeftCorner<2, 2>().inverse() * -fvec.head<2>();
return p;
}
// Find implicit scaling factors. double det = fjac.determinant();
for (int i = 0; i < size; i++) { if (std::abs(det) < 1e-12) {
double big = 0.0; throw DmpError("Jacobian is singular (order 3)");
for (int j = 0; j < size; j++) {
double temp = std::abs(fjac_[i][j]);
big = std::max(temp, big);
}
if (big == 0.0)
throw DmpError("LU decomposition: no non-zero row element");
scale_[i] = 1.0 / big;
}
int size_1 = size - 1;
for (int j = 0; j < size; j++) {
// Run down jth column from top to diag, to form the elements of U.
for (int i = 0; i < j; i++) {
double sum = fjac_[i][j];
for (int k = 0; k < i; k++)
sum -= fjac_[i][k] * fjac_[k][j];
fjac_[i][j] = sum;
}
// Run down jth subdiag to form the residuals after the elimination
// of the first j-1 subdiags. These residuals diviyded by the
// appropriate diagonal term will become the multipliers in the
// elimination of the jth. subdiag. Find index of largest scaled
// term in imax.
double big = 0.0;
int imax = 0;
for (int i = j; i < size; i++) {
double sum = fjac_[i][j];
for (int k = 0; k < j; k++)
sum -= fjac_[i][k] * fjac_[k][j];
fjac_[i][j] = sum;
double dum = scale_[i] * std::abs(sum);
if (dum >= big) {
big = dum;
imax = i;
}
}
// Permute current row with imax.
if (j != imax) {
// Yes, do so...
for (int k = 0; k < size; k++) {
double dum = fjac_[imax][k];
fjac_[imax][k] = fjac_[j][k];
fjac_[j][k] = dum;
}
scale_[imax] = scale_[j];
}
index_[j] = imax;
// If diag term is not zero divide subdiag to form multipliers.
if (fjac_[j][j] == 0.0)
fjac_[j][j] = tiny_double_;
if (j != size_1) {
double pivot = 1.0 / fjac_[j][j];
for (int i = j + 1; i < size; i++)
fjac_[i][j] *= pivot;
}
}
}
// Solves fjac_ * x = p_ for x, assuming fjac_ is LU form from luDecomp.
// Solution overwrites p_.
void
DmpAlg::luSolve()
{
const int size = nr_order_;
// Transform p_ allowing for leading zeros.
int non_zero = -1;
for (int i = 0; i < size; i++) {
int iperm = index_[i];
double sum = p_[iperm];
p_[iperm] = p_[i];
if (non_zero != -1) {
for (int j = non_zero; j <= i - 1; j++)
sum -= fjac_[i][j] * p_[j];
}
else {
if (sum != 0.0)
non_zero = i;
}
p_[i] = sum;
}
// Backsubstitution.
for (int i = size - 1; i >= 0; i--) {
double sum = p_[i];
for (int j = i + 1; j < size; j++)
sum -= fjac_[i][j] * p_[j];
p_[i] = sum / fjac_[i][i];
} }
p = fjac.inverse() * -fvec;
return p;
} }
//////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////

View File

@ -27,6 +27,9 @@
#include <optional> #include <optional>
#include <utility> #include <utility>
#include <Eigen/Core>
#include <Eigen/Dense>
#include "LibertyClass.hh" #include "LibertyClass.hh"
#include "LumpedCapDelayCalc.hh" #include "LumpedCapDelayCalc.hh"
@ -59,18 +62,21 @@ public:
double elmore); double elmore);
double ceff() { return ceff_; } double ceff() { return ceff_; }
// Given x_ as a vector of input parameters, fill fvec_ with the virtual void
// equations evaluated at x_ and fjac_ with the jabobian evaluated at x_. evalDmpEqns(Eigen::Vector3d &x,
virtual void evalDmpEqns() = 0; Eigen::Vector3d &fvec,
Eigen::Matrix3d &fjac) = 0;
// Output response to vs(t) ramp driving pi model load (vo, dvo_dt). // Output response to vs(t) ramp driving pi model load (vo, dvo_dt).
std::pair<double, double> Vo(double t); std::pair<double, double> Vo(double t);
// Load response to driver waveform (vl, dvl/dt). // Load response to driver waveform (vl, dvl/dt).
std::pair<double, double> Vl(double t); std::pair<double, double> Vl(double t);
protected: protected:
void luDecomp(); void newtonRaphson(Eigen::Vector3d &x);
void luSolve(); // Solves J * p = -f using a fast analytical solver with singularity checks.
void newtonRaphson(); // Can be easily swapped to LU with partial pivoting if needed.
Eigen::Vector3d solveNewtonStep(const Eigen::Matrix3d &fjac,
const Eigen::Vector3d &fvec);
// Find driver parameters t0, delta_t, Ceff. // Find driver parameters t0, delta_t, Ceff.
void findDriverParams(double ceff); void findDriverParams(double ceff);
std::pair<double, double> gateCapDelaySlew(double ceff); std::pair<double, double> gateCapDelaySlew(double ceff);
@ -84,9 +90,9 @@ protected:
double cl); double cl);
double y0dcl(double t, double y0dcl(double t,
double cl); double cl);
void showX(); void showX(const Eigen::Vector3d &x);
void showFvec(); void showFvec(const Eigen::Vector3d &fvec);
void showJacobian(); void showJacobian(const Eigen::Matrix3d &fjac);
std::pair<double, double> findDriverDelaySlew(); std::pair<double, double> findDriverDelaySlew();
double findVoCrossing(double vth, double findVoCrossing(double vth,
double t_lower, double t_lower,
@ -148,12 +154,7 @@ protected:
static constexpr int max_nr_order_ = 3; static constexpr int max_nr_order_ = 3;
std::array<double, max_nr_order_> x_;
std::array<double, max_nr_order_> fvec_;
std::array<std::array<double, max_nr_order_>, max_nr_order_> fjac_;
std::array<double, max_nr_order_> scale_;
std::array<double, max_nr_order_> p_;
std::array<int, max_nr_order_> index_;
// Driver slew used to check load delay. // Driver slew used to check load delay.
double drvr_slew_; double drvr_slew_;
@ -194,7 +195,10 @@ public:
std::pair<double, double> gateDelaySlew() override; std::pair<double, double> gateDelaySlew() override;
std::pair<double, double> loadDelaySlew(const Pin *, std::pair<double, double> loadDelaySlew(const Pin *,
double elmore) override; double elmore) override;
void evalDmpEqns() override; void
evalDmpEqns(Eigen::Vector3d &x,
Eigen::Vector3d &fvec,
Eigen::Matrix3d &fjac) override;
protected: protected:
double voCrossingUpperBound() override; double voCrossingUpperBound() override;
@ -219,7 +223,10 @@ public:
double rpi, double rpi,
double c1) override; double c1) override;
std::pair<double, double> gateDelaySlew() override; std::pair<double, double> gateDelaySlew() override;
void evalDmpEqns() override; void
evalDmpEqns(Eigen::Vector3d &x,
Eigen::Vector3d &fvec,
Eigen::Matrix3d &fjac) override;
protected: protected:
double voCrossingUpperBound() override; double voCrossingUpperBound() override;
@ -255,7 +262,10 @@ class DmpOnePole : public DmpAlg
{ {
public: public:
DmpOnePole(StaState *sta); DmpOnePole(StaState *sta);
void evalDmpEqns() override; void
evalDmpEqns(Eigen::Vector3d &x,
Eigen::Vector3d &fvec,
Eigen::Matrix3d &fjac) override;
protected: protected:
double voCrossingUpperBound() override; double voCrossingUpperBound() override;

View File

@ -248,7 +248,7 @@ GraphDelayCalc::delayInvalid(Vertex *vertex)
{ {
debugPrint(debug_, "delay_calc", 2, "delay invalid {}", debugPrint(debug_, "delay_calc", 2, "delay invalid {}",
vertex->to_string(this)); vertex->to_string(this));
if (graph_ && incremental_) { if (incremental_) {
invalid_delays_.insert(vertex); invalid_delays_.insert(vertex);
// Invalidate driver that triggers dcalc for multi-driver nets. // Invalidate driver that triggers dcalc for multi-driver nets.
MultiDrvrNet *multi_drvr = multiDrvrNet(vertex); MultiDrvrNet *multi_drvr = multiDrvrNet(vertex);
@ -338,47 +338,45 @@ FindVertexDelays::visit(Vertex *vertex)
void void
GraphDelayCalc::findDelays(Level level) GraphDelayCalc::findDelays(Level level)
{ {
if (arc_delay_calc_) { Stats stats(debug_, report_);
Stats stats(debug_, report_); int dcalc_count = 0;
int dcalc_count = 0; debugPrint(debug_, "delay_calc", 1, "find delays to level {}", level);
debugPrint(debug_, "delay_calc", 1, "find delays to level {}", level); if (!delays_seeded_) {
if (!delays_seeded_) { iter_->clear();
iter_->clear(); seedRootSlews();
seedRootSlews(); delays_seeded_ = true;
delays_seeded_ = true;
}
else
iter_->ensureSize();
if (incremental_)
seedInvalidDelays();
if (!iter_->empty()) {
FindVertexDelays visitor(this);
dcalc_count += iter_->visitParallel(level, &visitor);
}
// Timing checks require slews at both ends of the arc,
// so find their delays after all slews are known.
for (Edge *check_edge : invalid_check_edges_)
findCheckEdgeDelays(check_edge, arc_delay_calc_);
invalid_check_edges_.clear();
for (Edge *latch_edge : invalid_latch_edges_)
findLatchEdgeDelays(latch_edge);
invalid_latch_edges_.clear();
delays_exist_ = true;
incremental_ = true;
debugPrint(debug_, "delay_calc", 1, "found {} delays", dcalc_count);
stats.report("Delay calc");
} }
else
iter_->ensureSize();
if (incremental_)
seedInvalidDelays();
if (!iter_->empty()) {
FindVertexDelays visitor(this);
dcalc_count += iter_->visitParallel(level, &visitor);
}
// Timing checks require slews at both ends of the arc,
// so find their delays after all slews are known.
for (Edge *check_edge : invalid_check_edges_)
findCheckEdgeDelays(check_edge, arc_delay_calc_);
invalid_check_edges_.clear();
for (Edge *latch_edge : invalid_latch_edges_)
findLatchEdgeDelays(latch_edge);
invalid_latch_edges_.clear();
delays_exist_ = true;
incremental_ = true;
debugPrint(debug_, "delay_calc", 1, "found {} delays", dcalc_count);
stats.report("Delay calc");
} }
void void
GraphDelayCalc::seedInvalidDelays() GraphDelayCalc::seedInvalidDelays()
{ {
for (Vertex *vertex : invalid_delays_) for (Vertex *vertex : invalid_delays_)
iter_->enqueue(vertex); iter_->enqueue(vertex);
invalid_delays_.clear(); invalid_delays_.clear();
} }

View File

@ -24,6 +24,11 @@
This file summarizes STA API changes for each release. This file summarizes STA API changes for each release.
2026/06/22
----------
Liberty::hasSequentials has been renamed isSequential.
Release 3.1.0 2026/03/25 Release 3.1.0 2026/03/25
------------------------ ------------------------

View File

@ -55,7 +55,7 @@ link_design top
create_clock -period 50 clk create_clock -period 50 clk
set_input_delay -clock clk 1 {in1 in2} set_input_delay -clock clk 1 {in1 in2}
set sta_pocv_mode skew_normal set sta_pocv_mode skew_normal
report_checks -fields {slew variation input_pin variation} -digits 3 report_checks -fields {slew variation input_pin} -digits 3
Startpoint: r2 (rising edge-triggered flip-flop clocked by clk) Startpoint: r2 (rising edge-triggered flip-flop clocked by clk)
Endpoint: r3 (rising edge-triggered flip-flop clocked by clk) Endpoint: r3 (rising edge-triggered flip-flop clocked by clk)

File diff suppressed because it is too large Load Diff

View File

@ -32,6 +32,7 @@
#include "Mutex.hh" #include "Mutex.hh"
#include "Network.hh" #include "Network.hh"
#include "PortDirection.hh" #include "PortDirection.hh"
#include "SearchPred.hh"
#include "Stats.hh" #include "Stats.hh"
#include "TimingArc.hh" #include "TimingArc.hh"
#include "TimingRole.hh" #include "TimingRole.hh"
@ -49,9 +50,9 @@ namespace sta {
Graph::Graph(StaState *sta, Graph::Graph(StaState *sta,
DcalcAPIndex ap_count) : DcalcAPIndex ap_count) :
StaState(sta), StaState(sta),
ap_count_(ap_count),
period_check_annotations_(network_), period_check_annotations_(network_),
reg_clk_vertices_(makeVertexSet(this)) reg_clk_vertices_(makeVertexSet(this)),
ap_count_(ap_count)
{ {
// For the benifit of reg_clk_vertices_ that references graph_. // For the benifit of reg_clk_vertices_ that references graph_.
graph_ = this; graph_ = this;
@ -487,7 +488,7 @@ Graph::deleteVertex(Vertex *vertex)
EdgeId edge_id, next_id; EdgeId edge_id, next_id;
for (edge_id = vertex->in_edges_; edge_id; edge_id = next_id) { for (edge_id = vertex->in_edges_; edge_id; edge_id = next_id) {
Edge *edge = Graph::edge(edge_id); Edge *edge = Graph::edge(edge_id);
next_id = edge->vertex_in_link_; next_id = edge->vertex_in_next_;
deleteOutEdge(edge->from(this), edge); deleteOutEdge(edge->from(this), edge);
edge->clear(); edge->clear();
edges_->destroy(edge); edges_->destroy(edge);
@ -508,7 +509,7 @@ bool
Graph::hasFaninOne(Vertex *vertex) const Graph::hasFaninOne(Vertex *vertex) const
{ {
return vertex->in_edges_ return vertex->in_edges_
&& edge(vertex->in_edges_)->vertex_in_link_ == 0; && edge(vertex->in_edges_)->vertex_in_next_ == 0;
} }
void void
@ -519,12 +520,12 @@ Graph::deleteInEdge(Vertex *vertex,
EdgeId prev = 0; EdgeId prev = 0;
for (EdgeId i = vertex->in_edges_; for (EdgeId i = vertex->in_edges_;
i && i != edge_id; i && i != edge_id;
i = Graph::edge(i)->vertex_in_link_) i = Graph::edge(i)->vertex_in_next_)
prev = i; prev = i;
if (prev) if (prev)
Graph::edge(prev)->vertex_in_link_ = edge->vertex_in_link_; Graph::edge(prev)->vertex_in_next_ = edge->vertex_in_next_;
else else
vertex->in_edges_ = edge->vertex_in_link_; vertex->in_edges_ = edge->vertex_in_next_;
} }
void void
@ -574,6 +575,76 @@ Graph::gateEdgeArc(const Pin *in_pin,
//////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////
void
Graph::visitFanouts(Vertex *vertex,
SearchPred *pred,
const VertexFn &fn)
{
if (pred->searchFrom(vertex)) {
for (Edge *edge = this->edge(vertex->out_edges_);
edge;
edge = this->edge(edge->vertex_out_next_)) {
Vertex *to_vertex = this->vertex(edge->to_);
if (pred->searchThru(edge)
&& pred->searchTo(to_vertex))
fn(to_vertex);
}
}
}
void
Graph::visitFanoutEdges(Vertex *vertex,
SearchPred *pred,
const EdgeFn &fn)
{
if (pred->searchFrom(vertex)) {
for (Edge *edge = this->edge(vertex->out_edges_);
edge;
edge = this->edge(edge->vertex_out_next_)) {
Vertex *to_vertex = this->vertex(edge->to_);
if (pred->searchThru(edge)
&& pred->searchTo(to_vertex))
fn(edge, to_vertex);
}
}
}
void
Graph::visitFanins(Vertex *vertex,
SearchPred *pred,
const VertexFn &fn)
{
if (pred->searchFrom(vertex)) {
for (Edge *edge = this->edge(vertex->in_edges_);
edge;
edge = this->edge(edge->vertex_in_next_)) {
Vertex *from_vertex = this->vertex(edge->from_);
if (pred->searchThru(edge)
&& pred->searchFrom(from_vertex))
fn(from_vertex);
}
}
}
void
Graph::visitFaninEdges(Vertex *vertex,
SearchPred *pred,
const EdgeFn &fn)
{
if (pred->searchFrom(vertex)) {
for (Edge *edge = this->edge(vertex->in_edges_);
edge;
edge = this->edge(edge->vertex_in_next_)) {
Vertex *from_vertex = this->vertex(edge->from_);
if (pred->searchThru(edge)
&& pred->searchFrom(from_vertex))
fn(edge, from_vertex);
}
}
}
////////////////////////////////////////////////////////////////
Slew Slew
Graph::slew(const Vertex *vertex, Graph::slew(const Vertex *vertex,
const RiseFall *rf, const RiseFall *rf,
@ -650,7 +721,7 @@ Graph::makeEdge(Vertex *from,
from->out_edges_ = edge_id; from->out_edges_ = edge_id;
// Add in edge to to vertex. // Add in edge to to vertex.
edge->vertex_in_link_ = to->in_edges_; edge->vertex_in_next_ = to->in_edges_;
to->in_edges_ = edge_id; to->in_edges_ = edge_id;
initArcDelays(edge); initArcDelays(edge);
@ -969,22 +1040,22 @@ Vertex::init(Pin *pin,
bool is_reg_clk) bool is_reg_clk)
{ {
pin_ = pin; pin_ = pin;
is_reg_clk_ = is_reg_clk;
is_bidirect_drvr_ = is_bidirect_drvr;
in_edges_ = edge_id_null; in_edges_ = edge_id_null;
out_edges_ = edge_id_null; out_edges_ = edge_id_null;
slews_ = nullptr; slews_ = nullptr;
paths_ = nullptr; paths_ = nullptr;
tag_group_index_ = tag_group_index_max; tag_group_index_ = tag_group_index_max;
slew_annotated_ = false; bfs_in_queue_ = 0;
is_bidirect_drvr_ = is_bidirect_drvr;
is_reg_clk_ = is_reg_clk;
has_checks_ = false; has_checks_ = false;
is_check_clk_ = false; is_check_clk_ = false;
has_downstream_clk_pin_ = false; has_downstream_clk_pin_ = false;
level_ = 0;
visited1_ = false; visited1_ = false;
visited2_ = false; visited2_ = false;
has_sim_value_ = false; has_sim_value_ = false;
bfs_in_queue_ = 0; level_ = 0;
slew_annotated_ = false;
} }
Vertex::~Vertex() Vertex::~Vertex()
@ -1210,20 +1281,19 @@ Edge::init(VertexId from,
VertexId to, VertexId to,
TimingArcSet *arc_set) TimingArcSet *arc_set)
{ {
from_ = from;
to_ = to;
arc_set_ = arc_set; arc_set_ = arc_set;
vertex_in_link_ = edge_id_null;
vertex_out_next_ = edge_id_null;
vertex_out_prev_ = edge_id_null;
is_bidirect_inst_path_ = false;
is_bidirect_net_path_ = false;
is_bidirect_port_path_ = false;
arc_delays_ = nullptr; arc_delays_ = nullptr;
arc_delay_annotated_is_bits_ = true; arc_delay_annotated_is_bits_ = true;
arc_delay_annotated_.bits_ = 0; arc_delay_annotated_.bits_ = 0;
from_ = from;
to_ = to;
vertex_in_next_ = edge_id_null;
vertex_out_next_ = edge_id_null;
vertex_out_prev_ = edge_id_null;
delay_annotation_is_incremental_ = false; delay_annotation_is_incremental_ = false;
is_bidirect_inst_path_ = false;
is_bidirect_net_path_ = false;
is_bidirect_port_path_ = false;
is_disabled_loop_ = false; is_disabled_loop_ = false;
has_sim_sense_ = false; has_sim_sense_ = false;
has_disabled_cond_ = false; has_disabled_cond_ = false;
@ -1464,6 +1534,8 @@ VertexIterator::findNext()
findNextPin(); findNextPin();
} }
////////////////////////////////////////////////////////////////
VertexInEdgeIterator::VertexInEdgeIterator(Vertex *vertex, VertexInEdgeIterator::VertexInEdgeIterator(Vertex *vertex,
const Graph *graph) : const Graph *graph) :
next_(graph->edge(vertex->in_edges_)), next_(graph->edge(vertex->in_edges_)),
@ -1483,7 +1555,7 @@ VertexInEdgeIterator::next()
{ {
Edge *next = next_; Edge *next = next_;
if (next_) if (next_)
next_ = graph_->edge(next_->vertex_in_link_); next_ = graph_->edge(next_->vertex_in_next_);
return next; return next;
} }

View File

@ -81,12 +81,12 @@ protected:
void removeCell(ConcreteCell *cell); void removeCell(ConcreteCell *cell);
std::string name_; std::string name_;
ObjectId id_;
std::string filename_; std::string filename_;
ConcreteCellMap cell_map_;
ObjectId id_;
bool is_liberty_; bool is_liberty_;
char bus_brkt_left_{'['}; char bus_brkt_left_{'['};
char bus_brkt_right_{']'}; char bus_brkt_right_{']'};
ConcreteCellMap cell_map_;
private: private:
friend class ConcreteCell; friend class ConcreteCell;

View File

@ -366,10 +366,10 @@ protected:
ConcretePort *port_; ConcretePort *port_;
ConcreteNet *net_; ConcreteNet *net_;
ConcreteTerm *term_{nullptr}; ConcreteTerm *term_{nullptr};
ObjectId id_;
// Doubly linked list of net pins. // Doubly linked list of net pins.
ConcretePin *net_next_{nullptr}; ConcretePin *net_next_{nullptr};
ConcretePin *net_prev_{nullptr}; ConcretePin *net_prev_{nullptr};
ObjectId id_;
VertexId vertex_id_{vertex_id_null}; VertexId vertex_id_{vertex_id_null};
private: private:

View File

@ -87,6 +87,19 @@ public:
bool hasFaninOne(Vertex *vertex) const; bool hasFaninOne(Vertex *vertex) const;
VertexId vertexCount() { return vertices_->size(); } VertexId vertexCount() { return vertices_->size(); }
void visitFanouts(Vertex *vertex,
SearchPred *pred,
const VertexFn &fn);
void visitFanins(Vertex *vertex,
SearchPred *pred,
const VertexFn &fn);
void visitFanoutEdges(Vertex *vertex,
SearchPred *pred,
const EdgeFn &fn);
void visitFaninEdges(Vertex *vertex,
SearchPred *pred,
const EdgeFn &fn);
// Reported slew are the same as those in the liberty tables. // Reported slew are the same as those in the liberty tables.
// reported_slews = measured_slews / slew_derate_from_library // reported_slews = measured_slews / slew_derate_from_library
// Measured slews are between slew_lower_threshold and slew_upper_threshold. // Measured slews are between slew_lower_threshold and slew_upper_threshold.
@ -179,7 +192,7 @@ public:
VertexSet &regClkVertices() { return reg_clk_vertices_; } VertexSet &regClkVertices() { return reg_clk_vertices_; }
static constexpr int vertex_level_bits = 24; static constexpr int vertex_level_bits = 24;
static constexpr int vertex_level_max = (1<<vertex_level_bits)-1; static constexpr int vertex_level_max = (1<<vertex_level_bits) - 1;
protected: protected:
void makeVerticesAndEdges(); void makeVerticesAndEdges();
@ -216,11 +229,11 @@ protected:
// driver/source (top level input, instance pin output) vertex // driver/source (top level input, instance pin output) vertex
// in pin_bidirect_drvr_vertex_map // in pin_bidirect_drvr_vertex_map
PinVertexMap pin_bidirect_drvr_vertex_map_; PinVertexMap pin_bidirect_drvr_vertex_map_;
DcalcAPIndex ap_count_;
// Sdf period check annotations. // Sdf period check annotations.
PeriodCheckAnnotations period_check_annotations_; PeriodCheckAnnotations period_check_annotations_;
// Register/latch clock vertices to search from. // Register/latch clock vertices to search from.
VertexSet reg_clk_vertices_; VertexSet reg_clk_vertices_;
DcalcAPIndex ap_count_;
friend class Vertex; friend class Vertex;
friend class VertexIterator; friend class VertexIterator;
@ -311,21 +324,20 @@ protected:
// Each bit corresponds to a different BFS queue. // Each bit corresponds to a different BFS queue.
std::atomic<uint8_t> bfs_in_queue_; // 8 std::atomic<uint8_t> bfs_in_queue_; // 8
int level_:Graph::vertex_level_bits; // 24
unsigned int slew_annotated_:slew_annotated_bits; // 4
// Bidirect pins have two vertices. // Bidirect pins have two vertices.
// This flag distinguishes the driver and load vertices. // This flag distinguishes the driver and load vertices.
bool is_bidirect_drvr_:1; unsigned int is_bidirect_drvr_:1;
unsigned int is_reg_clk_:1;
bool is_reg_clk_:1;
// Constrained by timing check edge. // Constrained by timing check edge.
bool has_checks_:1; unsigned int has_checks_:1;
// Is the clock for a timing check. // Is the clock for a timing check.
bool is_check_clk_:1; unsigned int is_check_clk_:1;
bool has_downstream_clk_pin_:1; unsigned int has_downstream_clk_pin_:1;
bool visited1_:1; unsigned int visited1_:1;
bool visited2_:1; unsigned int visited2_:1;
bool has_sim_value_:1; unsigned int has_sim_value_:1;
int level_:Graph::vertex_level_bits; // 24
unsigned int slew_annotated_:slew_annotated_bits; // 4
private: private:
friend class Graph; friend class Graph;
@ -392,16 +404,16 @@ protected:
static uintptr_t arcDelayAnnotateBit(size_t index); static uintptr_t arcDelayAnnotateBit(size_t index);
TimingArcSet *arc_set_; TimingArcSet *arc_set_;
VertexId from_;
VertexId to_;
EdgeId vertex_in_link_; // Vertex in edges list.
EdgeId vertex_out_next_; // Vertex out edges doubly linked list.
EdgeId vertex_out_prev_;
float *arc_delays_; float *arc_delays_;
union { union {
uintptr_t bits_; uintptr_t bits_;
std::vector<bool> *seq_; std::vector<bool> *seq_;
} arc_delay_annotated_; } arc_delay_annotated_;
VertexId from_;
VertexId to_;
EdgeId vertex_in_next_; // Vertex in edges list.
EdgeId vertex_out_next_; // Vertex out edges doubly linked list.
EdgeId vertex_out_prev_;
bool arc_delay_annotated_is_bits_:1; bool arc_delay_annotated_is_bits_:1;
bool delay_annotation_is_incremental_:1; bool delay_annotation_is_incremental_:1;
bool is_bidirect_inst_path_:1; bool is_bidirect_inst_path_:1;

View File

@ -65,6 +65,8 @@ using Level = int;
using DcalcAPIndex = int; using DcalcAPIndex = int;
using TagGroupIndex = int; using TagGroupIndex = int;
using SlewSeq = std::vector<Slew>; using SlewSeq = std::vector<Slew>;
using VertexFn = std::function<void(Vertex*)>;
using EdgeFn = std::function<void(Edge *, Vertex*)>;
static constexpr int level_max = std::numeric_limits<Level>::max(); static constexpr int level_max = std::numeric_limits<Level>::max();

View File

@ -536,7 +536,9 @@ public:
bool leakagePowerExists() const { return leakage_power_exists_; } bool leakagePowerExists() const { return leakage_power_exists_; }
// Register, Latch or Statetable. // Register, Latch or Statetable.
bool hasSequentials() const; bool isSequential() const;
// deprecated 2026-06-22 (use isSequential)
bool hasSequentials() const __attribute__ ((deprecated));
const SequentialSeq &sequentials() const { return sequentials_; } const SequentialSeq &sequentials() const { return sequentials_; }
// Find the sequential with the output connected to an (internal) port. // Find the sequential with the output connected to an (internal) port.
Sequential *outputPortSequential(LibertyPort *port); Sequential *outputPortSequential(LibertyPort *port);
@ -861,6 +863,8 @@ public:
void setIsRegOutput(bool is_reg_out); void setIsRegOutput(bool is_reg_out);
bool isLatchData() const { return is_latch_data_; } bool isLatchData() const { return is_latch_data_; }
void setIsLatchData(bool is_latch_data); void setIsLatchData(bool is_latch_data);
bool isLatchOutput() const { return is_latch_output_; }
void setIsLatchOutput(bool is_latch_output);
// Is the clock for timing checks. // Is the clock for timing checks.
bool isCheckClk() const { return is_check_clk_; } bool isCheckClk() const { return is_check_clk_; }
void setIsCheckClk(bool is_clk); void setIsCheckClk(bool is_clk);
@ -956,6 +960,7 @@ protected:
bool is_reg_clk_:1 {false}; bool is_reg_clk_:1 {false};
bool is_reg_output_:1 {false}; bool is_reg_output_:1 {false};
bool is_latch_data_: 1 {false}; bool is_latch_data_: 1 {false};
bool is_latch_output_:1 {false};
bool is_check_clk_:1 {false}; bool is_check_clk_:1 {false};
bool is_clk_gate_clk_:1 {false}; bool is_clk_gate_clk_:1 {false};
bool is_clk_gate_enable_:1 {false}; bool is_clk_gate_enable_:1 {false};

View File

@ -319,6 +319,7 @@ public:
// Pin clocks a timing check. // Pin clocks a timing check.
[[nodiscard]] bool isCheckClk(const Pin *pin) const; [[nodiscard]] bool isCheckClk(const Pin *pin) const;
[[nodiscard]] bool isLatchData(const Pin *pin) const; [[nodiscard]] bool isLatchData(const Pin *pin) const;
[[nodiscard]] bool isLatchOutput(const Pin *pin) const;
// Iterate over all of the pins connected to a pin and the parent // Iterate over all of the pins connected to a pin and the parent
// and child nets it is hierarchically connected to (port, leaf and // and child nets it is hierarchically connected to (port, leaf and

View File

@ -42,6 +42,8 @@ class PropertyValue;
class Sta; class Sta;
class PropertyValue; class PropertyValue;
class Scene;
class Mode;
template<class TYPE> template<class TYPE>
class PropertyRegistry class PropertyRegistry
@ -86,6 +88,10 @@ public:
std::string_view property); std::string_view property);
PropertyValue getProperty(const Clock *clk, PropertyValue getProperty(const Clock *clk,
std::string_view property); std::string_view property);
PropertyValue getProperty(const Scene *scene,
std::string_view property);
PropertyValue getProperty(const Mode *mode,
std::string_view property);
PropertyValue getProperty(PathEnd *end, PropertyValue getProperty(PathEnd *end,
std::string_view property); std::string_view property);
PropertyValue getProperty(Path *path, PropertyValue getProperty(Path *path,

View File

@ -577,6 +577,7 @@ public:
const RiseFall *clk_rf, const RiseFall *clk_rf,
const MinMaxAll *min_max); const MinMaxAll *min_max);
void ensureInputDelayRefPinEdges(); void ensureInputDelayRefPinEdges();
void inputDelayRefPinEdgesInvalid();
void setOutputDelay(const Pin *pin, void setOutputDelay(const Pin *pin,
const RiseFallBoth *rf, const RiseFallBoth *rf,
const Clock *clk, const Clock *clk,

View File

@ -284,8 +284,8 @@ public:
Vertex *vertex, Vertex *vertex,
const Mode *mode, const Mode *mode,
TagGroupBldr *tag_bldr); TagGroupBldr *tag_bldr);
void enqueueLatchDataOutputs(Vertex *vertex); void postponeLatchDataOutputs(Vertex *vertex);
void enqueueLatchOutput(Vertex *vertex); void postponeArrivals(Vertex *vertex);
void enqueuePendingClkFanouts(); void enqueuePendingClkFanouts();
void postponeClkFanouts(Vertex *vertex); void postponeClkFanouts(Vertex *vertex);
void seedRequired(Vertex *vertex); void seedRequired(Vertex *vertex);
@ -406,7 +406,6 @@ public:
void checkPrevPaths() const; void checkPrevPaths() const;
void deletePaths(Vertex *vertex); void deletePaths(Vertex *vertex);
void deleteTagGroup(TagGroup *group); void deleteTagGroup(TagGroup *group);
bool postponeLatchOutputs() const { return postpone_latch_outputs_; }
void saveEnumPath(Path *path); void saveEnumPath(Path *path);
bool isSrchRoot(Vertex *vertex, bool isSrchRoot(Vertex *vertex,
const Mode *mode) const; const Mode *mode) const;
@ -530,9 +529,6 @@ protected:
const MinMax *min_max) const; const MinMax *min_max) const;
void seedRequireds(); void seedRequireds();
void seedInvalidRequireds(); void seedInvalidRequireds();
[[nodiscard]] bool havePendingLatchOutputs();
void clearPendingLatchOutputs();
void enqueuePendingLatchOutputs();
void findFilteredArrivals(bool thru_latches); void findFilteredArrivals(bool thru_latches);
void findArrivalsSeed(); void findArrivalsSeed();
void seedFilterStarts(); void seedFilterStarts();
@ -594,7 +590,7 @@ protected:
// Search predicates. // Search predicates.
SearchPred *search_thru_; SearchPred *search_thru_;
SearchAdj *search_adj_; SearchPred *search_adj_;
EvalPred *eval_pred_; EvalPred *eval_pred_;
// Some arrivals exist. // Some arrivals exist.
@ -651,11 +647,11 @@ protected:
std::mutex tag_group_lock_; std::mutex tag_group_lock_;
// Latches data outputs to queue on the next search pass. // Latches data outputs to queue on the next search pass.
VertexSet pending_latch_outputs_; VertexSet postponed_arrivals_;
std::mutex pending_latch_outputs_lock_; std::mutex postponed_arrivals_lock_;
// Clock network endpoints where arrival search was suppended by findClkArrivals(). // Clock network endpoints where arrival search was suppended by findClkArrivals().
VertexSet pending_clk_endpoints_; VertexSet postponed_clk_endpoints_;
std::mutex pending_clk_endpoints_lock_; std::mutex postponed_clk_endpoints_lock_;
VertexSet endpoints_; VertexSet endpoints_;
bool endpoints_initialized_{false}; bool endpoints_initialized_{false};
@ -669,7 +665,6 @@ protected:
std::mutex filtered_arrivals_lock_; std::mutex filtered_arrivals_lock_;
bool found_downstream_clk_pins_{false}; bool found_downstream_clk_pins_{false};
bool postpone_latch_outputs_{false};
std::vector<Path*> enum_paths_; std::vector<Path*> enum_paths_;
VisitPathEnds *visit_path_ends_; VisitPathEnds *visit_path_ends_;
@ -712,7 +707,8 @@ public:
bool make_tag_cache, bool make_tag_cache,
const StaState *sta); const StaState *sta);
~PathVisitor() override; ~PathVisitor() override;
virtual void visitFaninPaths(Vertex *to_vertex); virtual void visitFaninPaths(Vertex *to_vertex,
bool with_latch_edges);
virtual void visitFanoutPaths(Vertex *from_vertex); virtual void visitFanoutPaths(Vertex *from_vertex);
// Return false to stop visiting. // Return false to stop visiting.
virtual bool visitFromToPath(const Pin *from_pin, virtual bool visitFromToPath(const Pin *from_pin,
@ -779,6 +775,8 @@ public:
SearchPred *pred); SearchPred *pred);
void copyState(const StaState *sta) override; void copyState(const StaState *sta) override;
void visit(Vertex *vertex) override; void visit(Vertex *vertex) override;
void visit(Vertex *vertex,
bool with_latch_edges);
VertexVisitor *copy() const override; VertexVisitor *copy() const override;
// Return false to stop visiting. // Return false to stop visiting.
bool visitFromToPath(const Pin *from_pin, bool visitFromToPath(const Pin *from_pin,
@ -804,12 +802,14 @@ protected:
void pruneCrprArrivals(); void pruneCrprArrivals();
void constrainedRequiredsInvalid(Vertex *vertex, void constrainedRequiredsInvalid(Vertex *vertex,
bool is_clk); bool is_clk);
bool hasPendingLoopPaths(Edge *edge) const;
bool always_to_endpoints_; bool always_to_endpoints_;
bool always_save_prev_paths_; bool always_save_prev_paths_;
bool clks_only_; bool clks_only_;
TagGroupBldr *tag_bldr_; TagGroupBldr *tag_bldr_;
TagGroupBldr *tag_bldr_no_crpr_; TagGroupBldr *tag_bldr_no_crpr_;
SearchPred *adj_pred_; SearchPred *search_adj_;
bool crpr_active_; bool crpr_active_;
bool has_fanin_one_; bool has_fanin_one_;
}; };

View File

@ -51,19 +51,19 @@ public:
// Search is allowed from from_vertex. // Search is allowed from from_vertex.
virtual bool searchFrom(const Vertex *from_vertex, virtual bool searchFrom(const Vertex *from_vertex,
const Mode *mode) const = 0; const Mode *mode) const = 0;
bool searchFrom(const Vertex *from_vertex) const; virtual bool searchFrom(const Vertex *from_vertex) const;
// Search is allowed through edge. // Search is allowed through edge.
// from/to pins are NOT checked. // from/to pins are NOT checked.
// inst can be either the from_pin or to_pin instance because it // inst can be either the from_pin or to_pin instance because it
// is only referenced when they are the same (non-wire edge). // is only referenced when they are the same (non-wire edge).
virtual bool searchThru(Edge *edge, virtual bool searchThru(Edge *edge,
const Mode *mode) const = 0; const Mode *mode) const = 0;
bool searchThru(Edge *edge) const; virtual bool searchThru(Edge *edge) const;
// Search is allowed to to_pin. // Search is allowed to to_pin.
virtual bool searchTo(const Vertex *to_vertex, virtual bool searchTo(const Vertex *to_vertex,
const Mode *mode) const = 0; const Mode *mode) const = 0;
bool searchTo(const Vertex *to_vertex) const; virtual bool searchTo(const Vertex *to_vertex) const;
void copyState(const StaState *sta); virtual void copyState(const StaState *sta);
protected: protected:
const StaState *sta_; const StaState *sta_;

View File

@ -1438,6 +1438,12 @@ LibertyCell::outputPortSequential(LibertyPort *port)
return nullptr; return nullptr;
} }
bool
LibertyCell::isSequential() const
{
return !sequentials_.empty() || statetable_ != nullptr;
}
bool bool
LibertyCell::hasSequentials() const LibertyCell::hasSequentials() const
{ {
@ -1618,7 +1624,7 @@ void
LibertyCell::makeLatchEnables(Report *report, LibertyCell::makeLatchEnables(Report *report,
Debug *debug) Debug *debug)
{ {
if (hasSequentials() if (isSequential()
|| hasInferedRegTimingArcs()) { || hasInferedRegTimingArcs()) {
for (TimingArcSet *d_to_q : timing_arc_sets_) { for (TimingArcSet *d_to_q : timing_arc_sets_) {
if (d_to_q->role() == TimingRole::latchDtoQ()) { if (d_to_q->role() == TimingRole::latchDtoQ()) {
@ -1769,6 +1775,7 @@ LibertyCell::makeLatchEnable(LibertyPort *d,
latch_d_to_q_map_[d_to_q] = idx; latch_d_to_q_map_[d_to_q] = idx;
latch_check_map_[setup_check] = idx; latch_check_map_[setup_check] = idx;
d->setIsLatchData(true); d->setIsLatchData(true);
q->setIsLatchOutput(true);
debugPrint(debug, "liberty_latch", 1, debugPrint(debug, "liberty_latch", 1,
"latch {} -> {} | {} {} -> {} | {} {} -> {} setup", "latch {} -> {} | {} {} -> {} | {} {} -> {} setup",
d->name(), d->name(),
@ -2434,6 +2441,13 @@ LibertyPort::setIsLatchData(bool is_latch_data)
setMemberFlag(is_latch_data, &LibertyPort::setIsLatchData); setMemberFlag(is_latch_data, &LibertyPort::setIsLatchData);
} }
void
LibertyPort::setIsLatchOutput(bool is_latch_output)
{
is_latch_output_ = is_latch_output;
setMemberFlag(is_latch_output, &LibertyPort::setIsLatchOutput);
}
void void
LibertyPort::setIsCheckClk(bool is_clk) LibertyPort::setIsCheckClk(bool is_clk)
{ {

View File

@ -275,7 +275,7 @@ LibertyWriter::writeBusDcls()
sta::print(stream_, " type (\"{}\") {{\n", dcl->name()); sta::print(stream_, " type (\"{}\") {{\n", dcl->name());
sta::print(stream_, " base_type : array;\n"); sta::print(stream_, " base_type : array;\n");
sta::print(stream_, " data_type : bit;\n"); sta::print(stream_, " data_type : bit;\n");
sta::print(stream_, " bit_width : {};\n", std::abs(dcl->from() - dcl->to() + 1)); sta::print(stream_, " bit_width : {};\n", std::abs(dcl->from() - dcl->to()) + 1);
sta::print(stream_, " bit_from : {};\n", dcl->from()); sta::print(stream_, " bit_from : {};\n", dcl->from());
sta::print(stream_, " bit_to : {};\n", dcl->to()); sta::print(stream_, " bit_to : {};\n", dcl->to());
sta::print(stream_, " }}\n"); sta::print(stream_, " }}\n");

View File

@ -536,6 +536,9 @@ TimingArcSet::destroy()
delete wire_timing_arc_set_; delete wire_timing_arc_set_;
wire_timing_arc_set_ = nullptr; wire_timing_arc_set_ = nullptr;
wire_timing_arc_attrs_ = nullptr; wire_timing_arc_attrs_ = nullptr;
delete port_refpin_timing_arc_set_;
port_refpin_timing_arc_set_ = nullptr;
} }
//////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////

View File

@ -3230,7 +3230,7 @@ TEST(TestCellTest, HasInferedRegTimingArcs) {
TEST(TestCellTest, HasSequentials) { TEST(TestCellTest, HasSequentials) {
LibertyLibrary lib("test_lib", "test.lib"); LibertyLibrary lib("test_lib", "test.lib");
TestCell cell(&lib, "CELL1", "test.lib"); TestCell cell(&lib, "CELL1", "test.lib");
EXPECT_FALSE(cell.hasSequentials()); EXPECT_FALSE(cell.isSequential());
} }
TEST(TestCellTest, SequentialsEmpty) { TEST(TestCellTest, SequentialsEmpty) {

View File

@ -810,7 +810,7 @@ TEST_F(StaLibertyTest, LibraryDelayModelType) {
TEST_F(StaLibertyTest, CellHasSequentials) { TEST_F(StaLibertyTest, CellHasSequentials) {
LibertyCell *dff = lib_->findLibertyCell("DFF_X1"); LibertyCell *dff = lib_->findLibertyCell("DFF_X1");
if (dff) { if (dff) {
EXPECT_TRUE(dff->hasSequentials()); EXPECT_TRUE(dff->isSequential());
auto &seqs = dff->sequentials(); auto &seqs = dff->sequentials();
EXPECT_GT(seqs.size(), 0u); EXPECT_GT(seqs.size(), 0u);
} }
@ -3266,10 +3266,10 @@ TEST_F(StaLibertyTest, CellIsInverter2) {
TEST_F(StaLibertyTest, CellHasSequentials2) { TEST_F(StaLibertyTest, CellHasSequentials2) {
LibertyCell *buf = lib_->findLibertyCell("BUF_X1"); LibertyCell *buf = lib_->findLibertyCell("BUF_X1");
ASSERT_NE(buf, nullptr); ASSERT_NE(buf, nullptr);
EXPECT_FALSE(buf->hasSequentials()); EXPECT_FALSE(buf->isSequential());
LibertyCell *dff = lib_->findLibertyCell("DFF_X1"); LibertyCell *dff = lib_->findLibertyCell("DFF_X1");
if (dff) { if (dff) {
EXPECT_TRUE(dff->hasSequentials()); EXPECT_TRUE(dff->isSequential());
} }
} }

View File

@ -2166,14 +2166,14 @@ TEST_F(StaLibertyTest, CellSetClockGateType) {
TEST_F(StaLibertyTest, CellHasSequentialsBuf) { TEST_F(StaLibertyTest, CellHasSequentialsBuf) {
LibertyCell *buf = lib_->findLibertyCell("BUF_X1"); LibertyCell *buf = lib_->findLibertyCell("BUF_X1");
ASSERT_NE(buf, nullptr); ASSERT_NE(buf, nullptr);
EXPECT_FALSE(buf->hasSequentials()); EXPECT_FALSE(buf->isSequential());
} }
// LibertyCell::hasSequentials on DFF // LibertyCell::hasSequentials on DFF
TEST_F(StaLibertyTest, CellHasSequentialsDFF) { TEST_F(StaLibertyTest, CellHasSequentialsDFF) {
LibertyCell *dff = lib_->findLibertyCell("DFF_X1"); LibertyCell *dff = lib_->findLibertyCell("DFF_X1");
ASSERT_NE(dff, nullptr); ASSERT_NE(dff, nullptr);
EXPECT_TRUE(dff->hasSequentials()); EXPECT_TRUE(dff->isSequential());
} }
// LibertyCell::sequentials // LibertyCell::sequentials

View File

@ -3688,7 +3688,7 @@ library(test_r11_latch) {
LibertyCell *cell = lib->findLibertyCell("LATCH1"); LibertyCell *cell = lib->findLibertyCell("LATCH1");
EXPECT_NE(cell, nullptr); EXPECT_NE(cell, nullptr);
if (cell) { if (cell) {
EXPECT_TRUE(cell->hasSequentials()); EXPECT_TRUE(cell->isSequential());
} }
} }
remove(tmp_path.c_str()); remove(tmp_path.c_str());
@ -4338,7 +4338,7 @@ library(test_mbff_statetable) {
// But it has a statetable, so it IS sequential. // But it has a statetable, so it IS sequential.
EXPECT_NE(mbff->statetable(), nullptr); EXPECT_NE(mbff->statetable(), nullptr);
// hasSequentials() must return true for statetable-only cells. // hasSequentials() must return true for statetable-only cells.
EXPECT_TRUE(mbff->hasSequentials()) EXPECT_TRUE(mbff->isSequential())
<< "MBFF2 uses statetable (no ff/latch) but hasSequentials() " << "MBFF2 uses statetable (no ff/latch) but hasSequentials() "
"returned false — statetable cells are misclassified as " "returned false — statetable cells are misclassified as "
"combinational"; "combinational";

View File

@ -42,8 +42,8 @@ ConcreteLibrary::ConcreteLibrary(std::string_view name,
std::string_view filename, std::string_view filename,
bool is_liberty) : bool is_liberty) :
name_(name), name_(name),
id_(ConcreteNetwork::nextObjectId()),
filename_(filename), filename_(filename),
id_(ConcreteNetwork::nextObjectId()),
is_liberty_(is_liberty) is_liberty_(is_liberty)
{ {
} }

View File

@ -636,6 +636,16 @@ Network::isLatchData(const Pin *pin) const
return false; return false;
} }
bool
Network::isLatchOutput(const Pin *pin) const
{
LibertyPort *port = libertyPort(pin);
if (port)
return port->isLatchOutput();
else
return false;
}
//////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////
std::string std::string

View File

@ -443,7 +443,7 @@ Power::power(const Scene *scene,
pad.incr(inst_power); pad.incr(inst_power);
else if (inClockNetwork(inst, clk_network)) else if (inClockNetwork(inst, clk_network))
clock.incr(inst_power); clock.incr(inst_power);
else if (cell->hasSequentials()) else if (cell->isSequential())
sequential.incr(inst_power); sequential.incr(inst_power);
else else
combinational.incr(inst_power); combinational.incr(inst_power);
@ -554,9 +554,12 @@ ActivitySrchPred::searchThru(Edge *edge,
{ {
const Sdc *sdc = mode->sdc(); const Sdc *sdc = mode->sdc();
const TimingRole *role = edge->role(); const TimingRole *role = edge->role();
return !(edge->role()->isTimingCheck() || sdc->isDisabledConstraint(edge) return !(edge->role()->isTimingCheck()
|| sdc->isDisabledCondDefault(edge) || edge->isBidirectInstPath() || sdc->isDisabledConstraint(edge)
|| edge->isDisabledLoop() || role == TimingRole::regClkToQ() || sdc->isDisabledCondDefault(edge)
|| edge->isBidirectInstPath()
|| edge->isDisabledLoop()
|| role == TimingRole::regClkToQ()
|| role->isLatchDtoQ()); || role->isLatchDtoQ());
} }
@ -692,7 +695,8 @@ PropActivityVisitor::visit(Vertex *vertex)
if (cell) { if (cell) {
LibertyCell *test_cell = cell->libertyCell()->testCell(); LibertyCell *test_cell = cell->libertyCell()->testCell();
if (network_->isLoad(pin)) { if (network_->isLoad(pin)) {
if (cell->hasSequentials() || (test_cell && test_cell->hasSequentials())) { if (cell->isSequential()
|| (test_cell && test_cell->isSequential())) {
debugPrint(debug_, "power_activity", 3, "pending seq {}", debugPrint(debug_, "power_activity", 3, "pending seq {}",
network_->pathName(inst)); network_->pathName(inst));
visited_regs_.insert(inst); visited_regs_.insert(inst);
@ -701,10 +705,8 @@ PropActivityVisitor::visit(Vertex *vertex)
if (cell->isClockGate()) { if (cell->isClockGate()) {
const Pin *enable, *clk, *gclk; const Pin *enable, *clk, *gclk;
power_->clockGatePins(inst, enable, clk, gclk); power_->clockGatePins(inst, enable, clk, gclk);
if (gclk) { if (gclk)
Vertex *gclk_vertex = graph_->pinDrvrVertex(gclk); visited_regs_.insert(inst);
bfs_->enqueue(gclk_vertex);
}
} }
} }
bfs_->enqueueAdjacentVertices(vertex, mode_); bfs_->enqueueAdjacentVertices(vertex, mode_);
@ -746,9 +748,9 @@ PropActivityVisitor::setActivityCheck(const Pin *pin,
max_change_ = duty_delta; max_change_ = duty_delta;
max_change_pin_ = pin; max_change_pin_ = pin;
} }
bool changed = density_delta > change_tolerance_ || duty_delta > change_tolerance_ bool changed = density_delta > change_tolerance_
|| activity.origin() != prev_activity.origin(); || duty_delta > change_tolerance_
; || activity.origin() != prev_activity.origin();
power_->setActivity(pin, activity); power_->setActivity(pin, activity);
return changed; return changed;
} }
@ -905,8 +907,9 @@ Power::ensureActivities(const Scene *scene)
// unless it has been set by command. // unless it has been set by command.
if (input_activity_.origin() == PwrActivityOrigin::unknown) { if (input_activity_.origin() == PwrActivityOrigin::unknown) {
float min_period = clockMinPeriod(scene_->mode()->sdc()); float min_period = clockMinPeriod(scene_->mode()->sdc());
float density = if (min_period == 0.0)
0.1 / (min_period != 0.0 ? min_period : units_->timeUnit()->scale()); min_period = units_->timeUnit()->scale();
float density = 0.1 / min_period;
input_activity_.set(density, 0.5, PwrActivityOrigin::input); input_activity_.set(density, 0.5, PwrActivityOrigin::input);
} }
ActivitySrchPred activity_srch_pred(this); ActivitySrchPred activity_srch_pred(this);
@ -918,7 +921,7 @@ Power::ensureActivities(const Scene *scene)
// Propagate activiities through registers. // Propagate activiities through registers.
InstanceSet regs = std::move(visitor.visitedRegs()); InstanceSet regs = std::move(visitor.visitedRegs());
int pass = 1; int pass = 1;
while (!regs.empty() && pass < max_activity_passes_) { while (!regs.empty() && pass <= max_activity_passes_) {
visitor.init(); visitor.init();
for (const Instance *reg : regs) for (const Instance *reg : regs)
// Propagate activiities across register D->Q. // Propagate activiities across register D->Q.
@ -927,8 +930,10 @@ Power::ensureActivities(const Scene *scene)
// combinational logic. // combinational logic.
bfs.visit(levelize_->maxLevel(), &visitor); bfs.visit(levelize_->maxLevel(), &visitor);
regs = std::move(visitor.visitedRegs()); regs = std::move(visitor.visitedRegs());
debugPrint(debug_, "power_activity", 1, "Pass {} change {:.2f} {}", pass, debugPrint(debug_, "power_activity", 1, "Pass {} change {:.2f} {}",
visitor.maxChange(), network_->pathName(visitor.maxChangePin())); pass,
visitor.maxChange(),
network_->pathName(visitor.maxChangePin()));
pass++; pass++;
} }
} }
@ -972,6 +977,8 @@ Power::seedRegOutputActivities(const Instance *inst,
const SequentialSeq &seqs = test_cell->sequentials(); const SequentialSeq &seqs = test_cell->sequentials();
seedRegOutputActivities(inst, test_cell, seqs, bfs); seedRegOutputActivities(inst, test_cell, seqs, bfs);
} }
else if (cell->isClockGate())
seedClkGateOutputActivities(inst, bfs);
} }
} }
@ -1045,6 +1052,23 @@ Power::seedRegOutputActivities(const Instance *reg,
} }
} }
void
Power::seedClkGateOutputActivities(const Instance *inst,
BfsFwdIterator &bfs)
{
InstancePinIterator *pin_iter = network_->pinIterator(inst);
while (pin_iter->hasNext()) {
Pin *pin = pin_iter->next();
LibertyPort *port = network_->libertyPort(pin);
if (port && port->isClockGateOut()) {
Vertex *vertex = graph_->pinDrvrVertex(pin);
if (vertex)
bfs.enqueue(vertex);
}
}
delete pin_iter;
}
//////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////
void void
@ -1566,8 +1590,12 @@ Power::findActivity(const Pin *pin)
if (activity && activity->origin() != PwrActivityOrigin::unknown) if (activity && activity->origin() != PwrActivityOrigin::unknown)
return *activity; return *activity;
const Clock *clk = findClk(pin); const Clock *clk = findClk(pin);
float duty = clockDuty(clk); if (clk) {
return PwrActivity(2.0 / clk->period(), duty, PwrActivityOrigin::clock); float duty = clockDuty(clk);
return PwrActivity(2.0 / clk->period(), duty, PwrActivityOrigin::clock);
}
else
return PwrActivity();
} }
else if (global_activity_.isSet()) else if (global_activity_.isSet())
return global_activity_; return global_activity_;

View File

@ -224,6 +224,8 @@ protected:
const LibertyCell *test_cell, const LibertyCell *test_cell,
const SequentialSeq &seqs, const SequentialSeq &seqs,
BfsFwdIterator &bfs); BfsFwdIterator &bfs);
void seedClkGateOutputActivities(const Instance *inst,
BfsFwdIterator &bfs);
PwrActivity evalActivity(FuncExpr *expr, PwrActivity evalActivity(FuncExpr *expr,
const Instance *inst); const Instance *inst);
PwrActivity evalActivity(FuncExpr *expr, PwrActivity evalActivity(FuncExpr *expr,

View File

@ -1326,7 +1326,7 @@ TEST_F(PowerDesignTest, SequentialCellClassification) {
Instance *inst = child_iter->next(); Instance *inst = child_iter->next();
LibertyCell *cell = network->libertyCell(inst); LibertyCell *cell = network->libertyCell(inst);
ASSERT_NE(cell, nullptr); ASSERT_NE(cell, nullptr);
if (cell->hasSequentials()) { if (cell->isSequential()) {
seq_count++; seq_count++;
} else { } else {
comb_count++; comb_count++;

View File

@ -2840,6 +2840,18 @@ Sdc::ensureInputDelayRefPinEdges()
} }
} }
// The ref_pin edges are owned by the graph. When the graph is deleted the
// edges go with it, so the existence flags must be reset to force them to be
// rebuilt by ensureInputDelayRefPinEdges() with the new graph.
void
Sdc::inputDelayRefPinEdgesInvalid()
{
if (have_input_delay_ref_pins_) {
for (InputDelay *input_delay : input_delays_)
input_delay->setRefPinEdgesExist(false);
}
}
//////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////
void void

View File

@ -45,7 +45,7 @@ proc_redirect read_sdc {
if { [info exists keys(-mode)] } { if { [info exists keys(-mode)] } {
set mode_name $keys(-mode) set mode_name $keys(-mode)
set prev_mode [cmd_mode] set prev_mode [cmd_mode_name]
try { try {
set_cmd_mode $mode_name set_cmd_mode $mode_name
include_file $filename $echo 0 include_file $filename $echo 0
@ -69,7 +69,7 @@ proc write_sdc { args } {
flags {-map_hpins -compatible -gzip -no_timestamp} flags {-map_hpins -compatible -gzip -no_timestamp}
check_argc_eq1 "write_sdc" $args check_argc_eq1 "write_sdc" $args
set mode [cmd_mode] set mode [cmd_mode_name]
if { [info exists keys(-mode)] } { if { [info exists keys(-mode)] } {
set mode $keys(-mode) set mode $keys(-mode)
} }

View File

@ -344,11 +344,7 @@ BfsIterator::remove(Vertex *vertex)
BfsFwdIterator::BfsFwdIterator(BfsIndex bfs_index, BfsFwdIterator::BfsFwdIterator(BfsIndex bfs_index,
SearchPred *search_pred, SearchPred *search_pred,
StaState *sta) : StaState *sta) :
BfsIterator(bfs_index, BfsIterator(bfs_index, 0, Graph::vertex_level_max, search_pred, sta)
0,
level_max,
search_pred,
sta)
{ {
} }
@ -416,11 +412,7 @@ BfsFwdIterator::enqueueAdjacentVertices(Vertex *vertex,
BfsBkwdIterator::BfsBkwdIterator(BfsIndex bfs_index, BfsBkwdIterator::BfsBkwdIterator(BfsIndex bfs_index,
SearchPred *search_pred, SearchPred *search_pred,
StaState *sta) : StaState *sta) :
BfsIterator(bfs_index, BfsIterator(bfs_index, Graph::vertex_level_max, 0, search_pred, sta)
level_max,
0,
search_pred,
sta)
{ {
} }

View File

@ -26,7 +26,6 @@
#include <cmath> #include <cmath>
#include "Bfs.hh"
#include "Clock.hh" #include "Clock.hh"
#include "ContainerHelpers.hh" #include "ContainerHelpers.hh"
#include "Debug.hh" #include "Debug.hh"
@ -270,30 +269,38 @@ Genclks::ensureMaster(Clock *gclk,
} }
if (!found_master) { if (!found_master) {
// Search backward from generated clock source pin to a clock pin. // Search backward from generated clock source pin to a clock pin.
GenClkMasterSearchPred pred(this); GenClkMasterSearchPred srch_pred(this);
BfsBkwdIterator iter(BfsIndex::other, &pred, this); VertexQueue master_queue;
seedSrcPins(gclk, iter); VertexSet visited = makeVertexSet(this);
while (iter.hasNext()) { VertexSet src_vertices = makeVertexSet(this);
Vertex *vertex = iter.next(); gclk->srcPinVertices(src_vertices, network_, graph_);
Pin *pin = vertex->pin(); for (Vertex *vertex : src_vertices)
if (sdc->isLeafPinClock(pin)) { master_queue.push(vertex);
ClockSet *master_clks = sdc->findLeafPinClocks(pin); while (!master_queue.empty()) {
if (master_clks) { Vertex *vertex = master_queue.front();
ClockSet::iterator master_iter = master_clks->begin(); master_queue.pop();
if (master_iter != master_clks->end()) { if (!visited.contains(vertex)) {
master_clk = *master_iter++; visited.insert(vertex);
// Master source pin can actually be a clock source pin. Pin *pin = vertex->pin();
if (master_clk != gclk) { if (sdc->isLeafPinClock(pin)) {
gclk->setInferedMasterClk(master_clk); ClockSet *master_clks = sdc->findLeafPinClocks(pin);
debugPrint(debug_, "genclk", 2, " {} master clk {}", gclk->name(), if (master_clks) {
master_clk->name()); ClockSet::iterator master_iter = master_clks->begin();
master_clk_count++; if (master_iter != master_clks->end()) {
break; master_clk = *master_iter++;
// Master source pin can actually be a clock source pin.
if (master_clk != gclk) {
gclk->setInferedMasterClk(master_clk);
debugPrint(debug_, "genclk", 2, " {} master clk {}", gclk->name(),
master_clk->name());
master_clk_count++;
break;
}
} }
} }
} }
enqueueFanin(vertex, master_queue, srch_pred);
} }
iter.enqueueAdjacentVertices(vertex, mode_);
} }
} }
if (master_clk_count > 1) if (master_clk_count > 1)
@ -303,16 +310,6 @@ Genclks::ensureMaster(Clock *gclk,
} }
} }
void
Genclks::seedSrcPins(Clock *clk,
BfsBkwdIterator &iter)
{
VertexSet src_vertices = makeVertexSet(this);
clk->srcPinVertices(src_vertices, network_, graph_);
for (Vertex *vertex : src_vertices)
iter.enqueue(vertex);
}
//////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////
// Similar to ClkTreeSearchPred but // Similar to ClkTreeSearchPred but
@ -349,32 +346,34 @@ Genclks::findFanin(Clock *gclk,
{ {
// Search backward from generated clock source pin to a clock pin. // Search backward from generated clock source pin to a clock pin.
GenClkFaninSrchPred srch_pred(gclk, this); GenClkFaninSrchPred srch_pred(gclk, this);
BfsBkwdIterator iter(BfsIndex::other, &srch_pred, this); VertexQueue fanin_queue;
seedClkVertices(gclk, iter, fanins); seedClkVertices(gclk, fanin_queue, fanins, srch_pred);
while (iter.hasNext()) { while (!fanin_queue.empty()) {
Vertex *vertex = iter.next(); Vertex *vertex = fanin_queue.front();
fanin_queue.pop();
if (!fanins.contains(vertex)) { if (!fanins.contains(vertex)) {
fanins.insert(vertex); fanins.insert(vertex);
debugPrint(debug_, "genclk", 2, "gen clk {} fanin {}", gclk->name(), debugPrint(debug_, "genclk", 2, "gen clk {} fanin {}", gclk->name(),
vertex->to_string(this)); vertex->to_string(this));
iter.enqueueAdjacentVertices(vertex, mode_); enqueueFanin(vertex, fanin_queue, srch_pred);
} }
} }
} }
void void
Genclks::seedClkVertices(Clock *clk, Genclks::seedClkVertices(Clock *clk,
BfsBkwdIterator &iter, VertexQueue &fanin_queue,
VertexSet &fanins) VertexSet &fanins,
SearchPred &srch_pred)
{ {
for (const Pin *pin : clk->leafPins()) { for (const Pin *pin : clk->leafPins()) {
Vertex *vertex, *bidirect_drvr_vertex; Vertex *vertex, *bidirect_drvr_vertex;
graph_->pinVertices(pin, vertex, bidirect_drvr_vertex); graph_->pinVertices(pin, vertex, bidirect_drvr_vertex);
fanins.insert(vertex); fanins.insert(vertex);
iter.enqueueAdjacentVertices(vertex, mode_); enqueueFanin(vertex, fanin_queue, srch_pred);
if (bidirect_drvr_vertex) { if (bidirect_drvr_vertex) {
fanins.insert(bidirect_drvr_vertex); fanins.insert(bidirect_drvr_vertex);
iter.enqueueAdjacentVertices(bidirect_drvr_vertex, mode_); enqueueFanin(bidirect_drvr_vertex, fanin_queue, srch_pred);
} }
} }
} }
@ -457,11 +456,11 @@ Genclks::findInsertionDelays(Clock *gclk)
debugPrint(debug_, "genclk", 2, "find gen clk {} insertion", gclk->name()); debugPrint(debug_, "genclk", 2, "find gen clk {} insertion", gclk->name());
GenclkInfo *genclk_info = makeGenclkInfo(gclk); GenclkInfo *genclk_info = makeGenclkInfo(gclk);
FilterPath *src_filter = genclk_info->srcFilter(); FilterPath *src_filter = genclk_info->srcFilter();
VertexQueue insert_queue;
GenClkInsertionSearchPred srch_pred(gclk, genclk_info, this); GenClkInsertionSearchPred srch_pred(gclk, genclk_info, this);
BfsFwdIterator insert_iter(BfsIndex::other, &srch_pred, this); seedSrcPins(gclk, src_filter, insert_queue, srch_pred);
seedSrcPins(gclk, src_filter, insert_iter);
// Propagate arrivals to generated clk root pin level. // Propagate arrivals to generated clk root pin level.
findSrcArrivals(gclk, insert_iter, genclk_info); findSrcArrivals(gclk, genclk_info, insert_queue);
// Unregister the filter so that it is not triggered by other searches. // Unregister the filter so that it is not triggered by other searches.
// The exception itself has to stick around because the source path // The exception itself has to stick around because the source path
// tags reference it. // tags reference it.
@ -591,7 +590,8 @@ Genclks::makeSrcFilter(Clock *gclk,
void void
Genclks::seedSrcPins(Clock *gclk, Genclks::seedSrcPins(Clock *gclk,
FilterPath *src_filter, FilterPath *src_filter,
BfsFwdIterator &insert_iter) VertexQueue &insert_queue,
SearchPred &srch_pred)
{ {
Clock *master_clk = gclk->masterClk(); Clock *master_clk = gclk->masterClk();
for (const Pin *master_pin : master_clk->leafPins()) { for (const Pin *master_pin : master_clk->leafPins()) {
@ -613,13 +613,35 @@ Genclks::seedSrcPins(Clock *gclk,
tag_bldr.setArrival(tag, insert); tag_bldr.setArrival(tag, insert);
} }
} }
search_->setVertexArrivals(vertex, &tag_bldr);
insert_iter.enqueueAdjacentVertices(vertex, mode_);
} }
search_->setVertexArrivals(vertex, &tag_bldr);
enqueueFanout(vertex, insert_queue, srch_pred);
} }
} }
} }
void
Genclks::enqueueFanin(Vertex *vertex,
VertexQueue &insert_queue,
SearchPred &srch_pred)
{
graph_->visitFanins(vertex, &srch_pred,
[&insert_queue] (Vertex *fanin) {
insert_queue.push(fanin);
});
}
void
Genclks::enqueueFanout(Vertex *vertex,
VertexQueue &insert_queue,
SearchPred &srch_pred)
{
graph_->visitFanouts(vertex, &srch_pred,
[&insert_queue] (Vertex *fanout) {
insert_queue.push(fanout);
});
}
Tag * Tag *
Genclks::makeTag(const Clock *gclk, Genclks::makeTag(const Clock *gclk,
const Clock *master_clk, const Clock *master_clk,
@ -637,11 +659,12 @@ Genclks::makeTag(const Clock *gclk,
state = state->nextState(); state = state->nextState();
ExceptionStateSet *states = new ExceptionStateSet(); ExceptionStateSet *states = new ExceptionStateSet();
states->insert(state); states->insert(state);
const ClkInfo *clk_info = search_->findClkInfo( const ClkInfo *clk_info = search_->findClkInfo(scene, master_clk->edge(master_rf),
scene, master_clk->edge(master_rf), master_pin, true, nullptr, true, nullptr, master_pin, true, nullptr, true,
insert, 0.0, nullptr, min_max, nullptr); nullptr, insert, 0.0, nullptr,
return search_->findTag(scene, master_rf, min_max, clk_info, false, nullptr, false, min_max, nullptr);
states, true, nullptr); return search_->findTag(scene, master_rf, min_max, clk_info, false, nullptr,
false, states, true, nullptr);
} }
class GenClkArrivalSearchPred : public EvalPred class GenClkArrivalSearchPred : public EvalPred
@ -690,8 +713,8 @@ class GenclkSrcArrivalVisitor : public ArrivalVisitor
{ {
public: public:
GenclkSrcArrivalVisitor(Clock *gclk, GenclkSrcArrivalVisitor(Clock *gclk,
BfsFwdIterator *insert_iter,
GenclkInfo *genclk_info, GenclkInfo *genclk_info,
VertexQueue &insert_queue,
const Mode *mode); const Mode *mode);
GenclkSrcArrivalVisitor(const GenclkSrcArrivalVisitor &visitor); GenclkSrcArrivalVisitor(const GenclkSrcArrivalVisitor &visitor);
VertexVisitor *copy() const override; VertexVisitor *copy() const override;
@ -699,7 +722,7 @@ public:
protected: protected:
Clock *gclk_; Clock *gclk_;
BfsFwdIterator *insert_iter_; VertexQueue &insert_queue_;
GenclkInfo *genclk_info_; GenclkInfo *genclk_info_;
GenClkInsertionSearchPred srch_pred_; GenClkInsertionSearchPred srch_pred_;
const Mode *mode_; const Mode *mode_;
@ -708,12 +731,12 @@ protected:
}; };
GenclkSrcArrivalVisitor::GenclkSrcArrivalVisitor(Clock *gclk, GenclkSrcArrivalVisitor::GenclkSrcArrivalVisitor(Clock *gclk,
BfsFwdIterator *insert_iter,
GenclkInfo *genclk_info, GenclkInfo *genclk_info,
VertexQueue &insert_queue,
const Mode *mode) : const Mode *mode) :
ArrivalVisitor(mode->sta()), ArrivalVisitor(mode->sta()),
gclk_(gclk), gclk_(gclk),
insert_iter_(insert_iter), insert_queue_(insert_queue),
genclk_info_(genclk_info), genclk_info_(genclk_info),
srch_pred_(gclk_, genclk_info, mode->sta()), srch_pred_(gclk_, genclk_info, mode->sta()),
mode_(mode), mode_(mode),
@ -725,7 +748,7 @@ GenclkSrcArrivalVisitor::GenclkSrcArrivalVisitor(Clock *gclk,
GenclkSrcArrivalVisitor::GenclkSrcArrivalVisitor(const GenclkSrcArrivalVisitor &visitor) : GenclkSrcArrivalVisitor::GenclkSrcArrivalVisitor(const GenclkSrcArrivalVisitor &visitor) :
ArrivalVisitor(visitor), ArrivalVisitor(visitor),
gclk_(visitor.gclk_), gclk_(visitor.gclk_),
insert_iter_(visitor.insert_iter_), insert_queue_(visitor.insert_queue_),
genclk_info_(visitor.genclk_info_), genclk_info_(visitor.genclk_info_),
srch_pred_(visitor.gclk_, visitor.genclk_info_, this), srch_pred_(visitor.gclk_, visitor.genclk_info_, this),
mode_(visitor.mode_), mode_(visitor.mode_),
@ -748,24 +771,28 @@ GenclkSrcArrivalVisitor::visit(Vertex *vertex)
tag_bldr_->init(vertex); tag_bldr_->init(vertex);
has_fanin_one_ = graph_->hasFaninOne(vertex); has_fanin_one_ = graph_->hasFaninOne(vertex);
genclks_->copyGenClkSrcPaths(vertex, tag_bldr_); genclks_->copyGenClkSrcPaths(vertex, tag_bldr_);
visitFaninPaths(vertex); visitFaninPaths(vertex, true);
// Propagate beyond the clock tree to reach generated clk roots.
insert_iter_->enqueueAdjacentVertices(vertex, &srch_pred_, mode_);
search_->setVertexArrivals(vertex, tag_bldr_); search_->setVertexArrivals(vertex, tag_bldr_);
// Propagate beyond the clock tree to reach generated clk roots.
genclks_->enqueueFanout(vertex, insert_queue_, srch_pred_);
} }
void void
Genclks::findSrcArrivals(Clock *gclk, Genclks::findSrcArrivals(Clock *gclk,
BfsFwdIterator &insert_iter, GenclkInfo *genclk_info,
GenclkInfo *genclk_info) VertexQueue &insert_queue)
{ {
GenClkArrivalSearchPred eval_pred(gclk, this); GenClkArrivalSearchPred eval_pred(gclk, this);
GenclkSrcArrivalVisitor arrival_visitor(gclk, &insert_iter, genclk_info, mode_); GenclkSrcArrivalVisitor arrival_visitor(gclk, genclk_info, insert_queue, mode_);
arrival_visitor.init(true, false, &eval_pred); arrival_visitor.init(true, false, &eval_pred);
// This cannot restrict the search level because loops in the clock tree // This cannot restrict the search level because loops in the clock tree
// can circle back to the generated clock src pin. // can circle back to the generated clock src pin.
// Parallel visit is slightly slower (at last check). // Parallel visit is slightly slower (at last check).
insert_iter.visit(levelize_->maxLevel(), &arrival_visitor); while (!insert_queue.empty()) {
Vertex *vertex = insert_queue.front();
insert_queue.pop();
arrival_visitor.visit(vertex);
}
} }
// Copy generated clock source paths to tag_bldr. // Copy generated clock source paths to tag_bldr.
@ -886,15 +913,15 @@ void
Genclks::deleteGenclkSrcPaths(Clock *gclk) Genclks::deleteGenclkSrcPaths(Clock *gclk)
{ {
GenclkInfo *genclk_info = genclkInfo(gclk); GenclkInfo *genclk_info = genclkInfo(gclk);
GenClkInsertionSearchPred srch_pred(gclk, genclk_info, mode_->sta()); VertexQueue insert_queue;
BfsFwdIterator insert_iter(BfsIndex::other, &srch_pred, this); GenClkInsertionSearchPred srch_pred(gclk, genclk_info, this);
FilterPath *src_filter = genclk_info->srcFilter(); FilterPath *src_filter = genclk_info->srcFilter();
seedSrcPins(gclk, src_filter, insert_iter); seedSrcPins(gclk, src_filter, insert_queue, srch_pred);
GenClkArrivalSearchPred eval_pred(gclk, this); while (!insert_queue.empty()) {
while (insert_iter.hasNext()) { Vertex *vertex = insert_queue.front();
Vertex *vertex = insert_iter.next(); insert_queue.pop();
search_->deletePaths(vertex); search_->deletePaths(vertex);
insert_iter.enqueueAdjacentVertices(vertex, &srch_pred, mode_); enqueueFanout(vertex, insert_queue, srch_pred);
} }
} }

View File

@ -26,6 +26,7 @@
#include <cstddef> #include <cstddef>
#include <map> #include <map>
#include <queue>
#include <utility> #include <utility>
#include <vector> #include <vector>
@ -42,8 +43,6 @@
namespace sta { namespace sta {
class GenclkInfo; class GenclkInfo;
class BfsFwdIterator;
class BfsBkwdIterator;
class SearchPred; class SearchPred;
class TagGroupBldr; class TagGroupBldr;
@ -59,6 +58,7 @@ public:
using GenclkInfoMap = std::map<Clock*, GenclkInfo*>; using GenclkInfoMap = std::map<Clock*, GenclkInfo*>;
using GenclkSrcPathMap = std::map<ClockPinPair, std::vector<Path>, ClockPinPairLess>; using GenclkSrcPathMap = std::map<ClockPinPair, std::vector<Path>, ClockPinPairLess>;
using VertexGenclkSrcPathsMap = std::map<Vertex*, std::vector<const Path*>, VertexIdLess>; using VertexGenclkSrcPathsMap = std::map<Vertex*, std::vector<const Path*>, VertexIdLess>;
using VertexQueue = std::queue<Vertex*>;
class Genclks : public StaState class Genclks : public StaState
{ {
@ -102,18 +102,20 @@ private:
void recordSrcPaths(Clock *gclk); void recordSrcPaths(Clock *gclk);
void findInsertionDelays(Clock *gclk); void findInsertionDelays(Clock *gclk);
void seedClkVertices(Clock *clk, void seedClkVertices(Clock *clk,
BfsBkwdIterator &iter, VertexQueue &fanin_queue,
VertexSet &dfanins); VertexSet &fanins,
SearchPred &srch_pred);
size_t srcPathIndex(const RiseFall *clk_rf, size_t srcPathIndex(const RiseFall *clk_rf,
const MinMax *min_max) const; const MinMax *min_max) const;
bool matchesSrcFilter(Path *path, bool matchesSrcFilter(Path *path,
const Clock *gclk) const; const Clock *gclk) const;
void seedSrcPins(Clock *gclk, void seedSrcPins(Clock *gclk,
FilterPath *src_filter, FilterPath *src_filter,
BfsFwdIterator &insert_iter); VertexQueue &insert_queue,
SearchPred &srch_pred);
void findSrcArrivals(Clock *gclk, void findSrcArrivals(Clock *gclk,
BfsFwdIterator &insert_iter, GenclkInfo *genclk_info,
GenclkInfo *genclk_info); VertexQueue &insert_queue);
FilterPath *makeSrcFilter(Clock *gclk, FilterPath *makeSrcFilter(Clock *gclk,
Sdc *sdc); Sdc *sdc);
void deleteGenClkInfo(); void deleteGenClkInfo();
@ -125,9 +127,13 @@ private:
Arrival insert, Arrival insert,
Scene *scene, Scene *scene,
const MinMax *min_max); const MinMax *min_max);
void seedSrcPins(Clock *clk,
BfsBkwdIterator &iter);
void findInsertionDelay(Clock *gclk); void findInsertionDelay(Clock *gclk);
void enqueueFanin(Vertex *vertex,
VertexQueue &insert_queue,
SearchPred &srch_pred);
void enqueueFanout(Vertex *vertex,
VertexQueue &insert_queue,
SearchPred &srch_pred);
GenclkInfo *makeGenclkInfo(Clock *gclk); GenclkInfo *makeGenclkInfo(Clock *gclk);
FilterPath *srcFilter(Clock *gclk); FilterPath *srcFilter(Clock *gclk);
void findFanin(Clock *gclk, void findFanin(Clock *gclk,
@ -147,6 +153,8 @@ private:
GenclkSrcPathMap genclk_src_paths_; GenclkSrcPathMap genclk_src_paths_;
GenclkInfoMap genclk_info_map_; GenclkInfoMap genclk_info_map_;
VertexGenclkSrcPathsMap vertex_src_paths_map_; VertexGenclkSrcPathsMap vertex_src_paths_map_;
friend class GenclkSrcArrivalVisitor;
}; };
} // namespace sta } // namespace sta

View File

@ -359,16 +359,7 @@ Latches::latchOutArrival(const Path *data_path,
arc_delay = search_->deratedDelay(data_vertex, d_q_arc, d_q_edge, arc_delay = search_->deratedDelay(data_vertex, d_q_arc, d_q_edge,
false, min_max, dcalc_ap, sdc); false, min_max, dcalc_ap, sdc);
q_arrival = delaySum(data_path->arrival(), arc_delay, this); q_arrival = delaySum(data_path->arrival(), arc_delay, this);
// Copy the data tag but remove the drprClkPath. // Copy the data tag but remove the crprClkPath.
// Levelization does not traverse latch D->Q edges, so in some cases
// level(Q) < level(D)
// Note that
// level(crprClkPath(data)) < level(D)
// The danger is that
// level(crprClkPath(data)) == level(Q)
// or some other downstream vertex.
// This can lead to data races when finding arrivals at the same level
// use multiple threads.
// Kill the crprClklPath to be safe. // Kill the crprClklPath to be safe.
const ClkInfo *data_clk_info = data_path->clkInfo(this); const ClkInfo *data_clk_info = data_path->clkInfo(this);
const ClkInfo *q_clk_info = const ClkInfo *q_clk_info =

View File

@ -135,7 +135,6 @@ Levelize::findLevels()
findBackEdges(); findBackEdges();
VertexSeq topo_sorted = findTopologicalOrder(); VertexSeq topo_sorted = findTopologicalOrder();
assignLevels(topo_sorted); assignLevels(topo_sorted);
ensureLatchLevels();
// Set level of stranded vertices (constants) to zero. // Set level of stranded vertices (constants) to zero.
VertexIterator vertex_iter2(graph_); VertexIterator vertex_iter2(graph_);
@ -188,9 +187,7 @@ Levelize::isRoot(Vertex *vertex)
if (searchThru(edge)) if (searchThru(edge))
return false; return false;
} }
// Levelize bidirect driver as if it was a fanout of the bidirect load. return true;
return !(graph_delay_calc_->bidirectDrvrSlewFromLoad(vertex->pin())
&& vertex->isBidirectDriver());
} }
bool bool
@ -357,8 +354,6 @@ Levelize::findTopologicalOrder()
Vertex *to_vertex = edge->to(graph_); Vertex *to_vertex = edge->to(graph_);
if (searchThru(edge)) if (searchThru(edge))
in_degree[to_vertex] += 1; in_degree[to_vertex] += 1;
if (edge->role() == TimingRole::latchDtoQ())
latch_d_to_q_edges_.insert(edge);
} }
// Levelize bidirect driver as if it was a fanout of the bidirect load. // Levelize bidirect driver as if it was a fanout of the bidirect load.
const Pin *pin = vertex->pin(); const Pin *pin = vertex->pin();
@ -510,27 +505,6 @@ Levelize::assignLevels(VertexSeq &topo_sorted)
//////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////
// Make sure latch D input level is not the same as the Q level.
// This is because the Q arrival depends on the D arrival and
// to find them in parallel they have to be scheduled separately
// to avoid a race condition.
void
Levelize::ensureLatchLevels()
{
for (Edge *edge : latch_d_to_q_edges_) {
Vertex *from = edge->from(graph_);
Vertex *to = edge->to(graph_);
if (from->level() == to->level()) {
Level adjusted_level = from->level() + level_space_;
debugPrint(debug_, "levelize", 2, "latch {} {} (adjusted {}) -> {} {}",
from->to_string(this), from->level(), adjusted_level,
to->to_string(this), to->level());
setLevel(from, adjusted_level);
}
}
latch_d_to_q_edges_.clear();
}
void void
Levelize::setLevel(Vertex *vertex, Levelize::setLevel(Vertex *vertex,
Level level) Level level)
@ -607,7 +581,6 @@ Levelize::relevelize()
EdgeSeq path; EdgeSeq path;
visit(vertex, nullptr, vertex->level(), 1, path_vertices, path); visit(vertex, nullptr, vertex->level(), 1, path_vertices, path);
} }
ensureLatchLevels();
levels_valid_ = true; levels_valid_ = true;
relevelize_from_.clear(); relevelize_from_.clear();
} }
@ -638,18 +611,6 @@ Levelize::visit(Vertex *vertex,
visit(to_vertex, edge, level + level_space, level_space, path_vertices, visit(to_vertex, edge, level + level_space, level_space, path_vertices,
path); path);
} }
const TimingRole *role = edge->role();
if (role->isLatchDtoQ())
latch_d_to_q_edges_.insert(edge);
if (role->isLatchEnToQ()) {
VertexInEdgeIterator edge_iter2(to_vertex, graph_);
while (edge_iter2.hasNext()) {
Edge *edge2 = edge_iter2.next();
if (edge2->role()->isLatchDtoQ())
latch_d_to_q_edges_.insert(edge2);
}
}
} }
// Levelize bidirect driver as if it was a fanout of the bidirect load. // Levelize bidirect driver as if it was a fanout of the bidirect load.

View File

@ -83,7 +83,6 @@ protected:
EdgeSeq &path); EdgeSeq &path);
EdgeSeq *loopEdges(EdgeSeq &path, EdgeSeq *loopEdges(EdgeSeq &path,
Edge *closing_edge); Edge *closing_edge);
void ensureLatchLevels();
void findBackEdges(); void findBackEdges();
EdgeSet findBackEdges(EdgeSeq &path, EdgeSet findBackEdges(EdgeSeq &path,
FindBackEdgesStack &stack); FindBackEdgesStack &stack);
@ -113,7 +112,6 @@ protected:
GraphLoopSeq loops_; GraphLoopSeq loops_;
EdgeSet loop_edges_; EdgeSet loop_edges_;
EdgeSet disabled_loop_edges_; EdgeSet disabled_loop_edges_;
EdgeSet latch_d_to_q_edges_;
LevelizeObserver *observer_{nullptr}; LevelizeObserver *observer_{nullptr};
}; };

View File

@ -238,6 +238,28 @@ PathEnum::reportDiversionPath(Diversion *div)
using VisitedFanins = std::set<std::pair<const Vertex *, const TimingArc *>>; using VisitedFanins = std::set<std::pair<const Vertex *, const TimingArc *>>;
using VertexEdge = std::pair<const Vertex *, const RiseFall *>; using VertexEdge = std::pair<const Vertex *, const RiseFall *>;
class EnumPred : public EvalPred
{
public:
EnumPred(const StaState *sta);
bool searchThru(Edge *edge,
const Mode *mode) const override;
};
EnumPred::EnumPred(const StaState *sta) :
EvalPred(sta)
{
}
bool
EnumPred::searchThru(Edge *edge,
const Mode *mode) const
{
// Do not enum paths across disabled loop edges.
return EvalPred::searchThru(edge, mode)
&& !edge->isDisabledLoop();
}
class PathEnumFaninVisitor : public PathVisitor class PathEnumFaninVisitor : public PathVisitor
{ {
public: public:
@ -247,6 +269,7 @@ public:
bool unique_edges, bool unique_edges,
PathEnum *path_enum); PathEnum *path_enum);
PathEnumFaninVisitor(const PathEnumFaninVisitor &visitor); PathEnumFaninVisitor(const PathEnumFaninVisitor &visitor);
virtual ~PathEnumFaninVisitor();
VertexVisitor *copy() const override; VertexVisitor *copy() const override;
void visitFaninPathsThru(Path *before_div, void visitFaninPathsThru(Path *before_div,
Vertex *prev_vertex, Vertex *prev_vertex,
@ -311,7 +334,7 @@ PathEnumFaninVisitor::PathEnumFaninVisitor(PathEnd *path_end,
bool unique_pins, bool unique_pins,
bool unique_edges, bool unique_edges,
PathEnum *path_enum) : PathEnum *path_enum) :
PathVisitor(path_enum), PathVisitor(new EnumPred(path_enum), false, path_enum),
path_end_(path_end), path_end_(path_end),
before_div_(before_div), before_div_(before_div),
unique_pins_(unique_pins), unique_pins_(unique_pins),
@ -334,6 +357,11 @@ PathEnumFaninVisitor::PathEnumFaninVisitor(const PathEnumFaninVisitor &visitor)
{ {
} }
PathEnumFaninVisitor::~PathEnumFaninVisitor()
{
delete pred_;
}
VertexVisitor * VertexVisitor *
PathEnumFaninVisitor::copy() const PathEnumFaninVisitor::copy() const
{ {
@ -356,7 +384,7 @@ PathEnumFaninVisitor::visitFaninPathsThru(Path *before_div,
prev_vertex_ = prev_vertex; prev_vertex_ = prev_vertex;
visited_fanins_.clear(); visited_fanins_.clear();
unique_edge_divs_.clear(); unique_edge_divs_.clear();
visitFaninPaths(before_div_->vertex(this)); visitFaninPaths(before_div_->vertex(this), true);
if (unique_edges_) { if (unique_edges_) {
for (auto [vertex_edge, div] : unique_edge_divs_) for (auto [vertex_edge, div] : unique_edge_divs_)
@ -381,7 +409,8 @@ PathEnumFaninVisitor::visitEdge(const Pin *from_pin,
Path *from_path = from_iter.next(); Path *from_path = from_iter.next();
const Mode *mode = from_path->mode(this); const Mode *mode = from_path->mode(this);
const Sdc *sdc = mode->sdc(); const Sdc *sdc = mode->sdc();
if (pred_->searchFrom(from_vertex, mode) && pred_->searchThru(edge, mode) if (pred_->searchFrom(from_vertex, mode)
&& pred_->searchThru(edge, mode)
&& pred_->searchTo(to_vertex, mode) && pred_->searchTo(to_vertex, mode)
// Fanin paths are broken by path delay internal pin startpoints. // Fanin paths are broken by path delay internal pin startpoints.
&& !sdc->isPathDelayInternalFromBreak(to_pin)) { && !sdc->isPathDelayInternalFromBreak(to_pin)) {
@ -421,6 +450,14 @@ PathEnumFaninVisitor::visitFromToPath(const Pin *,
Arrival & /* to_arrival */, Arrival & /* to_arrival */,
const MinMax *) const MinMax *)
{ {
debugPrint(debug_, "path_enum", 3, "visit fanin {} -> {} {} {}",
from_path->to_string(this),
to_vertex->to_string(this), to_rf->shortName(),
delayAsString(
search_->deratedDelay(
from_vertex, arc, edge, false, from_path->minMax(this),
from_path->dcalcAnalysisPtIndex(this), from_path->sdc(this)),
this));
// These paths fanin to before_div_ so we know to_vertex matches. // These paths fanin to before_div_ so we know to_vertex matches.
if ((!unique_pins_ || from_vertex != prev_vertex_) if ((!unique_pins_ || from_vertex != prev_vertex_)
&& (!unique_edges_ || from_vertex != prev_vertex_ && (!unique_edges_ || from_vertex != prev_vertex_
@ -646,10 +683,10 @@ PathEnum::makeDivertedPath(Path *path,
Path *prev_copy = nullptr; Path *prev_copy = nullptr;
while (p) { while (p) {
// prev_path made in next pass. // prev_path made in next pass.
Path *copy = Path *copy = new Path(p->vertex(this), p->tag(this), p->arrival(),
new Path(p->vertex(this), p->tag(this), p->arrival(), // Replaced on next pass.
// Replaced on next pass. p->prevPath(), p->prevEdge(this), p->prevArc(this),
p->prevPath(), p->prevEdge(this), p->prevArc(this), true, this); true, this);
search_->saveEnumPath(copy); search_->saveEnumPath(copy);
if (prev_copy) if (prev_copy)
prev_copy->setPrevPath(copy); prev_copy->setPrevPath(copy);

View File

@ -33,6 +33,7 @@
#include "Graph.hh" #include "Graph.hh"
#include "Liberty.hh" #include "Liberty.hh"
#include "MinMax.hh" #include "MinMax.hh"
#include "Mode.hh"
#include "Network.hh" #include "Network.hh"
#include "Path.hh" #include "Path.hh"
#include "PathEnd.hh" #include "PathEnd.hh"
@ -1196,6 +1197,32 @@ Properties::getProperty(const Clock *clk,
//////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////
PropertyValue
Properties::getProperty(const Scene *scene,
std::string_view property)
{
if (property == "name"
|| property == "full_name")
return PropertyValue(scene->name());
else
throw PropertyUnknown("scene", property);
}
////////////////////////////////////////////////////////////////
PropertyValue
Properties::getProperty(const Mode *mode,
std::string_view property)
{
if (property == "name"
|| property == "full_name")
return PropertyValue(mode->name());
else
throw PropertyUnknown("mode", property);
}
////////////////////////////////////////////////////////////////
PropertyValue PropertyValue
Properties::getProperty(PathEnd *end, Properties::getProperty(PathEnd *end,
std::string_view property) std::string_view property)

View File

@ -121,6 +121,22 @@ clock_property(Clock *clk,
return properties.getProperty(clk, property); return properties.getProperty(clk, property);
} }
PropertyValue
scene_property(Scene *scene,
const char *property)
{
Properties &properties = Sta::sta()->properties();
return properties.getProperty(scene, property);
}
PropertyValue
mode_property(Mode *mode,
const char *property)
{
Properties &properties = Sta::sta()->properties();
return properties.getProperty(mode, property);
}
PropertyValue PropertyValue
path_end_property(PathEnd *end, path_end_property(PathEnd *end,
const char *property) const char *property)

View File

@ -95,9 +95,9 @@ EvalPred::searchThru(Edge *edge,
{ {
const TimingRole *role = edge->role(); const TimingRole *role = edge->role();
return SearchPred0::searchThru(edge, mode) return SearchPred0::searchThru(edge, mode)
&& (sta_->variables()->dynamicLoopBreaking() || !edge->isDisabledLoop()) && (search_thru_latches_
&& (search_thru_latches_ || role->isLatchDtoQ() || role->isLatchDtoQ()
|| sta_->latches()->latchDtoQState(edge, mode) == LatchEnableState::open); || sta_->latches()->latchDtoQState(edge, mode) == LatchEnableState::open);
} }
bool bool
@ -133,54 +133,56 @@ bool
SearchThru::searchThru(Edge *edge, SearchThru::searchThru(Edge *edge,
const Mode *mode) const const Mode *mode) const
{ {
return EvalPred::searchThru(edge, mode) && !edge->role()->isLatchDtoQ(); return EvalPred::searchThru(edge, mode)
&& !edge->role()->isLatchDtoQ();
} }
//////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////
// SearchAdj is mode independent. Search unless // SearchAdjLoop is mode independent. Search unless
// disabled to break combinational loop
// latch D->Q edge // latch D->Q edge
// timing check edge // timing check edge
// dynamic loop breaking pending tags // register set/clear
class SearchAdj : public SearchPred // bidirect load->driver
class SearchAdjLoop : public SearchPred
{ {
public: public:
SearchAdj(TagGroupBldr *tag_bldr, SearchAdjLoop(const StaState *sta);
const StaState *sta); bool searchFrom(const Vertex *from_vertex) const override;
bool searchFrom(const Vertex *from_vertex, bool searchFrom(const Vertex *from_vertex,
const Mode *mode) const override; const Mode *mode) const override;
bool searchThru(Edge *edge) const override;
bool searchThru(Edge *edge, bool searchThru(Edge *edge,
const Mode *mode) const override; const Mode *mode) const override;
bool searchTo(const Vertex *to_vertex) const override;
bool searchTo(const Vertex *to_vertex, bool searchTo(const Vertex *to_vertex,
const Mode *mode) const override; const Mode *mode) const override;
protected: protected:
bool loopEnabled(Edge *edge) const;
bool hasPendingLoopPaths(Edge *edge) const;
TagGroupBldr *tag_bldr_;
const StaState *sta_; const StaState *sta_;
}; };
SearchAdj::SearchAdj(TagGroupBldr *tag_bldr, SearchAdjLoop::SearchAdjLoop(const StaState *sta) :
const StaState *sta) :
SearchPred(sta), SearchPred(sta),
tag_bldr_(tag_bldr),
sta_(sta) sta_(sta)
{ {
} }
bool bool
SearchAdj::searchFrom(const Vertex * /* from_vertex */, SearchAdjLoop::searchFrom(const Vertex *) const
const Mode *) const
{ {
return true; return true;
} }
bool bool
SearchAdj::searchThru(Edge *edge, SearchAdjLoop::searchFrom(const Vertex *,
const Mode *) const const Mode *) const
{
return true;
}
bool
SearchAdjLoop::searchThru(Edge *edge) const
{ {
const TimingRole *role = edge->role(); const TimingRole *role = edge->role();
const Variables *variables = sta_->variables(); const Variables *variables = sta_->variables();
@ -189,55 +191,57 @@ SearchAdj::searchThru(Edge *edge,
// Register/latch preset/clr edges are disabled by default. // Register/latch preset/clr edges are disabled by default.
|| (role == TimingRole::regSetClr() || (role == TimingRole::regSetClr()
&& !variables->presetClrArcsEnabled()) && !variables->presetClrArcsEnabled())
|| sta_->isDisabledBidirectInstPath(edge) || sta_->isDisabledBidirectInstPath(edge));
|| (edge->isDisabledLoop()
&& !(variables->dynamicLoopBreaking() && hasPendingLoopPaths(edge))));
} }
bool bool
SearchAdj::loopEnabled(Edge *edge) const SearchAdjLoop::searchThru(Edge *edge,
const Mode *) const
{ {
return !edge->isDisabledLoop() return searchThru(edge);
|| (sta_->variables()->dynamicLoopBreaking() && hasPendingLoopPaths(edge));
} }
bool bool
SearchAdj::hasPendingLoopPaths(Edge *edge) const SearchAdjLoop::searchTo(const Vertex *) const
{
if (tag_bldr_ && tag_bldr_->hasLoopTag()) {
const Graph *graph = sta_->graph();
Search *search = sta_->search();
Vertex *from_vertex = edge->from(graph);
TagGroup *prev_tag_group = search->tagGroup(from_vertex);
for (auto const [from_tag, path_index] : tag_bldr_->pathIndexMap()) {
if (from_tag->isLoop()) {
// Loop false path exceptions apply to rise/fall edges so to_rf
// does not matter.
Tag *to_tag = search->thruTag(from_tag, edge, RiseFall::rise(), nullptr);
if (to_tag
&& (prev_tag_group == nullptr || !prev_tag_group->hasTag(from_tag)))
return true;
}
}
}
return false;
}
bool
SearchAdj::searchTo(const Vertex * /* to_vertex */,
const Mode *) const
{ {
return true; return true;
} }
bool
SearchAdjLoop::searchTo(const Vertex *,
const Mode *) const
{
return true;
}
// SearchAdj is mode independent. Search unless
// SearchAdjLoop but not thru disabled loop edges.
class SearchAdj : public SearchAdjLoop
{
public:
SearchAdj(const StaState *sta);
bool searchThru(Edge *edge) const override;
};
SearchAdj::SearchAdj(const StaState *sta) :
SearchAdjLoop(sta)
{
}
bool
SearchAdj::searchThru(Edge *edge) const
{
return SearchAdjLoop::searchThru(edge)
&& !edge->isDisabledLoop();
}
//////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////
Search::Search(StaState *sta) : Search::Search(StaState *sta) :
StaState(sta), StaState(sta),
search_thru_(new SearchThru(this)), search_thru_(new SearchThru(this)),
search_adj_(new SearchAdj(nullptr, search_adj_(new SearchAdj(this)),
this)),
eval_pred_(new EvalPred(this)), eval_pred_(new EvalPred(this)),
invalid_arrivals_(makeVertexSet(this)), invalid_arrivals_(makeVertexSet(this)),
@ -247,9 +251,7 @@ Search::Search(StaState *sta) :
arrival_visitor_(new ArrivalVisitor(this)), arrival_visitor_(new ArrivalVisitor(this)),
invalid_requireds_(makeVertexSet(this)), invalid_requireds_(makeVertexSet(this)),
required_iter_(new BfsBkwdIterator(BfsIndex::required, required_iter_(new BfsBkwdIterator(BfsIndex::required, search_adj_, this)),
search_adj_,
this)),
invalid_tns_(makeVertexSet(this)), invalid_tns_(makeVertexSet(this)),
clk_info_set_(new ClkInfoSet(ClkInfoLess(this))), clk_info_set_(new ClkInfoSet(ClkInfoLess(this))),
@ -261,8 +263,8 @@ Search::Search(StaState *sta) :
tag_group_capacity_(tag_capacity_), tag_group_capacity_(tag_capacity_),
tag_groups_(new TagGroup *[tag_group_capacity_]), tag_groups_(new TagGroup *[tag_group_capacity_]),
tag_group_set_(new TagGroupSet(tag_group_capacity_)), tag_group_set_(new TagGroupSet(tag_group_capacity_)),
pending_latch_outputs_(makeVertexSet(this)), postponed_arrivals_(makeVertexSet(this)),
pending_clk_endpoints_(makeVertexSet(this)), postponed_clk_endpoints_(makeVertexSet(this)),
endpoints_(makeVertexSet(this)), endpoints_(makeVertexSet(this)),
invalid_endpoints_(makeVertexSet(this)), invalid_endpoints_(makeVertexSet(this)),
@ -326,8 +328,8 @@ Search::clear()
deletePathGroups(); deletePathGroups();
deletePaths(); deletePaths();
deleteTags(); deleteTags();
clearPendingLatchOutputs(); postponed_arrivals_.clear();
pending_clk_endpoints_.clear(); postponed_clk_endpoints_.clear();
deleteFilter(); deleteFilter();
found_downstream_clk_pins_ = false; found_downstream_clk_pins_ = false;
} }
@ -644,18 +646,23 @@ Search::findFilteredArrivals(bool thru_latches)
// Search always_to_endpoint to search from exisiting arrivals at // Search always_to_endpoint to search from exisiting arrivals at
// fanin startpoints to reach -thru/-to endpoints. // fanin startpoints to reach -thru/-to endpoints.
arrival_visitor_->init(true, false, eval_pred_); arrival_visitor_->init(true, false, eval_pred_);
// Iterate until data arrivals at all latches stop changing.
postpone_latch_outputs_ = true;
enqueuePendingClkFanouts(); enqueuePendingClkFanouts();
for (int pass = 1; pass == 1 || (thru_latches && havePendingLatchOutputs()); bool have_pending_latch_outputs = false;
// Iterate until data arrivals at all latches stop changing.
for (int pass = 1;
pass == 1 || (thru_latches && have_pending_latch_outputs);
pass++) { pass++) {
if (thru_latches)
enqueuePendingLatchOutputs();
debugPrint(debug_, "search", 1, "find arrivals pass {}", pass); debugPrint(debug_, "search", 1, "find arrivals pass {}", pass);
int arrival_count = arrival_iter_->visitParallel(max_level, arrival_visitor_); int arrival_count = arrival_iter_->visitParallel(max_level, arrival_visitor_);
deleteTagsPrev();
debugPrint(debug_, "search", 1, "found {} arrivals", arrival_count); debugPrint(debug_, "search", 1, "found {} arrivals", arrival_count);
postpone_latch_outputs_ = false;
have_pending_latch_outputs = !postponed_arrivals_.empty();
for (Vertex *latch_output : postponed_arrivals_)
arrival_visitor_->visit(latch_output, true);
postponed_arrivals_.clear();
deleteTagsPrev();
} }
arrivals_exist_ = true; arrivals_exist_ = true;
} }
@ -983,57 +990,39 @@ Search::findAllArrivals(bool thru_latches,
if (!clks_only) if (!clks_only)
enqueuePendingClkFanouts(); enqueuePendingClkFanouts();
arrival_visitor_->init(false, clks_only, eval_pred_); arrival_visitor_->init(false, clks_only, eval_pred_);
bool have_pending_latch_outputs = false;
// Iterate until data arrivals at all latches stop changing. // Iterate until data arrivals at all latches stop changing.
postpone_latch_outputs_ = true; for (int pass = 1;
for (int pass = 1; pass == 1 || (thru_latches && havePendingLatchOutputs()); pass == 1 || (thru_latches && have_pending_latch_outputs);
pass++) { pass++) {
enqueuePendingLatchOutputs();
debugPrint(debug_, "search", 1, "find arrivals pass {}", pass); debugPrint(debug_, "search", 1, "find arrivals pass {}", pass);
findArrivals1(levelize_->maxLevel()); findArrivals1(levelize_->maxLevel());
if (pass > 2)
postpone_latch_outputs_ = false; have_pending_latch_outputs = !postponed_arrivals_.empty();
for (Vertex *latch_output : postponed_arrivals_)
arrival_visitor_->visit(latch_output, true);
postponed_arrivals_.clear();
} }
} }
bool // Pick up where the search stopped at the clock network boundary.
Search::havePendingLatchOutputs()
{
return !pending_latch_outputs_.empty();
}
void
Search::clearPendingLatchOutputs()
{
pending_latch_outputs_.clear();
}
void
Search::enqueuePendingLatchOutputs()
{
for (Vertex *latch_vertex : pending_latch_outputs_) {
debugPrint(debug_, "search", 2, "enqueue latch output {}",
latch_vertex->to_string(this));
arrival_iter_->enqueue(latch_vertex);
}
clearPendingLatchOutputs();
}
void void
Search::enqueuePendingClkFanouts() Search::enqueuePendingClkFanouts()
{ {
for (Vertex *vertex : pending_clk_endpoints_) { for (Vertex *vertex : postponed_clk_endpoints_) {
debugPrint(debug_, "search", 2, "enqueue clk fanout {}", debugPrint(debug_, "search", 2, "enqueue clk fanout {}",
vertex->to_string(this)); vertex->to_string(this));
arrival_iter_->enqueueAdjacentVertices(vertex, search_adj_); arrival_iter_->enqueueAdjacentVertices(vertex, search_adj_);
} }
pending_clk_endpoints_.clear(); postponed_clk_endpoints_.clear();
} }
void void
Search::postponeClkFanouts(Vertex *vertex) Search::postponeClkFanouts(Vertex *vertex)
{ {
LockGuard lock(pending_clk_endpoints_lock_); LockGuard lock(postponed_clk_endpoints_lock_);
pending_clk_endpoints_.insert(vertex); postponed_clk_endpoints_.insert(vertex);
} }
void void
@ -1103,7 +1092,7 @@ ArrivalVisitor::init0()
{ {
tag_bldr_ = new TagGroupBldr(true, this); tag_bldr_ = new TagGroupBldr(true, this);
tag_bldr_no_crpr_ = new TagGroupBldr(false, this); tag_bldr_no_crpr_ = new TagGroupBldr(false, this);
adj_pred_ = new SearchAdj(tag_bldr_, this); search_adj_ = new SearchAdjLoop(this);
} }
void void
@ -1127,14 +1116,14 @@ void
ArrivalVisitor::copyState(const StaState *sta) ArrivalVisitor::copyState(const StaState *sta)
{ {
StaState::copyState(sta); StaState::copyState(sta);
adj_pred_->copyState(sta); search_adj_->copyState(sta);
} }
ArrivalVisitor::~ArrivalVisitor() ArrivalVisitor::~ArrivalVisitor()
{ {
delete tag_bldr_; delete tag_bldr_;
delete tag_bldr_no_crpr_; delete tag_bldr_no_crpr_;
delete adj_pred_; delete search_adj_;
} }
void void
@ -1145,16 +1134,27 @@ ArrivalVisitor::setAlwaysToEndpoints(bool to_endpoints)
void void
ArrivalVisitor::visit(Vertex *vertex) ArrivalVisitor::visit(Vertex *vertex)
{
if (network_->isLatchOutput(vertex->pin()))
search_->postponeArrivals(vertex);
else
visit(vertex, false);
}
void
ArrivalVisitor::visit(Vertex *vertex,
bool with_latch_edges)
{ {
debugPrint(debug_, "search", 2, "find arrivals {}", debugPrint(debug_, "search", 2, "find arrivals {}",
vertex->to_string(this)); vertex->to_string(this));
Pin *pin = vertex->pin(); Pin *pin = vertex->pin();
tag_bldr_->init(vertex); tag_bldr_->init(vertex);
has_fanin_one_ = graph_->hasFaninOne(vertex); has_fanin_one_ = graph_->hasFaninOne(vertex);
if (crpr_active_ && !has_fanin_one_) if (crpr_active_ && !has_fanin_one_)
tag_bldr_no_crpr_->init(vertex); tag_bldr_no_crpr_->init(vertex);
visitFaninPaths(vertex); visitFaninPaths(vertex, with_latch_edges);
if (crpr_active_ if (crpr_active_
&& search_->crprPathPruningEnabled() && search_->crprPathPruningEnabled()
// No crpr for ideal clocks. // No crpr for ideal clocks.
@ -1170,15 +1170,25 @@ ArrivalVisitor::visit(Vertex *vertex)
// previous eval pass enqueue the latch outputs to be re-evaled on the // previous eval pass enqueue the latch outputs to be re-evaled on the
// next pass. // next pass.
if (arrivals_changed && network_->isLatchData(pin)) if (arrivals_changed && network_->isLatchData(pin))
search_->enqueueLatchDataOutputs(vertex); search_->postponeLatchDataOutputs(vertex);
if ((always_to_endpoints_ || arrivals_changed)) { if ((always_to_endpoints_ || arrivals_changed)) {
if (clks_only_ && vertex->isRegClk()) { if (clks_only_ && vertex->isRegClk()) {
debugPrint(debug_, "search", 3, "postponing clk fanout"); debugPrint(debug_, "search", 3, "postponing clk fanout");
search_->postponeClkFanouts(vertex); search_->postponeClkFanouts(vertex);
} }
else else {
search_->arrivalIterator()->enqueueAdjacentVertices(vertex, adj_pred_); graph_->visitFanoutEdges(vertex, search_adj_,
[this] (Edge *edge,
Vertex *fanout) {
if (edge->isDisabledLoop()) {
if (hasPendingLoopPaths(edge))
search_->postponeArrivals(fanout);
}
else
search_->arrivalIterator()->enqueue(fanout);
});
}
} }
if (arrivals_changed) { if (arrivals_changed) {
debugPrint(debug_, "search", 4, "arrivals changed"); debugPrint(debug_, "search", 4, "arrivals changed");
@ -1188,6 +1198,26 @@ ArrivalVisitor::visit(Vertex *vertex)
} }
} }
bool
ArrivalVisitor::hasPendingLoopPaths(Edge *edge) const
{
if (tag_bldr_ && tag_bldr_->hasLoopTag()) {
Vertex *from_vertex = edge->from(graph_);
TagGroup *prev_tag_group = search_->tagGroup(from_vertex);
for (auto const [from_tag, path_index] : tag_bldr_->pathIndexMap()) {
if (from_tag->isLoop()) {
// Loop false path exceptions apply to rise/fall edges so to_rf
// does not matter.
Tag *to_tag = search_->thruTag(from_tag, edge, RiseFall::rise(), nullptr);
if (to_tag
&& (prev_tag_group == nullptr || !prev_tag_group->hasTag(from_tag)))
return true;
}
}
}
return false;
}
void void
ArrivalVisitor::seedArrivals(Vertex *vertex) ArrivalVisitor::seedArrivals(Vertex *vertex)
{ {
@ -1388,24 +1418,24 @@ ArrivalVisitor::pruneCrprArrivals()
} }
void void
Search::enqueueLatchDataOutputs(Vertex *vertex) Search::postponeLatchDataOutputs(Vertex *latch_data)
{ {
VertexOutEdgeIterator edge_iter(vertex, graph_); VertexOutEdgeIterator edge_iter(latch_data, graph_);
while (edge_iter.hasNext()) { while (edge_iter.hasNext()) {
Edge *edge = edge_iter.next(); Edge *edge = edge_iter.next();
if (edge->role() == TimingRole::latchDtoQ()) { if (edge->role() == TimingRole::latchDtoQ()) {
Vertex *out_vertex = edge->to(graph_); Vertex *out_vertex = edge->to(graph_);
LockGuard lock(pending_latch_outputs_lock_); LockGuard lock(postponed_arrivals_lock_);
pending_latch_outputs_.insert(out_vertex); postponed_arrivals_.insert(out_vertex);
} }
} }
} }
void void
Search::enqueueLatchOutput(Vertex *vertex) Search::postponeArrivals(Vertex *vertex)
{ {
LockGuard lock(pending_latch_outputs_lock_); LockGuard lock(postponed_arrivals_lock_);
pending_latch_outputs_.insert(vertex); postponed_arrivals_.insert(vertex);
} }
void void
@ -1948,12 +1978,16 @@ PathVisitor::~PathVisitor()
} }
void void
PathVisitor::visitFaninPaths(Vertex *to_vertex) PathVisitor::visitFaninPaths(Vertex *to_vertex,
bool with_latch_edges)
{ {
VertexInEdgeIterator edge_iter(to_vertex, graph_); VertexInEdgeIterator edge_iter(to_vertex, graph_);
while (edge_iter.hasNext()) { while (edge_iter.hasNext()) {
Edge *edge = edge_iter.next(); Edge *edge = edge_iter.next();
if (!edge->role()->isTimingCheck()) { const TimingRole *role = edge->role();
if (!(role->isTimingCheck()
|| (role == TimingRole::latchDtoQ()
&& !with_latch_edges))) {
Vertex *from_vertex = edge->from(graph_); Vertex *from_vertex = edge->from(graph_);
const Pin *from_pin = from_vertex->pin(); const Pin *from_pin = from_vertex->pin();
const Pin *to_pin = to_vertex->pin(); const Pin *to_pin = to_vertex->pin();
@ -1994,7 +2028,8 @@ PathVisitor::visitEdge(const Pin *from_pin,
Path *from_path = from_iter.next(); Path *from_path = from_iter.next();
const Mode *mode = from_path->mode(this); const Mode *mode = from_path->mode(this);
if (mode == prev_mode if (mode == prev_mode
|| (pred_->searchFrom(from_vertex, mode) && pred_->searchThru(edge, mode) || (pred_->searchFrom(from_vertex, mode)
&& pred_->searchThru(edge, mode)
&& pred_->searchTo(to_vertex, mode))) { && pred_->searchTo(to_vertex, mode))) {
prev_mode = mode; prev_mode = mode;
const MinMax *min_max = from_path->minMax(this); const MinMax *min_max = from_path->minMax(this);
@ -2068,9 +2103,8 @@ PathVisitor::visitFromPath(const Pin *from_pin,
if (gclk) { if (gclk) {
Genclks *genclks = mode->genclks(); Genclks *genclks = mode->genclks();
VertexSet *fanins = genclks->fanins(gclk); VertexSet *fanins = genclks->fanins(gclk);
// Note: encountering a latch d->q edge means find the // Note: encountering a latch d->q edge means we need to find
// latch feedback edges, but they are referenced for // latch feedback edges.
// other edges in the gen clk fanout.
if (role == TimingRole::latchDtoQ()) if (role == TimingRole::latchDtoQ())
genclks->findLatchFdbkEdges(gclk); genclks->findLatchFdbkEdges(gclk);
EdgeSet &fdbk_edges = genclks->latchFdbkEdges(gclk); EdgeSet &fdbk_edges = genclks->latchFdbkEdges(gclk);
@ -2140,33 +2174,12 @@ PathVisitor::visitFromPath(const Pin *from_pin,
} }
else if (edge->role() == TimingRole::latchDtoQ()) { else if (edge->role() == TimingRole::latchDtoQ()) {
if (min_max == MinMax::max() && clk) { if (min_max == MinMax::max() && clk) {
bool postponed = false; arc_delay = search_->deratedDelay(from_vertex, arc, edge, false, min_max,
if (search_->postponeLatchOutputs()) { dcalc_ap, sdc);
const Path *from_clk_path = from_clk_info->crprClkPath(this); latches_->latchOutArrival(from_path, arc, edge, to_tag, arc_delay,
if (from_clk_path) { to_arrival);
Vertex *d_clk_vertex = from_clk_path->vertex(this); if (to_tag)
Level d_clk_level = d_clk_vertex->level(); to_tag = search_->thruTag(to_tag, edge, to_rf, tag_cache_);
Level q_level = to_vertex->level();
if (d_clk_level >= q_level) {
// Crpr clk path on latch data input is required to find Q
// arrival. If the data clk path level is >= Q level the
// crpr clk path prev_path pointers are not complete.
debugPrint(debug_, "search", 3, "postponed latch eval {} {} -> {} {}",
d_clk_level, d_clk_vertex->to_string(this),
edge->to_string(this), q_level);
postponed = true;
search_->enqueueLatchOutput(to_vertex);
}
}
}
if (!postponed) {
arc_delay = search_->deratedDelay(from_vertex, arc, edge, false, min_max,
dcalc_ap, sdc);
latches_->latchOutArrival(from_path, arc, edge, to_tag, arc_delay,
to_arrival);
if (to_tag)
to_tag = search_->thruTag(to_tag, edge, to_rf, tag_cache_);
}
} }
} }
else if (from_tag->isClock()) { else if (from_tag->isClock()) {

View File

@ -72,6 +72,20 @@ private:
~PathEnd(); ~PathEnd();
}; };
class Scene
{
private:
Scene();
~Scene();
};
class Mode
{
private:
Mode();
~Mode();
};
%inline %{ %inline %{
using std::string; using std::string;

View File

@ -544,6 +544,8 @@ Sta::clearNonSdc()
for (Mode *mode : modes_) { for (Mode *mode : modes_) {
mode->clkNetwork()->clkPinsInvalid(); mode->clkNetwork()->clkPinsInvalid();
mode->sim()->clear(); mode->sim()->clear();
// ref_pin edges are owned by the graph deleted below; force a rebuild.
mode->sdc()->inputDelayRefPinEdgesInvalid();
} }
search_->clear(); search_->clear();
@ -4432,9 +4434,15 @@ Sta::makeNet(const char *name,
{ {
NetworkEdit *network = networkCmdEdit(); NetworkEdit *network = networkCmdEdit();
std::string escaped = escapeBrackets(name, network); std::string escaped = escapeBrackets(name, network);
Net *net = network->makeNet(escaped, parent); if (network->findNet(parent, escaped)) {
// Sta notification unnecessary. report_->warn(1557, "net {} already exists.", name);
return net; return nullptr;
}
else {
Net *net = network->makeNet(escaped, parent);
// Sta notification unnecessary.
return net;
}
} }
void void
@ -5095,9 +5103,11 @@ Sta::delaysInvalidFromFanin(Vertex *vertex)
VertexInEdgeIterator edge_iter(vertex, graph_); VertexInEdgeIterator edge_iter(vertex, graph_);
while (edge_iter.hasNext()) { while (edge_iter.hasNext()) {
Edge *edge = edge_iter.next(); Edge *edge = edge_iter.next();
Vertex *from_vertex = edge->from(graph_); if (edge->isWire()) {
delaysInvalidFrom(from_vertex); Vertex *from_vertex = edge->from(graph_);
search_->requiredInvalid(from_vertex); delaysInvalidFrom(from_vertex);
search_->requiredInvalid(from_vertex);
}
} }
} }

View File

@ -14,7 +14,7 @@ report_checks > /dev/null
puts "--- Corner commands ---" puts "--- Corner commands ---"
set corner [sta::cmd_scene] set corner [sta::cmd_scene]
puts "Corner name: $corner" puts "Corner name: [get_name $corner]"
puts "Multi corner: [sta::multi_scene]" puts "Multi corner: [sta::multi_scene]"
puts "--- ClkSkew report with propagated clock ---" puts "--- ClkSkew report with propagated clock ---"

View File

@ -536,14 +536,23 @@ proc parse_scenes_or_all { keys_var } {
} }
} }
proc find_scenes { scene_names } { proc find_scenes { scenes_arg } {
set scenes {} set scenes {}
foreach scene_name $scene_names { foreach scene_arg $scenes_arg {
set scene [find_scene $scene_name] if { [is_object $scene_arg] } {
if { $scene == "NULL" } { set object_type [object_type $scene_arg]
sta_error 134 "$scene_name is not the name of a scene." if { $object_type == "Scene" } {
lappend scenes $scene_arg
} else {
sta_error 135 "scene object type '$object_type' is not a scene."
}
} else { } else {
lappend scenes $scene set scene [find_scene $scene_arg]
if { $scene == "NULL" } {
sta_error 134 "$scene_arg is not the name of a scene."
} else {
lappend scenes $scene
}
} }
} }
return $scenes return $scenes

View File

@ -57,6 +57,10 @@ proc get_object_property { object prop } {
return [net_property $object $prop] return [net_property $object $prop]
} elseif { $object_type == "Clock" } { } elseif { $object_type == "Clock" } {
return [clock_property $object $prop] return [clock_property $object $prop]
} elseif { $object_type == "Scene" } {
return [scene_property $object $prop]
} elseif { $object_type == "Mode" } {
return [mode_property $object $prop]
} elseif { $object_type == "Port" } { } elseif { $object_type == "Port" } {
return [port_property $object $prop] return [port_property $object $prop]
} elseif { $object_type == "LibertyPort" } { } elseif { $object_type == "LibertyPort" } {

View File

@ -102,7 +102,12 @@ define_cmd_args "set_scene" {scene_name}
proc set_scene { args } { proc set_scene { args } {
check_argc_eq1 "set_scene" $args check_argc_eq1 "set_scene" $args
set_cmd_scene [lindex $args 0] set scene_name [lindex $args 0]
set scene [find_scene $scene_name]
if { $scene == "NULL" } {
sta_error 578 "$scene_name is not the name of a scene."
}
set_cmd_scene $scene
} }
################################################################ ################################################################
@ -118,10 +123,16 @@ proc get_scenes { args } {
} else { } else {
set scene_name [lindex $args 0] set scene_name [lindex $args 0]
} }
set mode_names {}
if { [info exists keys(-modes)] } { if { [info exists keys(-modes)] } {
set mode_names $keys(-modes) set modes {}
return [find_mode_scenes_matching $scene_name $mode_names] foreach mode_name $keys(-modes) {
set mode [find_mode $mode_name]
if { $mode == "NULL" } {
sta_error 579 "$mode_name is not the name of a mode."
}
lappend modes $mode
}
return [find_mode_scenes_matching $scene_name $modes]
} else { } else {
return [find_scenes_matching $scene_name] return [find_scenes_matching $scene_name]
} }

View File

@ -1189,106 +1189,70 @@ using namespace sta;
$1 = tclListSetConstChar($input, interp); $1 = tclListSetConstChar($input, interp);
} }
%typemap(in) Mode* {
Tcl_Size length;
const char *arg = Tcl_GetStringFromObj($input, &length);
if (stringEqual(arg, "NULL"))
$1 = nullptr;
else {
void *obj;
if (SWIG_ConvertPtr($input, &obj, SWIGTYPE_p_Mode, false) != TCL_OK) {
tclArgError(interp, 2174, "{} is not a mode object.", arg);
return TCL_ERROR;
}
$1 = reinterpret_cast<Mode*>(obj);
}
}
%typemap(out) Mode* { %typemap(out) Mode* {
const Mode *mode = $1; Mode *mode = $1;
if (mode) if (mode) {
Tcl_SetResult(interp, const_cast<char*>($1->name().c_str()), TCL_VOLATILE); Tcl_Obj *obj = SWIG_NewInstanceObj(mode, SWIGTYPE_p_Mode, false);
Tcl_SetObjResult(interp, obj);
}
else else
Tcl_SetResult(interp, const_cast<char*>("NULL"), TCL_STATIC); Tcl_SetResult(interp, const_cast<char*>("NULL"), TCL_STATIC);
} }
%typemap(in) ModeSeq { %typemap(in) ModeSeq {
Tcl_Size argc; $1 = tclListSeq<Mode*>($input, SWIGTYPE_p_Mode, interp);
Tcl_Obj **argv;
Sta *sta = Sta::sta();
std::vector<Mode*> seq;
if (Tcl_ListObjGetElements(interp, $input, &argc, &argv) == TCL_OK
&& argc > 0) {
for (int i = 0; i < argc; i++) {
Tcl_Size length;
const char *mode_name = Tcl_GetStringFromObj(argv[i], &length);
Mode *mode = sta->findMode(mode_name);
if (mode)
seq.push_back(mode);
else {
tclArgError(interp, 2174, "mode {} not found.", mode_name);
return TCL_ERROR;
}
}
}
$1 = seq;
} }
%typemap(out) ModeSeq { %typemap(out) ModeSeq {
Tcl_Obj *list = Tcl_NewListObj(0, nullptr); seqTclList<ModeSeq, Mode>($1, SWIGTYPE_p_Mode, interp);
ModeSeq &modes = $1;
for (Mode *mode : modes) {
const std::string &mode_name = mode->name();
Tcl_Obj *obj = Tcl_NewStringObj(mode_name.c_str(), mode_name.size());
Tcl_ListObjAppendElement(interp, list, obj);
}
Tcl_SetObjResult(interp, list);
} }
%typemap(in) Scene* { %typemap(in) Scene* {
sta::Sta *sta = Sta::sta();
Tcl_Size length; Tcl_Size length;
std::string scene_name = Tcl_GetStringFromObj($input, &length); const char *arg = Tcl_GetStringFromObj($input, &length);
// parse_scene_or_all support depreated 11/21/2025 if (stringEqual(arg, "NULL"))
if (scene_name == "NULL")
$1 = nullptr; $1 = nullptr;
else { else {
Scene *scene = sta->findScene(scene_name); void *obj;
if (scene) if (SWIG_ConvertPtr($input, &obj, SWIGTYPE_p_Scene, false) != TCL_OK) {
$1 = scene; tclArgError(interp, 2173, "{} is not a scene object.", arg);
else {
tclArgError(interp, 2173, "scene {} not found.", scene_name.c_str());
return TCL_ERROR; return TCL_ERROR;
} }
$1 = reinterpret_cast<Scene*>(obj);
} }
} }
%typemap(out) Scene* { %typemap(out) Scene* {
const Scene *scene = $1; Scene *scene = $1;
if (scene) if (scene) {
Tcl_SetResult(interp, const_cast<char*>($1->name().c_str()), TCL_VOLATILE); Tcl_Obj *obj = SWIG_NewInstanceObj(scene, SWIGTYPE_p_Scene, false);
Tcl_SetObjResult(interp, obj);
}
else else
Tcl_SetResult(interp, const_cast<char*>("NULL"), TCL_STATIC); Tcl_SetResult(interp, const_cast<char*>("NULL"), TCL_STATIC);
} }
%typemap(in) SceneSeq { %typemap(in) SceneSeq {
Tcl_Size argc; $1 = tclListSeq<Scene*>($input, SWIGTYPE_p_Scene, interp);
Tcl_Obj **argv;
Sta *sta = Sta::sta();
std::vector<Scene*> seq;
if (Tcl_ListObjGetElements(interp, $input, &argc, &argv) == TCL_OK
&& argc > 0) {
for (int i = 0; i < argc; i++) {
Tcl_Size length;
const char *scene_name = Tcl_GetStringFromObj(argv[i], &length);
Scene *scene = sta->findScene(scene_name);
if (scene)
seq.push_back(scene);
else {
tclArgError(interp, 2172, "scene {} not found.", scene_name);
return TCL_ERROR;
}
}
}
$1 = seq;
} }
%typemap(out) SceneSeq { %typemap(out) SceneSeq {
Tcl_Obj *list = Tcl_NewListObj(0, nullptr); seqTclList<SceneSeq, Scene>($1, SWIGTYPE_p_Scene, interp);
SceneSeq &scenes = $1;
for (Scene *scene : scenes) {
const std::string &scene_name = scene->name();
Tcl_Obj *obj = Tcl_NewStringObj(scene_name.c_str(), scene_name.size());
Tcl_ListObjAppendElement(interp, list, obj);
}
Tcl_SetObjResult(interp, list);
} }
%typemap(in) PropertyValue { %typemap(in) PropertyValue {

6
test/get_scenes.ok Normal file
View File

@ -0,0 +1,6 @@
[get_scenes *]
scene1
scene2
[get_modes *]
mode1
mode2

20
test/get_scenes.tcl Normal file
View File

@ -0,0 +1,20 @@
# get_scenes / get_modes return objects
read_liberty ../examples/asap7_small_ff.lib.gz
read_liberty ../examples/asap7_small_ss.lib.gz
read_verilog ../examples/reg1_asap7.v
link_design top
read_sdc -mode mode1 ../examples/mcmm2_mode1.sdc
read_sdc -mode mode2 ../examples/mcmm2_mode2.sdc
read_spef -name reg1_ff ../examples/reg1_asap7.spef
read_spef -name reg1_ss ../examples/reg1_asap7_ss.spef
define_scene scene1 -mode mode1 -liberty asap7_small_ff -spef reg1_ff
define_scene scene2 -mode mode2 -liberty asap7_small_ss -spef reg1_ss
puts {[get_scenes *]}
report_object_names [get_scenes *]
puts {[get_modes *]}
report_object_names [get_modes *]

View File

@ -0,0 +1,112 @@
=== before rebuild: in1 (ref_pin) ===
Startpoint: in1 (input port clocked by clk)
Endpoint: r1 (rising edge-triggered flip-flop clocked by clk)
Path Group: clk
Path Type: max
Delay Time Description
---------------------------------------------------------
0.00 0.00 clock clk (rise edge)
0.00 0.00 clock network delay (ideal)
100.00 100.00 ^ input external delay
0.00 100.00 ^ in1 (in)
0.00 100.00 ^ r1/D (DFFHQx4_ASAP7_75t_R)
100.00 data arrival time
500.00 500.00 clock clk (rise edge)
0.00 500.00 clock network delay (ideal)
0.00 500.00 clock reconvergence pessimism
500.00 ^ r1/CLK (DFFHQx4_ASAP7_75t_R)
-11.41 488.59 library setup time
488.59 data required time
---------------------------------------------------------
488.59 data required time
-100.00 data arrival time
---------------------------------------------------------
388.59 slack (MET)
=== before rebuild: in2 (control) ===
Startpoint: in2 (input port clocked by clk)
Endpoint: r2 (rising edge-triggered flip-flop clocked by clk)
Path Group: clk
Path Type: max
Delay Time Description
---------------------------------------------------------
0.00 0.00 clock clk (rise edge)
0.00 0.00 clock network delay (ideal)
100.00 100.00 ^ input external delay
0.00 100.00 ^ in2 (in)
0.00 100.00 ^ r2/D (DFFHQx4_ASAP7_75t_R)
100.00 data arrival time
500.00 500.00 clock clk (rise edge)
0.00 500.00 clock network delay (ideal)
0.00 500.00 clock reconvergence pessimism
500.00 ^ r2/CLK (DFFHQx4_ASAP7_75t_R)
-13.11 486.89 library setup time
486.89 data required time
---------------------------------------------------------
486.89 data required time
-100.00 data arrival time
---------------------------------------------------------
386.89 slack (MET)
=== after rebuild: in1 (ref_pin) ===
Startpoint: in1 (input port clocked by clk)
Endpoint: r1 (rising edge-triggered flip-flop clocked by clk)
Path Group: clk
Path Type: max
Delay Time Description
---------------------------------------------------------
0.00 0.00 clock clk (rise edge)
0.00 0.00 clock network delay (ideal)
100.00 100.00 ^ input external delay
0.00 100.00 ^ in1 (in)
0.00 100.00 ^ r1/D (DFFHQx4_ASAP7_75t_R)
100.00 data arrival time
500.00 500.00 clock clk (rise edge)
0.00 500.00 clock network delay (ideal)
0.00 500.00 clock reconvergence pessimism
500.00 ^ r1/CLK (DFFHQx4_ASAP7_75t_R)
-11.41 488.59 library setup time
488.59 data required time
---------------------------------------------------------
488.59 data required time
-100.00 data arrival time
---------------------------------------------------------
388.59 slack (MET)
=== after rebuild: in2 (control) ===
Startpoint: in2 (input port clocked by clk)
Endpoint: r2 (rising edge-triggered flip-flop clocked by clk)
Path Group: clk
Path Type: max
Delay Time Description
---------------------------------------------------------
0.00 0.00 clock clk (rise edge)
0.00 0.00 clock network delay (ideal)
100.00 100.00 ^ input external delay
0.00 100.00 ^ in2 (in)
0.00 100.00 ^ r2/D (DFFHQx4_ASAP7_75t_R)
100.00 data arrival time
500.00 500.00 clock clk (rise edge)
0.00 500.00 clock network delay (ideal)
0.00 500.00 clock reconvergence pessimism
500.00 ^ r2/CLK (DFFHQx4_ASAP7_75t_R)
-13.11 486.89 library setup time
486.89 data required time
---------------------------------------------------------
486.89 data required time
-100.00 data arrival time
---------------------------------------------------------
386.89 slack (MET)

View File

@ -0,0 +1,28 @@
# set_input_delay -reference_pin must survive a graph rebuild that keeps SDC
# (resizer-like: sta::network_changed_non_sdc deletes the graph but not the
# constraints). The ref_pin->input graph edge is owned by the graph; its
# existence flag (PortDelay::ref_pin_edges_exist_) must be reset on graph
# teardown so ensureInputDelayRefPinEdges() rebuilds the edge. Otherwise in1
# loses its arrival seeding and reports "No paths found" after the rebuild.
# in2 is a plain input delay (no ref_pin) used as a control.
read_liberty asap7_small.lib.gz
read_verilog reg1_asap7.v
link_design top
create_clock -name clk -period 500 {clk1 clk2 clk3}
set_input_delay -clock clk 100 -reference_pin r2/CLK [get_ports in1]
set_input_delay -clock clk 100 [get_ports in2]
set_input_transition 10 {in1 in2 clk1 clk2 clk3}
set_output_delay -clock clk 1 [get_ports out]
puts "=== before rebuild: in1 (ref_pin) ==="
report_checks -from in1 -path_delay max
puts "=== before rebuild: in2 (control) ==="
report_checks -from in2 -path_delay max
sta::network_changed_non_sdc
puts "=== after rebuild: in1 (ref_pin) ==="
report_checks -from in1 -path_delay max
puts "=== after rebuild: in2 (control) ==="
report_checks -from in2 -path_delay max

View File

@ -147,7 +147,9 @@ record_public_tests {
get_is_memory get_is_memory
get_lib_pins_of_objects get_lib_pins_of_objects
get_noargs get_noargs
get_scenes
get_objrefs get_objrefs
input_delay_ref_pin_rebuild
liberty_arcs_one2one_1 liberty_arcs_one2one_1
liberty_arcs_one2one_2 liberty_arcs_one2one_2
liberty_backslash_eol liberty_backslash_eol

View File

@ -1468,8 +1468,7 @@ VerilogReader::linkNetwork(std::string_view top_cell_name,
while (net_name_iter->hasNext()) { while (net_name_iter->hasNext()) {
const std::string &net_name = net_name_iter->next(); const std::string &net_name = net_name_iter->next();
Port *port = network_->findPort(top_cell, net_name); Port *port = network_->findPort(top_cell, net_name);
Net *net = Net *net = bindings.ensureNetBinding(net_name, top_instance, network_);
bindings.ensureNetBinding(net_name, top_instance, network_);
// Guard against repeated port name. // Guard against repeated port name.
if (network_->findPin(top_instance, port) == nullptr) { if (network_->findPin(top_instance, port) == nullptr) {
Pin *pin = network_->makePin(top_instance, port, nullptr); Pin *pin = network_->makePin(top_instance, port, nullptr);
@ -1521,13 +1520,11 @@ VerilogReader::makeModuleInstBody(VerilogModule *module,
if (assign) if (assign)
mergeAssignNet(assign, module, inst, bindings); mergeAssignNet(assign, module, inst, bindings);
if (dir->isGround()) { if (dir->isGround()) {
Net *net = Net *net = bindings->ensureNetBinding(arg->netName(), inst, network_);
bindings->ensureNetBinding(arg->netName(), inst, network_);
network_->addConstantNet(net, LogicValue::zero); network_->addConstantNet(net, LogicValue::zero);
} }
if (dir->isPower()) { if (dir->isPower()) {
Net *net = Net *net = bindings->ensureNetBinding(arg->netName(), inst, network_);
bindings->ensureNetBinding(arg->netName(), inst, network_);
network_->addConstantNet(net, LogicValue::one); network_->addConstantNet(net, LogicValue::one);
} }
} }