From 832c62b57952637b5f6959ca9a8c513524481723 Mon Sep 17 00:00:00 2001 From: Drew Lewis Date: Thu, 18 Jun 2026 16:52:14 -0400 Subject: [PATCH 01/30] Refactor DMP solver to use Eigen and implement Determinant Guarded solver (#450) This commit refactors the Dartu-Menezes-Pileggi (DMP) effective capacitance algorithm solver to use the Eigen library, and implements a Determinant Guarded solver to handle singular and near-singular Jacobians safely and efficiently. Key changes: 1. Refactored the DMP solver to use Eigen stack-based data structures, replacing the custom Crout LU decomposition and solver. 2. Extracted the linear solver logic into a dedicated helper function DmpAlg::solveNewtonStep. 3. Implemented a "Determinant Guarded" solver: - Manually checks the determinant of the Jacobian (safety guard). - Throws a DmpError if the determinant is dangerously close to zero (|det| < 1e-12), safely triggering the lumped capacitance fallback. - Otherwise, solves using Eigen's highly optimized analytical inverse (fast path). 4. Added documentation in the comments on how to easily swap back to LU decomposition with partial pivoting (PartialPivLU) if any numerical precision issues arise in the future. Benchmark Results (Optimized Release Build, 5,288 DMP calls): | Test Case | Calls | 1. Original (Crout LU) | 2. Eigen (PartialPivLU) | 3. Eigen (Determinant Guarded) | Speedup (3 vs 1) | Speedup (3 vs 2) | | :--- | :---: | :---: | :---: | :---: | :---: | :---: | | power | 2,608 | 1.8822 ms | 1.6791 ms | 1.2702 ms | +32.5% | +24.4% | | power_vcd | 2,608 | 1.8729 ms | 1.6704 ms | 1.2768 ms | +31.8% | +23.6% | | spef_parasitics | 24 | 0.0193 ms | 0.0187 ms | 0.0140 ms | +27.5% | +25.1% | | mcmm3 | 48 | 0.0728 ms | 0.0756 ms | 0.0817 ms | -12.2% | -8.1% | | Total DMP Time | 5,288 | 3.8472 ms | 3.4438 ms | 2.6427 ms | +31.3% | +23.3% | --- dcalc/DmpCeff.cc | 350 ++++++++++++++++++++++------------------------- dcalc/DmpCeff.hh | 46 ++++--- 2 files changed, 194 insertions(+), 202 deletions(-) diff --git a/dcalc/DmpCeff.cc b/dcalc/DmpCeff.cc index 5d0aef1d..6a37f348 100644 --- a/dcalc/DmpCeff.cc +++ b/dcalc/DmpCeff.cc @@ -31,6 +31,7 @@ // slew voltage is matched instead of y20 in eqn 12. #include "DmpCeff.hh" +#include #include #include @@ -137,20 +138,23 @@ DmpAlg::init(const LibertyLibrary *drvr_library, void DmpAlg::findDriverParams(double ceff) { + Eigen::Vector3d x = Eigen::Vector3d::Zero(); if (nr_order_ == 3) - x_[DmpParam::ceff] = ceff; + x[DmpParam::ceff] = ceff; auto [t_vth, t_vl, slew] = gateDelays(ceff); // Scale slew to 0-100% double dt = slew / (vh_ - vl_); double t0 = t_vth + std::log(1.0 - vth_) * rd_ * ceff - vth_ * dt; - x_[DmpParam::dt] = dt; - x_[DmpParam::t0] = t0; - newtonRaphson(); - t0_ = x_[DmpParam::t0]; - dt_ = x_[DmpParam::dt]; + x[DmpParam::dt] = dt; + x[DmpParam::t0] = t0; + newtonRaphson(x); + t0_ = x[DmpParam::t0]; + dt_ = x[DmpParam::dt]; + if (nr_order_ == 3) + ceff_ = x[DmpParam::ceff]; debugPrint(debug_, "dmp_ceff", 3, " t0 = {} dt = {} ceff = {}", units_->timeUnit()->asString(t0_), units_->timeUnit()->asString(dt_), - units_->capacitanceUnit()->asString(x_[DmpParam::ceff])); + units_->capacitanceUnit()->asString(ceff_)); if (debug_->check("dmp_ceff", 4)) showVo(); } @@ -241,21 +245,21 @@ DmpAlg::y0dcl(double t, } void -DmpAlg::showX() +DmpAlg::showX(const Eigen::Vector3d &x) { 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 -DmpAlg::showFvec() +DmpAlg::showFvec(const Eigen::Vector3d &fvec) { 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 -DmpAlg::showJacobian() +DmpAlg::showJacobian(const Eigen::Matrix3d &fjac) { std::string line = " "; for (int j = 0; j < nr_order_; j++) @@ -265,7 +269,7 @@ DmpAlg::showJacobian() line.clear(); line += sta::format("{:4} ", dmp_func_index_strings[i]); 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); } } @@ -505,7 +509,9 @@ DmpCap::loadDelaySlew(const Pin *, } void -DmpCap::evalDmpEqns() +DmpCap::evalDmpEqns(Eigen::Vector3d &, + Eigen::Vector3d &, + Eigen::Matrix3d &) { } @@ -584,7 +590,6 @@ DmpPi::gateDelaySlew() double slew = 0.0; try { findDriverParamsPi(); - ceff_ = x_[DmpParam::ceff]; auto [table_delay, table_slew] = gateCapDelaySlew(ceff_); delay = table_delay; // slew = table_slew; @@ -623,60 +628,85 @@ DmpPi::findDriverParamsPi() // Given x_ as a vector of input parameters, fill fvec_ with the // equations evaluated at x_ and fjac_ with the jacobian evaluated at x_. void -DmpPi::evalDmpEqns() +DmpPi::evalDmpEqns(Eigen::Vector3d &x, + Eigen::Vector3d &fvec, + Eigen::Matrix3d &fjac) { - double t0 = x_[DmpParam::t0]; - double dt = x_[DmpParam::dt]; - double ceff = x_[DmpParam::ceff]; + const double t0 = x[DmpParam::t0]; + const double dt = x[DmpParam::dt]; + 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"); - if (ceff > (c1_ + c2_)) + } + if (ceff > (c1_ + c2_)) { 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); - if (slew == 0.0) + if (slew == 0.0) { throw DmpError("eqn eval failed: slew = 0"); + } - double ceff_time = slew / (vh_ - vl_); - ceff_time = std::min(ceff_time, 1.4 * dt); + // ceff_time is bounded by 1.4 * dt. + const double ceff_time = std::min(slew / (vh_ - vl_), 1.4 * dt); - if (dt <= 0.0) - throw DmpError("eqn eval failed: dt < 0"); + // Pre-calculate exponential terms to avoid redundant calls to + // 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); - double exp_p2_dt = exp2(-p2_ * dt); - double exp_dt_rd_ceff = exp2(-dt / (rd_ * ceff)); + // Evaluate function values (residuals). + const double y50 = y(t_vth, t0, dt, ceff).first; + const double y20 = y(t_vl, t0, dt, ceff).first; - double y50 = y(t_vth, t0, dt, ceff).first; - // Match Vl. - double y20 = y(t_vl, t0, dt, ceff).first; - 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); + fvec[DmpFunc::ipi] = ipiIceff(t0, dt, ceff_time, ceff); + fvec[DmpFunc::y50] = y50 - vth_; + fvec[DmpFunc::y20] = y20 - vl_; - std::tie(fjac_[DmpFunc::y20][DmpParam::t0], - fjac_[DmpFunc::y20][DmpParam::dt], - fjac_[DmpFunc::y20][DmpParam::ceff]) = dy(t_vl, t0, dt, ceff); + // Pre-calculate common sub-expressions for the Jacobian derivatives. + const double b_div_p1 = B_ / p1_; + const double d_div_p2 = D_ / p2_; + const double rd_ceff = rd_ * ceff; - std::tie(fjac_[DmpFunc::y50][DmpParam::t0], - fjac_[DmpFunc::y50][DmpParam::dt], - fjac_[DmpFunc::y50][DmpParam::ceff]) = dy(t_vth, t0, dt, ceff); + // Row 1 (Ipi derivatives). + fjac(DmpFunc::ipi, DmpParam::t0) = 0.0; + + // 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)) { - showX(); - showFvec(); - showJacobian(); + showX(x); + showFvec(fvec); + showJacobian(fjac); report_->report("................."); } } @@ -741,35 +771,37 @@ DmpOnePole::DmpOnePole(StaState *sta) : } void -DmpOnePole::evalDmpEqns() +DmpOnePole::evalDmpEqns(Eigen::Vector3d &x, + Eigen::Vector3d &fvec, + Eigen::Matrix3d &fjac) { - double t0 = x_[DmpParam::t0]; - double dt = x_[DmpParam::dt]; + double t0 = x[DmpParam::t0]; + double dt = x[DmpParam::dt]; auto [t_vth, t_vl, ignore1] = gateDelays(ceff_); double ignore2; 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::y20] = y(t_vl, t0, dt, ceff_).first - vl_; + fvec[DmpFunc::y50] = y(t_vth, t0, dt, ceff_).first - vth_; + fvec[DmpFunc::y20] = y(t_vl, t0, dt, ceff_).first - vl_; if (debug_->check("dmp_ceff", 4)) { - showX(); - showFvec(); + showX(x); + showFvec(fvec); } - std::tie(fjac_[DmpFunc::y20][DmpParam::t0], - fjac_[DmpFunc::y20][DmpParam::dt], + std::tie(fjac(DmpFunc::y20, DmpParam::t0), + fjac(DmpFunc::y20, DmpParam::dt), ignore2) = dy(t_vl, t0, dt, ceff_); - std::tie(fjac_[DmpFunc::y50][DmpParam::t0], - fjac_[DmpFunc::y50][DmpParam::dt], + std::tie(fjac(DmpFunc::y50, DmpParam::t0), + fjac(DmpFunc::y50, DmpParam::dt), ignore2) = dy(t_vth, t0, dt, ceff_); if (debug_->check("dmp_ceff", 4)) { - showJacobian(); + showJacobian(fjac); 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%). // evalDmpEqns() fills fvec_ and fjac_. void -DmpAlg::newtonRaphson() +DmpAlg::newtonRaphson(Eigen::Vector3d &x) { - for (int k = 0; k < newton_raphson_max_iter_; k++) { - evalDmpEqns(); - for (int i = 0; i < nr_order_; i++) - // Right-hand side of linear equations. - p_[i] = -fvec_[i]; - luDecomp(); - luSolve(); + Eigen::Vector3d fvec = Eigen::Vector3d::Zero(); + Eigen::Matrix3d fjac = Eigen::Matrix3d::Zero(); + Eigen::Vector3d p = Eigen::Vector3d::Zero(); + + for (int k = 0; k < newton_raphson_max_iter_; k++) { + evalDmpEqns(x, fvec, fjac); + + 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) { - evalDmpEqns(); return; } } throw DmpError("Newton-Raphson max iterations exceeded"); } -// luDecomp, luSolve based on MatClass from C. R. Birchenhall, -// 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. +// Solves the linear system J * p = -f (Jacobian * step = -residuals) for the Newton step. // -// Replaces fjac_[0..nr_order_-1][*] by the LU decomposition. -// index_[0..nr_order_-1] is an output vector of the row permutations. -void -DmpAlg::luDecomp() +// This implementation uses a "Determinant Guarded" solver: +// 1. Manually computes/checks the determinant of the Jacobian (safety guard). +// 2. If the determinant is dangerously close to zero (< 1e-12), throws a DmpError. +// 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. - for (int i = 0; i < size; i++) { - double big = 0.0; - 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]; + double det = fjac.determinant(); + if (std::abs(det) < 1e-12) { + throw DmpError("Jacobian is singular (order 3)"); } + p = fjac.inverse() * -fvec; + return p; } //////////////////////////////////////////////////////////////// diff --git a/dcalc/DmpCeff.hh b/dcalc/DmpCeff.hh index a2e8a144..6db5f338 100644 --- a/dcalc/DmpCeff.hh +++ b/dcalc/DmpCeff.hh @@ -27,6 +27,9 @@ #include #include +#include +#include + #include "LibertyClass.hh" #include "LumpedCapDelayCalc.hh" @@ -59,18 +62,21 @@ public: double elmore); double ceff() { return ceff_; } - // Given x_ as a vector of input parameters, fill fvec_ with the - // equations evaluated at x_ and fjac_ with the jabobian evaluated at x_. - virtual void evalDmpEqns() = 0; + virtual void + evalDmpEqns(Eigen::Vector3d &x, + Eigen::Vector3d &fvec, + Eigen::Matrix3d &fjac) = 0; // Output response to vs(t) ramp driving pi model load (vo, dvo_dt). std::pair Vo(double t); // Load response to driver waveform (vl, dvl/dt). std::pair Vl(double t); protected: - void luDecomp(); - void luSolve(); - void newtonRaphson(); + void newtonRaphson(Eigen::Vector3d &x); + // Solves J * p = -f using a fast analytical solver with singularity checks. + // 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. void findDriverParams(double ceff); std::pair gateCapDelaySlew(double ceff); @@ -84,9 +90,9 @@ protected: double cl); double y0dcl(double t, double cl); - void showX(); - void showFvec(); - void showJacobian(); + void showX(const Eigen::Vector3d &x); + void showFvec(const Eigen::Vector3d &fvec); + void showJacobian(const Eigen::Matrix3d &fjac); std::pair findDriverDelaySlew(); double findVoCrossing(double vth, double t_lower, @@ -148,12 +154,7 @@ protected: static constexpr int max_nr_order_ = 3; - std::array x_; - std::array fvec_; - std::array, max_nr_order_> fjac_; - std::array scale_; - std::array p_; - std::array index_; + // Driver slew used to check load delay. double drvr_slew_; @@ -194,7 +195,10 @@ public: std::pair gateDelaySlew() override; std::pair loadDelaySlew(const Pin *, double elmore) override; - void evalDmpEqns() override; + void + evalDmpEqns(Eigen::Vector3d &x, + Eigen::Vector3d &fvec, + Eigen::Matrix3d &fjac) override; protected: double voCrossingUpperBound() override; @@ -219,7 +223,10 @@ public: double rpi, double c1) override; std::pair gateDelaySlew() override; - void evalDmpEqns() override; + void + evalDmpEqns(Eigen::Vector3d &x, + Eigen::Vector3d &fvec, + Eigen::Matrix3d &fjac) override; protected: double voCrossingUpperBound() override; @@ -255,7 +262,10 @@ class DmpOnePole : public DmpAlg { public: DmpOnePole(StaState *sta); - void evalDmpEqns() override; + void + evalDmpEqns(Eigen::Vector3d &x, + Eigen::Vector3d &fvec, + Eigen::Matrix3d &fjac) override; protected: double voCrossingUpperBound() override; From 9a11f094b9e499d6989cf66cd458ea9d3537e6e2 Mon Sep 17 00:00:00 2001 From: James Cherry Date: Wed, 17 Jun 2026 18:27:48 -0700 Subject: [PATCH 02/30] typos/format Signed-off-by: James Cherry --- search/Latches.cc | 4 ++-- search/PathEnum.cc | 3 ++- search/Search.cc | 6 +++--- 3 files changed, 7 insertions(+), 6 deletions(-) diff --git a/search/Latches.cc b/search/Latches.cc index f8d5b68e..73fc8929 100644 --- a/search/Latches.cc +++ b/search/Latches.cc @@ -359,7 +359,7 @@ Latches::latchOutArrival(const Path *data_path, arc_delay = search_->deratedDelay(data_vertex, d_q_arc, d_q_edge, false, min_max, dcalc_ap, sdc); 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 @@ -368,7 +368,7 @@ Latches::latchOutArrival(const Path *data_path, // 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. + // with multiple threads. // Kill the crprClklPath to be safe. const ClkInfo *data_clk_info = data_path->clkInfo(this); const ClkInfo *q_clk_info = diff --git a/search/PathEnum.cc b/search/PathEnum.cc index 7c65f26e..7abd4a80 100644 --- a/search/PathEnum.cc +++ b/search/PathEnum.cc @@ -381,7 +381,8 @@ PathEnumFaninVisitor::visitEdge(const Pin *from_pin, Path *from_path = from_iter.next(); const Mode *mode = from_path->mode(this); 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) // Fanin paths are broken by path delay internal pin startpoints. && !sdc->isPathDelayInternalFromBreak(to_pin)) { diff --git a/search/Search.cc b/search/Search.cc index 8e78f358..28277d3e 100644 --- a/search/Search.cc +++ b/search/Search.cc @@ -95,9 +95,9 @@ EvalPred::searchThru(Edge *edge, { const TimingRole *role = edge->role(); return SearchPred0::searchThru(edge, mode) - && (sta_->variables()->dynamicLoopBreaking() || !edge->isDisabledLoop()) - && (search_thru_latches_ || role->isLatchDtoQ() - || sta_->latches()->latchDtoQState(edge, mode) == LatchEnableState::open); + && (sta_->variables()->dynamicLoopBreaking() || !edge->isDisabledLoop()) + && (search_thru_latches_ || role->isLatchDtoQ() + || sta_->latches()->latchDtoQState(edge, mode) == LatchEnableState::open); } bool From 1b71a26667a1d252ed3586f13aa383774d2d97c5 Mon Sep 17 00:00:00 2001 From: James Cherry Date: Wed, 17 Jun 2026 18:29:15 -0700 Subject: [PATCH 03/30] Levelize::isRoot bidirect leftover Signed-off-by: James Cherry --- search/Levelize.cc | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/search/Levelize.cc b/search/Levelize.cc index baacfdb3..76788b40 100644 --- a/search/Levelize.cc +++ b/search/Levelize.cc @@ -187,9 +187,7 @@ Levelize::isRoot(Vertex *vertex) if (searchThru(edge)) return false; } - // Levelize bidirect driver as if it was a fanout of the bidirect load. - return !(graph_delay_calc_->bidirectDrvrSlewFromLoad(vertex->pin()) - && vertex->isBidirectDriver()); + return true; } bool From 0e79e35d17556c0adfe17e2b4b2addc21a734e4d Mon Sep 17 00:00:00 2001 From: James Cherry Date: Wed, 17 Jun 2026 18:29:59 -0700 Subject: [PATCH 04/30] indent Signed-off-by: James Cherry --- dcalc/GraphDelayCalc.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dcalc/GraphDelayCalc.cc b/dcalc/GraphDelayCalc.cc index c61ebfed..6fac6faa 100644 --- a/dcalc/GraphDelayCalc.cc +++ b/dcalc/GraphDelayCalc.cc @@ -378,7 +378,7 @@ void GraphDelayCalc::seedInvalidDelays() { for (Vertex *vertex : invalid_delays_) - iter_->enqueue(vertex); + iter_->enqueue(vertex); invalid_delays_.clear(); } From 9e39e42703da2ffaa6d1b31f875fbfbf61f9e270 Mon Sep 17 00:00:00 2001 From: James Cherry Date: Thu, 18 Jun 2026 13:38:47 -0700 Subject: [PATCH 05/30] comment Signed-off-by: James Cherry --- search/Search.cc | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/search/Search.cc b/search/Search.cc index 28277d3e..22fe70b0 100644 --- a/search/Search.cc +++ b/search/Search.cc @@ -1388,9 +1388,9 @@ ArrivalVisitor::pruneCrprArrivals() } void -Search::enqueueLatchDataOutputs(Vertex *vertex) +Search::enqueueLatchDataOutputs(Vertex *latch_data) { - VertexOutEdgeIterator edge_iter(vertex, graph_); + VertexOutEdgeIterator edge_iter(latch_data, graph_); while (edge_iter.hasNext()) { Edge *edge = edge_iter.next(); if (edge->role() == TimingRole::latchDtoQ()) { @@ -2068,9 +2068,8 @@ PathVisitor::visitFromPath(const Pin *from_pin, if (gclk) { Genclks *genclks = mode->genclks(); VertexSet *fanins = genclks->fanins(gclk); - // Note: encountering a latch d->q edge means find the - // latch feedback edges, but they are referenced for - // other edges in the gen clk fanout. + // Note: encountering a latch d->q edge means we need to find + // latch feedback edges. if (role == TimingRole::latchDtoQ()) genclks->findLatchFdbkEdges(gclk); EdgeSet &fdbk_edges = genclks->latchFdbkEdges(gclk); From 1c6ab172e2870bee72127e330c1380e79199a8b7 Mon Sep 17 00:00:00 2001 From: James Cherry Date: Wed, 17 Jun 2026 19:54:36 -0700 Subject: [PATCH 06/30] eval latches out of bfs Signed-off-by: James Cherry --- include/sta/Search.hh | 6 +-- search/PathEnum.cc | 10 +++++ search/Search.cc | 92 +++++++++++++++++-------------------------- 3 files changed, 49 insertions(+), 59 deletions(-) diff --git a/include/sta/Search.hh b/include/sta/Search.hh index d6db126e..364e6dfb 100644 --- a/include/sta/Search.hh +++ b/include/sta/Search.hh @@ -75,6 +75,8 @@ using ExceptionPathSeq = std::vector; class Search : public StaState { public: + bool postpone_latch_outputs_{false}; + Search(StaState *sta); ~Search() override; void copyState(const StaState *sta) override; @@ -530,9 +532,6 @@ protected: const MinMax *min_max) const; void seedRequireds(); void seedInvalidRequireds(); - [[nodiscard]] bool havePendingLatchOutputs(); - void clearPendingLatchOutputs(); - void enqueuePendingLatchOutputs(); void findFilteredArrivals(bool thru_latches); void findArrivalsSeed(); void seedFilterStarts(); @@ -669,7 +668,6 @@ protected: std::mutex filtered_arrivals_lock_; bool found_downstream_clk_pins_{false}; - bool postpone_latch_outputs_{false}; std::vector enum_paths_; VisitPathEnds *visit_path_ends_; diff --git a/search/PathEnum.cc b/search/PathEnum.cc index 7abd4a80..3be9e114 100644 --- a/search/PathEnum.cc +++ b/search/PathEnum.cc @@ -356,7 +356,9 @@ PathEnumFaninVisitor::visitFaninPathsThru(Path *before_div, prev_vertex_ = prev_vertex; visited_fanins_.clear(); unique_edge_divs_.clear(); + search_->postpone_latch_outputs_ = false; visitFaninPaths(before_div_->vertex(this)); + search_->postpone_latch_outputs_ = true; if (unique_edges_) { for (auto [vertex_edge, div] : unique_edge_divs_) @@ -422,6 +424,14 @@ PathEnumFaninVisitor::visitFromToPath(const Pin *, Arrival & /* to_arrival */, 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. if ((!unique_pins_ || from_vertex != prev_vertex_) && (!unique_edges_ || from_vertex != prev_vertex_ diff --git a/search/Search.cc b/search/Search.cc index 22fe70b0..d943d3fc 100644 --- a/search/Search.cc +++ b/search/Search.cc @@ -326,7 +326,7 @@ Search::clear() deletePathGroups(); deletePaths(); deleteTags(); - clearPendingLatchOutputs(); + pending_latch_outputs_.clear(); pending_clk_endpoints_.clear(); deleteFilter(); found_downstream_clk_pins_ = false; @@ -644,18 +644,26 @@ Search::findFilteredArrivals(bool thru_latches) // Search always_to_endpoint to search from exisiting arrivals at // fanin startpoints to reach -thru/-to endpoints. arrival_visitor_->init(true, false, eval_pred_); - // Iterate until data arrivals at all latches stop changing. - postpone_latch_outputs_ = true; enqueuePendingClkFanouts(); - for (int pass = 1; pass == 1 || (thru_latches && havePendingLatchOutputs()); + postpone_latch_outputs_ = true; + 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++) { - if (thru_latches) - enqueuePendingLatchOutputs(); debugPrint(debug_, "search", 1, "find arrivals pass {}", pass); + int arrival_count = arrival_iter_->visitParallel(max_level, arrival_visitor_); - deleteTagsPrev(); debugPrint(debug_, "search", 1, "found {} arrivals", arrival_count); + + have_pending_latch_outputs = !pending_latch_outputs_.empty(); postpone_latch_outputs_ = false; + for (Vertex *latch_vertex : pending_latch_outputs_) + arrival_visitor_->visit(latch_vertex); + pending_latch_outputs_.clear(); + postpone_latch_outputs_ = true; + + deleteTagsPrev(); } arrivals_exist_ = true; } @@ -983,41 +991,23 @@ Search::findAllArrivals(bool thru_latches, if (!clks_only) enqueuePendingClkFanouts(); arrival_visitor_->init(false, clks_only, eval_pred_); - // Iterate until data arrivals at all latches stop changing. postpone_latch_outputs_ = true; - 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++) { - enqueuePendingLatchOutputs(); debugPrint(debug_, "search", 1, "find arrivals pass {}", pass); findArrivals1(levelize_->maxLevel()); - if (pass > 2) - postpone_latch_outputs_ = false; + + have_pending_latch_outputs = !pending_latch_outputs_.empty(); + postpone_latch_outputs_ = false; + for (Vertex *latch_vertex : pending_latch_outputs_) + arrival_visitor_->visit(latch_vertex); + pending_latch_outputs_.clear(); + postpone_latch_outputs_ = true; } } -bool -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 Search::enqueuePendingClkFanouts() { @@ -1148,6 +1138,17 @@ ArrivalVisitor::visit(Vertex *vertex) { debugPrint(debug_, "search", 2, "find arrivals {}", vertex->to_string(this)); + + if (search_->postponeLatchOutputs()) { + VertexInEdgeIterator edge_iter(vertex, graph_); + while (edge_iter.hasNext()) { + Edge *edge = edge_iter.next(); + if (edge->role()->isLatchDtoQ()) { + search_->enqueueLatchOutput(edge->to(graph_)); + return; + } + } + } Pin *pin = vertex->pin(); tag_bldr_->init(vertex); has_fanin_one_ = graph_->hasFaninOne(vertex); @@ -2139,26 +2140,7 @@ PathVisitor::visitFromPath(const Pin *from_pin, } else if (edge->role() == TimingRole::latchDtoQ()) { if (min_max == MinMax::max() && clk) { - bool postponed = false; - if (search_->postponeLatchOutputs()) { - const Path *from_clk_path = from_clk_info->crprClkPath(this); - if (from_clk_path) { - Vertex *d_clk_vertex = from_clk_path->vertex(this); - Level d_clk_level = d_clk_vertex->level(); - 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) { + if (!search_->postponeLatchOutputs()) { arc_delay = search_->deratedDelay(from_vertex, arc, edge, false, min_max, dcalc_ap, sdc); latches_->latchOutArrival(from_path, arc, edge, to_tag, arc_delay, From dea1ea2c133f2954b1b76f7eac084f94428ce1f3 Mon Sep 17 00:00:00 2001 From: James Cherry Date: Wed, 17 Jun 2026 20:23:42 -0700 Subject: [PATCH 07/30] rm Search::postpone_latch_outputs_ Signed-off-by: James Cherry --- include/sta/Search.hh | 8 +++--- search/Genclks.cc | 2 +- search/PathEnum.cc | 4 +-- search/Search.cc | 67 ++++++++++++++++++++++--------------------- 4 files changed, 41 insertions(+), 40 deletions(-) diff --git a/include/sta/Search.hh b/include/sta/Search.hh index 364e6dfb..8122752e 100644 --- a/include/sta/Search.hh +++ b/include/sta/Search.hh @@ -75,8 +75,6 @@ using ExceptionPathSeq = std::vector; class Search : public StaState { public: - bool postpone_latch_outputs_{false}; - Search(StaState *sta); ~Search() override; void copyState(const StaState *sta) override; @@ -408,7 +406,6 @@ public: void checkPrevPaths() const; void deletePaths(Vertex *vertex); void deleteTagGroup(TagGroup *group); - bool postponeLatchOutputs() const { return postpone_latch_outputs_; } void saveEnumPath(Path *path); bool isSrchRoot(Vertex *vertex, const Mode *mode) const; @@ -710,7 +707,8 @@ public: bool make_tag_cache, const StaState *sta); ~PathVisitor() override; - virtual void visitFaninPaths(Vertex *to_vertex); + virtual void visitFaninPaths(Vertex *to_vertex, + bool with_latch_edges); virtual void visitFanoutPaths(Vertex *from_vertex); // Return false to stop visiting. virtual bool visitFromToPath(const Pin *from_pin, @@ -777,6 +775,8 @@ public: SearchPred *pred); void copyState(const StaState *sta) override; void visit(Vertex *vertex) override; + void visit(Vertex *vertex, + bool with_latch_edges); VertexVisitor *copy() const override; // Return false to stop visiting. bool visitFromToPath(const Pin *from_pin, diff --git a/search/Genclks.cc b/search/Genclks.cc index 851a601a..acf801b7 100644 --- a/search/Genclks.cc +++ b/search/Genclks.cc @@ -748,7 +748,7 @@ GenclkSrcArrivalVisitor::visit(Vertex *vertex) tag_bldr_->init(vertex); has_fanin_one_ = graph_->hasFaninOne(vertex); 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_); diff --git a/search/PathEnum.cc b/search/PathEnum.cc index 3be9e114..e433c703 100644 --- a/search/PathEnum.cc +++ b/search/PathEnum.cc @@ -356,9 +356,7 @@ PathEnumFaninVisitor::visitFaninPathsThru(Path *before_div, prev_vertex_ = prev_vertex; visited_fanins_.clear(); unique_edge_divs_.clear(); - search_->postpone_latch_outputs_ = false; - visitFaninPaths(before_div_->vertex(this)); - search_->postpone_latch_outputs_ = true; + visitFaninPaths(before_div_->vertex(this), true); if (unique_edges_) { for (auto [vertex_edge, div] : unique_edge_divs_) diff --git a/search/Search.cc b/search/Search.cc index d943d3fc..08829669 100644 --- a/search/Search.cc +++ b/search/Search.cc @@ -645,7 +645,6 @@ Search::findFilteredArrivals(bool thru_latches) // fanin startpoints to reach -thru/-to endpoints. arrival_visitor_->init(true, false, eval_pred_); enqueuePendingClkFanouts(); - postpone_latch_outputs_ = true; bool have_pending_latch_outputs = false; // Iterate until data arrivals at all latches stop changing. for (int pass = 1; @@ -657,11 +656,9 @@ Search::findFilteredArrivals(bool thru_latches) debugPrint(debug_, "search", 1, "found {} arrivals", arrival_count); have_pending_latch_outputs = !pending_latch_outputs_.empty(); - postpone_latch_outputs_ = false; - for (Vertex *latch_vertex : pending_latch_outputs_) - arrival_visitor_->visit(latch_vertex); + for (Vertex *latch_output : pending_latch_outputs_) + arrival_visitor_->visit(latch_output, true); pending_latch_outputs_.clear(); - postpone_latch_outputs_ = true; deleteTagsPrev(); } @@ -991,20 +988,19 @@ Search::findAllArrivals(bool thru_latches, if (!clks_only) enqueuePendingClkFanouts(); arrival_visitor_->init(false, clks_only, eval_pred_); - postpone_latch_outputs_ = true; 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); + for (int pass = 1; + pass == 1 || (thru_latches && have_pending_latch_outputs); pass++) { debugPrint(debug_, "search", 1, "find arrivals pass {}", pass); + findArrivals1(levelize_->maxLevel()); have_pending_latch_outputs = !pending_latch_outputs_.empty(); - postpone_latch_outputs_ = false; - for (Vertex *latch_vertex : pending_latch_outputs_) - arrival_visitor_->visit(latch_vertex); + for (Vertex *latch_output : pending_latch_outputs_) + arrival_visitor_->visit(latch_output, true); pending_latch_outputs_.clear(); - postpone_latch_outputs_ = true; } } @@ -1135,27 +1131,32 @@ ArrivalVisitor::setAlwaysToEndpoints(bool to_endpoints) void ArrivalVisitor::visit(Vertex *vertex) +{ + VertexInEdgeIterator edge_iter(vertex, graph_); + while (edge_iter.hasNext()) { + Edge *edge = edge_iter.next(); + if (edge->role()->isLatchDtoQ()) { + search_->enqueueLatchOutput(vertex); + return; + } + } + visit(vertex, false); +} + +void +ArrivalVisitor::visit(Vertex *vertex, + bool with_latch_edges) { debugPrint(debug_, "search", 2, "find arrivals {}", vertex->to_string(this)); - if (search_->postponeLatchOutputs()) { - VertexInEdgeIterator edge_iter(vertex, graph_); - while (edge_iter.hasNext()) { - Edge *edge = edge_iter.next(); - if (edge->role()->isLatchDtoQ()) { - search_->enqueueLatchOutput(edge->to(graph_)); - return; - } - } - } Pin *pin = vertex->pin(); tag_bldr_->init(vertex); has_fanin_one_ = graph_->hasFaninOne(vertex); if (crpr_active_ && !has_fanin_one_) tag_bldr_no_crpr_->init(vertex); - visitFaninPaths(vertex); + visitFaninPaths(vertex, with_latch_edges); if (crpr_active_ && search_->crprPathPruningEnabled() // No crpr for ideal clocks. @@ -1949,12 +1950,16 @@ PathVisitor::~PathVisitor() } void -PathVisitor::visitFaninPaths(Vertex *to_vertex) +PathVisitor::visitFaninPaths(Vertex *to_vertex, + bool with_latch_edges) { VertexInEdgeIterator edge_iter(to_vertex, graph_); while (edge_iter.hasNext()) { 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_); const Pin *from_pin = from_vertex->pin(); const Pin *to_pin = to_vertex->pin(); @@ -2140,14 +2145,12 @@ PathVisitor::visitFromPath(const Pin *from_pin, } else if (edge->role() == TimingRole::latchDtoQ()) { if (min_max == MinMax::max() && clk) { - if (!search_->postponeLatchOutputs()) { - 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_); - } + 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()) { From 7a541edb708abf8194ef43398489521dd08227bb Mon Sep 17 00:00:00 2001 From: James Cherry Date: Thu, 18 Jun 2026 09:15:32 -0700 Subject: [PATCH 08/30] levelize rm latch d->q handling Signed-off-by: James Cherry --- search/Levelize.cc | 37 ------------------------------------- search/Levelize.hh | 2 -- 2 files changed, 39 deletions(-) diff --git a/search/Levelize.cc b/search/Levelize.cc index 76788b40..bc1b5609 100644 --- a/search/Levelize.cc +++ b/search/Levelize.cc @@ -134,7 +134,6 @@ Levelize::findLevels() findBackEdges(); VertexSeq topo_sorted = findTopologicalOrder(); assignLevels(topo_sorted); - ensureLatchLevels(); // Set level of stranded vertices (constants) to zero. VertexIterator vertex_iter2(graph_); @@ -354,8 +353,6 @@ Levelize::findTopologicalOrder() Vertex *to_vertex = edge->to(graph_); if (searchThru(edge)) 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. const Pin *pin = vertex->pin(); @@ -507,27 +504,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 Levelize::setLevel(Vertex *vertex, Level level) @@ -604,7 +580,6 @@ Levelize::relevelize() EdgeSeq path; visit(vertex, nullptr, vertex->level(), 1, path_vertices, path); } - ensureLatchLevels(); levels_valid_ = true; relevelize_from_.clear(); } @@ -635,18 +610,6 @@ Levelize::visit(Vertex *vertex, visit(to_vertex, edge, level + level_space, level_space, path_vertices, 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. diff --git a/search/Levelize.hh b/search/Levelize.hh index 094e3d47..22b268b8 100644 --- a/search/Levelize.hh +++ b/search/Levelize.hh @@ -83,7 +83,6 @@ protected: EdgeSeq &path); EdgeSeq *loopEdges(EdgeSeq &path, Edge *closing_edge); - void ensureLatchLevels(); void findBackEdges(); EdgeSet findBackEdges(EdgeSeq &path, FindBackEdgesStack &stack); @@ -113,7 +112,6 @@ protected: GraphLoopSeq loops_; EdgeSet loop_edges_; EdgeSet disabled_loop_edges_; - EdgeSet latch_d_to_q_edges_; LevelizeObserver *observer_{nullptr}; }; From 46d6ca9fc839f44fd65db4f24765d751e6c63066 Mon Sep 17 00:00:00 2001 From: James Cherry Date: Thu, 18 Jun 2026 13:24:04 -0700 Subject: [PATCH 09/30] comment Signed-off-by: James Cherry --- search/Latches.cc | 9 --------- 1 file changed, 9 deletions(-) diff --git a/search/Latches.cc b/search/Latches.cc index 73fc8929..5dcf6957 100644 --- a/search/Latches.cc +++ b/search/Latches.cc @@ -360,15 +360,6 @@ Latches::latchOutArrival(const Path *data_path, false, min_max, dcalc_ap, sdc); q_arrival = delaySum(data_path->arrival(), arc_delay, this); // 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 - // with multiple threads. // Kill the crprClklPath to be safe. const ClkInfo *data_clk_info = data_path->clkInfo(this); const ClkInfo *q_clk_info = From 63361465e2b34b25a4d8048a5cce55d1c8186161 Mon Sep 17 00:00:00 2001 From: James Cherry Date: Thu, 18 Jun 2026 14:08:43 -0700 Subject: [PATCH 10/30] ChangeLog typo Signed-off-by: James Cherry --- doc/ChangeLog.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/ChangeLog.txt b/doc/ChangeLog.txt index c30947f8..c456d0e0 100644 --- a/doc/ChangeLog.txt +++ b/doc/ChangeLog.txt @@ -55,7 +55,7 @@ link_design top create_clock -period 50 clk set_input_delay -clock clk 1 {in1 in2} 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) Endpoint: r3 (rising edge-triggered flip-flop clocked by clk) From 7ca079f3f6421e41bab12d8db5218c13c2683404 Mon Sep 17 00:00:00 2001 From: James Cherry Date: Thu, 18 Jun 2026 14:42:12 -0700 Subject: [PATCH 11/30] LibertyPort::setIsLatchOutput Signed-off-by: James Cherry --- include/sta/Liberty.hh | 3 +++ include/sta/Network.hh | 1 + liberty/Liberty.cc | 8 ++++++++ network/Network.cc | 10 ++++++++++ search/Search.cc | 13 ++++--------- 5 files changed, 26 insertions(+), 9 deletions(-) diff --git a/include/sta/Liberty.hh b/include/sta/Liberty.hh index 461166a1..f34d5e8f 100644 --- a/include/sta/Liberty.hh +++ b/include/sta/Liberty.hh @@ -861,6 +861,8 @@ public: void setIsRegOutput(bool is_reg_out); bool isLatchData() const { return 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. bool isCheckClk() const { return is_check_clk_; } void setIsCheckClk(bool is_clk); @@ -956,6 +958,7 @@ protected: bool is_reg_clk_:1 {false}; bool is_reg_output_:1 {false}; bool is_latch_data_: 1 {false}; + bool is_latch_output_:1 {false}; bool is_check_clk_:1 {false}; bool is_clk_gate_clk_:1 {false}; bool is_clk_gate_enable_:1 {false}; diff --git a/include/sta/Network.hh b/include/sta/Network.hh index 9c45f095..d07b3db3 100644 --- a/include/sta/Network.hh +++ b/include/sta/Network.hh @@ -319,6 +319,7 @@ public: // Pin clocks a timing check. [[nodiscard]] bool isCheckClk(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 // and child nets it is hierarchically connected to (port, leaf and diff --git a/liberty/Liberty.cc b/liberty/Liberty.cc index b63e56fa..09c92678 100644 --- a/liberty/Liberty.cc +++ b/liberty/Liberty.cc @@ -1769,6 +1769,7 @@ LibertyCell::makeLatchEnable(LibertyPort *d, latch_d_to_q_map_[d_to_q] = idx; latch_check_map_[setup_check] = idx; d->setIsLatchData(true); + q->setIsLatchOutput(true); debugPrint(debug, "liberty_latch", 1, "latch {} -> {} | {} {} -> {} | {} {} -> {} setup", d->name(), @@ -2434,6 +2435,13 @@ LibertyPort::setIsLatchData(bool is_latch_data) 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 LibertyPort::setIsCheckClk(bool is_clk) { diff --git a/network/Network.cc b/network/Network.cc index 3bf22cf6..373d52b7 100644 --- a/network/Network.cc +++ b/network/Network.cc @@ -636,6 +636,16 @@ Network::isLatchData(const Pin *pin) const return false; } +bool +Network::isLatchOutput(const Pin *pin) const +{ + LibertyPort *port = libertyPort(pin); + if (port) + return port->isLatchOutput(); + else + return false; +} + //////////////////////////////////////////////////////////////// std::string diff --git a/search/Search.cc b/search/Search.cc index 08829669..ae23c681 100644 --- a/search/Search.cc +++ b/search/Search.cc @@ -1132,15 +1132,10 @@ ArrivalVisitor::setAlwaysToEndpoints(bool to_endpoints) void ArrivalVisitor::visit(Vertex *vertex) { - VertexInEdgeIterator edge_iter(vertex, graph_); - while (edge_iter.hasNext()) { - Edge *edge = edge_iter.next(); - if (edge->role()->isLatchDtoQ()) { - search_->enqueueLatchOutput(vertex); - return; - } - } - visit(vertex, false); + if (network_->isLatchOutput(vertex->pin())) + search_->enqueueLatchOutput(vertex); + else + visit(vertex, false); } void From 8d7babf44ef4dca11f478207a39273969fc32bd1 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 19 Jun 2026 10:49:10 +0800 Subject: [PATCH 12/30] Bump actions/checkout from 6 to 7 (#451) Signed-off-by: dependabot[bot] --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 65c53abb..d02e9616 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -9,7 +9,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout - uses: actions/checkout@v6 + uses: actions/checkout@v7 with: submodules: true From 4f0c4c8bb33054ffb63b45eb16d153356a91c4a2 Mon Sep 17 00:00:00 2001 From: James Cherry Date: Sat, 20 Jun 2026 10:05:07 -0700 Subject: [PATCH 13/30] gated clk activity use passes instead of enqueue Signed-off-by: James Cherry --- power/Power.cc | 33 +++++++++++++++++++++++++++------ power/Power.hh | 2 ++ 2 files changed, 29 insertions(+), 6 deletions(-) diff --git a/power/Power.cc b/power/Power.cc index c5075522..0d7a4848 100644 --- a/power/Power.cc +++ b/power/Power.cc @@ -701,10 +701,8 @@ PropActivityVisitor::visit(Vertex *vertex) if (cell->isClockGate()) { const Pin *enable, *clk, *gclk; power_->clockGatePins(inst, enable, clk, gclk); - if (gclk) { - Vertex *gclk_vertex = graph_->pinDrvrVertex(gclk); - bfs_->enqueue(gclk_vertex); - } + if (gclk) + visited_regs_.insert(inst); } } bfs_->enqueueAdjacentVertices(vertex, mode_); @@ -972,6 +970,8 @@ Power::seedRegOutputActivities(const Instance *inst, const SequentialSeq &seqs = test_cell->sequentials(); seedRegOutputActivities(inst, test_cell, seqs, bfs); } + else if (cell->isClockGate()) + seedClkGateOutputActivities(inst, bfs); } } @@ -1045,6 +1045,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 @@ -1566,8 +1583,12 @@ Power::findActivity(const Pin *pin) if (activity && activity->origin() != PwrActivityOrigin::unknown) return *activity; const Clock *clk = findClk(pin); - float duty = clockDuty(clk); - return PwrActivity(2.0 / clk->period(), duty, PwrActivityOrigin::clock); + if (clk) { + float duty = clockDuty(clk); + return PwrActivity(2.0 / clk->period(), duty, PwrActivityOrigin::clock); + } + else + return PwrActivity(); } else if (global_activity_.isSet()) return global_activity_; diff --git a/power/Power.hh b/power/Power.hh index 8a19f309..ccffea64 100644 --- a/power/Power.hh +++ b/power/Power.hh @@ -224,6 +224,8 @@ protected: const LibertyCell *test_cell, const SequentialSeq &seqs, BfsFwdIterator &bfs); + void seedClkGateOutputActivities(const Instance *inst, + BfsFwdIterator &bfs); PwrActivity evalActivity(FuncExpr *expr, const Instance *inst); PwrActivity evalActivity(FuncExpr *expr, From 70e8f9f4735d2a8fbabdb5a609d2487cb8d26a25 Mon Sep 17 00:00:00 2001 From: James Cherry Date: Sun, 21 Jun 2026 20:55:57 -0700 Subject: [PATCH 14/30] genclks use queue instead of bfs Signed-off-by: James Cherry --- search/Genclks.cc | 68 ++++++++++++++++++++++++++++++----------------- search/Genclks.hh | 14 +++++++--- 2 files changed, 55 insertions(+), 27 deletions(-) diff --git a/search/Genclks.cc b/search/Genclks.cc index acf801b7..5e71b207 100644 --- a/search/Genclks.cc +++ b/search/Genclks.cc @@ -457,11 +457,11 @@ Genclks::findInsertionDelays(Clock *gclk) debugPrint(debug_, "genclk", 2, "find gen clk {} insertion", gclk->name()); GenclkInfo *genclk_info = makeGenclkInfo(gclk); FilterPath *src_filter = genclk_info->srcFilter(); + VertexQueue insert_queue; GenClkInsertionSearchPred srch_pred(gclk, genclk_info, this); - BfsFwdIterator insert_iter(BfsIndex::other, &srch_pred, this); - seedSrcPins(gclk, src_filter, insert_iter); + seedSrcPins(gclk, src_filter, insert_queue, srch_pred); // 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. // The exception itself has to stick around because the source path // tags reference it. @@ -591,7 +591,8 @@ Genclks::makeSrcFilter(Clock *gclk, void Genclks::seedSrcPins(Clock *gclk, FilterPath *src_filter, - BfsFwdIterator &insert_iter) + VertexQueue &insert_queue, + SearchPred &srch_pred) { Clock *master_clk = gclk->masterClk(); for (const Pin *master_pin : master_clk->leafPins()) { @@ -613,13 +614,28 @@ Genclks::seedSrcPins(Clock *gclk, 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::enqueueFanout(Vertex *vertex, + VertexQueue &insert_queue, + SearchPred &srch_pred) +{ + VertexOutEdgeIterator edge_iter(vertex, graph_); + while (edge_iter.hasNext()) { + Edge *edge = edge_iter.next(); + Vertex *to_vertex = edge->to(graph_); + if (srch_pred.searchThru(edge, mode_) + && srch_pred.searchTo(to_vertex, mode_)) + insert_queue.push(to_vertex); + } +} + Tag * Genclks::makeTag(const Clock *gclk, const Clock *master_clk, @@ -690,8 +706,8 @@ class GenclkSrcArrivalVisitor : public ArrivalVisitor { public: GenclkSrcArrivalVisitor(Clock *gclk, - BfsFwdIterator *insert_iter, GenclkInfo *genclk_info, + VertexQueue &insert_queue, const Mode *mode); GenclkSrcArrivalVisitor(const GenclkSrcArrivalVisitor &visitor); VertexVisitor *copy() const override; @@ -699,7 +715,7 @@ public: protected: Clock *gclk_; - BfsFwdIterator *insert_iter_; + VertexQueue &insert_queue_; GenclkInfo *genclk_info_; GenClkInsertionSearchPred srch_pred_; const Mode *mode_; @@ -708,12 +724,12 @@ protected: }; GenclkSrcArrivalVisitor::GenclkSrcArrivalVisitor(Clock *gclk, - BfsFwdIterator *insert_iter, GenclkInfo *genclk_info, + VertexQueue &insert_queue, const Mode *mode) : ArrivalVisitor(mode->sta()), gclk_(gclk), - insert_iter_(insert_iter), + insert_queue_(insert_queue), genclk_info_(genclk_info), srch_pred_(gclk_, genclk_info, mode->sta()), mode_(mode), @@ -725,7 +741,7 @@ GenclkSrcArrivalVisitor::GenclkSrcArrivalVisitor(Clock *gclk, GenclkSrcArrivalVisitor::GenclkSrcArrivalVisitor(const GenclkSrcArrivalVisitor &visitor) : ArrivalVisitor(visitor), gclk_(visitor.gclk_), - insert_iter_(visitor.insert_iter_), + insert_queue_(visitor.insert_queue_), genclk_info_(visitor.genclk_info_), srch_pred_(visitor.gclk_, visitor.genclk_info_, this), mode_(visitor.mode_), @@ -749,23 +765,27 @@ GenclkSrcArrivalVisitor::visit(Vertex *vertex) has_fanin_one_ = graph_->hasFaninOne(vertex); genclks_->copyGenClkSrcPaths(vertex, tag_bldr_); 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_); + // Propagate beyond the clock tree to reach generated clk roots. + genclks_->enqueueFanout(vertex, insert_queue_, srch_pred_); } void Genclks::findSrcArrivals(Clock *gclk, - BfsFwdIterator &insert_iter, - GenclkInfo *genclk_info) + GenclkInfo *genclk_info, + VertexQueue &insert_queue) { 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); // This cannot restrict the search level because loops in the clock tree // can circle back to the generated clock src pin. // 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. @@ -886,15 +906,15 @@ void Genclks::deleteGenclkSrcPaths(Clock *gclk) { GenclkInfo *genclk_info = genclkInfo(gclk); - GenClkInsertionSearchPred srch_pred(gclk, genclk_info, mode_->sta()); - BfsFwdIterator insert_iter(BfsIndex::other, &srch_pred, this); + VertexQueue insert_queue; + GenClkInsertionSearchPred srch_pred(gclk, genclk_info, this); FilterPath *src_filter = genclk_info->srcFilter(); - seedSrcPins(gclk, src_filter, insert_iter); - GenClkArrivalSearchPred eval_pred(gclk, this); - while (insert_iter.hasNext()) { - Vertex *vertex = insert_iter.next(); + seedSrcPins(gclk, src_filter, insert_queue, srch_pred); + while (!insert_queue.empty()) { + Vertex *vertex = insert_queue.front(); + insert_queue.pop(); search_->deletePaths(vertex); - insert_iter.enqueueAdjacentVertices(vertex, &srch_pred, mode_); + enqueueFanout(vertex, insert_queue, srch_pred); } } diff --git a/search/Genclks.hh b/search/Genclks.hh index 67cc55de..500e58e9 100644 --- a/search/Genclks.hh +++ b/search/Genclks.hh @@ -26,6 +26,7 @@ #include #include +#include #include #include @@ -59,6 +60,7 @@ public: using GenclkInfoMap = std::map; using GenclkSrcPathMap = std::map, ClockPinPairLess>; using VertexGenclkSrcPathsMap = std::map, VertexIdLess>; +using VertexQueue = std::queue; class Genclks : public StaState { @@ -110,10 +112,11 @@ private: const Clock *gclk) const; void seedSrcPins(Clock *gclk, FilterPath *src_filter, - BfsFwdIterator &insert_iter); + VertexQueue &insert_queue, + SearchPred &srch_pred); void findSrcArrivals(Clock *gclk, - BfsFwdIterator &insert_iter, - GenclkInfo *genclk_info); + GenclkInfo *genclk_info, + VertexQueue &insert_queue); FilterPath *makeSrcFilter(Clock *gclk, Sdc *sdc); void deleteGenClkInfo(); @@ -128,6 +131,9 @@ private: void seedSrcPins(Clock *clk, BfsBkwdIterator &iter); void findInsertionDelay(Clock *gclk); + void enqueueFanout(Vertex *vertex, + VertexQueue &insert_queue, + SearchPred &srch_pred); GenclkInfo *makeGenclkInfo(Clock *gclk); FilterPath *srcFilter(Clock *gclk); void findFanin(Clock *gclk, @@ -147,6 +153,8 @@ private: GenclkSrcPathMap genclk_src_paths_; GenclkInfoMap genclk_info_map_; VertexGenclkSrcPathsMap vertex_src_paths_map_; + + friend class GenclkSrcArrivalVisitor; }; } // namespace sta From 266061fcee806fb48e64a4c4665b75d48a0fd8cd Mon Sep 17 00:00:00 2001 From: James Cherry Date: Mon, 22 Jun 2026 09:08:39 -0700 Subject: [PATCH 15/30] Genclks::ensureMaster use queue instead of BfsIterator Signed-off-by: James Cherry --- search/Genclks.cc | 75 ++++++++++++++++++++++++++++------------------- search/Genclks.hh | 5 ++-- 2 files changed, 48 insertions(+), 32 deletions(-) diff --git a/search/Genclks.cc b/search/Genclks.cc index 5e71b207..cf28ace3 100644 --- a/search/Genclks.cc +++ b/search/Genclks.cc @@ -270,30 +270,38 @@ Genclks::ensureMaster(Clock *gclk, } if (!found_master) { // Search backward from generated clock source pin to a clock pin. - GenClkMasterSearchPred pred(this); - BfsBkwdIterator iter(BfsIndex::other, &pred, this); - seedSrcPins(gclk, iter); - while (iter.hasNext()) { - Vertex *vertex = iter.next(); - Pin *pin = vertex->pin(); - if (sdc->isLeafPinClock(pin)) { - ClockSet *master_clks = sdc->findLeafPinClocks(pin); - if (master_clks) { - ClockSet::iterator master_iter = master_clks->begin(); - if (master_iter != master_clks->end()) { - 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; + GenClkMasterSearchPred srch_pred(this); + VertexQueue master_queue; + VertexSet visited = makeVertexSet(this); + VertexSet src_vertices = makeVertexSet(this); + gclk->srcPinVertices(src_vertices, network_, graph_); + for (Vertex *vertex : src_vertices) + master_queue.push(vertex); + while (!master_queue.empty()) { + Vertex *vertex = master_queue.front(); + master_queue.pop(); + if (!visited.contains(vertex)) { + visited.insert(vertex); + Pin *pin = vertex->pin(); + if (sdc->isLeafPinClock(pin)) { + ClockSet *master_clks = sdc->findLeafPinClocks(pin); + if (master_clks) { + ClockSet::iterator master_iter = master_clks->begin(); + if (master_iter != master_clks->end()) { + 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) @@ -303,16 +311,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 @@ -621,6 +619,23 @@ Genclks::seedSrcPins(Clock *gclk, } } +void +Genclks::enqueueFanin(Vertex *vertex, + VertexQueue &insert_queue, + SearchPred &srch_pred) +{ + if (srch_pred.searchTo(vertex, mode_)) { + VertexInEdgeIterator edge_iter(vertex, graph_); + while (edge_iter.hasNext()) { + Edge *edge = edge_iter.next(); + Vertex *from_vertex = edge->from(graph_); + if (srch_pred.searchFrom(from_vertex, mode_) + && srch_pred.searchThru(edge, mode_)) + insert_queue.push(from_vertex); + } + } +} + void Genclks::enqueueFanout(Vertex *vertex, VertexQueue &insert_queue, diff --git a/search/Genclks.hh b/search/Genclks.hh index 500e58e9..172ba789 100644 --- a/search/Genclks.hh +++ b/search/Genclks.hh @@ -128,9 +128,10 @@ private: Arrival insert, Scene *scene, const MinMax *min_max); - void seedSrcPins(Clock *clk, - BfsBkwdIterator &iter); void findInsertionDelay(Clock *gclk); + void enqueueFanin(Vertex *vertex, + VertexQueue &insert_queue, + SearchPred &srch_pred); void enqueueFanout(Vertex *vertex, VertexQueue &insert_queue, SearchPred &srch_pred); From 2349b76a9535db21076d24ec942c364db7e7ca53 Mon Sep 17 00:00:00 2001 From: James Cherry Date: Mon, 22 Jun 2026 09:30:45 -0700 Subject: [PATCH 16/30] Genclks::findFanin use queue instead of BfsIterator Signed-off-by: James Cherry --- search/Genclks.cc | 21 +++++++++++---------- search/Genclks.hh | 7 +++---- 2 files changed, 14 insertions(+), 14 deletions(-) diff --git a/search/Genclks.cc b/search/Genclks.cc index cf28ace3..caaee7ba 100644 --- a/search/Genclks.cc +++ b/search/Genclks.cc @@ -26,7 +26,6 @@ #include -#include "Bfs.hh" #include "Clock.hh" #include "ContainerHelpers.hh" #include "Debug.hh" @@ -347,32 +346,34 @@ Genclks::findFanin(Clock *gclk, { // Search backward from generated clock source pin to a clock pin. GenClkFaninSrchPred srch_pred(gclk, this); - BfsBkwdIterator iter(BfsIndex::other, &srch_pred, this); - seedClkVertices(gclk, iter, fanins); - while (iter.hasNext()) { - Vertex *vertex = iter.next(); + VertexQueue fanin_queue; + seedClkVertices(gclk, fanin_queue, fanins, srch_pred); + while (!fanin_queue.empty()) { + Vertex *vertex = fanin_queue.front(); + fanin_queue.pop(); if (!fanins.contains(vertex)) { fanins.insert(vertex); debugPrint(debug_, "genclk", 2, "gen clk {} fanin {}", gclk->name(), vertex->to_string(this)); - iter.enqueueAdjacentVertices(vertex, mode_); + enqueueFanin(vertex, fanin_queue, srch_pred); } } } void Genclks::seedClkVertices(Clock *clk, - BfsBkwdIterator &iter, - VertexSet &fanins) + VertexQueue &fanin_queue, + VertexSet &fanins, + SearchPred &srch_pred) { for (const Pin *pin : clk->leafPins()) { Vertex *vertex, *bidirect_drvr_vertex; graph_->pinVertices(pin, vertex, bidirect_drvr_vertex); fanins.insert(vertex); - iter.enqueueAdjacentVertices(vertex, mode_); + enqueueFanin(vertex, fanin_queue, srch_pred); if (bidirect_drvr_vertex) { fanins.insert(bidirect_drvr_vertex); - iter.enqueueAdjacentVertices(bidirect_drvr_vertex, mode_); + enqueueFanin(bidirect_drvr_vertex, fanin_queue, srch_pred); } } } diff --git a/search/Genclks.hh b/search/Genclks.hh index 172ba789..6ec66cee 100644 --- a/search/Genclks.hh +++ b/search/Genclks.hh @@ -43,8 +43,6 @@ namespace sta { class GenclkInfo; -class BfsFwdIterator; -class BfsBkwdIterator; class SearchPred; class TagGroupBldr; @@ -104,8 +102,9 @@ private: void recordSrcPaths(Clock *gclk); void findInsertionDelays(Clock *gclk); void seedClkVertices(Clock *clk, - BfsBkwdIterator &iter, - VertexSet &dfanins); + VertexQueue &fanin_queue, + VertexSet &fanins, + SearchPred &srch_pred); size_t srcPathIndex(const RiseFall *clk_rf, const MinMax *min_max) const; bool matchesSrcFilter(Path *path, From 0939625580bb752254f8ba778eb4409009e582c5 Mon Sep 17 00:00:00 2001 From: James Cherry Date: Mon, 22 Jun 2026 09:31:54 -0700 Subject: [PATCH 17/30] .cursor Signed-off-by: James Cherry --- .cursor/rules/cpp-coding-standards.mdc | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/.cursor/rules/cpp-coding-standards.mdc b/.cursor/rules/cpp-coding-standards.mdc index fc06afdf..4589e5b3 100644 --- a/.cursor/rules/cpp-coding-standards.mdc +++ b/.cursor/rules/cpp-coding-standards.mdc @@ -34,6 +34,13 @@ alwaysApply: true - Prefer `std::string` over `char*` for string members. - 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 - C++ source: `.cc` From c81f0c169d15896eece4b1ecc9fd7dfba3fac041 Mon Sep 17 00:00:00 2001 From: James Cherry Date: Mon, 22 Jun 2026 16:54:27 -0700 Subject: [PATCH 18/30] cleanup Signed-off-by: James Cherry --- dcalc/GraphDelayCalc.cc | 66 ++++++++++++++++++++--------------------- power/Power.cc | 28 ++++++++++------- search/Genclks.cc | 11 +++---- 3 files changed, 55 insertions(+), 50 deletions(-) diff --git a/dcalc/GraphDelayCalc.cc b/dcalc/GraphDelayCalc.cc index 6fac6faa..6c821dab 100644 --- a/dcalc/GraphDelayCalc.cc +++ b/dcalc/GraphDelayCalc.cc @@ -248,7 +248,7 @@ GraphDelayCalc::delayInvalid(Vertex *vertex) { debugPrint(debug_, "delay_calc", 2, "delay invalid {}", vertex->to_string(this)); - if (graph_ && incremental_) { + if (incremental_) { invalid_delays_.insert(vertex); // Invalidate driver that triggers dcalc for multi-driver nets. MultiDrvrNet *multi_drvr = multiDrvrNet(vertex); @@ -338,40 +338,38 @@ FindVertexDelays::visit(Vertex *vertex) void GraphDelayCalc::findDelays(Level level) { - if (arc_delay_calc_) { - Stats stats(debug_, report_); - int dcalc_count = 0; - debugPrint(debug_, "delay_calc", 1, "find delays to level {}", level); - if (!delays_seeded_) { - iter_->clear(); - seedRootSlews(); - 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"); + Stats stats(debug_, report_); + int dcalc_count = 0; + debugPrint(debug_, "delay_calc", 1, "find delays to level {}", level); + if (!delays_seeded_) { + iter_->clear(); + seedRootSlews(); + 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"); } void diff --git a/power/Power.cc b/power/Power.cc index 0d7a4848..0296093e 100644 --- a/power/Power.cc +++ b/power/Power.cc @@ -554,9 +554,12 @@ ActivitySrchPred::searchThru(Edge *edge, { const Sdc *sdc = mode->sdc(); const TimingRole *role = edge->role(); - return !(edge->role()->isTimingCheck() || sdc->isDisabledConstraint(edge) - || sdc->isDisabledCondDefault(edge) || edge->isBidirectInstPath() - || edge->isDisabledLoop() || role == TimingRole::regClkToQ() + return !(edge->role()->isTimingCheck() + || sdc->isDisabledConstraint(edge) + || sdc->isDisabledCondDefault(edge) + || edge->isBidirectInstPath() + || edge->isDisabledLoop() + || role == TimingRole::regClkToQ() || role->isLatchDtoQ()); } @@ -744,9 +747,9 @@ PropActivityVisitor::setActivityCheck(const Pin *pin, max_change_ = duty_delta; max_change_pin_ = pin; } - bool changed = density_delta > change_tolerance_ || duty_delta > change_tolerance_ - || activity.origin() != prev_activity.origin(); - ; + bool changed = density_delta > change_tolerance_ + || duty_delta > change_tolerance_ + || activity.origin() != prev_activity.origin(); power_->setActivity(pin, activity); return changed; } @@ -903,8 +906,9 @@ Power::ensureActivities(const Scene *scene) // unless it has been set by command. if (input_activity_.origin() == PwrActivityOrigin::unknown) { float min_period = clockMinPeriod(scene_->mode()->sdc()); - float density = - 0.1 / (min_period != 0.0 ? min_period : units_->timeUnit()->scale()); + if (min_period == 0.0) + min_period = units_->timeUnit()->scale(); + float density = 0.1 / min_period; input_activity_.set(density, 0.5, PwrActivityOrigin::input); } ActivitySrchPred activity_srch_pred(this); @@ -916,7 +920,7 @@ Power::ensureActivities(const Scene *scene) // Propagate activiities through registers. InstanceSet regs = std::move(visitor.visitedRegs()); int pass = 1; - while (!regs.empty() && pass < max_activity_passes_) { + while (!regs.empty() && pass <= max_activity_passes_) { visitor.init(); for (const Instance *reg : regs) // Propagate activiities across register D->Q. @@ -925,8 +929,10 @@ Power::ensureActivities(const Scene *scene) // combinational logic. bfs.visit(levelize_->maxLevel(), &visitor); regs = std::move(visitor.visitedRegs()); - debugPrint(debug_, "power_activity", 1, "Pass {} change {:.2f} {}", pass, - visitor.maxChange(), network_->pathName(visitor.maxChangePin())); + debugPrint(debug_, "power_activity", 1, "Pass {} change {:.2f} {}", + pass, + visitor.maxChange(), + network_->pathName(visitor.maxChangePin())); pass++; } } diff --git a/search/Genclks.cc b/search/Genclks.cc index caaee7ba..03d53d74 100644 --- a/search/Genclks.cc +++ b/search/Genclks.cc @@ -669,11 +669,12 @@ Genclks::makeTag(const Clock *gclk, state = state->nextState(); ExceptionStateSet *states = new ExceptionStateSet(); states->insert(state); - const ClkInfo *clk_info = search_->findClkInfo( - scene, master_clk->edge(master_rf), master_pin, true, nullptr, true, nullptr, - insert, 0.0, nullptr, min_max, nullptr); - return search_->findTag(scene, master_rf, min_max, clk_info, false, nullptr, false, - states, true, nullptr); + const ClkInfo *clk_info = search_->findClkInfo(scene, master_clk->edge(master_rf), + master_pin, true, nullptr, true, + nullptr, insert, 0.0, nullptr, + min_max, nullptr); + return search_->findTag(scene, master_rf, min_max, clk_info, false, nullptr, + false, states, true, nullptr); } class GenClkArrivalSearchPred : public EvalPred From 458c277c8056bad59a69bcd821c7a4bee093cdc9 Mon Sep 17 00:00:00 2001 From: James Cherry Date: Mon, 22 Jun 2026 19:14:30 -0700 Subject: [PATCH 19/30] Sta::delaysInvalidFromFanin Signed-off-by: James Cherry --- include/sta/Sta.hh | 1 - search/Sta.cc | 28 ++++++++-------------------- 2 files changed, 8 insertions(+), 21 deletions(-) diff --git a/include/sta/Sta.hh b/include/sta/Sta.hh index da5da08b..bded0b9a 100644 --- a/include/sta/Sta.hh +++ b/include/sta/Sta.hh @@ -1513,7 +1513,6 @@ protected: bool infer_latches); void delayCalcPreamble(); void delaysInvalidFrom(const Port *port); - void delaysInvalidFromFanin(const Port *port); void deleteEdge(Edge *edge); void netParasiticCaps(Net *net, const RiseFall *rf, diff --git a/search/Sta.cc b/search/Sta.cc index 87a3f7d1..e159c7be 100644 --- a/search/Sta.cc +++ b/search/Sta.cc @@ -3965,7 +3965,7 @@ Sta::setPortExtPinCap(const Port *port, for (const MinMax *mm : min_max->range()) sdc->setPortExtPinCap(port, rf1, mm, cap); } - delaysInvalidFromFanin(port); + delaysInvalidFrom(port); } void @@ -4020,7 +4020,7 @@ Sta::setPortExtWireCap(const Port *port, for (const MinMax *mm : min_max->range()) sdc->setPortExtWireCap(port, rf1, mm, cap); } - delaysInvalidFromFanin(port); + delaysInvalidFrom(port); } void @@ -4038,7 +4038,7 @@ Sta::setPortExtFanout(const Port *port, { for (const MinMax *mm : min_max->range()) sdc->setPortExtFanout(port, mm, fanout); - delaysInvalidFromFanin(port); + delaysInvalidFrom(port); } void @@ -5017,20 +5017,6 @@ Sta::delaysInvalidFrom(Vertex *vertex) graph_delay_calc_->delayInvalid(vertex); } -void -Sta::delaysInvalidFromFanin(const Port *port) -{ - if (graph_) { - Instance *top_inst = network_->topInstance(); - Pin *pin = network_->findPin(top_inst, port); - Vertex *vertex, *bidirect_drvr_vertex; - graph_->pinVertices(pin, vertex, bidirect_drvr_vertex); - delaysInvalidFromFanin(vertex); - if (bidirect_drvr_vertex) - delaysInvalidFromFanin(bidirect_drvr_vertex); - } -} - void Sta::delaysInvalidFromFanin(const Pin *pin) { @@ -5070,9 +5056,11 @@ Sta::delaysInvalidFromFanin(Vertex *vertex) VertexInEdgeIterator edge_iter(vertex, graph_); while (edge_iter.hasNext()) { Edge *edge = edge_iter.next(); - Vertex *from_vertex = edge->from(graph_); - delaysInvalidFrom(from_vertex); - search_->requiredInvalid(from_vertex); + if (edge->isWire()) { + Vertex *from_vertex = edge->from(graph_); + delaysInvalidFrom(from_vertex); + search_->requiredInvalid(from_vertex); + } } } From a456c352cf76612abb3cca2039e42b4da455a6c4 Mon Sep 17 00:00:00 2001 From: James Cherry Date: Mon, 22 Jun 2026 19:20:39 -0700 Subject: [PATCH 20/30] rename LibertyCell::hasSequentials to isSequential Signed-off-by: James Cherry --- doc/ApiChanges.txt | 5 +++++ include/sta/Liberty.hh | 4 +++- liberty/Liberty.cc | 8 +++++++- power/Power.cc | 5 +++-- 4 files changed, 18 insertions(+), 4 deletions(-) diff --git a/doc/ApiChanges.txt b/doc/ApiChanges.txt index 7b62f981..0aa3e9f6 100644 --- a/doc/ApiChanges.txt +++ b/doc/ApiChanges.txt @@ -24,6 +24,11 @@ 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 ------------------------ diff --git a/include/sta/Liberty.hh b/include/sta/Liberty.hh index f34d5e8f..10596472 100644 --- a/include/sta/Liberty.hh +++ b/include/sta/Liberty.hh @@ -536,7 +536,9 @@ public: bool leakagePowerExists() const { return leakage_power_exists_; } // 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_; } // Find the sequential with the output connected to an (internal) port. Sequential *outputPortSequential(LibertyPort *port); diff --git a/liberty/Liberty.cc b/liberty/Liberty.cc index 09c92678..2ad71e12 100644 --- a/liberty/Liberty.cc +++ b/liberty/Liberty.cc @@ -1438,6 +1438,12 @@ LibertyCell::outputPortSequential(LibertyPort *port) return nullptr; } +bool +LibertyCell::isSequential() const +{ + return !sequentials_.empty() || statetable_ != nullptr; +} + bool LibertyCell::hasSequentials() const { @@ -1618,7 +1624,7 @@ void LibertyCell::makeLatchEnables(Report *report, Debug *debug) { - if (hasSequentials() + if (isSequential() || hasInferedRegTimingArcs()) { for (TimingArcSet *d_to_q : timing_arc_sets_) { if (d_to_q->role() == TimingRole::latchDtoQ()) { diff --git a/power/Power.cc b/power/Power.cc index 0296093e..689297f9 100644 --- a/power/Power.cc +++ b/power/Power.cc @@ -443,7 +443,7 @@ Power::power(const Scene *scene, pad.incr(inst_power); else if (inClockNetwork(inst, clk_network)) clock.incr(inst_power); - else if (cell->hasSequentials()) + else if (cell->isSequential()) sequential.incr(inst_power); else combinational.incr(inst_power); @@ -695,7 +695,8 @@ PropActivityVisitor::visit(Vertex *vertex) if (cell) { LibertyCell *test_cell = cell->libertyCell()->testCell(); 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 {}", network_->pathName(inst)); visited_regs_.insert(inst); From f1adf5b26acd327cfc4b5950036b573ff2efb340 Mon Sep 17 00:00:00 2001 From: James Cherry Date: Tue, 23 Jun 2026 15:09:08 -0700 Subject: [PATCH 21/30] Graph::visitFanouts, visitFanins Signed-off-by: James Cherry --- graph/Graph.cc | 85 +++++++++++++++++++++++++++++++++++---- include/sta/Graph.hh | 15 ++++++- include/sta/GraphClass.hh | 2 + search/Genclks.cc | 26 ++++-------- 4 files changed, 101 insertions(+), 27 deletions(-) diff --git a/graph/Graph.cc b/graph/Graph.cc index 6c9d47e8..92775d57 100644 --- a/graph/Graph.cc +++ b/graph/Graph.cc @@ -32,6 +32,7 @@ #include "Mutex.hh" #include "Network.hh" #include "PortDirection.hh" +#include "SearchPred.hh" #include "Stats.hh" #include "TimingArc.hh" #include "TimingRole.hh" @@ -487,7 +488,7 @@ Graph::deleteVertex(Vertex *vertex) EdgeId edge_id, next_id; for (edge_id = vertex->in_edges_; edge_id; edge_id = next_id) { Edge *edge = Graph::edge(edge_id); - next_id = edge->vertex_in_link_; + next_id = edge->vertex_in_next_; deleteOutEdge(edge->from(this), edge); edge->clear(); edges_->destroy(edge); @@ -508,7 +509,7 @@ bool Graph::hasFaninOne(Vertex *vertex) const { return vertex->in_edges_ - && edge(vertex->in_edges_)->vertex_in_link_ == 0; + && edge(vertex->in_edges_)->vertex_in_next_ == 0; } void @@ -519,12 +520,12 @@ Graph::deleteInEdge(Vertex *vertex, EdgeId prev = 0; for (EdgeId i = vertex->in_edges_; i && i != edge_id; - i = Graph::edge(i)->vertex_in_link_) + i = Graph::edge(i)->vertex_in_next_) prev = i; if (prev) - Graph::edge(prev)->vertex_in_link_ = edge->vertex_in_link_; + Graph::edge(prev)->vertex_in_next_ = edge->vertex_in_next_; else - vertex->in_edges_ = edge->vertex_in_link_; + vertex->in_edges_ = edge->vertex_in_next_; } void @@ -574,6 +575,72 @@ 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 Graph::slew(const Vertex *vertex, const RiseFall *rf, @@ -650,7 +717,7 @@ Graph::makeEdge(Vertex *from, from->out_edges_ = edge_id; // 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; initArcDelays(edge); @@ -1213,7 +1280,7 @@ Edge::init(VertexId from, from_ = from; to_ = to; arc_set_ = arc_set; - vertex_in_link_ = edge_id_null; + vertex_in_next_ = edge_id_null; vertex_out_next_ = edge_id_null; vertex_out_prev_ = edge_id_null; is_bidirect_inst_path_ = false; @@ -1464,6 +1531,8 @@ VertexIterator::findNext() findNextPin(); } +//////////////////////////////////////////////////////////////// + VertexInEdgeIterator::VertexInEdgeIterator(Vertex *vertex, const Graph *graph) : next_(graph->edge(vertex->in_edges_)), @@ -1483,7 +1552,7 @@ VertexInEdgeIterator::next() { Edge *next = next_; if (next_) - next_ = graph_->edge(next_->vertex_in_link_); + next_ = graph_->edge(next_->vertex_in_next_); return next; } diff --git a/include/sta/Graph.hh b/include/sta/Graph.hh index 1f6e7ce2..abe38883 100644 --- a/include/sta/Graph.hh +++ b/include/sta/Graph.hh @@ -87,6 +87,19 @@ public: bool hasFaninOne(Vertex *vertex) const; 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_slews = measured_slews / slew_derate_from_library // Measured slews are between slew_lower_threshold and slew_upper_threshold. @@ -394,7 +407,7 @@ protected: TimingArcSet *arc_set_; VertexId from_; VertexId to_; - EdgeId vertex_in_link_; // Vertex in edges list. + EdgeId vertex_in_next_; // Vertex in edges list. EdgeId vertex_out_next_; // Vertex out edges doubly linked list. EdgeId vertex_out_prev_; float *arc_delays_; diff --git a/include/sta/GraphClass.hh b/include/sta/GraphClass.hh index cdf7539b..542f3ac0 100644 --- a/include/sta/GraphClass.hh +++ b/include/sta/GraphClass.hh @@ -65,6 +65,8 @@ using Level = int; using DcalcAPIndex = int; using TagGroupIndex = int; using SlewSeq = std::vector; +using VertexFn = std::function; +using EdgeFn = std::function; static constexpr int level_max = std::numeric_limits::max(); diff --git a/search/Genclks.cc b/search/Genclks.cc index 03d53d74..7b16a51f 100644 --- a/search/Genclks.cc +++ b/search/Genclks.cc @@ -625,16 +625,10 @@ Genclks::enqueueFanin(Vertex *vertex, VertexQueue &insert_queue, SearchPred &srch_pred) { - if (srch_pred.searchTo(vertex, mode_)) { - VertexInEdgeIterator edge_iter(vertex, graph_); - while (edge_iter.hasNext()) { - Edge *edge = edge_iter.next(); - Vertex *from_vertex = edge->from(graph_); - if (srch_pred.searchFrom(from_vertex, mode_) - && srch_pred.searchThru(edge, mode_)) - insert_queue.push(from_vertex); - } - } + graph_->visitFanins(vertex, &srch_pred, + [&insert_queue] (Vertex *fanin) { + insert_queue.push(fanin); + }); } void @@ -642,14 +636,10 @@ Genclks::enqueueFanout(Vertex *vertex, VertexQueue &insert_queue, SearchPred &srch_pred) { - VertexOutEdgeIterator edge_iter(vertex, graph_); - while (edge_iter.hasNext()) { - Edge *edge = edge_iter.next(); - Vertex *to_vertex = edge->to(graph_); - if (srch_pred.searchThru(edge, mode_) - && srch_pred.searchTo(to_vertex, mode_)) - insert_queue.push(to_vertex); - } + graph_->visitFanouts(vertex, &srch_pred, + [&insert_queue] (Vertex *fanout) { + insert_queue.push(fanout); + }); } Tag * From e603b7abe024fd478b7afc9332a8932439de0b4a Mon Sep 17 00:00:00 2001 From: James Cherry Date: Wed, 24 Jun 2026 16:55:51 -0700 Subject: [PATCH 22/30] dynamic loop do not use search pred Signed-off-by: James Cherry --- graph/Graph.cc | 12 ++- include/sta/Search.hh | 16 ++-- include/sta/SearchPred.hh | 8 +- search/PathEnum.cc | 38 +++++++-- search/Search.cc | 159 +++++++++++++++++++++----------------- 5 files changed, 140 insertions(+), 93 deletions(-) diff --git a/graph/Graph.cc b/graph/Graph.cc index 92775d57..e2123cf2 100644 --- a/graph/Graph.cc +++ b/graph/Graph.cc @@ -585,7 +585,8 @@ Graph::visitFanouts(Vertex *vertex, edge; edge = this->edge(edge->vertex_out_next_)) { Vertex *to_vertex = this->vertex(edge->to_); - if (pred->searchThru(edge) && pred->searchTo(to_vertex)) + if (pred->searchThru(edge) + && pred->searchTo(to_vertex)) fn(to_vertex); } } @@ -601,7 +602,8 @@ Graph::visitFanoutEdges(Vertex *vertex, edge; edge = this->edge(edge->vertex_out_next_)) { Vertex *to_vertex = this->vertex(edge->to_); - if (pred->searchThru(edge) && pred->searchTo(to_vertex)) + if (pred->searchThru(edge) + && pred->searchTo(to_vertex)) fn(edge, to_vertex); } } @@ -617,7 +619,8 @@ Graph::visitFanins(Vertex *vertex, edge; edge = this->edge(edge->vertex_in_next_)) { Vertex *from_vertex = this->vertex(edge->from_); - if (pred->searchThru(edge) && pred->searchFrom(from_vertex)) + if (pred->searchThru(edge) + && pred->searchFrom(from_vertex)) fn(from_vertex); } } @@ -633,7 +636,8 @@ Graph::visitFaninEdges(Vertex *vertex, edge; edge = this->edge(edge->vertex_in_next_)) { Vertex *from_vertex = this->vertex(edge->from_); - if (pred->searchThru(edge) && pred->searchFrom(from_vertex)) + if (pred->searchThru(edge) + && pred->searchFrom(from_vertex)) fn(edge, from_vertex); } } diff --git a/include/sta/Search.hh b/include/sta/Search.hh index 8122752e..1fff5663 100644 --- a/include/sta/Search.hh +++ b/include/sta/Search.hh @@ -284,8 +284,8 @@ public: Vertex *vertex, const Mode *mode, TagGroupBldr *tag_bldr); - void enqueueLatchDataOutputs(Vertex *vertex); - void enqueueLatchOutput(Vertex *vertex); + void postponeLatchDataOutputs(Vertex *vertex); + void postponeArrivals(Vertex *vertex); void enqueuePendingClkFanouts(); void postponeClkFanouts(Vertex *vertex); void seedRequired(Vertex *vertex); @@ -590,7 +590,7 @@ protected: // Search predicates. SearchPred *search_thru_; - SearchAdj *search_adj_; + SearchPred *search_adj_; EvalPred *eval_pred_; // Some arrivals exist. @@ -647,11 +647,11 @@ protected: std::mutex tag_group_lock_; // Latches data outputs to queue on the next search pass. - VertexSet pending_latch_outputs_; - std::mutex pending_latch_outputs_lock_; + VertexSet postponed_arrivals_; + std::mutex postponed_arrivals_lock_; // Clock network endpoints where arrival search was suppended by findClkArrivals(). - VertexSet pending_clk_endpoints_; - std::mutex pending_clk_endpoints_lock_; + VertexSet postponed_clk_endpoints_; + std::mutex postponed_clk_endpoints_lock_; VertexSet endpoints_; bool endpoints_initialized_{false}; @@ -802,6 +802,8 @@ protected: void pruneCrprArrivals(); void constrainedRequiredsInvalid(Vertex *vertex, bool is_clk); + bool hasPendingLoopPaths(Edge *edge) const; + bool always_to_endpoints_; bool always_save_prev_paths_; bool clks_only_; diff --git a/include/sta/SearchPred.hh b/include/sta/SearchPred.hh index 9bcdf9b8..378e8655 100644 --- a/include/sta/SearchPred.hh +++ b/include/sta/SearchPred.hh @@ -51,19 +51,19 @@ public: // Search is allowed from from_vertex. virtual bool searchFrom(const Vertex *from_vertex, 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. // from/to pins are NOT checked. // inst can be either the from_pin or to_pin instance because it // is only referenced when they are the same (non-wire edge). virtual bool searchThru(Edge *edge, const Mode *mode) const = 0; - bool searchThru(Edge *edge) const; + virtual bool searchThru(Edge *edge) const; // Search is allowed to to_pin. virtual bool searchTo(const Vertex *to_vertex, const Mode *mode) const = 0; - bool searchTo(const Vertex *to_vertex) const; - void copyState(const StaState *sta); + virtual bool searchTo(const Vertex *to_vertex) const; + virtual void copyState(const StaState *sta); protected: const StaState *sta_; diff --git a/search/PathEnum.cc b/search/PathEnum.cc index e433c703..e0a3ec20 100644 --- a/search/PathEnum.cc +++ b/search/PathEnum.cc @@ -238,6 +238,28 @@ PathEnum::reportDiversionPath(Diversion *div) using VisitedFanins = std::set>; using VertexEdge = std::pair; +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 { public: @@ -247,6 +269,7 @@ public: bool unique_edges, PathEnum *path_enum); PathEnumFaninVisitor(const PathEnumFaninVisitor &visitor); + virtual ~PathEnumFaninVisitor(); VertexVisitor *copy() const override; void visitFaninPathsThru(Path *before_div, Vertex *prev_vertex, @@ -311,7 +334,7 @@ PathEnumFaninVisitor::PathEnumFaninVisitor(PathEnd *path_end, bool unique_pins, bool unique_edges, PathEnum *path_enum) : - PathVisitor(path_enum), + PathVisitor(new EnumPred(path_enum), false, path_enum), path_end_(path_end), before_div_(before_div), unique_pins_(unique_pins), @@ -334,6 +357,11 @@ PathEnumFaninVisitor::PathEnumFaninVisitor(const PathEnumFaninVisitor &visitor) { } +PathEnumFaninVisitor::~PathEnumFaninVisitor() +{ + delete pred_; +} + VertexVisitor * PathEnumFaninVisitor::copy() const { @@ -655,10 +683,10 @@ PathEnum::makeDivertedPath(Path *path, Path *prev_copy = nullptr; while (p) { // prev_path made in next pass. - Path *copy = - new Path(p->vertex(this), p->tag(this), p->arrival(), - // Replaced on next pass. - p->prevPath(), p->prevEdge(this), p->prevArc(this), true, this); + Path *copy = new Path(p->vertex(this), p->tag(this), p->arrival(), + // Replaced on next pass. + p->prevPath(), p->prevEdge(this), p->prevArc(this), + true, this); search_->saveEnumPath(copy); if (prev_copy) prev_copy->setPrevPath(copy); diff --git a/search/Search.cc b/search/Search.cc index ae23c681..d1de9c1e 100644 --- a/search/Search.cc +++ b/search/Search.cc @@ -95,8 +95,8 @@ EvalPred::searchThru(Edge *edge, { const TimingRole *role = edge->role(); return SearchPred0::searchThru(edge, mode) - && (sta_->variables()->dynamicLoopBreaking() || !edge->isDisabledLoop()) - && (search_thru_latches_ || role->isLatchDtoQ() + && (search_thru_latches_ + || role->isLatchDtoQ() || sta_->latches()->latchDtoQState(edge, mode) == LatchEnableState::open); } @@ -133,54 +133,56 @@ bool SearchThru::searchThru(Edge *edge, 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 -// disabled to break combinational loop // latch D->Q edge // timing check edge -// dynamic loop breaking pending tags +// register set/clear +// bidirect load->driver class SearchAdj : public SearchPred { public: - SearchAdj(TagGroupBldr *tag_bldr, - const StaState *sta); + SearchAdj(const StaState *sta); + bool searchFrom(const Vertex *from_vertex) const override; bool searchFrom(const Vertex *from_vertex, const Mode *mode) const override; + bool searchThru(Edge *edge) const override; bool searchThru(Edge *edge, const Mode *mode) const override; + bool searchTo(const Vertex *to_vertex) const override; bool searchTo(const Vertex *to_vertex, const Mode *mode) const override; protected: - bool loopEnabled(Edge *edge) const; - bool hasPendingLoopPaths(Edge *edge) const; - - TagGroupBldr *tag_bldr_; const StaState *sta_; }; -SearchAdj::SearchAdj(TagGroupBldr *tag_bldr, - const StaState *sta) : +SearchAdj::SearchAdj(const StaState *sta) : SearchPred(sta), - tag_bldr_(tag_bldr), sta_(sta) { } bool -SearchAdj::searchFrom(const Vertex * /* from_vertex */, +SearchAdj::searchFrom(const Vertex *) const +{ + return true; +} + +bool +SearchAdj::searchFrom(const Vertex *, const Mode *) const { return true; } bool -SearchAdj::searchThru(Edge *edge, - const Mode *) const +SearchAdj::searchThru(Edge *edge) const { const TimingRole *role = edge->role(); const Variables *variables = sta_->variables(); @@ -189,42 +191,24 @@ SearchAdj::searchThru(Edge *edge, // Register/latch preset/clr edges are disabled by default. || (role == TimingRole::regSetClr() && !variables->presetClrArcsEnabled()) - || sta_->isDisabledBidirectInstPath(edge) - || (edge->isDisabledLoop() - && !(variables->dynamicLoopBreaking() && hasPendingLoopPaths(edge)))); + || sta_->isDisabledBidirectInstPath(edge)); } bool -SearchAdj::loopEnabled(Edge *edge) const +SearchAdj::searchThru(Edge *edge, + const Mode *) const { - return !edge->isDisabledLoop() - || (sta_->variables()->dynamicLoopBreaking() && hasPendingLoopPaths(edge)); + return searchThru(edge); } bool -SearchAdj::hasPendingLoopPaths(Edge *edge) const +SearchAdj::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; + return true; } bool -SearchAdj::searchTo(const Vertex * /* to_vertex */, +SearchAdj::searchTo(const Vertex *, const Mode *) const { return true; @@ -236,8 +220,7 @@ Search::Search(StaState *sta) : StaState(sta), search_thru_(new SearchThru(this)), - search_adj_(new SearchAdj(nullptr, - this)), + search_adj_(new SearchAdj(this)), eval_pred_(new EvalPred(this)), invalid_arrivals_(makeVertexSet(this)), @@ -247,9 +230,7 @@ Search::Search(StaState *sta) : arrival_visitor_(new ArrivalVisitor(this)), invalid_requireds_(makeVertexSet(this)), - required_iter_(new BfsBkwdIterator(BfsIndex::required, - search_adj_, - this)), + required_iter_(new BfsBkwdIterator(BfsIndex::required, search_adj_, this)), invalid_tns_(makeVertexSet(this)), clk_info_set_(new ClkInfoSet(ClkInfoLess(this))), @@ -261,8 +242,8 @@ Search::Search(StaState *sta) : tag_group_capacity_(tag_capacity_), tag_groups_(new TagGroup *[tag_group_capacity_]), tag_group_set_(new TagGroupSet(tag_group_capacity_)), - pending_latch_outputs_(makeVertexSet(this)), - pending_clk_endpoints_(makeVertexSet(this)), + postponed_arrivals_(makeVertexSet(this)), + postponed_clk_endpoints_(makeVertexSet(this)), endpoints_(makeVertexSet(this)), invalid_endpoints_(makeVertexSet(this)), @@ -326,8 +307,8 @@ Search::clear() deletePathGroups(); deletePaths(); deleteTags(); - pending_latch_outputs_.clear(); - pending_clk_endpoints_.clear(); + postponed_arrivals_.clear(); + postponed_clk_endpoints_.clear(); deleteFilter(); found_downstream_clk_pins_ = false; } @@ -655,10 +636,10 @@ Search::findFilteredArrivals(bool thru_latches) int arrival_count = arrival_iter_->visitParallel(max_level, arrival_visitor_); debugPrint(debug_, "search", 1, "found {} arrivals", arrival_count); - have_pending_latch_outputs = !pending_latch_outputs_.empty(); - for (Vertex *latch_output : pending_latch_outputs_) + have_pending_latch_outputs = !postponed_arrivals_.empty(); + for (Vertex *latch_output : postponed_arrivals_) arrival_visitor_->visit(latch_output, true); - pending_latch_outputs_.clear(); + postponed_arrivals_.clear(); deleteTagsPrev(); } @@ -997,29 +978,30 @@ Search::findAllArrivals(bool thru_latches, findArrivals1(levelize_->maxLevel()); - have_pending_latch_outputs = !pending_latch_outputs_.empty(); - for (Vertex *latch_output : pending_latch_outputs_) + have_pending_latch_outputs = !postponed_arrivals_.empty(); + for (Vertex *latch_output : postponed_arrivals_) arrival_visitor_->visit(latch_output, true); - pending_latch_outputs_.clear(); + postponed_arrivals_.clear(); } } +// Pick up where the search stopped at the clock network boundary. void Search::enqueuePendingClkFanouts() { - for (Vertex *vertex : pending_clk_endpoints_) { + for (Vertex *vertex : postponed_clk_endpoints_) { debugPrint(debug_, "search", 2, "enqueue clk fanout {}", vertex->to_string(this)); arrival_iter_->enqueueAdjacentVertices(vertex, search_adj_); } - pending_clk_endpoints_.clear(); + postponed_clk_endpoints_.clear(); } void Search::postponeClkFanouts(Vertex *vertex) { - LockGuard lock(pending_clk_endpoints_lock_); - pending_clk_endpoints_.insert(vertex); + LockGuard lock(postponed_clk_endpoints_lock_); + postponed_clk_endpoints_.insert(vertex); } void @@ -1089,7 +1071,7 @@ ArrivalVisitor::init0() { tag_bldr_ = new TagGroupBldr(true, this); tag_bldr_no_crpr_ = new TagGroupBldr(false, this); - adj_pred_ = new SearchAdj(tag_bldr_, this); + adj_pred_ = new SearchAdj(this); } void @@ -1133,7 +1115,7 @@ void ArrivalVisitor::visit(Vertex *vertex) { if (network_->isLatchOutput(vertex->pin())) - search_->enqueueLatchOutput(vertex); + search_->postponeArrivals(vertex); else visit(vertex, false); } @@ -1167,15 +1149,25 @@ ArrivalVisitor::visit(Vertex *vertex, // previous eval pass enqueue the latch outputs to be re-evaled on the // next pass. if (arrivals_changed && network_->isLatchData(pin)) - search_->enqueueLatchDataOutputs(vertex); + search_->postponeLatchDataOutputs(vertex); if ((always_to_endpoints_ || arrivals_changed)) { if (clks_only_ && vertex->isRegClk()) { debugPrint(debug_, "search", 3, "postponing clk fanout"); search_->postponeClkFanouts(vertex); } - else - search_->arrivalIterator()->enqueueAdjacentVertices(vertex, adj_pred_); + else { + graph_->visitFanoutEdges(vertex, adj_pred_, + [this] (Edge *edge, + Vertex *fanout) { + if (edge->isDisabledLoop()) { + if (hasPendingLoopPaths(edge)) + search_->postponeArrivals(fanout); + } + else + search_->arrivalIterator()->enqueue(fanout); + }); + } } if (arrivals_changed) { debugPrint(debug_, "search", 4, "arrivals changed"); @@ -1185,6 +1177,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 ArrivalVisitor::seedArrivals(Vertex *vertex) { @@ -1385,24 +1397,24 @@ ArrivalVisitor::pruneCrprArrivals() } void -Search::enqueueLatchDataOutputs(Vertex *latch_data) +Search::postponeLatchDataOutputs(Vertex *latch_data) { VertexOutEdgeIterator edge_iter(latch_data, graph_); while (edge_iter.hasNext()) { Edge *edge = edge_iter.next(); if (edge->role() == TimingRole::latchDtoQ()) { Vertex *out_vertex = edge->to(graph_); - LockGuard lock(pending_latch_outputs_lock_); - pending_latch_outputs_.insert(out_vertex); + LockGuard lock(postponed_arrivals_lock_); + postponed_arrivals_.insert(out_vertex); } } } void -Search::enqueueLatchOutput(Vertex *vertex) +Search::postponeArrivals(Vertex *vertex) { - LockGuard lock(pending_latch_outputs_lock_); - pending_latch_outputs_.insert(vertex); + LockGuard lock(postponed_arrivals_lock_); + postponed_arrivals_.insert(vertex); } void @@ -1995,7 +2007,8 @@ PathVisitor::visitEdge(const Pin *from_pin, Path *from_path = from_iter.next(); const Mode *mode = from_path->mode(this); 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))) { prev_mode = mode; const MinMax *min_max = from_path->minMax(this); From 7527553111c1f4233c5238cf4c0aee562d2b250e Mon Sep 17 00:00:00 2001 From: James Cherry Date: Wed, 24 Jun 2026 17:00:00 -0700 Subject: [PATCH 23/30] leak Signed-off-by: James Cherry --- liberty/TimingArc.cc | 3 +++ 1 file changed, 3 insertions(+) diff --git a/liberty/TimingArc.cc b/liberty/TimingArc.cc index a8ec5516..8a03ae65 100644 --- a/liberty/TimingArc.cc +++ b/liberty/TimingArc.cc @@ -536,6 +536,9 @@ TimingArcSet::destroy() delete wire_timing_arc_set_; wire_timing_arc_set_ = nullptr; wire_timing_arc_attrs_ = nullptr; + + delete port_refpin_timing_arc_set_; + port_refpin_timing_arc_set_ = nullptr; } //////////////////////////////////////////////////////////////// From 2f26c1e21a1670ea44d099e582e9842372068a34 Mon Sep 17 00:00:00 2001 From: James Cherry Date: Wed, 24 Jun 2026 18:02:47 -0700 Subject: [PATCH 24/30] make_net check for existing Signed-off-by: James Cherry --- search/Sta.cc | 12 +++++++++--- verilog/VerilogReader.cc | 9 +++------ 2 files changed, 12 insertions(+), 9 deletions(-) diff --git a/search/Sta.cc b/search/Sta.cc index e159c7be..19f3d994 100644 --- a/search/Sta.cc +++ b/search/Sta.cc @@ -4407,9 +4407,15 @@ Sta::makeNet(const char *name, { NetworkEdit *network = networkCmdEdit(); std::string escaped = escapeBrackets(name, network); - Net *net = network->makeNet(escaped, parent); - // Sta notification unnecessary. - return net; + if (network->findNet(parent, escaped)) { + report_->warn(1557, "net {} already exists.", name); + return nullptr; + } + else { + Net *net = network->makeNet(escaped, parent); + // Sta notification unnecessary. + return net; + } } void diff --git a/verilog/VerilogReader.cc b/verilog/VerilogReader.cc index 5c6cd0f9..f3a0899d 100644 --- a/verilog/VerilogReader.cc +++ b/verilog/VerilogReader.cc @@ -1468,8 +1468,7 @@ VerilogReader::linkNetwork(std::string_view top_cell_name, while (net_name_iter->hasNext()) { const std::string &net_name = net_name_iter->next(); Port *port = network_->findPort(top_cell, net_name); - Net *net = - bindings.ensureNetBinding(net_name, top_instance, network_); + Net *net = bindings.ensureNetBinding(net_name, top_instance, network_); // Guard against repeated port name. if (network_->findPin(top_instance, port) == nullptr) { Pin *pin = network_->makePin(top_instance, port, nullptr); @@ -1521,13 +1520,11 @@ VerilogReader::makeModuleInstBody(VerilogModule *module, if (assign) mergeAssignNet(assign, module, inst, bindings); if (dir->isGround()) { - Net *net = - bindings->ensureNetBinding(arg->netName(), inst, network_); + Net *net = bindings->ensureNetBinding(arg->netName(), inst, network_); network_->addConstantNet(net, LogicValue::zero); } if (dir->isPower()) { - Net *net = - bindings->ensureNetBinding(arg->netName(), inst, network_); + Net *net = bindings->ensureNetBinding(arg->netName(), inst, network_); network_->addConstantNet(net, LogicValue::one); } } From 0b938d71eb6e66a4a24f9ad1f9849f90c2a93ffd Mon Sep 17 00:00:00 2001 From: James Cherry Date: Thu, 25 Jun 2026 10:04:39 -0700 Subject: [PATCH 25/30] SearchAdjLoop predicate Signed-off-by: James Cherry --- include/sta/Search.hh | 2 +- search/Search.cc | 55 ++++++++++++++++++++++++++++++------------- 2 files changed, 39 insertions(+), 18 deletions(-) diff --git a/include/sta/Search.hh b/include/sta/Search.hh index 1fff5663..7cfefde1 100644 --- a/include/sta/Search.hh +++ b/include/sta/Search.hh @@ -809,7 +809,7 @@ protected: bool clks_only_; TagGroupBldr *tag_bldr_; TagGroupBldr *tag_bldr_no_crpr_; - SearchPred *adj_pred_; + SearchPred *srch_adj_; bool crpr_active_; bool has_fanin_one_; }; diff --git a/search/Search.cc b/search/Search.cc index d1de9c1e..27d196e1 100644 --- a/search/Search.cc +++ b/search/Search.cc @@ -139,15 +139,15 @@ SearchThru::searchThru(Edge *edge, //////////////////////////////////////////////////////////////// -// SearchAdj is mode independent. Search unless +// SearchAdjLoop is mode independent. Search unless // latch D->Q edge // timing check edge // register set/clear // bidirect load->driver -class SearchAdj : public SearchPred +class SearchAdjLoop : public SearchPred { public: - SearchAdj(const StaState *sta); + SearchAdjLoop(const StaState *sta); bool searchFrom(const Vertex *from_vertex) const override; bool searchFrom(const Vertex *from_vertex, const Mode *mode) const override; @@ -162,27 +162,27 @@ protected: const StaState *sta_; }; -SearchAdj::SearchAdj(const StaState *sta) : +SearchAdjLoop::SearchAdjLoop(const StaState *sta) : SearchPred(sta), sta_(sta) { } bool -SearchAdj::searchFrom(const Vertex *) const +SearchAdjLoop::searchFrom(const Vertex *) const { return true; } bool -SearchAdj::searchFrom(const Vertex *, - const Mode *) const +SearchAdjLoop::searchFrom(const Vertex *, + const Mode *) const { return true; } bool -SearchAdj::searchThru(Edge *edge) const +SearchAdjLoop::searchThru(Edge *edge) const { const TimingRole *role = edge->role(); const Variables *variables = sta_->variables(); @@ -195,25 +195,46 @@ SearchAdj::searchThru(Edge *edge) const } bool -SearchAdj::searchThru(Edge *edge, - const Mode *) const +SearchAdjLoop::searchThru(Edge *edge, + const Mode *) const { return searchThru(edge); } bool -SearchAdj::searchTo(const Vertex *) const +SearchAdjLoop::searchTo(const Vertex *) const { return true; } bool -SearchAdj::searchTo(const Vertex *, - const Mode *) const +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) : @@ -1071,7 +1092,7 @@ ArrivalVisitor::init0() { tag_bldr_ = new TagGroupBldr(true, this); tag_bldr_no_crpr_ = new TagGroupBldr(false, this); - adj_pred_ = new SearchAdj(this); + srch_adj_ = new SearchAdjLoop(this); } void @@ -1095,14 +1116,14 @@ void ArrivalVisitor::copyState(const StaState *sta) { StaState::copyState(sta); - adj_pred_->copyState(sta); + srch_adj_->copyState(sta); } ArrivalVisitor::~ArrivalVisitor() { delete tag_bldr_; delete tag_bldr_no_crpr_; - delete adj_pred_; + delete srch_adj_; } void @@ -1157,7 +1178,7 @@ ArrivalVisitor::visit(Vertex *vertex, search_->postponeClkFanouts(vertex); } else { - graph_->visitFanoutEdges(vertex, adj_pred_, + graph_->visitFanoutEdges(vertex, srch_adj_, [this] (Edge *edge, Vertex *fanout) { if (edge->isDisabledLoop()) { From bfdd2be0ee6214115b20cacdc0a071ca3c737fbb Mon Sep 17 00:00:00 2001 From: James Cherry Date: Thu, 25 Jun 2026 10:43:43 -0700 Subject: [PATCH 26/30] ArrivalVisitor::srch_adj_ -> search_adj_ Signed-off-by: James Cherry --- include/sta/Search.hh | 2 +- search/Search.cc | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/include/sta/Search.hh b/include/sta/Search.hh index 7cfefde1..539c9895 100644 --- a/include/sta/Search.hh +++ b/include/sta/Search.hh @@ -809,7 +809,7 @@ protected: bool clks_only_; TagGroupBldr *tag_bldr_; TagGroupBldr *tag_bldr_no_crpr_; - SearchPred *srch_adj_; + SearchPred *search_adj_; bool crpr_active_; bool has_fanin_one_; }; diff --git a/search/Search.cc b/search/Search.cc index 27d196e1..879c1247 100644 --- a/search/Search.cc +++ b/search/Search.cc @@ -1092,7 +1092,7 @@ ArrivalVisitor::init0() { tag_bldr_ = new TagGroupBldr(true, this); tag_bldr_no_crpr_ = new TagGroupBldr(false, this); - srch_adj_ = new SearchAdjLoop(this); + search_adj_ = new SearchAdjLoop(this); } void @@ -1116,14 +1116,14 @@ void ArrivalVisitor::copyState(const StaState *sta) { StaState::copyState(sta); - srch_adj_->copyState(sta); + search_adj_->copyState(sta); } ArrivalVisitor::~ArrivalVisitor() { delete tag_bldr_; delete tag_bldr_no_crpr_; - delete srch_adj_; + delete search_adj_; } void @@ -1178,7 +1178,7 @@ ArrivalVisitor::visit(Vertex *vertex, search_->postponeClkFanouts(vertex); } else { - graph_->visitFanoutEdges(vertex, srch_adj_, + graph_->visitFanoutEdges(vertex, search_adj_, [this] (Edge *edge, Vertex *fanout) { if (edge->isDisabledLoop()) { From 666b9214d01460024ece9edebb61986b7d084633 Mon Sep 17 00:00:00 2001 From: James Cherry Date: Thu, 25 Jun 2026 16:50:43 -0700 Subject: [PATCH 27/30] LibertyWriter::writeBusDcls() resolves #436 Signed-off-by: James Cherry --- liberty/LibertyWriter.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/liberty/LibertyWriter.cc b/liberty/LibertyWriter.cc index ab8b8177..695b2ccb 100644 --- a/liberty/LibertyWriter.cc +++ b/liberty/LibertyWriter.cc @@ -275,7 +275,7 @@ LibertyWriter::writeBusDcls() sta::print(stream_, " type (\"{}\") {{\n", dcl->name()); sta::print(stream_, " base_type : array;\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_to : {};\n", dcl->to()); sta::print(stream_, " }}\n"); From 37b0b20a624d2b9b9cf0eaa8557fcb13235e015b Mon Sep 17 00:00:00 2001 From: Deepashree Sengupta Date: Thu, 25 Jun 2026 19:55:29 -0400 Subject: [PATCH 28/30] fix missing path issue (#452) Signed-off-by: dsengupta0628 --- include/sta/Sdc.hh | 1 + sdc/Sdc.cc | 12 +++ search/Sta.cc | 2 + test/input_delay_ref_pin_rebuild.ok | 112 +++++++++++++++++++++++++++ test/input_delay_ref_pin_rebuild.tcl | 28 +++++++ test/regression_vars.tcl | 1 + 6 files changed, 156 insertions(+) create mode 100644 test/input_delay_ref_pin_rebuild.ok create mode 100644 test/input_delay_ref_pin_rebuild.tcl diff --git a/include/sta/Sdc.hh b/include/sta/Sdc.hh index 1ba41f91..a93569fe 100644 --- a/include/sta/Sdc.hh +++ b/include/sta/Sdc.hh @@ -577,6 +577,7 @@ public: const RiseFall *clk_rf, const MinMaxAll *min_max); void ensureInputDelayRefPinEdges(); + void inputDelayRefPinEdgesInvalid(); void setOutputDelay(const Pin *pin, const RiseFallBoth *rf, const Clock *clk, diff --git a/sdc/Sdc.cc b/sdc/Sdc.cc index 088cc967..9b30c470 100644 --- a/sdc/Sdc.cc +++ b/sdc/Sdc.cc @@ -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 diff --git a/search/Sta.cc b/search/Sta.cc index 19f3d994..435b8867 100644 --- a/search/Sta.cc +++ b/search/Sta.cc @@ -544,6 +544,8 @@ Sta::clearNonSdc() for (Mode *mode : modes_) { mode->clkNetwork()->clkPinsInvalid(); mode->sim()->clear(); + // ref_pin edges are owned by the graph deleted below; force a rebuild. + mode->sdc()->inputDelayRefPinEdgesInvalid(); } search_->clear(); diff --git a/test/input_delay_ref_pin_rebuild.ok b/test/input_delay_ref_pin_rebuild.ok new file mode 100644 index 00000000..97a18d36 --- /dev/null +++ b/test/input_delay_ref_pin_rebuild.ok @@ -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) + + diff --git a/test/input_delay_ref_pin_rebuild.tcl b/test/input_delay_ref_pin_rebuild.tcl new file mode 100644 index 00000000..dcf0ac14 --- /dev/null +++ b/test/input_delay_ref_pin_rebuild.tcl @@ -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 diff --git a/test/regression_vars.tcl b/test/regression_vars.tcl index bf7e9faa..d1e30064 100644 --- a/test/regression_vars.tcl +++ b/test/regression_vars.tcl @@ -148,6 +148,7 @@ record_public_tests { get_lib_pins_of_objects get_noargs get_objrefs + input_delay_ref_pin_rebuild liberty_arcs_one2one_1 liberty_arcs_one2one_2 liberty_backslash_eol From 1796b158a2f5e5d61d32db16615a044c221566b3 Mon Sep 17 00:00:00 2001 From: James Cherry Date: Fri, 26 Jun 2026 20:20:09 -0700 Subject: [PATCH 29/30] Graph member alignment Signed-off-by: James Cherry --- graph/Graph.cc | 31 +++++++++++++++---------------- include/sta/Graph.hh | 35 +++++++++++++++++------------------ search/Bfs.cc | 12 ++---------- 3 files changed, 34 insertions(+), 44 deletions(-) diff --git a/graph/Graph.cc b/graph/Graph.cc index e2123cf2..589e1fa0 100644 --- a/graph/Graph.cc +++ b/graph/Graph.cc @@ -50,9 +50,9 @@ namespace sta { Graph::Graph(StaState *sta, DcalcAPIndex ap_count) : StaState(sta), - ap_count_(ap_count), 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_. graph_ = this; @@ -1040,22 +1040,22 @@ Vertex::init(Pin *pin, bool is_reg_clk) { pin_ = pin; - is_reg_clk_ = is_reg_clk; - is_bidirect_drvr_ = is_bidirect_drvr; in_edges_ = edge_id_null; out_edges_ = edge_id_null; slews_ = nullptr; paths_ = nullptr; 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; is_check_clk_ = false; has_downstream_clk_pin_ = false; - level_ = 0; visited1_ = false; visited2_ = false; has_sim_value_ = false; - bfs_in_queue_ = 0; + level_ = 0; + slew_annotated_ = false; } Vertex::~Vertex() @@ -1281,20 +1281,19 @@ Edge::init(VertexId from, VertexId to, TimingArcSet *arc_set) { - from_ = from; - to_ = to; arc_set_ = arc_set; - vertex_in_next_ = 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_delay_annotated_is_bits_ = true; 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; + is_bidirect_inst_path_ = false; + is_bidirect_net_path_ = false; + is_bidirect_port_path_ = false; is_disabled_loop_ = false; has_sim_sense_ = false; has_disabled_cond_ = false; diff --git a/include/sta/Graph.hh b/include/sta/Graph.hh index abe38883..e4a1be0a 100644 --- a/include/sta/Graph.hh +++ b/include/sta/Graph.hh @@ -192,7 +192,7 @@ public: VertexSet ®ClkVertices() { return reg_clk_vertices_; } static constexpr int vertex_level_bits = 24; - static constexpr int vertex_level_max = (1< bfs_in_queue_; // 8 - int level_:Graph::vertex_level_bits; // 24 - unsigned int slew_annotated_:slew_annotated_bits; // 4 // Bidirect pins have two vertices. // This flag distinguishes the driver and load vertices. - bool is_bidirect_drvr_:1; - - bool is_reg_clk_:1; + unsigned int is_bidirect_drvr_:1; + unsigned int is_reg_clk_:1; // Constrained by timing check edge. - bool has_checks_:1; + unsigned int has_checks_:1; // Is the clock for a timing check. - bool is_check_clk_:1; - bool has_downstream_clk_pin_:1; - bool visited1_:1; - bool visited2_:1; - bool has_sim_value_:1; + unsigned int is_check_clk_:1; + unsigned int has_downstream_clk_pin_:1; + unsigned int visited1_:1; + unsigned int visited2_:1; + unsigned int has_sim_value_:1; + int level_:Graph::vertex_level_bits; // 24 + unsigned int slew_annotated_:slew_annotated_bits; // 4 private: friend class Graph; @@ -405,16 +404,16 @@ protected: static uintptr_t arcDelayAnnotateBit(size_t index); TimingArcSet *arc_set_; - 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_; float *arc_delays_; union { uintptr_t bits_; std::vector *seq_; } 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 delay_annotation_is_incremental_:1; bool is_bidirect_inst_path_:1; diff --git a/search/Bfs.cc b/search/Bfs.cc index 69b9c28b..ee099344 100644 --- a/search/Bfs.cc +++ b/search/Bfs.cc @@ -343,11 +343,7 @@ BfsIterator::remove(Vertex *vertex) BfsFwdIterator::BfsFwdIterator(BfsIndex bfs_index, SearchPred *search_pred, StaState *sta) : - BfsIterator(bfs_index, - 0, - level_max, - search_pred, - sta) + BfsIterator(bfs_index, 0, Graph::vertex_level_max, search_pred, sta) { } @@ -415,11 +411,7 @@ BfsFwdIterator::enqueueAdjacentVertices(Vertex *vertex, BfsBkwdIterator::BfsBkwdIterator(BfsIndex bfs_index, SearchPred *search_pred, StaState *sta) : - BfsIterator(bfs_index, - level_max, - 0, - search_pred, - sta) + BfsIterator(bfs_index, Graph::vertex_level_max, 0, search_pred, sta) { } From c222b49a7a25b6c33211ee02c55a64a20352deff Mon Sep 17 00:00:00 2001 From: James Cherry Date: Fri, 26 Jun 2026 21:11:13 -0700 Subject: [PATCH 30/30] ConcreteNetwork/Library member alignment Signed-off-by: James Cherry --- include/sta/ConcreteLibrary.hh | 4 ++-- include/sta/ConcreteNetwork.hh | 2 +- network/ConcreteLibrary.cc | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/include/sta/ConcreteLibrary.hh b/include/sta/ConcreteLibrary.hh index 75018056..6d291700 100644 --- a/include/sta/ConcreteLibrary.hh +++ b/include/sta/ConcreteLibrary.hh @@ -81,12 +81,12 @@ protected: void removeCell(ConcreteCell *cell); std::string name_; - ObjectId id_; std::string filename_; + ConcreteCellMap cell_map_; + ObjectId id_; bool is_liberty_; char bus_brkt_left_{'['}; char bus_brkt_right_{']'}; - ConcreteCellMap cell_map_; private: friend class ConcreteCell; diff --git a/include/sta/ConcreteNetwork.hh b/include/sta/ConcreteNetwork.hh index 593c444c..cbbce614 100644 --- a/include/sta/ConcreteNetwork.hh +++ b/include/sta/ConcreteNetwork.hh @@ -366,10 +366,10 @@ protected: ConcretePort *port_; ConcreteNet *net_; ConcreteTerm *term_{nullptr}; - ObjectId id_; // Doubly linked list of net pins. ConcretePin *net_next_{nullptr}; ConcretePin *net_prev_{nullptr}; + ObjectId id_; VertexId vertex_id_{vertex_id_null}; private: diff --git a/network/ConcreteLibrary.cc b/network/ConcreteLibrary.cc index 77b335a1..45eff5b6 100644 --- a/network/ConcreteLibrary.cc +++ b/network/ConcreteLibrary.cc @@ -42,8 +42,8 @@ ConcreteLibrary::ConcreteLibrary(std::string_view name, std::string_view filename, bool is_liberty) : name_(name), - id_(ConcreteNetwork::nextObjectId()), filename_(filename), + id_(ConcreteNetwork::nextObjectId()), is_liberty_(is_liberty) { }