1231 lines
34 KiB
C++
1231 lines
34 KiB
C++
// OpenSTA, Static Timing Analyzer
|
|
// Copyright (c) 2026, Parallax Software, Inc.
|
|
//
|
|
// This program is free software: you can redistribute it and/or modify
|
|
// it under the terms of the GNU General Public License as published by
|
|
// the Free Software Foundation, either version 3 of the License, or
|
|
// (at your option) any later version.
|
|
//
|
|
// This program is distributed in the hope that it will be useful,
|
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
// GNU General Public License for more details.
|
|
//
|
|
// You should have received a copy of the GNU General Public License
|
|
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
//
|
|
// The origin of this software must not be misrepresented; you must not
|
|
// claim that you wrote the original software.
|
|
//
|
|
// Altered source versions must be plainly marked as such, and must not be
|
|
// misrepresented as being the original software.
|
|
//
|
|
// This notice may not be removed or altered from any source distribution.
|
|
|
|
// "Performance Computation for Precharacterized CMOS Gates with RC Loads",
|
|
// Florentin Dartu, Noel Menezes and Lawrence Pileggi, IEEE Transactions
|
|
// on Computer-Aided Design of Integrated Circuits and Systems, Vol 15, No 5,
|
|
// May 1996, pg 544-553.
|
|
//
|
|
// The only real change from the paper is that Vl, the measured low
|
|
// slew voltage is matched instead of y20 in eqn 12.
|
|
|
|
#include "DmpCeff.hh"
|
|
#include <Eigen/Dense>
|
|
|
|
#include <algorithm>
|
|
#include <array>
|
|
#include <cmath>
|
|
#include <optional>
|
|
#include <string_view>
|
|
#include <tuple>
|
|
#include <utility>
|
|
|
|
#include "ArcDelayCalc.hh"
|
|
#include "Debug.hh"
|
|
#include "FindRoot.hh"
|
|
#include "Format.hh"
|
|
#include "Liberty.hh"
|
|
#include "Parasitics.hh"
|
|
#include "Report.hh"
|
|
#include "Sdc.hh"
|
|
#include "TableModel.hh"
|
|
#include "TimingArc.hh"
|
|
#include "Units.hh"
|
|
#include "Variables.hh"
|
|
|
|
namespace sta {
|
|
|
|
// Indices of Newton-Raphson parameter vector.
|
|
enum DmpParam { t0, dt, ceff };
|
|
|
|
static const char *dmp_param_index_strings[] = {"t0", "dt", "Ceff"};
|
|
|
|
// Indices of Newton-Raphson function value vector.
|
|
enum DmpFunc { y20, y50, ipi };
|
|
|
|
static const char *dmp_func_index_strings[] = {"y20", "y50", "Ipi"};
|
|
|
|
static double
|
|
exp2(double x);
|
|
|
|
static double
|
|
gateModelRd(const LibertyCell *cell,
|
|
const GateTableModel *gate_model,
|
|
const RiseFall *rf,
|
|
double in_slew,
|
|
double c2,
|
|
double c1,
|
|
const Pvt *pvt);
|
|
|
|
////////////////////////////////////////////////////////////////
|
|
|
|
class DmpError : public Exception
|
|
{
|
|
public:
|
|
DmpError(std::string_view what);
|
|
const char *what() const noexcept override { return what_.c_str(); }
|
|
|
|
private:
|
|
std::string what_;
|
|
};
|
|
|
|
DmpError::DmpError(std::string_view what) :
|
|
what_(what)
|
|
{
|
|
//sta::print(stdout, "DmpError {}\n", what);
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////
|
|
|
|
DmpAlg::DmpAlg(int nr_order,
|
|
StaState *sta) :
|
|
StaState(sta),
|
|
nr_order_(nr_order)
|
|
{
|
|
}
|
|
|
|
void
|
|
DmpAlg::init(const LibertyLibrary *drvr_library,
|
|
const LibertyCell *drvr_cell,
|
|
const Pvt *pvt,
|
|
const GateTableModel *gate_model,
|
|
const RiseFall *rf,
|
|
double rd,
|
|
double in_slew,
|
|
// Pi model.
|
|
double c2,
|
|
double rpi,
|
|
double c1)
|
|
{
|
|
drvr_library_ = drvr_library;
|
|
drvr_cell_ = drvr_cell;
|
|
pvt_ = pvt;
|
|
gate_model_ = gate_model;
|
|
rd_ = rd;
|
|
in_slew_ = in_slew;
|
|
c2_ = c2;
|
|
rpi_ = rpi;
|
|
c1_ = c1;
|
|
driver_valid_ = false;
|
|
vth_ = drvr_library->outputThreshold(rf);
|
|
vl_ = drvr_library->slewLowerThreshold(rf);
|
|
vh_ = drvr_library->slewUpperThreshold(rf);
|
|
slew_derate_ = drvr_library->slewDerateFromLibrary();
|
|
}
|
|
|
|
// Find Ceff, delta_t and t0 for the driver.
|
|
void
|
|
DmpAlg::findDriverParams(double ceff)
|
|
{
|
|
Eigen::Vector3d x = Eigen::Vector3d::Zero();
|
|
if (nr_order_ == 3)
|
|
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(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(ceff_));
|
|
if (debug_->check("dmp_ceff", 4))
|
|
showVo();
|
|
}
|
|
|
|
std::pair<double, double>
|
|
DmpAlg::gateCapDelaySlew(double ceff)
|
|
{
|
|
float model_delay, model_slew;
|
|
gate_model_->gateDelay(pvt_, in_slew_, ceff, model_delay, model_slew);
|
|
double delay = model_delay;
|
|
double slew = model_slew;
|
|
return {delay, slew};
|
|
}
|
|
|
|
std::tuple<double, double, double>
|
|
DmpAlg::gateDelays(double ceff)
|
|
{
|
|
auto [t_vth, table_slew] = gateCapDelaySlew(ceff);
|
|
// Convert reported/table slew to measured slew.
|
|
double slew = table_slew * slew_derate_;
|
|
double t_vl = t_vth - slew * (vth_ - vl_) / (vh_ - vl_);
|
|
return {t_vth, t_vl, slew};
|
|
}
|
|
|
|
std::pair<double, double>
|
|
DmpAlg::y(double t,
|
|
double t0,
|
|
double dt,
|
|
double cl)
|
|
{
|
|
double t1 = t - t0;
|
|
if (t1 <= 0.0) {
|
|
double y = 0.0;
|
|
return {y, t1};
|
|
}
|
|
if (t1 <= dt) {
|
|
double y = y0(t1, cl) / dt;
|
|
return {y, t1};
|
|
}
|
|
double y = (y0(t1, cl) - y0(t1 - dt, cl)) / dt;
|
|
return {y, t1};
|
|
}
|
|
|
|
double
|
|
DmpAlg::y0(double t,
|
|
double cl)
|
|
{
|
|
return t - rd_ * cl * (1.0 - exp2(-t / (rd_ * cl)));
|
|
}
|
|
|
|
std::tuple<double, double, double>
|
|
DmpAlg::dy(double t,
|
|
double t0,
|
|
double dt,
|
|
double cl)
|
|
{
|
|
double t1 = t - t0;
|
|
if (t1 <= 0.0) {
|
|
double dydt0 = 0.0;
|
|
double dyddt = 0.0;
|
|
double dydcl = 0.0;
|
|
return {dydt0, dyddt, dydcl};
|
|
}
|
|
if (t1 <= dt) {
|
|
double dydt0 = -y0dt(t1, cl) / dt;
|
|
double dyddt = -y0(t1, cl) / (dt * dt);
|
|
double dydcl = y0dcl(t1, cl) / dt;
|
|
return {dydt0, dyddt, dydcl};
|
|
}
|
|
double dydt0 = -(y0dt(t1, cl) - y0dt(t1 - dt, cl)) / dt;
|
|
double dyddt = -(y0(t1, cl) + y0(t1 - dt, cl)) / (dt * dt) + y0dt(t1 - dt, cl) / dt;
|
|
double dydcl = (y0dcl(t1, cl) - y0dcl(t1 - dt, cl)) / dt;
|
|
return {dydt0, dyddt, dydcl};
|
|
}
|
|
|
|
double
|
|
DmpAlg::y0dt(double t,
|
|
double cl)
|
|
{
|
|
return 1.0 - exp2(-t / (rd_ * cl));
|
|
}
|
|
|
|
double
|
|
DmpAlg::y0dcl(double t,
|
|
double cl)
|
|
{
|
|
return rd_ * ((1.0 + t / (rd_ * cl)) * exp2(-t / (rd_ * cl)) - 1);
|
|
}
|
|
|
|
void
|
|
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]);
|
|
}
|
|
|
|
void
|
|
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]);
|
|
}
|
|
|
|
void
|
|
DmpAlg::showJacobian(const Eigen::Matrix3d &fjac)
|
|
{
|
|
std::string line = " ";
|
|
for (int j = 0; j < nr_order_; j++)
|
|
line += sta::format("{:>12}", dmp_param_index_strings[j]);
|
|
report_->reportLine(line);
|
|
for (int i = 0; i < nr_order_; i++) {
|
|
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));
|
|
report_->reportLine(line);
|
|
}
|
|
}
|
|
|
|
std::pair<double, double>
|
|
DmpAlg::findDriverDelaySlew()
|
|
{
|
|
double t_upper = voCrossingUpperBound();
|
|
double delay = findVoCrossing(vth_, t0_, t_upper);
|
|
double tl = findVoCrossing(vl_, t0_, delay);
|
|
double th = findVoCrossing(vh_, delay, t_upper);
|
|
// Convert measured slew to table slew.
|
|
double slew = (th - tl) / slew_derate_;
|
|
return {delay, slew};
|
|
}
|
|
|
|
// Find t such that vo(t)=v.
|
|
double
|
|
DmpAlg::findVoCrossing(double vth,
|
|
double t_lower,
|
|
double t_upper)
|
|
{
|
|
FindRootFunc vo_func = [&](double t, double &y, double &dy) {
|
|
auto [vo, dvo_dt] = Vo(t);
|
|
y = vo - vth;
|
|
dy = dvo_dt;
|
|
};
|
|
auto [t_vth, failed] = findRoot(vo_func, t_lower, t_upper, vth_time_tol_,
|
|
find_root_max_iter_);
|
|
if (failed)
|
|
throw DmpError("find Vo crossing failed");
|
|
return t_vth;
|
|
}
|
|
|
|
std::pair<double, double>
|
|
DmpAlg::Vo(double t)
|
|
{
|
|
double t1 = t - t0_;
|
|
if (t1 <= 0.0) {
|
|
double vo = 0.0;
|
|
double dvo_dt = 0.0;
|
|
return {vo, dvo_dt};
|
|
}
|
|
if (t1 <= dt_) {
|
|
auto [v0, dv0_dt] = V0(t1);
|
|
|
|
double vo = v0 / dt_;
|
|
double dvo_dt = dv0_dt / dt_;
|
|
return {vo, dvo_dt};
|
|
}
|
|
auto [v0, dv0_dt] = V0(t1);
|
|
|
|
auto [v0_dt, dv0_dt_dt] = V0(t1 - dt_);
|
|
|
|
double vo = (v0 - v0_dt) / dt_;
|
|
double dvo_dt = (dv0_dt - dv0_dt_dt) / dt_;
|
|
return {vo, dvo_dt};
|
|
}
|
|
|
|
void
|
|
DmpAlg::showVo()
|
|
{
|
|
report_->report(" t vo(t)");
|
|
double ub = voCrossingUpperBound();
|
|
const double step = dt_ / 10.0;
|
|
for (int i = 0;; ++i) {
|
|
double t = t0_ + step * i;
|
|
if (!(t < t0_ + ub))
|
|
break;
|
|
report_->report(" {:g} {:g}", t, Vo(t).first);
|
|
}
|
|
}
|
|
|
|
std::pair<double, double>
|
|
DmpAlg::loadDelaySlew(const Pin *,
|
|
double elmore)
|
|
{
|
|
if (!driver_valid_
|
|
|| elmore == 0.0
|
|
// Elmore delay is small compared to driver slew.
|
|
|| elmore < drvr_slew_ * 1e-3) {
|
|
double delay = elmore;
|
|
double slew = drvr_slew_;
|
|
return {delay, slew};
|
|
}
|
|
// Use the driver thresholds and rely on thresholdAdjust to
|
|
// convert the delay and slew to the load's thresholds.
|
|
try {
|
|
elmore_ = elmore;
|
|
p3_ = 1.0 / elmore;
|
|
if (debug_->check("dmp_ceff", 4))
|
|
showVl();
|
|
double t_lower = t0_;
|
|
double t_upper = vlCrossingUpperBound();
|
|
double load_delay = findVlCrossing(vth_, t_lower, t_upper);
|
|
double tl = findVlCrossing(vl_, t_lower, load_delay);
|
|
double th = findVlCrossing(vh_, load_delay, t_upper);
|
|
// Measure delay from Vo, the load dependent source excitation.
|
|
double delay = load_delay - vo_delay_;
|
|
// Convert measured slew to reported/table slew.
|
|
double slew = (th - tl) / slew_derate_;
|
|
if (delay < 0.0) {
|
|
// Only report a problem if the difference is significant.
|
|
if (-delay > vth_time_tol_ * vo_delay_)
|
|
fail("load delay less than zero");
|
|
// Use elmore delay.
|
|
delay = elmore;
|
|
}
|
|
if (slew < drvr_slew_) {
|
|
// Only report a problem if the difference is significant.
|
|
if ((drvr_slew_ - slew) > vth_time_tol_ * drvr_slew_)
|
|
fail("load slew less than driver slew");
|
|
slew = drvr_slew_;
|
|
}
|
|
return {delay, slew};
|
|
} catch (DmpError &error) {
|
|
fail(error.what());
|
|
double delay = elmore_;
|
|
double slew = drvr_slew_;
|
|
return {delay, slew};
|
|
}
|
|
}
|
|
|
|
// Find t such that vl(t)=v.
|
|
double
|
|
DmpAlg::findVlCrossing(double vth,
|
|
double t_lower,
|
|
double t_upper)
|
|
{
|
|
FindRootFunc vl_func = [&](double t, double &y, double &dy) {
|
|
auto [vl, vl_dt] = Vl(t);
|
|
y = vl - vth;
|
|
dy = vl_dt;
|
|
};
|
|
auto [t_vth, failed] = findRoot(vl_func, t_lower, t_upper, vth_time_tol_,
|
|
find_root_max_iter_);
|
|
if (failed)
|
|
throw DmpError("find Vl crossing failed");
|
|
return t_vth;
|
|
}
|
|
|
|
double
|
|
DmpAlg::vlCrossingUpperBound()
|
|
{
|
|
return voCrossingUpperBound() + elmore_ * 2.0;
|
|
}
|
|
|
|
std::pair<double, double>
|
|
DmpAlg::Vl(double t)
|
|
{
|
|
double t1 = t - t0_;
|
|
if (t1 <= 0.0)
|
|
return {0.0, 0.0};
|
|
if (t1 <= dt_) {
|
|
auto [vl0, dvl0_dt] = Vl0(t1);
|
|
return {vl0 / dt_, dvl0_dt / dt_};
|
|
}
|
|
auto [vl0, dvl0_dt] = Vl0(t1);
|
|
|
|
auto [vl0_dt, dvl0_dt_dt] = Vl0(t1 - dt_);
|
|
|
|
double vl = (vl0 - vl0_dt) / dt_;
|
|
double dvl_dt = (dvl0_dt - dvl0_dt_dt) / dt_;
|
|
return {vl, dvl_dt};
|
|
}
|
|
|
|
void
|
|
DmpAlg::showVl()
|
|
{
|
|
report_->report(" t vl(t)");
|
|
double ub = vlCrossingUpperBound();
|
|
const double step = ub / 10.0;
|
|
const double t_end = t0_ + ub * 2.0;
|
|
for (int i = 0;; ++i) {
|
|
double t = t0_ + step * i;
|
|
if (!(t < t_end))
|
|
break;
|
|
report_->report(" {:g} {:g}", t, Vl(t).first);
|
|
}
|
|
}
|
|
|
|
void
|
|
DmpAlg::fail(std::string_view reason)
|
|
{
|
|
// Report failures with a unique debug flag.
|
|
if (debug_->check("dmp_ceff", 1) || debug_->check("dcalc_error", 1))
|
|
report_->report("delay_calc: DMP failed - {} c2={} rpi={} c1={} rd={}", reason,
|
|
units_->capacitanceUnit()->asString(c2_),
|
|
units_->resistanceUnit()->asString(rpi_),
|
|
units_->capacitanceUnit()->asString(c1_),
|
|
units_->resistanceUnit()->asString(rd_));
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////
|
|
|
|
DmpCap::DmpCap(StaState *sta) :
|
|
DmpAlg(1,
|
|
sta)
|
|
{
|
|
}
|
|
|
|
void
|
|
DmpCap::init(const LibertyLibrary *drvr_library,
|
|
const LibertyCell *drvr_cell,
|
|
const Pvt *pvt,
|
|
const GateTableModel *gate_model,
|
|
const RiseFall *rf,
|
|
double rd,
|
|
double in_slew,
|
|
double c2,
|
|
double rpi,
|
|
double c1)
|
|
{
|
|
debugPrint(debug_, "dmp_ceff", 3, "Using DMP cap");
|
|
DmpAlg::init(drvr_library, drvr_cell, pvt, gate_model, rf, rd, in_slew,
|
|
c2, rpi, c1);
|
|
ceff_ = c1 + c2;
|
|
}
|
|
|
|
std::pair<double, double>
|
|
DmpCap::gateDelaySlew()
|
|
{
|
|
debugPrint(debug_, "dmp_ceff", 3, " ceff = {}",
|
|
units_->capacitanceUnit()->asString(ceff_));
|
|
auto [delay, slew] = gateCapDelaySlew(ceff_);
|
|
drvr_slew_ = slew;
|
|
return {delay, slew};
|
|
}
|
|
|
|
std::pair<double, double>
|
|
DmpCap::loadDelaySlew(const Pin *,
|
|
double elmore)
|
|
{
|
|
double delay = elmore;
|
|
double slew = drvr_slew_;
|
|
return {delay, slew};
|
|
}
|
|
|
|
void
|
|
DmpCap::evalDmpEqns(Eigen::Vector3d &,
|
|
Eigen::Vector3d &,
|
|
Eigen::Matrix3d &)
|
|
{
|
|
}
|
|
|
|
std::pair<double, double>
|
|
DmpCap::V0(double)
|
|
{
|
|
double vo = 0.0;
|
|
double dvo_dt = 0.0;
|
|
return {vo, dvo_dt};
|
|
}
|
|
|
|
double
|
|
DmpCap::voCrossingUpperBound()
|
|
{
|
|
return 0.0;
|
|
}
|
|
|
|
std::pair<double, double>
|
|
DmpCap::Vl0(double)
|
|
{
|
|
double vl = 0.0;
|
|
double dvl_dt = 0.0;
|
|
return {vl, dvl_dt};
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////
|
|
|
|
DmpPi::DmpPi(StaState *sta) :
|
|
DmpAlg(3,
|
|
sta)
|
|
{
|
|
}
|
|
|
|
void
|
|
DmpPi::init(const LibertyLibrary *drvr_library,
|
|
const LibertyCell *drvr_cell,
|
|
const Pvt *pvt,
|
|
const GateTableModel *gate_model,
|
|
const RiseFall *rf,
|
|
double rd,
|
|
double in_slew,
|
|
double c2,
|
|
double rpi,
|
|
double c1)
|
|
{
|
|
debugPrint(debug_, "dmp_ceff", 3, "Using DMP Pi");
|
|
DmpAlg::init(drvr_library, drvr_cell, pvt, gate_model, rf, rd, in_slew,
|
|
c2, rpi, c1);
|
|
|
|
// Find poles/zeros.
|
|
z1_ = 1.0 / (rpi_ * c1_);
|
|
k0_ = 1.0 / (rd_ * c2_);
|
|
double a = rpi_ * rd_ * c1_ * c2_;
|
|
double b = rd_ * (c1_ + c2_) + rpi_ * c1_;
|
|
double sqrt_ = std::sqrt(b * b - 4 * a);
|
|
p1_ = (b + sqrt_) / (2 * a);
|
|
p2_ = (b - sqrt_) / (2 * a);
|
|
|
|
double p1p2 = (p1_ * p2_);
|
|
k2_ = z1_ / p1p2;
|
|
k1_ = (1.0 - k2_ * (p1_ + p2_)) / p1p2;
|
|
k4_ = (k1_ * p1_ + k2_) / (p2_ - p1_);
|
|
k3_ = -k1_ - k4_;
|
|
|
|
double z_ = (c1_ + c2_) / (rpi_ * c1_ * c2_);
|
|
A_ = z_ / p1p2;
|
|
B_ = (z_ - p1_) / (p1_ * (p1_ - p2_));
|
|
D_ = (z_ - p2_) / (p2_ * (p2_ - p1_));
|
|
}
|
|
|
|
std::pair<double, double>
|
|
DmpPi::gateDelaySlew()
|
|
{
|
|
driver_valid_ = false;
|
|
double delay = 0.0;
|
|
double slew = 0.0;
|
|
try {
|
|
findDriverParamsPi();
|
|
auto [table_delay, table_slew] = gateCapDelaySlew(ceff_);
|
|
delay = table_delay;
|
|
// slew = table_slew;
|
|
try {
|
|
auto [vo_delay, vo_slew] = findDriverDelaySlew();
|
|
driver_valid_ = true;
|
|
// Save Vo delay to measure load wire delay waveform.
|
|
vo_delay_ = vo_delay;
|
|
// delay = vo_delay;
|
|
slew = vo_slew;
|
|
} catch (DmpError &error) {
|
|
fail(error.what());
|
|
// Fall back to table slew.
|
|
slew = table_slew;
|
|
}
|
|
} catch (DmpError &error) {
|
|
fail(error.what());
|
|
// Driver calculation failed - use Ceff=c1+c2.
|
|
ceff_ = c1_ + c2_;
|
|
std::tie(delay, slew) = gateCapDelaySlew(ceff_);
|
|
}
|
|
drvr_slew_ = slew;
|
|
return {delay, slew};
|
|
}
|
|
|
|
void
|
|
DmpPi::findDriverParamsPi()
|
|
{
|
|
try {
|
|
findDriverParams(c2_ + c1_);
|
|
} catch (DmpError &) {
|
|
findDriverParams(c2_);
|
|
}
|
|
}
|
|
|
|
// 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(Eigen::Vector3d &x,
|
|
Eigen::Vector3d &fvec,
|
|
Eigen::Matrix3d &fjac)
|
|
{
|
|
const double t0 = x[DmpParam::t0];
|
|
const double dt = x[DmpParam::dt];
|
|
const double ceff = x[DmpParam::ceff];
|
|
|
|
// Validate bounds to prevent mathematical domain errors.
|
|
if (ceff < 0.0) {
|
|
throw DmpError("eqn eval failed: ceff < 0");
|
|
}
|
|
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) {
|
|
throw DmpError("eqn eval failed: slew = 0");
|
|
}
|
|
|
|
// ceff_time is bounded by 1.4 * dt.
|
|
const double ceff_time = std::min(slew / (vh_ - vl_), 1.4 * dt);
|
|
|
|
// 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));
|
|
|
|
// Evaluate function values (residuals).
|
|
const double y50 = y(t_vth, t0, dt, ceff).first;
|
|
const 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_;
|
|
|
|
// 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;
|
|
|
|
// 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(x);
|
|
showFvec(fvec);
|
|
showJacobian(fjac);
|
|
report_->report(".................");
|
|
}
|
|
}
|
|
|
|
// Eqn 13, Eqn 14.
|
|
double
|
|
DmpPi::ipiIceff(double,
|
|
double dt,
|
|
double ceff_time,
|
|
double ceff)
|
|
{
|
|
double exp_p1_dt = exp2(-p1_ * ceff_time);
|
|
double exp_p2_dt = exp2(-p2_ * ceff_time);
|
|
double exp_dt_rd_ceff = exp2(-ceff_time / (rd_ * ceff));
|
|
double ipi = (A_ * ceff_time + (B_ / p1_) * (1.0 - exp_p1_dt)
|
|
+ (D_ / p2_) * (1.0 - exp_p2_dt))
|
|
/ (rd_ * ceff_time * dt);
|
|
double iceff =
|
|
(rd_ * ceff * ceff_time - (rd_ * ceff) * (rd_ * ceff) * (1.0 - exp_dt_rd_ceff))
|
|
/ (rd_ * ceff_time * dt);
|
|
return ipi - iceff;
|
|
}
|
|
|
|
std::pair<double, double>
|
|
DmpPi::V0(double t)
|
|
{
|
|
double exp_p1 = exp2(-p1_ * t);
|
|
double exp_p2 = exp2(-p2_ * t);
|
|
double vo = k0_ * (k1_ + k2_ * t + k3_ * exp_p1 + k4_ * exp_p2);
|
|
double dvo_dt = k0_ * (k2_ - k3_ * p1_ * exp_p1 - k4_ * p2_ * exp_p2);
|
|
return {vo, dvo_dt};
|
|
}
|
|
|
|
std::pair<double, double>
|
|
DmpPi::Vl0(double t)
|
|
{
|
|
double D1 = k0_ * (k1_ - k2_ / p3_);
|
|
double D3 = -p3_ * k0_ * k3_ / (p1_ - p3_);
|
|
double D4 = -p3_ * k0_ * k4_ / (p2_ - p3_);
|
|
double D5 =
|
|
k0_ * (k2_ / p3_ - k1_ + p3_ * k3_ / (p1_ - p3_) + p3_ * k4_ / (p2_ - p3_));
|
|
double exp_p1 = exp2(-p1_ * t);
|
|
double exp_p2 = exp2(-p2_ * t);
|
|
double exp_p3 = exp2(-p3_ * t);
|
|
double vl = D1 + t + D3 * exp_p1 + D4 * exp_p2 + D5 * exp_p3;
|
|
double dvl_dt = 1.0 - D3 * p1_ * exp_p1 - D4 * p2_ * exp_p2 - D5 * p3_ * exp_p3;
|
|
return {vl, dvl_dt};
|
|
}
|
|
|
|
double
|
|
DmpPi::voCrossingUpperBound()
|
|
{
|
|
return t0_ + dt_ + (c1_ + c2_) * (rd_ + rpi_) * 2.0;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////
|
|
|
|
DmpOnePole::DmpOnePole(StaState *sta) :
|
|
DmpAlg(2,
|
|
sta)
|
|
{
|
|
}
|
|
|
|
void
|
|
DmpOnePole::evalDmpEqns(Eigen::Vector3d &x,
|
|
Eigen::Vector3d &fvec,
|
|
Eigen::Matrix3d &fjac)
|
|
{
|
|
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;
|
|
|
|
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(x);
|
|
showFvec(fvec);
|
|
}
|
|
|
|
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),
|
|
ignore2) = dy(t_vth, t0, dt, ceff_);
|
|
|
|
if (debug_->check("dmp_ceff", 4)) {
|
|
showJacobian(fjac);
|
|
report_->report(".................");
|
|
}
|
|
}
|
|
|
|
double
|
|
DmpOnePole::voCrossingUpperBound()
|
|
{
|
|
return t0_ + dt_ + ceff_ * rd_ * 2.0;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////
|
|
|
|
DmpZeroC2::DmpZeroC2(StaState *sta) :
|
|
DmpOnePole(sta)
|
|
{
|
|
}
|
|
|
|
void
|
|
DmpZeroC2::init(const LibertyLibrary *drvr_library,
|
|
const LibertyCell *drvr_cell,
|
|
const Pvt *pvt,
|
|
const GateTableModel *gate_model,
|
|
const RiseFall *rf,
|
|
double rd,
|
|
double in_slew,
|
|
double c2,
|
|
double rpi,
|
|
double c1)
|
|
{
|
|
debugPrint(debug_, "dmp_ceff", 3, "Using DMP C2=0");
|
|
DmpAlg::init(drvr_library, drvr_cell, pvt, gate_model, rf, rd, in_slew,
|
|
c2, rpi, c1);
|
|
ceff_ = c1;
|
|
|
|
z1_ = 1.0 / (rpi_ * c1_);
|
|
p1_ = 1.0 / (c1_ * (rd_ + rpi_));
|
|
|
|
k0_ = p1_ / z1_;
|
|
k2_ = 1.0 / k0_;
|
|
k1_ = (p1_ - z1_) / (p1_ * p1_);
|
|
k3_ = -k1_;
|
|
}
|
|
|
|
std::pair<double, double>
|
|
DmpZeroC2::gateDelaySlew()
|
|
{
|
|
double delay = 0.0;
|
|
double slew = 0.0;
|
|
try {
|
|
findDriverParams(c1_);
|
|
ceff_ = c1_;
|
|
std::tie(delay, slew) = findDriverDelaySlew();
|
|
driver_valid_ = true;
|
|
vo_delay_ = delay;
|
|
}
|
|
catch (DmpError &error) {
|
|
fail(error.what());
|
|
// Fall back to table slew.
|
|
driver_valid_ = false;
|
|
ceff_ = c1_;
|
|
std::tie(delay, slew) = gateCapDelaySlew(ceff_);
|
|
}
|
|
drvr_slew_ = slew;
|
|
return {delay, slew};
|
|
}
|
|
|
|
std::pair<double, double>
|
|
DmpZeroC2::V0(double t)
|
|
{
|
|
double exp_p1 = exp2(-p1_ * t);
|
|
double vo = k0_ * (k1_ + k2_ * t + k3_ * exp_p1);
|
|
double dvo_dt = k0_ * (k2_ - k3_ * p1_ * exp_p1);
|
|
return {vo, dvo_dt};
|
|
}
|
|
|
|
std::pair<double, double>
|
|
DmpZeroC2::Vl0(double t)
|
|
{
|
|
double D1 = k0_ * (k1_ - k2_ / p3_);
|
|
double D3 = -p3_ * k0_ * k3_ / (p1_ - p3_);
|
|
double D5 = k0_ * (k2_ / p3_ - k1_ + p3_ * k3_ / (p1_ - p3_));
|
|
double exp_p1 = exp2(-p1_ * t);
|
|
double exp_p3 = exp2(-p3_ * t);
|
|
double vl = D1 + t + D3 * exp_p1 + D5 * exp_p3;
|
|
double dvl_dt = 1.0 - D3 * p1_ * exp_p1 - D5 * p3_ * exp_p3;
|
|
return {vl, dvl_dt};
|
|
}
|
|
|
|
double
|
|
DmpZeroC2::voCrossingUpperBound()
|
|
{
|
|
return t0_ + dt_ + c1_ * (rd_ + rpi_) * 2.0;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////
|
|
|
|
// Newton-Raphson iteration to find zeros of a function.
|
|
// 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(Eigen::Vector3d &x)
|
|
{
|
|
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_);
|
|
|
|
if (all_under_x_tol) {
|
|
return;
|
|
}
|
|
}
|
|
throw DmpError("Newton-Raphson max iterations exceeded");
|
|
}
|
|
|
|
// Solves the linear system J * p = -f (Jacobian * step = -residuals) for the Newton step.
|
|
//
|
|
// 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)
|
|
{
|
|
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;
|
|
}
|
|
|
|
double det = fjac.determinant();
|
|
if (std::abs(det) < 1e-12) {
|
|
throw DmpError("Jacobian is singular (order 3)");
|
|
}
|
|
p = fjac.inverse() * -fvec;
|
|
return p;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////
|
|
|
|
bool DmpCeffDelayCalc::unsuppored_model_warned_ = false;
|
|
|
|
DmpCeffDelayCalc::DmpCeffDelayCalc(StaState *sta) :
|
|
LumpedCapDelayCalc(sta),
|
|
dmp_cap_(sta),
|
|
dmp_pi_(sta),
|
|
dmp_zero_c2_(sta)
|
|
{
|
|
}
|
|
|
|
ArcDcalcResult
|
|
DmpCeffDelayCalc::gateDelay(const Pin *drvr_pin,
|
|
const TimingArc *arc,
|
|
const Slew &in_slew,
|
|
float load_cap,
|
|
const Parasitic *parasitic,
|
|
const LoadPinIndexMap &load_pin_index_map,
|
|
const Scene *scene,
|
|
const MinMax *min_max)
|
|
{
|
|
parasitics_ = scene->parasitics(min_max);
|
|
const RiseFall *rf = arc->toEdge()->asRiseFall();
|
|
const LibertyCell *drvr_cell = arc->from()->libertyCell();
|
|
const LibertyLibrary *drvr_library = drvr_cell->libertyLibrary();
|
|
|
|
GateTableModel *table_model = arc->gateTableModel(scene, min_max);
|
|
if (table_model && parasitic) {
|
|
float in_slew1 = delayAsFloat(in_slew);
|
|
float c2, rpi, c1;
|
|
parasitics_->piModel(parasitic, c2, rpi, c1);
|
|
if (std::isnan(c2) || std::isnan(c1) || std::isnan(rpi))
|
|
report_->error(1040, "parasitic Pi model has NaNs.");
|
|
const Pvt *pvt = pinPvt(drvr_pin, scene, min_max);
|
|
setCeffAlgorithm(drvr_library, drvr_cell, pvt,
|
|
table_model, rf, in_slew1, c2, rpi, c1);
|
|
auto [gate_delay, drvr_slew] = gateDelaySlew();
|
|
|
|
// Fill in pocv parameters.
|
|
double ceff = dmp_alg_->ceff();
|
|
ArcDelay gate_delay2(gate_delay);
|
|
Slew drvr_slew2(drvr_slew);
|
|
if (variables_->pocvEnabled())
|
|
table_model->gateDelayPocv(pvt, in_slew1, ceff, min_max,
|
|
variables_->pocvMode(),
|
|
gate_delay2, drvr_slew2);
|
|
ArcDcalcResult dcalc_result(load_pin_index_map.size());
|
|
dcalc_result.setGateDelay(gate_delay2);
|
|
dcalc_result.setDrvrSlew(drvr_slew2);
|
|
|
|
for (const auto &[load_pin, load_idx] : load_pin_index_map) {
|
|
double wire_delay;
|
|
double load_slew;
|
|
loadDelaySlew(load_pin, drvr_slew, rf, drvr_library, parasitic,
|
|
wire_delay, load_slew);
|
|
// Copy pocv params from driver.
|
|
ArcDelay wire_delay2(gate_delay2);
|
|
Slew load_slew2(drvr_slew2);
|
|
delaySetMean(wire_delay2, wire_delay);
|
|
delaySetMean(load_slew2, load_slew);
|
|
dcalc_result.setWireDelay(load_idx, wire_delay2);
|
|
dcalc_result.setLoadSlew(load_idx, load_slew2);
|
|
}
|
|
return dcalc_result;
|
|
}
|
|
else {
|
|
ArcDcalcResult dcalc_result =
|
|
LumpedCapDelayCalc::gateDelay(drvr_pin, arc, in_slew, load_cap, parasitic,
|
|
load_pin_index_map, scene, min_max);
|
|
if (parasitic && !unsuppored_model_warned_) {
|
|
unsuppored_model_warned_ = true;
|
|
report_->warn(1041,
|
|
"cell {} delay model not supported on SPF parasitics by DMP "
|
|
"delay calculator",
|
|
drvr_cell->name());
|
|
}
|
|
return dcalc_result;
|
|
}
|
|
}
|
|
|
|
void
|
|
DmpCeffDelayCalc::setCeffAlgorithm(const LibertyLibrary *drvr_library,
|
|
const LibertyCell *drvr_cell,
|
|
const Pvt *pvt,
|
|
const GateTableModel *gate_model,
|
|
const RiseFall *rf,
|
|
double in_slew,
|
|
double c2,
|
|
double rpi,
|
|
double c1)
|
|
{
|
|
double rd = 0.0;
|
|
if (gate_model) {
|
|
rd = gateModelRd(drvr_cell, gate_model, rf, in_slew, c2, c1, pvt);
|
|
// Zero Rd means the table is constant and thus independent of load cap.
|
|
if (rd < 1e-2
|
|
// Rpi is small compared to Rd, which makes the load capacitive.
|
|
|| rpi < rd * 1e-3
|
|
// c1/Rpi can be ignored.
|
|
|| (c1 == 0.0 || c1 < c2 * 1e-3 || rpi == 0.0))
|
|
dmp_alg_ = &dmp_cap_;
|
|
else if (c2 < c1 * 1e-3)
|
|
dmp_alg_ = &dmp_zero_c2_;
|
|
else
|
|
// The full monty.
|
|
dmp_alg_ = &dmp_pi_;
|
|
}
|
|
else
|
|
dmp_alg_ = &dmp_cap_;
|
|
dmp_alg_->init(drvr_library, drvr_cell, pvt, gate_model, rf, rd, in_slew,
|
|
c2, rpi, c1);
|
|
debugPrint(debug_, "dmp_ceff", 3,
|
|
" DMP in_slew = {} c2 = {} rpi = {} c1 = {} Rd = {} ({} alg)",
|
|
units_->timeUnit()->asString(in_slew),
|
|
units_->capacitanceUnit()->asString(c2),
|
|
units_->resistanceUnit()->asString(rpi),
|
|
units_->capacitanceUnit()->asString(c1),
|
|
units_->resistanceUnit()->asString(rd), dmp_alg_->name());
|
|
}
|
|
|
|
std::string
|
|
DmpCeffDelayCalc::reportGateDelay(const Pin *drvr_pin,
|
|
const TimingArc *arc,
|
|
const Slew &in_slew,
|
|
float load_cap,
|
|
const Parasitic *parasitic,
|
|
const LoadPinIndexMap &load_pin_index_map,
|
|
const Scene *scene,
|
|
const MinMax *min_max,
|
|
int digits)
|
|
{
|
|
ArcDcalcResult dcalc_result =
|
|
gateDelay(drvr_pin, arc, in_slew, load_cap, parasitic, load_pin_index_map,
|
|
scene, min_max);
|
|
GateTableModel *model = arc->gateTableModel(scene, min_max);
|
|
float c_eff = 0.0;
|
|
std::string result;
|
|
const LibertyCell *drvr_cell = arc->to()->libertyCell();
|
|
const LibertyLibrary *drvr_library = drvr_cell->libertyLibrary();
|
|
const Units *units = drvr_library->units();
|
|
const Unit *cap_unit = units->capacitanceUnit();
|
|
const Unit *res_unit = units->resistanceUnit();
|
|
if (parasitic && dmp_alg_) {
|
|
Parasitics *parasitics = scene->parasitics(min_max);
|
|
|
|
c_eff = dmp_alg_->ceff();
|
|
float c2, rpi, c1;
|
|
parasitics->piModel(parasitic, c2, rpi, c1);
|
|
result += "Pi model C2=";
|
|
result += cap_unit->asString(c2, digits);
|
|
result += " Rpi=";
|
|
result += res_unit->asString(rpi, digits);
|
|
result += " C1=";
|
|
result += cap_unit->asString(c1, digits);
|
|
result += ", Ceff=";
|
|
result += cap_unit->asString(c_eff, digits);
|
|
result += '\n';
|
|
}
|
|
else
|
|
c_eff = load_cap;
|
|
if (model) {
|
|
float in_slew1 = delayAsFloat(in_slew);
|
|
result += model->reportGateDelay(pinPvt(drvr_pin, scene, min_max),
|
|
in_slew1, c_eff, min_max,
|
|
variables_->pocvMode(), digits);
|
|
result += "Driver waveform slew = ";
|
|
result += delayAsString(dcalc_result.drvrSlew(), min_max, digits, this);
|
|
result += '\n';
|
|
}
|
|
return result;
|
|
}
|
|
|
|
static double
|
|
gateModelRd(const LibertyCell *cell,
|
|
const GateTableModel *gate_model,
|
|
const RiseFall *rf,
|
|
double in_slew,
|
|
double c2,
|
|
double c1,
|
|
const Pvt *pvt)
|
|
{
|
|
float cap1 = c1 + c2;
|
|
float cap2 = cap1 + 1e-15;
|
|
float d1, d2, s1, s2;
|
|
gate_model->gateDelay(pvt, in_slew, cap1, d1, s1);
|
|
gate_model->gateDelay(pvt, in_slew, cap2, d2, s2);
|
|
double vth = cell->libertyLibrary()->outputThreshold(rf);
|
|
float rd = -std::log(vth) * std::abs(d1 - d2) / (cap2 - cap1);
|
|
return rd;
|
|
}
|
|
|
|
std::pair<double, double>
|
|
DmpCeffDelayCalc::gateDelaySlew()
|
|
{
|
|
return dmp_alg_->gateDelaySlew();
|
|
}
|
|
|
|
std::optional<std::pair<double, double>>
|
|
DmpCeffDelayCalc::loadDelaySlewElmore(const Pin *load_pin,
|
|
double elmore)
|
|
{
|
|
if (dmp_alg_)
|
|
return dmp_alg_->loadDelaySlew(load_pin, elmore);
|
|
return std::nullopt;
|
|
}
|
|
|
|
// Notify algorithm components.
|
|
void
|
|
DmpCeffDelayCalc::copyState(const StaState *sta)
|
|
{
|
|
StaState::copyState(sta);
|
|
dmp_cap_.copyState(sta);
|
|
dmp_pi_.copyState(sta);
|
|
dmp_zero_c2_.copyState(sta);
|
|
}
|
|
|
|
// This saves about 2.5% in overall run time on designs with SPEF.
|
|
// https://codingforspeed.com/using-faster-exponential-approximation
|
|
static double
|
|
exp2(double x)
|
|
{
|
|
if (x < -12.0)
|
|
// exp(-12) = 6.1e-6
|
|
return 0.0;
|
|
else {
|
|
double y = 1.0 + x / 4096.0;
|
|
y *= y;
|
|
y *= y;
|
|
y *= y;
|
|
y *= y;
|
|
y *= y;
|
|
y *= y;
|
|
y *= y;
|
|
y *= y;
|
|
y *= y;
|
|
y *= y;
|
|
y *= y;
|
|
y *= y;
|
|
return y;
|
|
}
|
|
}
|
|
|
|
} // namespace sta
|