// OpenSTA, Static Timing Analyzer // Copyright (c) 2020, 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 . #include "DmpDelayCalc.hh" #include "TableModel.hh" #include "TimingArc.hh" #include "Liberty.hh" #include "Sdc.hh" #include "Parasitics.hh" #include "DcalcAnalysisPt.hh" #include "GraphDelayCalc.hh" #include "DmpCeff.hh" namespace sta { // PiElmore parasitic delay calculator using Dartu/Menezes/Pileggi // effective capacitance and elmore delay. class DmpCeffElmoreDelayCalc : public DmpCeffDelayCalc { public: DmpCeffElmoreDelayCalc(StaState *sta); virtual ArcDelayCalc *copy(); virtual void gateDelay(const LibertyCell *drvr_cell, TimingArc *arc, const Slew &in_slew, float load_cap, Parasitic *drvr_parasitic, float related_out_cap, const Pvt *pvt, const DcalcAnalysisPt *dcalc_ap, // Return values. ArcDelay &gate_, Slew &drvr_slew); virtual void loadDelay(const Pin *load_pin, ArcDelay &wire_delay, Slew &load_slew); }; ArcDelayCalc * makeDmpCeffElmoreDelayCalc(StaState *sta) { return new DmpCeffElmoreDelayCalc(sta); } DmpCeffElmoreDelayCalc::DmpCeffElmoreDelayCalc(StaState *sta) : DmpCeffDelayCalc(sta) { } ArcDelayCalc * DmpCeffElmoreDelayCalc::copy() { return new DmpCeffElmoreDelayCalc(this); } void DmpCeffElmoreDelayCalc::gateDelay(const LibertyCell *drvr_cell, TimingArc *arc, const Slew &in_slew, float load_cap, Parasitic *drvr_parasitic, float related_out_cap, const Pvt *pvt, const DcalcAnalysisPt *dcalc_ap, // Return values. ArcDelay &gate_delay, Slew &drvr_slew) { DmpCeffDelayCalc::gateDelay(drvr_cell, arc, in_slew, load_cap, drvr_parasitic, related_out_cap, pvt, dcalc_ap, gate_delay, drvr_slew); } void DmpCeffElmoreDelayCalc::loadDelay(const Pin *load_pin, ArcDelay &wire_delay, Slew &load_slew) { ArcDelay wire_delay1 = 0.0; Slew load_slew1 = drvr_slew_; bool elmore_exists = false; float elmore = 0.0; if (drvr_parasitic_) parasitics_->findElmore(drvr_parasitic_, load_pin, elmore, elmore_exists); if (elmore_exists) { if (input_port_) { // Input port with no external driver. if (parasitics_->isReducedParasiticNetwork(drvr_parasitic_)) dspfWireDelaySlew(load_pin, elmore, wire_delay1, load_slew1); else { // The elmore delay on an input port is used for the wire // delay and the load slew is the same as the driver slew. wire_delay1 = elmore; load_slew1 = drvr_slew_; } } else loadDelaySlew(load_pin, elmore, wire_delay1, load_slew1); } thresholdAdjust(load_pin, wire_delay1, load_slew1); wire_delay = wire_delay1; load_slew = load_slew1 * multi_drvr_slew_factor_; } //////////////////////////////////////////////////////////////// // PiPoleResidue parasitic delay calculator using Dartu/Menezes/Pileggi // effective capacitance and two poles/residues. class DmpCeffTwoPoleDelayCalc : public DmpCeffDelayCalc { public: DmpCeffTwoPoleDelayCalc(StaState *sta); virtual ArcDelayCalc *copy(); virtual Parasitic *findParasitic(const Pin *drvr_pin, const RiseFall *rf, const DcalcAnalysisPt *dcalc_ap); virtual ReducedParasiticType reducedParasiticType() const; virtual void inputPortDelay(const Pin *port_pin, float in_slew, const RiseFall *rf, Parasitic *parasitic, const DcalcAnalysisPt *dcalc_ap); virtual void gateDelay(const LibertyCell *drvr_cell, TimingArc *arc, const Slew &in_slew, float load_cap, Parasitic *drvr_parasitic, float related_out_cap, const Pvt *pvt, const DcalcAnalysisPt *dcalc_ap, // Return values. ArcDelay &gate_delay, Slew &drvr_slew); virtual void loadDelay(const Pin *load_pin, ArcDelay &wire_delay, Slew &load_slew); private: void loadDelay(Parasitic *pole_residue, double p1, double k1, ArcDelay &wire_delay, Slew &load_slew); float loadDelay(double vth, double p1, double p2, double k1, double k2, double B, double k1_p1_2, double k2_p2_2, double tt, double y_tt); bool parasitic_is_pole_residue_; float vth_; float vl_; float vh_; float slew_derate_; }; ArcDelayCalc * makeDmpCeffTwoPoleDelayCalc(StaState *sta) { return new DmpCeffTwoPoleDelayCalc(sta); } DmpCeffTwoPoleDelayCalc::DmpCeffTwoPoleDelayCalc(StaState *sta) : DmpCeffDelayCalc(sta), parasitic_is_pole_residue_(false), vth_(0.0), vl_(0.0), vh_(0.0), slew_derate_(0.0) { } ArcDelayCalc * DmpCeffTwoPoleDelayCalc::copy() { return new DmpCeffTwoPoleDelayCalc(this); } Parasitic * DmpCeffTwoPoleDelayCalc::findParasitic(const Pin *drvr_pin, const RiseFall *rf, const DcalcAnalysisPt *dcalc_ap) { // set_load has precidence over parasitics. if (!sdc_->drvrPinHasWireCap(drvr_pin)) { Parasitic *parasitic = nullptr; const ParasiticAnalysisPt *parasitic_ap = dcalc_ap->parasiticAnalysisPt(); if (parasitics_->haveParasitics()) { // Prefer PiPoleResidue. parasitic = parasitics_->findPiPoleResidue(drvr_pin, rf, parasitic_ap); if (parasitic) return parasitic; parasitic = parasitics_->findPiElmore(drvr_pin, rf, parasitic_ap); if (parasitic) return parasitic; Parasitic *parasitic_network = parasitics_->findParasiticNetwork(drvr_pin, parasitic_ap); if (parasitic_network) { parasitics_->reduceToPiPoleResidue2(parasitic_network, drvr_pin, dcalc_ap->operatingConditions(), dcalc_ap->corner(), dcalc_ap->constraintMinMax(), parasitic_ap); parasitic = parasitics_->findPiPoleResidue(drvr_pin, rf, parasitic_ap); reduced_parasitic_drvrs_.push_back(drvr_pin); return parasitic; } } const MinMax *cnst_min_max = dcalc_ap->constraintMinMax(); Wireload *wireload = sdc_->wireloadDefaulted(cnst_min_max); if (wireload) { float pin_cap, wire_cap, fanout; bool has_wire_cap; graph_delay_calc_->netCaps(drvr_pin, rf, dcalc_ap, pin_cap, wire_cap, fanout, has_wire_cap); parasitic = parasitics_->estimatePiElmore(drvr_pin, rf, wireload, fanout, pin_cap, dcalc_ap->operatingConditions(), dcalc_ap->corner(), cnst_min_max, parasitic_ap); // Estimated parasitics are not recorded in the "database", so // it for deletion after the drvr pin delay calc is finished. if (parasitic) unsaved_parasitics_.push_back(parasitic); return parasitic; } } return nullptr; } ReducedParasiticType DmpCeffTwoPoleDelayCalc::reducedParasiticType() const { return ReducedParasiticType::pi_pole_residue2; } void DmpCeffTwoPoleDelayCalc::inputPortDelay(const Pin *port_pin, float in_slew, const RiseFall *rf, Parasitic *parasitic, const DcalcAnalysisPt *dcalc_ap) { parasitic_is_pole_residue_ = parasitics_->isPiPoleResidue(parasitic); DmpCeffDelayCalc::inputPortDelay(port_pin, in_slew, rf, parasitic, dcalc_ap); } void DmpCeffTwoPoleDelayCalc::gateDelay(const LibertyCell *drvr_cell, TimingArc *arc, const Slew &in_slew, float load_cap, Parasitic *drvr_parasitic, float related_out_cap, const Pvt *pvt, const DcalcAnalysisPt *dcalc_ap, // Return values. ArcDelay &gate_delay, Slew &drvr_slew) { parasitic_is_pole_residue_ = parasitics_->isPiPoleResidue(drvr_parasitic); const LibertyLibrary *drvr_library = drvr_cell->libertyLibrary(); const RiseFall *rf = arc->toTrans()->asRiseFall(); vth_ = drvr_library->outputThreshold(rf); vl_ = drvr_library->slewLowerThreshold(rf); vh_ = drvr_library->slewUpperThreshold(rf); slew_derate_ = drvr_library->slewDerateFromLibrary(); DmpCeffDelayCalc::gateDelay(drvr_cell, arc, in_slew, load_cap, drvr_parasitic, related_out_cap, pvt, dcalc_ap, gate_delay, drvr_slew); } void DmpCeffTwoPoleDelayCalc::loadDelay(const Pin *load_pin, ArcDelay &wire_delay, Slew &load_slew) { // NEED to handle PiElmore parasitic. ArcDelay wire_delay1 = 0.0; Slew load_slew1 = drvr_slew_; Parasitic *pole_residue = 0; if (parasitic_is_pole_residue_) pole_residue = parasitics_->findPoleResidue(drvr_parasitic_, load_pin); if (pole_residue) { size_t pole_count = parasitics_->poleResidueCount(pole_residue); if (pole_count >= 1) { ComplexFloat pole1, residue1; // Find the 1st (elmore) pole. parasitics_->poleResidue(pole_residue, 0, pole1, residue1); if (pole1.imag() == 0.0 && residue1.imag() == 0.0) { float p1 = pole1.real(); float k1 = residue1.real(); if (input_port_) { float elmore = 1.0F / p1; // Input port with no external driver. if (parasitics_->isReducedParasiticNetwork(drvr_parasitic_)) dspfWireDelaySlew(load_pin, elmore, wire_delay1, load_slew1); else { // For RSPF on an input port the elmore delay is used for the // wire delay and the load slew is the same as the driver slew. wire_delay1 = elmore; load_slew1 = drvr_slew_; } } else { if (pole_count >= 2) loadDelay(pole_residue, p1, k1, wire_delay1, load_slew1); else { float elmore = 1.0F / p1; wire_delay1 = elmore; load_slew1 = drvr_slew_; } } } } } thresholdAdjust(load_pin, wire_delay1, load_slew1); wire_delay = wire_delay1; load_slew = load_slew1 * multi_drvr_slew_factor_; } void DmpCeffTwoPoleDelayCalc::loadDelay(Parasitic *pole_residue, double p1, double k1, ArcDelay &wire_delay, Slew &load_slew) { ComplexFloat pole2, residue2; parasitics_->poleResidue(pole_residue, 1, pole2, residue2); if (!delayZero(drvr_slew_) && pole2.imag() == 0.0 && residue2.imag() == 0.0) { double p2 = pole2.real(); double k2 = residue2.real(); double k1_p1_2 = k1 / (p1 * p1); double k2_p2_2 = k2 / (p2 * p2); double B = k1_p1_2 + k2_p2_2; // Convert tt to 0:1 range. float tt = delayAsFloat(drvr_slew_) * slew_derate_ / (vh_ - vl_); double y_tt = (tt - B + k1_p1_2 * exp(-p1 * tt) + k2_p2_2 * exp(-p2 * tt)) / tt; wire_delay = loadDelay(vth_, p1, p2, k1, k2, B, k1_p1_2, k2_p2_2, tt, y_tt) - tt * vth_; float tl = loadDelay(vl_, p1, p2, k1, k2, B, k1_p1_2, k2_p2_2, tt, y_tt); float th = loadDelay(vh_, p1, p2, k1, k2, B, k1_p1_2, k2_p2_2, tt, y_tt); load_slew = (th - tl) / slew_derate_; } } float DmpCeffTwoPoleDelayCalc::loadDelay(double vth, double p1, double p2, double k1, double k2, double B, double k1_p1_2, double k2_p2_2, double tt, double y_tt) { if (y_tt < vth) { // t1 > tt // Initial guess. double t1 = log(k1 * (exp(p1 * tt) - 1.0) / ((1.0 - vth) * p1 * p1 * tt))/p1; // Take one newton-raphson step. double exp_p1_t1 = exp(-p1 * t1); double exp_p2_t1 = exp(-p2 * t1); double exp_p1_t1_tt = exp(-p1 * (t1 - tt)); double exp_p2_t1_tt = exp(-p2 * (t1 - tt)); double y_t1 = (tt - k1_p1_2 * (exp_p1_t1_tt - exp_p1_t1) - k2_p2_2 * (exp_p2_t1_tt - exp_p2_t1)) / tt; double yp_t1 = (k1 / p1 * (exp_p1_t1_tt - exp_p1_t1) - k2 / p2 * (exp_p2_t1_tt - exp_p2_t1)) / tt; double delay = t1 - (y_t1 - vth) / yp_t1; return static_cast(delay); } else { // t1 < tt // Initial guess based on y(tt). double t1 = vth * tt / y_tt; // Take one newton-raphson step. double exp_p1_t1 = exp(-p1 * t1); double exp_p2_t1 = exp(-p2 * t1); double y_t1 = (t1 - B + k1_p1_2 * exp_p1_t1 + k2_p2_2 * exp_p1_t1) / tt; double yp_t1 = (1 - k1 / p1 * exp_p1_t1 - k2 / p2 * exp_p2_t1) / tt; double delay = t1 - (y_t1 - vth) / yp_t1; return static_cast(delay); } } } // namespace