From ddf897d4e67481479755e05399956a08a25905ee Mon Sep 17 00:00:00 2001 From: James Cherry Date: Mon, 26 Nov 2018 09:15:52 -0800 Subject: [PATCH] report_power, pocv support --- configure.ac | 2 +- dcalc/ArcDelayCalc.hh | 28 +- dcalc/ArnoldiDelayCalc.cc | 30 ++- dcalc/DmpCeff.cc | 33 ++- dcalc/DmpCeff.hh | 8 + dcalc/GraphDelayCalc.cc | 8 + dcalc/GraphDelayCalc.hh | 7 + dcalc/GraphDelayCalc1.cc | 82 +++++- dcalc/GraphDelayCalc1.hh | 7 + dcalc/LumpedCapDelayCalc.cc | 26 +- dcalc/LumpedCapDelayCalc.hh | 24 +- dcalc/UnitDelayCalc.cc | 21 +- dcalc/UnitDelayCalc.hh | 24 +- doc/OpenSTA.odt | Bin 80416 -> 72031 bytes graph/Delay.cc | 39 +++ graph/Delay.hh | 98 ++++++- graph/DelayFloat.cc | 27 +- graph/DelayFloat.hh | 67 +---- graph/DelayFloatClass.cc | 40 +-- graph/DelayFloatClass.hh | 85 ++---- graph/DelayNormal2.cc | 389 +++++++++++++++++++++++++++ graph/DelayNormal2.hh | 93 +++++++ graph/Graph.cc | 19 +- graph/Graph.hh | 1 + graph/Makefile.am | 3 + liberty/InternalPower.cc | 26 +- liberty/InternalPower.hh | 6 + liberty/Liberty.cc | 41 ++- liberty/Liberty.hh | 12 + liberty/LibertyReader.cc | 217 +++++++++++++-- liberty/LibertyReaderPvt.hh | 29 +- liberty/LinearModel.cc | 11 +- liberty/LinearModel.hh | 12 +- liberty/TableModel.cc | 144 +++++++--- liberty/TableModel.hh | 30 ++- liberty/TimingArc.cc | 39 +-- liberty/TimingArc.hh | 2 +- liberty/TimingModel.hh | 17 +- liberty/Units.cc | 6 + liberty/Units.hh | 1 + parasitics/ConcreteParasitics.cc | 21 +- parasitics/ConcreteParasitics.hh | 8 +- parasitics/ConcreteParasiticsPvt.hh | 6 +- parasitics/NullParasitics.cc | 122 ++++++--- parasitics/NullParasitics.hh | 11 +- parasitics/Parasitics.cc | 2 +- parasitics/Parasitics.hh | 3 + parasitics/Parasitics.i | 7 +- parasitics/Parasitics.tcl | 38 +-- parasitics/ReadParasitics.cc | 9 +- parasitics/ReadParasitics.hh | 1 + parasitics/ReduceParasitics.cc | 104 +++++--- parasitics/ReduceParasitics.hh | 6 +- parasitics/SpefReader.cc | 15 +- parasitics/SpefReader.hh | 1 + parasitics/SpefReaderPvt.hh | 1 + parasitics/SpfReader.cc | 12 +- parasitics/SpfReader.hh | 1 + parasitics/SpfReaderPvt.hh | 1 + parasitics/SpfSpefReader.cc | 6 +- parasitics/SpfSpefReader.hh | 2 + sdc/ClockInsertion.cc | 4 +- sdc/Sdc.cc | 132 +++++---- sdc/Sdc.hh | 13 +- sdc/WriteSdc.cc | 4 +- sdf/SdfWriter.cc | 6 +- search/ClkSkew.cc | 4 +- search/Corner.hh | 1 + search/Latches.cc | 7 +- search/Latches.hh | 2 +- search/Makefile.am | 2 + search/PathEnd.cc | 33 ++- search/PathEnd.hh | 8 +- search/PathEnum.cc | 10 +- search/PathEnum.hh | 6 +- search/Power.cc | 394 +++++++++++++++++++++++++++ search/Power.hh | 115 ++++++++ search/ReportPath.cc | 400 ++++++++++++++++++---------- search/ReportPath.hh | 46 +++- search/Search.cc | 61 +++-- search/Search.hh | 8 +- search/Sta.cc | 62 ++++- search/Sta.hh | 24 ++ tcl/Cmds.tcl | 16 ++ tcl/Graph.tcl | 16 +- tcl/Liberty.tcl | 41 --- tcl/Network.tcl | 34 +-- tcl/Power.tcl | 138 ++++++++++ tcl/Sdc.tcl | 9 +- tcl/Search.tcl | 51 +++- tcl/StaTcl.i | 277 ++++++++++--------- util/MinMax.cc | 12 +- util/MinMax.hh | 9 + 93 files changed, 3147 insertions(+), 929 deletions(-) create mode 100644 graph/Delay.cc create mode 100644 graph/DelayNormal2.cc create mode 100644 graph/DelayNormal2.hh create mode 100644 search/Power.cc create mode 100644 search/Power.hh create mode 100644 tcl/Power.tcl diff --git a/configure.ac b/configure.ac index af6a0969..ac18ea6e 100644 --- a/configure.ac +++ b/configure.ac @@ -534,7 +534,7 @@ STA_LIBS="../search/libsearch.la ../sdf/libsdf.la ../graph/libgraph.la ../dcalc/ SWIG_DEPEND="../tcl/StaException.i ../tcl/StaTcl.i ../tcl/NetworkEdit.i ../sdf/Sdf.i ../dcalc/DelayCalc.i ../parasitics/Parasitics.i ../tcl/StaTcl.i" -TCL_INIT_FILES="../tcl/Util.tcl ../dcalc/DelayCalc.tcl ../tcl/Graph.tcl ../tcl/Liberty.tcl ../tcl/Link.tcl ../tcl/Network.tcl ../tcl/NetworkEdit.tcl ../parasitics/Parasitics.tcl ../tcl/Sdc.tcl ../sdf/Sdf.tcl ../tcl/Search.tcl ../tcl/Cmds.tcl ../tcl/Variables.tcl ../tcl/Sta.tcl ../tcl/Splash.tcl" +TCL_INIT_FILES="../tcl/Util.tcl ../dcalc/DelayCalc.tcl ../tcl/Graph.tcl ../tcl/Liberty.tcl ../tcl/Link.tcl ../tcl/Network.tcl ../tcl/NetworkEdit.tcl ../parasitics/Parasitics.tcl ../tcl/Sdc.tcl ../sdf/Sdf.tcl ../tcl/Search.tcl ../tcl/Cmds.tcl ../tcl/Variables.tcl ../tcl/Sta.tcl ../tcl/Power.tcl ../tcl/Splash.tcl" if test "$TCL_INCLUDE"; then STA_INCLUDE="$STA_INCLUDE -I$TCL_INCLUDE" diff --git a/dcalc/ArcDelayCalc.hh b/dcalc/ArcDelayCalc.hh index 15c31429..5ddb9bc4 100644 --- a/dcalc/ArcDelayCalc.hh +++ b/dcalc/ArcDelayCalc.hh @@ -86,7 +86,8 @@ public: float related_out_cap, const Pvt *pvt, const DcalcAnalysisPt *dcalc_ap, // Return values. - ArcDelay &gate_delay, Slew &drvr_slew) = 0; + ArcDelay &gate_delay, + Slew &drvr_slew) = 0; // Find the wire delay and load slew of a load pin. // Called after inputPortDelay or gateDelay. virtual void loadDelay(const Pin *load_pin, @@ -94,16 +95,27 @@ public: ArcDelay &wire_delay, Slew &load_slew) = 0; virtual void setMultiDrvrSlewFactor(float factor) = 0; + // Ceff for parasitics with pi models. + virtual float ceff(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) = 0; // Find the delay for a timing check arc given the arc's // from/clock, to/data slews and related output pin parasitic. - virtual ArcDelay checkDelay(const LibertyCell *drvr_cell, - TimingArc *arc, - const Slew &from_slew, - const Slew &to_slew, - float related_out_cap, - const Pvt *pvt, - const DcalcAnalysisPt *dcalc_ap) = 0; + virtual void checkDelay(const LibertyCell *drvr_cell, + TimingArc *arc, + const Slew &from_slew, + const Slew &to_slew, + float related_out_cap, + const Pvt *pvt, + const DcalcAnalysisPt *dcalc_ap, + // Return values. + ArcDelay &margin) = 0; // Report delay and slew calculation. virtual void reportGateDelay(const LibertyCell *drvr_cell, TimingArc *arc, diff --git a/dcalc/ArnoldiDelayCalc.cc b/dcalc/ArnoldiDelayCalc.cc index 8c65e2d1..637a8780 100644 --- a/dcalc/ArnoldiDelayCalc.cc +++ b/dcalc/ArnoldiDelayCalc.cc @@ -418,7 +418,7 @@ ArnoldiDelayCalc::gateDelaySlew(const LibertyCell *drvr_cell, tab.table = table_model; tab.cell = drvr_cell; tab.pvt = pvt; - tab.in_slew = in_slew; + tab.in_slew = delayAsFloat(in_slew); tab.relcap = related_out_cap; ar1_ceff_delay(delay_work_, &tab, rcmodel_, _delayV, _slewV); @@ -1303,12 +1303,14 @@ ArnoldiDelayCalc::ra_get_r(delay_work *D, delay_c *c = D->c; double slew_derate = c->slew_derate; double c_log = c->vlg; - float c1,d1,s1; + float c1; double tlohi,r; c1 = ctot; + ArcDelay d1; + Slew s1; tab->table->gateDelay(tab->cell, tab->pvt, tab->in_slew, c1, tab->relcap, d1, s1); - tlohi = slew_derate*s1; + tlohi = slew_derate*delayAsFloat(s1); r = tlohi/(c_log*c1); if (rdelay>0.0 && r > rdelay) r = rdelay; @@ -1327,10 +1329,11 @@ ArnoldiDelayCalc::ra_get_s(delay_work *D, double c_log = con->vlg; double c_smin = con->smin; double tlohi,smin,s; - float d1,s1; + ArcDelay d1; + Slew s1; tab->table->gateDelay(tab->cell, tab->pvt, tab->in_slew, c, tab->relcap, d1, s1); - tlohi = slew_derate*s1; + tlohi = slew_derate*delayAsFloat(s1); smin = r*c*c_smin; // c_smin = ra_hinv((1-vhi)/vhi-log(vhi)) + log(vhi); if (c_log*r*c >= tlohi) { // printf("hit smin\n"); @@ -1360,13 +1363,13 @@ ArnoldiDelayCalc::ra_rdelay_1(timing_table *tab, float c2 = 0.5*c1; if (c1==c2) return 0.0; - float d1, s1; + ArcDelay d1, d2; + Slew s1, s2; tab->table->gateDelay(tab->cell, tab->pvt, tab->in_slew, c1, tab->relcap, d1, s1); - float d2, s2; tab->table->gateDelay(tab->cell, tab->pvt, tab->in_slew, c2, tab->relcap, d2, s2); - double dt50 = d1-d2; + double dt50 = delayAsFloat(d1)-delayAsFloat(d2); if (dt50 <= 0.0) return 0.0; double rdelay = dt50/(c1-c2); @@ -1386,7 +1389,8 @@ ArnoldiDelayCalc::ar1_ceff_delay(delay_work *D, double vlo = con->vlo; double ctot = mod->ctot; double ceff,tlohi,t50_sy,r,s,t50_sr,rdelay; - float df,sf; + ArcDelay df; + Slew sf; debugPrint1(debug_, "arnoldi", 1, "\nctot=%s\n", units_->capacitanceUnit()->asString(ctot)); @@ -1422,8 +1426,8 @@ ArnoldiDelayCalc::ar1_ceff_delay(delay_work *D, "table slew (in_slew %s ctot %s) = %s\n", units_->timeUnit()->asString(tab->in_slew), units_->capacitanceUnit()->asString(ctot), - units_->timeUnit()->asString(sf)); - tlohi = slew_derate*sf; + delayAsString(sf, units_)); + tlohi = slew_derate*delayAsFloat(sf); debugPrint2(debug_, "arnoldi", 1, "tlohi %s %s\n", units_->timeUnit()->asString(tlohi), units_->timeUnit()->asString(tlox-thix)); @@ -1431,7 +1435,7 @@ ArnoldiDelayCalc::ar1_ceff_delay(delay_work *D, ceff = ctot; tab->table->gateDelay(tab->cell, tab->pvt, tab->in_slew, ceff, tab->relcap, df, sf); - t50_sy = df; + t50_sy = delayAsFloat(df); t50_sr = ra_solve_for_t(1.0/(r*ceff),s,0.5); // calculate s,r,mod -> t50_srmod, @@ -1473,7 +1477,7 @@ ArnoldiDelayCalc::ar1_ceff_delay(delay_work *D, tab->table->gateDelay(tab->cell, tab->pvt, tab->in_slew, ceff, tab->relcap, df, sf); - t50_sy = df; + t50_sy = delayAsFloat(df); t50_sr = ra_solve_for_t(1.0/(r*ceff),s,0.5); for (j=0;jn;j++) { rr = delay_work_get_residues(D,j); diff --git a/dcalc/DmpCeff.cc b/dcalc/DmpCeff.cc index f7014031..5a8a94dc 100644 --- a/dcalc/DmpCeff.cc +++ b/dcalc/DmpCeff.cc @@ -372,14 +372,15 @@ DmpAlg::gateCapDelaySlew(double ceff, double &delay, double &slew) { - float model_delay, model_slew; + ArcDelay model_delay; + Slew model_slew; gate_model_->gateDelay(drvr_cell_, pvt_, static_cast(in_slew_), static_cast(ceff), related_out_cap_, model_delay, model_slew); - delay = model_delay; - slew = model_slew; + delay = delayAsFloat(model_delay); + slew = delayAsFloat(model_slew); } void @@ -1665,6 +1666,24 @@ DmpCeffDelayCalc::setCeffAlgorithm(const LibertyLibrary *drvr_library, dmp_alg_->name()); } +float +DmpCeffDelayCalc::ceff(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) +{ + ArcDelay gate_delay; + Slew drvr_slew; + gateDelay(drvr_cell, arc, in_slew, load_cap, + drvr_parasitic, related_out_cap, pvt, dcalc_ap, + gate_delay, drvr_slew); + return ceff(); +} + void DmpCeffDelayCalc::reportGateDelay(const LibertyCell *drvr_cell, TimingArc *arc, @@ -1723,9 +1742,11 @@ gateModelRd(const LibertyCell *cell, float cap1 = static_cast((c1 + c2) * .75); float cap2 = cap1 * 1.1F; float in_slew1 = static_cast(in_slew); - float d1 = gate_model->gateDelay(cell, pvt, in_slew1, cap1, related_out_cap); - float d2 = gate_model->gateDelay(cell, pvt, in_slew1, cap2, related_out_cap); - return abs(d1 - d2) / (cap2 - cap1); + ArcDelay d1, d2; + Slew s1, s2; + gate_model->gateDelay(cell, pvt, in_slew1, cap1, related_out_cap, d1, s1); + gate_model->gateDelay(cell, pvt, in_slew1, cap2, related_out_cap, d2, s2); + return abs(delayAsFloat(d1) - delayAsFloat(d2)) / (cap2 - cap1); } void diff --git a/dcalc/DmpCeff.hh b/dcalc/DmpCeff.hh index d984805c..0c033519 100644 --- a/dcalc/DmpCeff.hh +++ b/dcalc/DmpCeff.hh @@ -50,6 +50,14 @@ public: // return values ArcDelay &gate_delay, Slew &drvr_slew); + virtual float ceff(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); virtual void reportGateDelay(const LibertyCell *drvr_cell, TimingArc *arc, const Slew &in_slew, diff --git a/dcalc/GraphDelayCalc.cc b/dcalc/GraphDelayCalc.cc index 232b5912..b07e87db 100644 --- a/dcalc/GraphDelayCalc.cc +++ b/dcalc/GraphDelayCalc.cc @@ -70,6 +70,14 @@ GraphDelayCalc::loadCap(const Pin *, pin_cap = wire_cap = 0.0F; } +float +GraphDelayCalc::loadCap(const Pin *, + const TransRiseFall *, + const DcalcAnalysisPt *) const +{ + return 0.0F; +} + float GraphDelayCalc::loadCap(const Pin *, Parasitic *, diff --git a/dcalc/GraphDelayCalc.hh b/dcalc/GraphDelayCalc.hh index 9b1a713d..b1e438f7 100644 --- a/dcalc/GraphDelayCalc.hh +++ b/dcalc/GraphDelayCalc.hh @@ -80,6 +80,10 @@ public: float &pin_cap, float &wire_cap) const; // Load pin_cap + wire_cap. + virtual float loadCap(const Pin *drvr_pin, + const TransRiseFall *to_tr, + const DcalcAnalysisPt *dcalc_ap) const; + // Load pin_cap + wire_cap. virtual float loadCap(const Pin *drvr_pin, Parasitic *drvr_parasitic, const TransRiseFall *tr, @@ -92,6 +96,9 @@ public: float &wire_cap, float &fanout, bool &has_set_load) const; + virtual float ceff(Edge *edge, + TimingArc *arc, + const DcalcAnalysisPt *dcalc_ap) = 0; // Precedence: // SDF annotation // Liberty library diff --git a/dcalc/GraphDelayCalc1.cc b/dcalc/GraphDelayCalc1.cc index 256ecd38..c8a1ad75 100644 --- a/dcalc/GraphDelayCalc1.cc +++ b/dcalc/GraphDelayCalc1.cc @@ -394,7 +394,7 @@ FindVertexDelays::copy() { // Copy StaState::arc_delay_calc_ because it needs separate state // for each thread. - return new FindVertexDelays(graph_delay_calc1_, arc_delay_calc_->copy(), true); + return new FindVertexDelays(graph_delay_calc1_,arc_delay_calc_->copy(),true); } void @@ -1023,8 +1023,7 @@ GraphDelayCalc1::findDriverEdgeDelays(LibertyCell *drvr_cell, DcalcAnalysisPtIterator ap_iter(this); while (ap_iter.hasNext()) { DcalcAnalysisPt *dcalc_ap = ap_iter.next(); - const Pvt *pvt = sdc_->pvt(drvr_inst, - dcalc_ap->constraintMinMax()); + const Pvt *pvt = sdc_->pvt(drvr_inst, dcalc_ap->constraintMinMax()); if (pvt == NULL) pvt = dcalc_ap->operatingConditions(); TimingArcSetArcIterator *arc_iter = arc_set->timingArcIterator(); @@ -1065,13 +1064,28 @@ GraphDelayCalc1::findDriverEdgeDelays(LibertyCell *drvr_cell, return delay_changed; } +float +GraphDelayCalc1::loadCap(const Pin *drvr_pin, + const TransRiseFall *drvr_tr, + const DcalcAnalysisPt *dcalc_ap) const +{ + Parasitic *drvr_parasitic; + bool delete_parasitic; + arc_delay_calc_->findParasitic(drvr_pin, drvr_tr, dcalc_ap, + drvr_parasitic, delete_parasitic); + float load_cap = loadCap(drvr_pin, NULL, drvr_parasitic, drvr_tr, dcalc_ap); + arc_delay_calc_->finish(drvr_pin, drvr_tr, dcalc_ap, + drvr_parasitic, delete_parasitic); + return load_cap; +} + float GraphDelayCalc1::loadCap(const Pin *drvr_pin, Parasitic *drvr_parasitic, const TransRiseFall *tr, const DcalcAnalysisPt *dcalc_ap) const { - return loadCap(drvr_pin, 0, drvr_parasitic, tr, dcalc_ap); + return loadCap(drvr_pin, NULL, drvr_parasitic, tr, dcalc_ap); } float @@ -1160,7 +1174,7 @@ GraphDelayCalc1::netCaps(const Pin *drvr_pin, const MinMax *min_max = dcalc_ap->constraintMinMax(); // Find pin and external pin/wire capacitance. sdc_->connectedCap(drvr_pin, tr, op_cond, corner, min_max, - pin_cap, wire_cap, fanout, has_set_load); + pin_cap, wire_cap, fanout, has_set_load); } } @@ -1602,10 +1616,12 @@ GraphDelayCalc1::findCheckEdgeDelays(Edge *edge, arc_delay_calc->finish(related_out_pin, to_tr, dcalc_ap, related_out_parasitic, delete_related); } - ArcDelay check_delay = arc_delay_calc->checkDelay(cell, arc, - from_slew, to_slew, - related_out_cap, - pvt, dcalc_ap); + ArcDelay check_delay; + arc_delay_calc->checkDelay(cell, arc, + from_slew, to_slew, + related_out_cap, + pvt, dcalc_ap, + check_delay); debugPrint1(debug_, "delay_calc", 3, " check_delay = %s\n", delayAsString(check_delay, units_)); @@ -1727,6 +1743,54 @@ GraphDelayCalc1::isIdealClk(const Vertex *vertex) const && clks->size() > 0; } +float +GraphDelayCalc1::ceff(Edge *edge, + TimingArc *arc, + const DcalcAnalysisPt *dcalc_ap) +{ + Vertex *from_vertex = edge->from(graph_); + Vertex *to_vertex = edge->to(graph_); + Pin *to_pin = to_vertex->pin(); + Instance *inst = network_->instance(to_pin); + LibertyCell *cell = network_->libertyCell(inst); + TimingArcSet *arc_set = edge->timingArcSet(); + float ceff = 0.0; + const Pvt *pvt = sdc_->pvt(inst, dcalc_ap->constraintMinMax()); + if (pvt == NULL) + pvt = dcalc_ap->operatingConditions(); + TransRiseFall *from_tr = arc->fromTrans()->asRiseFall(); + TransRiseFall *to_tr = arc->toTrans()->asRiseFall(); + if (from_tr && to_tr) { + const LibertyPort *related_out_port = arc_set->relatedOut(); + const Pin *related_out_pin = 0; + if (related_out_port) + related_out_pin = network_->findPin(inst, related_out_port); + float related_out_cap = 0.0; + if (related_out_pin) { + Parasitic *related_out_parasitic; + bool delete_related; + arc_delay_calc_->findParasitic(related_out_pin, to_tr, dcalc_ap, + related_out_parasitic, delete_related); + related_out_cap = loadCap(related_out_pin, related_out_parasitic, + to_tr, dcalc_ap); + arc_delay_calc_->finish(related_out_pin, to_tr, dcalc_ap, + related_out_parasitic, delete_related); + } + Parasitic *to_parasitic; + bool delete_to_parasitic; + arc_delay_calc_->findParasitic(to_pin, to_tr, dcalc_ap, + to_parasitic, delete_to_parasitic); + const Slew &from_slew = edgeFromSlew(from_vertex, from_tr, edge, dcalc_ap); + float load_cap = loadCap(to_pin, to_parasitic, to_tr, dcalc_ap); + ceff = arc_delay_calc_->ceff(cell, arc, + from_slew, load_cap, to_parasitic, + related_out_cap, pvt, dcalc_ap); + arc_delay_calc_->finish(to_pin, to_tr, dcalc_ap, + to_parasitic, delete_to_parasitic); + } + return ceff; +} + //////////////////////////////////////////////////////////////// string * diff --git a/dcalc/GraphDelayCalc1.hh b/dcalc/GraphDelayCalc1.hh index bbbd91aa..b329abc1 100644 --- a/dcalc/GraphDelayCalc1.hh +++ b/dcalc/GraphDelayCalc1.hh @@ -51,6 +51,10 @@ public: virtual float incrementalDelayTolerance(); virtual void setIncrementalDelayTolerance(float tol); virtual void setObserver(DelayCalcObserver *observer); + // Load pin_cap + wire_cap. + virtual float loadCap(const Pin *drvr_pin, + const TransRiseFall *drvr_tr, + const DcalcAnalysisPt *dcalc_ap) const; virtual void loadCap(const Pin *drvr_pin, Parasitic *drvr_parasitic, const TransRiseFall *tr, @@ -70,6 +74,9 @@ public: float &wire_cap, float &fanout, bool &has_set_load) const; + float ceff(Edge *edge, + TimingArc *arc, + const DcalcAnalysisPt *dcalc_ap); protected: void seedInvalidDelays(); diff --git a/dcalc/LumpedCapDelayCalc.cc b/dcalc/LumpedCapDelayCalc.cc index caa5cd0f..a6174d63 100644 --- a/dcalc/LumpedCapDelayCalc.cc +++ b/dcalc/LumpedCapDelayCalc.cc @@ -122,6 +122,19 @@ LumpedCapDelayCalc::inputPortDelay(const Pin *, float in_slew, multi_drvr_slew_factor_ = 1.0F; } +float +LumpedCapDelayCalc::ceff(const LibertyCell *, + TimingArc *, + const Slew &, + float load_cap, + Parasitic *, + float, + const Pvt *, + const DcalcAnalysisPt *) +{ + return load_cap; +} + void LumpedCapDelayCalc::gateDelay(const LibertyCell *drvr_cell, TimingArc *arc, @@ -142,7 +155,8 @@ LumpedCapDelayCalc::gateDelay(const LibertyCell *drvr_cell, units()->capacitanceUnit()->asString(load_cap), units()->capacitanceUnit()->asString(related_out_cap)); if (model) { - float gate_delay1, drvr_slew1; + ArcDelay gate_delay1; + Slew drvr_slew1; float in_slew1 = delayAsFloat(in_slew); model->gateDelay(drvr_cell, pvt, in_slew1, load_cap, related_out_cap, gate_delay1, drvr_slew1); @@ -235,23 +249,25 @@ LumpedCapDelayCalc::reportGateDelay(const LibertyCell *drvr_cell, } } -ArcDelay +void LumpedCapDelayCalc::checkDelay(const LibertyCell *cell, TimingArc *arc, const Slew &from_slew, const Slew &to_slew, float related_out_cap, const Pvt *pvt, - const DcalcAnalysisPt *dcalc_ap) + const DcalcAnalysisPt *dcalc_ap, + // Return values. + ArcDelay &margin) { CheckTimingModel *model = checkModel(arc, dcalc_ap); if (model) { float from_slew1 = delayAsFloat(from_slew); float to_slew1 = delayAsFloat(to_slew); - return model->checkDelay(cell, pvt, from_slew1, to_slew1, related_out_cap); + model->checkDelay(cell, pvt, from_slew1, to_slew1, related_out_cap, margin); } else - return delay_zero; + margin = delay_zero; } void diff --git a/dcalc/LumpedCapDelayCalc.hh b/dcalc/LumpedCapDelayCalc.hh index 8f9145c4..abea6e9e 100644 --- a/dcalc/LumpedCapDelayCalc.hh +++ b/dcalc/LumpedCapDelayCalc.hh @@ -60,13 +60,23 @@ public: // Return values. ArcDelay &wire_delay, Slew &load_slew); - virtual ArcDelay checkDelay(const LibertyCell *cell, - TimingArc *arc, - const Slew &from_slew, - const Slew &to_slew, - float related_out_cap, - const Pvt *pvt, - const DcalcAnalysisPt *dcalc_ap); + virtual void checkDelay(const LibertyCell *cell, + TimingArc *arc, + const Slew &from_slew, + const Slew &to_slew, + float related_out_cap, + const Pvt *pvt, + const DcalcAnalysisPt *dcalc_ap, + // Return values. + ArcDelay &margin); + virtual float ceff(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); virtual void reportGateDelay(const LibertyCell *drvr_cell, TimingArc *arc, const Slew &in_slew, diff --git a/dcalc/UnitDelayCalc.cc b/dcalc/UnitDelayCalc.cc index 1296da05..31363819 100644 --- a/dcalc/UnitDelayCalc.cc +++ b/dcalc/UnitDelayCalc.cc @@ -92,6 +92,19 @@ UnitDelayCalc::loadDelay(const Pin *, load_slew = 0.0; } +float +UnitDelayCalc::ceff(const LibertyCell *, + TimingArc *, + const Slew &, + float, + Parasitic *, + float, + const Pvt *, + const DcalcAnalysisPt *) +{ + return 0.0; +} + void UnitDelayCalc::reportGateDelay(const LibertyCell *, TimingArc *, @@ -108,16 +121,18 @@ UnitDelayCalc::reportGateDelay(const LibertyCell *, *result += "Slew = 0.0\n"; } -ArcDelay +void UnitDelayCalc::checkDelay(const LibertyCell *, TimingArc *, const Slew &, const Slew &, float, const Pvt *, - const DcalcAnalysisPt *) + const DcalcAnalysisPt *, + // Return values. + ArcDelay &margin) { - return units_->timeUnit()->scale(); + margin = units_->timeUnit()->scale(); } void diff --git a/dcalc/UnitDelayCalc.hh b/dcalc/UnitDelayCalc.hh index 7e006651..3fa1aeb4 100644 --- a/dcalc/UnitDelayCalc.hh +++ b/dcalc/UnitDelayCalc.hh @@ -54,18 +54,28 @@ public: ArcDelay &wire_delay, Slew &load_slew); virtual void setMultiDrvrSlewFactor(float) {} + virtual float ceff(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); virtual void inputPortDelay(const Pin *port_pin, float in_slew, const TransRiseFall *tr, Parasitic *parasitic, const DcalcAnalysisPt *dcalc_ap); - virtual ArcDelay checkDelay(const LibertyCell *cell, - TimingArc *arc, - const Slew &from_slew, - const Slew &to_slew, - float related_out_cap, - const Pvt *pvt, - const DcalcAnalysisPt *dcalc_ap); + virtual void checkDelay(const LibertyCell *cell, + TimingArc *arc, + const Slew &from_slew, + const Slew &to_slew, + float related_out_cap, + const Pvt *pvt, + const DcalcAnalysisPt *dcalc_ap, + // Return values. + ArcDelay &margin); virtual void reportGateDelay(const LibertyCell *drvr_cell, TimingArc *arc, const Slew &in_slew, diff --git a/doc/OpenSTA.odt b/doc/OpenSTA.odt index 89dc8aa0b183794ebafa19b893d8d312b42fe0dc..048d53afcb4376aedd88bd61bfbe7e61fa3c94e8 100644 GIT binary patch delta 61270 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 delta 69648 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

G^&S?W?Ok?z9#Z__ zq)6UGl6?dKuKvL7lmj{D9B+s1s#pk2f@a-xq(g|GIJIuxC=%IFG=N z<$A~9c}9CZTE(^E9BPzrmp%V|TcX8L@lKC2*>6W1znUC}^j1ajHE^8^h=s#T#xMW~lLa=pu>0qG z{s^P~4Uz`{kPX~?U>FyOfg<_AY<^8$buE1djQ)O2z5N&+NJaoufoW+U!RkXt1i&It z6PgnMPwHvO$@qJb-S>wD279U#0z(4G|B=IBwD#$I*9kuE!R`dISMZNXU2T1B9XT}A zBM3@26bXWtxHg#3peqcLH}L>fihx@D>v*6B?|oYpmcmdHUx5CDX> z0l+#k{vIJD03f=D2ZoT<3GM_RFPt^VB?9~nz^!Q9`L~S|4UwT(QBagas!dcvwn$KX zo4z0j5s}~~E1~IiDUBmNK!QpeM}{;I9O+MvL`8rKTO>9|vi>P;821T*&`SiUB>Qjb zN&n*@M9wm4B&hrou$wtbIWp|$hJT21(vEVZW>HXOGwntWvE3lF1NA*eEz@TZ#wk<$ z|CPy(v&;?!ifuXrP@p&^O&A17D#P)j(kV-mHTjr~0o4C2wR(#d6MG$PECZTTB2jiG{SpK&9<&+MH?Nf3xs2. + +#include "Machine.hh" +#include "StringUtil.hh" +#include "Units.hh" +#include "StaState.hh" +#include "Delay.hh" + +namespace sta { + +const char * +delayAsString(const Delay &delay, + const Units *units) +{ + return delayAsString(delay, units, units->timeUnit()->digits()); +} + +const char * +delayAsString(const Delay &delay, + const StaState *sta) +{ + return delayAsString(delay, sta->units(), sta->units()->timeUnit()->digits()); +} + +} // namespace diff --git a/graph/Delay.hh b/graph/Delay.hh index dfbe1573..159ca218 100644 --- a/graph/Delay.hh +++ b/graph/Delay.hh @@ -14,12 +14,20 @@ // You should have received a copy of the GNU General Public License // along with this program. If not, see . +#ifndef STA_DELAY_H +#define STA_DELAY_H + +// Define one of the following: + // Define DELAY_FLOAT to use the float definitions. #define DELAY_FLOAT -// Define DELAY_FLOAT_CLASS to use the Delay class definitions. +// Define DELAY_FLOAT_CLASS to use the DelayClass definitions. //#define DELAY_FLOAT_CLASS +// Define DELAY_NORMAL2 to use the DelayNormal2 definitions. +//#define DELAY_NORMAL2 + #ifdef DELAY_FLOAT #include "DelayFloat.hh" #endif @@ -27,3 +35,91 @@ #ifdef DELAY_FLOAT_CLASS #include "DelayFloatClass.hh" #endif + +#ifdef DELAY_NORMAL2 + #include "DelayNormal2.hh" +#endif + +namespace sta { + +class Units; +class StaState; + +typedef Delay ArcDelay; +typedef Delay Slew; +typedef Delay Arrival; +typedef Delay Required; +typedef Delay Slack; + +void +initDelayConstants(); +Delay +makeDelay(float delay, + float sigma_early, + float sigma_late); +float +delayAsFloat(const Delay &delay); +const char * +delayAsString(const Delay &delay, + const Units *units); +const char * +delayAsString(const Delay &delay, + const StaState *sta); +const char * +delayAsString(const Delay &delay, + const Units *units, + int digits); +// mean late+/early- sigma +// early_late = NULL returns mean. +float +delayMeanSigma(const Delay &delay, + const EarlyLate *early_late); +const char * +delayMeanSigmaString(const Delay &delay, + const EarlyLate *early_late, + const Units *units, + int digits); +const Delay & +delayInitValue(const MinMax *min_max); +bool +delayIsInitValue(const Delay &delay, + const MinMax *min_max); +bool +delayFuzzyZero(const Delay &delay); +bool +delayFuzzyEqual(const Delay &delay1, + const Delay &delay2); +bool +delayFuzzyLess(const Delay &delay1, + const Delay &delay2); +bool +delayFuzzyLess(const Delay &delay1, + const Delay &delay2, + const MinMax *min_max); +bool +delayFuzzyLessEqual(const Delay &delay1, + const Delay &delay2); +bool +delayFuzzyLessEqual(const Delay &delay1, + const Delay &delay2, + const MinMax *min_max); +bool +delayFuzzyGreater(const Delay &delay1, + const Delay &delay2); +bool +delayFuzzyGreaterEqual(const Delay &delay1, + const Delay &delay2); +bool +delayFuzzyGreaterEqual(const Delay &delay1, + const Delay &delay2, + const MinMax *min_max); +bool +delayFuzzyGreater(const Delay &delay1, + const Delay &delay2, + const MinMax *min_max); +float +delayRatio(const Delay &delay1, + const Delay &delay2); + +} // namespace +#endif diff --git a/graph/DelayFloat.cc b/graph/DelayFloat.cc index bbc5fcf1..1ab8b4b2 100644 --- a/graph/DelayFloat.cc +++ b/graph/DelayFloat.cc @@ -47,6 +47,12 @@ delayIsInitValue(const Delay &delay, return fuzzyEqual(delay, min_max->initValue()); } +float +delayAsFloat(const Delay &delay) +{ + return delay; +} + bool delayFuzzyZero(const Delay &delay) { @@ -141,16 +147,27 @@ delayRatio(const Delay &delay1, const char * delayAsString(const Delay &delay, - Units *units) + const Units *units, + int digits) { - return units->timeUnit()->asString(delay); + return units->timeUnit()->asString(delay, digits); +} + +float +delayMeanSigma(const Delay &delay, + const EarlyLate *) +{ + return delay; } const char * -delayAsString(const Delay &delay, - const StaState *sta) +delayMeanSigmaString(const Delay &delay, + const EarlyLate *, + const Units *units, + int digits) { - return delayAsString(delay, sta->units()); + const Unit *unit = units->timeUnit(); + return unit->asString(delay, digits); } } // namespace diff --git a/graph/DelayFloat.hh b/graph/DelayFloat.hh index bce126c6..071e8aa3 100644 --- a/graph/DelayFloat.hh +++ b/graph/DelayFloat.hh @@ -18,75 +18,22 @@ #define STA_DELAY_FLOAT_H #include "MinMax.hh" -#include "Pool.hh" // Delay values defined as floats. namespace sta { -class Units; -class StaState; - typedef float Delay; -typedef Delay ArcDelay; -typedef Delay Slew; -typedef Delay Arrival; -typedef Delay Required; -typedef Delay Slack; -typedef Pool DelayPool; const Delay delay_zero = 0.0; -void initDelayConstants(); - -inline float delayAsFloat(const Delay &delay) { return delay; } -const char * -delayAsString(const Delay &delay, - Units *units); -const char * -delayAsString(const Delay &delay, - const StaState *sta); - -const Delay & -delayInitValue(const MinMax *min_max); -bool -delayIsInitValue(const Delay &delay, - const MinMax *min_max); -bool -delayFuzzyZero(const Delay &delay); -bool -delayFuzzyEqual(const Delay &delay1, - const Delay &delay2); -bool -delayFuzzyLess(const Delay &delay1, - const Delay &delay2); -bool -delayFuzzyLessEqual(const Delay &delay1, - const Delay &delay2); -bool -delayFuzzyLessEqual(const Delay &delay1, - const Delay &delay2, - const MinMax *min_max); -bool delayFuzzyGreater(const Delay &delay1, - const Delay &delay2); -bool -delayFuzzyGreaterEqual(const Delay &delay1, - const Delay &delay2); -bool -delayFuzzyGreater(const Delay &delay1, - const Delay &delay2, - const MinMax *min_max); -bool -delayFuzzyGreaterEqual(const Delay &delay1, - const Delay &delay2, - const MinMax *min_max); -bool -delayFuzzyLess(const Delay &delay1, const - Delay &delay2, - const MinMax *min_max); -float -delayRatio(const Delay &delay1, - const Delay &delay2); +inline Delay +makeDelay(float delay, + float, + float) +{ + return delay; +} } // namespace #endif diff --git a/graph/DelayFloatClass.cc b/graph/DelayFloatClass.cc index fa4081b3..d420184e 100644 --- a/graph/DelayFloatClass.cc +++ b/graph/DelayFloatClass.cc @@ -50,12 +50,6 @@ Delay::Delay(float delay) : { } -float -Delay::asFloat() const -{ - return delay_; -} - void Delay::operator=(const Delay &delay) { @@ -273,6 +267,13 @@ operator+(float delay1, return Delay(delay1 + delayAsFloat(delay2)); } +Delay +operator-(float delay1, + const Delay &delay2) +{ + return Delay(delay1 - delayAsFloat(delay2)); +} + Delay operator/(float delay1, const Delay &delay2) @@ -280,13 +281,6 @@ operator/(float delay1, return Delay(delay1 / delayAsFloat(delay2)); } -Delay -operator*(float delay1, - const Delay &delay2) -{ - return Delay(delay1 * delayAsFloat(delay2)); -} - Delay operator*(const Delay &delay1, float delay2) @@ -303,16 +297,26 @@ delayRatio(const Delay &delay1, const char * delayAsString(const Delay &delay, - Units *units) + const Units *units, + int digits) { - return units->timeUnit()->asString(delayAsFloat(delay)); + return units->timeUnit()->asString(delay.delay(), digits); +} + +float +delayMeanSigma(const Delay &delay, + const EarlyLate *) +{ + return delay.delay(); } const char * -delayAsString(const Delay &delay, - const StaState *sta) +delayMeanSigmaString(const Delay &delay, + const EarlyLate *, + const Units *units, + int digits) { - return delayAsString(delay, sta->units()); + return units->timeUnit()->asString(delay.delay(), digits); } } // namespace diff --git a/graph/DelayFloatClass.hh b/graph/DelayFloatClass.hh index 7ffa8208..028ebd64 100644 --- a/graph/DelayFloatClass.hh +++ b/graph/DelayFloatClass.hh @@ -18,29 +18,19 @@ #define STA_DELAY_FLOAT_CLASS_H #include "MinMax.hh" -#include "Pool.hh" namespace sta { -// Delay values defined as objects that hold a float value. - -class Units; -class Delay; -class StaState; - -typedef Delay ArcDelay; -typedef Delay Slew; -typedef Delay Arrival; -typedef Delay Required; -typedef Delay Slack; -typedef Pool DelayPool; +// Delay values defined as class objects that hold a float value. +// This is really a trial balloon for delay values as objects +// instead of simple floats. class Delay { public: Delay(); Delay(float delay); - float asFloat() const; + float delay() const { return delay_; } void operator=(const Delay &delay); void operator=(float delay); void operator+=(const Delay &delay); @@ -63,8 +53,16 @@ private: const Delay delay_zero(0.0); -void -initDelayConstants(); +inline Delay +makeDelay(float delay, + float, + float) +{ + return Delay(delay); +} + +inline float +delayAsFloat(const Delay &delay) { return delay.delay(); } // Most non-operator functions on Delay are not defined as member // functions so they can be defined on floats, where there is no class @@ -72,70 +70,29 @@ initDelayConstants(); Delay operator+(float delay1, const Delay &delay2); +Delay operator-(float delay1, + const Delay &delay2); +// Used for parallel gate delay calc. Delay operator/(float delay1, const Delay &delay2); -Delay operator*(float delay1, - const Delay &delay2); +// Used for parallel gate delay calc. +Delay operator*(const Delay &delay2, + float delay1); Delay operator*(const Delay &delay1, float delay2); -inline float -delayAsFloat(const Delay &delay) { return delay.asFloat(); } -const char *delayAsString(const Delay &delay, - Units *units); -const char *delayAsString(const Delay &delay, - const StaState *sta); -const Delay &delayInitValue(const MinMax *min_max); -bool -delayIsInitValue(const Delay &delay, - const MinMax *min_max); -bool -delayFuzzyZero(const Delay &delay); -bool -delayFuzzyEqual(const Delay &delay1, - const Delay &delay2); -bool -delayFuzzyLess(const Delay &delay1, - const Delay &delay2); + bool delayFuzzyLess(const Delay &delay1, float delay2); bool -delayFuzzyLess(const Delay &delay1, - const Delay &delay2, - const MinMax *min_max); -bool -delayFuzzyLessEqual(const Delay &delay1, - const Delay &delay2); -bool -delayFuzzyLessEqual(const Delay &delay1, - const Delay &delay2, - const MinMax *min_max); -bool delayFuzzyLessEqual(const Delay &delay1, float delay2); bool -delayFuzzyGreater(const Delay &delay1, - const Delay &delay2); -bool delayFuzzyGreater(const Delay &delay1, float delay2); bool -delayFuzzyGreaterEqual(const Delay &delay1, - const Delay &delay2); -bool delayFuzzyGreaterEqual(const Delay &delay1, float delay2); -bool -delayFuzzyGreaterEqual(const Delay &delay1, - const Delay &delay2, - const MinMax *min_max); -bool -delayFuzzyGreater(const Delay &delay1, - const Delay &delay2, - const MinMax *min_max); -float -delayRatio(const Delay &delay1, - const Delay &delay2); } // namespace #endif diff --git a/graph/DelayNormal2.cc b/graph/DelayNormal2.cc new file mode 100644 index 00000000..7a5218a9 --- /dev/null +++ b/graph/DelayNormal2.cc @@ -0,0 +1,389 @@ +// OpenSTA, Static Timing Analyzer +// Copyright (c) 2018, 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 // sqrt +#include "Machine.hh" +#include "StringUtil.hh" +#include "Fuzzy.hh" +#include "Units.hh" +#include "StaState.hh" +#include "Delay.hh" + +// Conditional compilation based on delay abstraction from Delay.hh. +#ifdef DELAY_NORMAL2 + +namespace sta { + +#define square(x) ((x)*(x)) + +static Delay delay_init_values[MinMax::index_count]; + +void +initDelayConstants() +{ + delay_init_values[MinMax::minIndex()] = MinMax::min()->initValue(); + delay_init_values[MinMax::maxIndex()] = MinMax::max()->initValue(); +} + +const Delay & +delayInitValue(const MinMax *min_max) +{ + return delay_init_values[min_max->index()]; +} + +Delay::Delay() : + mean_(0.0), + sigma_{0.0, 0.0} +{ +} + +Delay::Delay(float mean) : + mean_(mean), + sigma_{0.0, 0.0} +{ +} + +Delay::Delay(float mean, + float sigma_early, + float sigma_late) : + mean_(mean), + sigma_{sigma_early, sigma_late} +{ +} + +float +Delay::sigma(const EarlyLate *early_late) const +{ + return sigma_[early_late->index()]; +} + +void +Delay::operator=(const Delay &delay) +{ + mean_ = delay.mean_; + sigma_[early_index] = delay.sigma_[early_index]; + sigma_[late_index] = delay.sigma_[late_index]; +} + +void +Delay::operator=(float delay) +{ + mean_ = delay; + sigma_[early_index] = 0.0; + sigma_[late_index] = 0.0; +} + +void +Delay::operator+=(const Delay &delay) +{ + mean_ += delay.mean_; + sigma_[early_index] = sqrt(square(sigma_[early_index]) + + square(delay.sigma_[early_index])); + sigma_[late_index] = sqrt(square(sigma_[late_index]) + + square(delay.sigma_[late_index])); +} + +void +Delay::operator+=(float delay) +{ + mean_ += delay; +} + +Delay +Delay::operator+(const Delay &delay) const +{ + return Delay(mean_ + delay.mean_, + sqrt(square(sigma_[early_index]) + + square(delay.sigma_[early_index])), + sqrt(square(sigma_[late_index]) + + square(delay.sigma_[late_index]))); +} + +Delay +Delay::operator+(float delay) const +{ + return Delay(mean_ + delay, sigma_[early_index], sigma_[late_index]); +} + +Delay +Delay::operator-(const Delay &delay) const +{ + return Delay(mean_ - delay.mean_, + sqrt(square(sigma_[early_index]) + + square(delay.sigma_[early_index])), + sqrt(square(sigma_[late_index]) + + square(delay.sigma_[late_index]))); +} + +Delay +Delay::operator-(float delay) const +{ + return Delay(mean_ - delay, sigma_[early_index], sigma_[late_index]); +} + +Delay +Delay::operator-() const +{ + return Delay(-mean_, sigma_[early_index], sigma_[late_index]); +} + +void +Delay::operator-=(float delay) +{ + mean_ -= - delay; +} + +bool +Delay::operator==(const Delay &delay) const +{ + return mean_ == delay.mean_ + && sigma_[early_index] == delay.sigma_[early_index] + && sigma_[late_index] == delay.sigma_[late_index]; +} + +bool +Delay::operator>(const Delay &delay) const +{ + return mean_ > delay.mean_; +} + +bool +Delay::operator>=(const Delay &delay) const +{ + return mean_ >= delay.mean_; +} + +bool +Delay::operator<(const Delay &delay) const +{ + return mean_ < delay.mean_; +} + +bool +Delay::operator<=(const Delay &delay) const +{ + return mean_ <= delay.mean_; +} + +bool +delayIsInitValue(const Delay &delay, + const MinMax *min_max) +{ + return fuzzyEqual(delay.mean(), min_max->initValue()) + && delay.sigmaEarly() == 0.0 + && delay.sigmaLate() == 0.0; +} + +bool +delayFuzzyZero(const Delay &delay) +{ + return fuzzyZero(delay.mean()) + && fuzzyZero(delay.sigmaEarly()) + && fuzzyZero(delay.sigmaLate()); +} + +bool +delayFuzzyEqual(const Delay &delay1, + const Delay &delay2) +{ + return fuzzyEqual(delay1.mean(), delay2.mean()) + && fuzzyEqual(delay1.sigmaEarly(), delay2.sigmaEarly()) + && fuzzyEqual(delay1.sigmaLate(), delay2.sigmaLate()); +} + +bool +delayFuzzyLess(const Delay &delay1, + const Delay &delay2) +{ + return fuzzyLess(delay1.mean(), delay2.mean()); +} + +bool +delayFuzzyLess(const Delay &delay1, + float delay2) +{ + return fuzzyLess(delay1.mean(), delay2); +} + +bool +delayFuzzyLessEqual(const Delay &delay1, + const Delay &delay2) +{ + return fuzzyLessEqual(delay1.mean(), delay2.mean()); +} + +bool +delayFuzzyLessEqual(const Delay &delay1, + float delay2) +{ + return fuzzyLessEqual(delay1.mean(), delay2); +} + +bool +delayFuzzyLessEqual(const Delay &delay1, + const Delay &delay2, + const MinMax *min_max) +{ + if (min_max == MinMax::max()) + return fuzzyLessEqual(delay1.mean(), delay2.mean()); + else + return fuzzyGreaterEqual(delay1.mean(), delay2.mean()); +} + +bool +delayFuzzyGreater(const Delay &delay1, + const Delay &delay2) +{ + return fuzzyGreater(delay1.mean(), delay2.mean()); +} + +bool +delayFuzzyGreater(const Delay &delay1, + float delay2) +{ + return fuzzyGreater(delay1.mean(), delay2); +} + +bool +delayFuzzyGreaterEqual(const Delay &delay1, + const Delay &delay2) +{ + return fuzzyGreaterEqual(delay1.mean(), delay2.mean()); +} + +bool +delayFuzzyGreaterEqual(const Delay &delay1, + float delay2) +{ + return fuzzyGreaterEqual(delay1.mean(), delay2); +} + +bool +delayFuzzyGreater(const Delay &delay1, + const Delay &delay2, + const MinMax *min_max) +{ + if (min_max == MinMax::max()) + return fuzzyGreater(delay1.mean(), delay2.mean()); + else + return fuzzyLess(delay1.mean(), delay2.mean()); +} + +bool +delayFuzzyGreaterEqual(const Delay &delay1, + const Delay &delay2, + const MinMax *min_max) +{ + if (min_max == MinMax::max()) + return fuzzyGreaterEqual(delay1.mean(), delay2.mean()); + else + return fuzzyLessEqual(delay1.mean(), delay2.mean()); +} + +bool +delayFuzzyLess(const Delay &delay1, + const Delay &delay2, + const MinMax *min_max) +{ + if (min_max == MinMax::max()) + return fuzzyLess(delay1.mean(), delay2.mean()); + else + return fuzzyGreater(delay1.mean(), delay2.mean()); +} + +Delay +operator+(float delay1, + const Delay &delay2) +{ + return Delay(delay1 + delay2.mean(), + delay2.sigmaEarly(), + delay2.sigmaLate()); +} + +Delay +operator/(float delay1, + const Delay &delay2) +{ + return Delay(delay1 / delay2.mean(), + delay2.sigmaEarly(), + delay2.sigmaLate()); +} + +Delay +operator*(const Delay &delay1, + float delay2) +{ + return Delay(delay1.mean() * delay2, + delay1.sigmaEarly() * delay2, + delay1.sigmaLate() * delay2); +} + +float +delayRatio(const Delay &delay1, + const Delay &delay2) +{ + return delay1.mean() / delay2.mean(); +} + +const char * +delayAsString(const Delay &delay, + const Units *units, + int digits) +{ + const Unit *unit = units->timeUnit(); + float sigma_early = delay.sigmaEarly(); + float sigma_late = delay.sigmaLate(); + if (fuzzyEqual(sigma_early, sigma_late)) + return stringPrintTmp((digits + 2) * 2 + 2, + "%s|%s", + unit->asString(delay.mean(), digits), + unit->asString(sigma_early, digits)); + else + return stringPrintTmp((digits + 2) * 3 + 3, + "%s|%s:%s", + unit->asString(delay.mean(), digits), + unit->asString(sigma_early, digits), + unit->asString(sigma_late, digits)); +} + +const char * +delayMeanSigmaString(const Delay &delay, + const EarlyLate *early_late, + const Units *units, + int digits) +{ + float mean_sigma = delay.mean(); + if (early_late == EarlyLate::early()) + mean_sigma -= delay.sigmaEarly(); + else if (early_late == EarlyLate::late()) + mean_sigma += delay.sigmaLate(); + return units->timeUnit()->asString(mean_sigma, digits); +} + +float +delayMeanSigma(const Delay &delay, + const EarlyLate *early_late) +{ + if (early_late == EarlyLate::early()) + return delay.mean() - delay.sigmaEarly(); + else if (early_late == EarlyLate::late()) + return delay.mean() + delay.sigmaLate(); + else + return delay.mean(); +} + +} // namespace +#endif diff --git a/graph/DelayNormal2.hh b/graph/DelayNormal2.hh new file mode 100644 index 00000000..7f320be0 --- /dev/null +++ b/graph/DelayNormal2.hh @@ -0,0 +1,93 @@ +// OpenSTA, Static Timing Analyzer +// Copyright (c) 2018, 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 . + +#ifndef STA_DELAY_FLOAT_CLASS_H +#define STA_DELAY_FLOAT_CLASS_H + +#include "MinMax.hh" + +namespace sta { + +// Delay values defined as objects that hold a float value. + +class Delay; + +// Normal distribution with early(left)/late(right) std deviations. +class Delay +{ +public: + Delay(); + Delay(float mean); + Delay(float mean, + float sigma_early, + float sigma_late); + float mean() const { return mean_; } + float sigma(const EarlyLate *early_late) const; + float sigmaEarly() const { return sigma_[early_index]; } + float sigmaLate() const { return sigma_[late_index]; } + void operator=(const Delay &delay); + void operator=(float delay); + void operator+=(const Delay &delay); + void operator+=(float delay); + Delay operator+(const Delay &delay) const; + Delay operator+(float delay) const; + Delay operator-(const Delay &delay) const; + Delay operator-(float delay) const; + Delay operator-() const; + void operator-=(float delay); + bool operator==(const Delay &delay) const; + bool operator>(const Delay &delay) const; + bool operator>=(const Delay &delay) const; + bool operator<(const Delay &delay) const; + bool operator<=(const Delay &delay) const; + +protected: + static const int early_index = 0; + static const int late_index = 1; + +private: + float mean_; + float sigma_[EarlyLate::index_count]; +}; + +const Delay delay_zero(0.0); + +inline Delay +makeDelay(float delay, + float sigma_early, + float sigma_late) +{ + return Delay(delay, sigma_early, sigma_late); +} + +inline float +delayAsFloat(const Delay &delay) { return delay.mean(); } + +// Most non-operator functions on Delay are not defined as member +// functions so they can be defined on floats, where there is no class +// to define them. + +Delay operator+(float delay1, + const Delay &delay2); +// Used for parallel gate delay calc. +Delay operator/(float delay1, + const Delay &delay2); +// Used for parallel gate delay calc. +Delay operator*(const Delay &delay1, + float delay2); + +} // namespace +#endif diff --git a/graph/Graph.cc b/graph/Graph.cc index df30bf0c..53b91b00 100644 --- a/graph/Graph.cc +++ b/graph/Graph.cc @@ -715,6 +715,7 @@ Graph::makeArcDelayPools(ArcIndex arc_count, arc_delays_[i] = pool; } + // Leave some room for edits. unsigned annot_size = arc_count * 1.2; arc_delay_annotated_.resize(annot_size * ap_count); } @@ -747,7 +748,7 @@ Graph::makeEdgeArcDelays(Edge *edge) } edge->setArcDelays(arc_index); // Make sure there is room for delay_annotated flags. - unsigned max_annot_index = arc_index + (arc_count * ap_count_); + unsigned max_annot_index = (arc_index + arc_count) * ap_count_; if (max_annot_index >= arc_delay_annotated_.size()) { unsigned size = max_annot_index * 1.2; arc_delay_annotated_.resize(size); @@ -832,7 +833,9 @@ Graph::arcDelayAnnotated(Edge *edge, TimingArc *arc, DcalcAPIndex ap_index) const { - int index = (edge->arcDelays() + arc->index()) * ap_count_ + ap_index; + unsigned index = (edge->arcDelays() + arc->index()) * ap_count_ + ap_index; + if (index >= arc_delay_annotated_.size()) + internalError("arc_delay_annotated array bounds exceeded"); return arc_delay_annotated_[index]; } @@ -842,7 +845,9 @@ Graph::setArcDelayAnnotated(Edge *edge, DcalcAPIndex ap_index, bool annotated) { - int index = (edge->arcDelays() + arc->index()) * ap_count_ + ap_index; + unsigned index = (edge->arcDelays() + arc->index()) * ap_count_ + ap_index; + if (index >= arc_delay_annotated_.size()) + internalError("arc_delay_annotated array bounds exceeded"); arc_delay_annotated_[index] = annotated; } @@ -851,8 +856,10 @@ Graph::wireDelayAnnotated(Edge *edge, const TransRiseFall *tr, DcalcAPIndex ap_index) const { - int index = (edge->arcDelays() + TimingArcSet::wireArcIndex(tr)) * ap_count_ + unsigned index = (edge->arcDelays() + TimingArcSet::wireArcIndex(tr)) * ap_count_ + ap_index; + if (index >= arc_delay_annotated_.size()) + internalError("arc_delay_annotated array bounds exceeded"); return arc_delay_annotated_[index]; } @@ -862,8 +869,10 @@ Graph::setWireDelayAnnotated(Edge *edge, DcalcAPIndex ap_index, bool annotated) { - int index = (edge->arcDelays() + TimingArcSet::wireArcIndex(tr)) * ap_count_ + unsigned index = (edge->arcDelays() + TimingArcSet::wireArcIndex(tr)) * ap_count_ + ap_index; + if (index >= arc_delay_annotated_.size()) + internalError("arc_delay_annotated array bounds exceeded"); arc_delay_annotated_[index] = annotated; } diff --git a/graph/Graph.hh b/graph/Graph.hh index e2651f5f..794a5113 100644 --- a/graph/Graph.hh +++ b/graph/Graph.hh @@ -40,6 +40,7 @@ enum VertexColor { vertex_color_black }; +typedef Pool DelayPool; typedef Pool VertexPool; typedef Pool EdgePool; typedef Map PinVertexMap; diff --git a/graph/Makefile.am b/graph/Makefile.am index ebb6cb6f..9825a8fc 100644 --- a/graph/Makefile.am +++ b/graph/Makefile.am @@ -20,13 +20,16 @@ include_HEADERS = \ Delay.hh \ DelayFloat.hh \ DelayFloatClass.hh \ + DelayNormal2.hh \ Graph.hh \ GraphClass.hh \ GraphCmp.hh libgraph_la_SOURCES = \ + Delay.cc \ DelayFloat.cc \ DelayFloatClass.cc \ + DelayNormal2.cc \ Graph.cc \ GraphCmp.cc diff --git a/liberty/InternalPower.cc b/liberty/InternalPower.cc index 3050fc63..2caadaf7 100644 --- a/liberty/InternalPower.cc +++ b/liberty/InternalPower.cc @@ -23,14 +23,15 @@ namespace sta { InternalPowerAttrs::InternalPowerAttrs() : - when_(NULL) + when_(NULL), + models_{NULL, NULL}, + related_pg_pin_(NULL) { - TransRiseFallIterator tr_iter; - while (tr_iter.hasNext()) { - TransRiseFall *tr = tr_iter.next(); - int tr_index = tr->index(); - models_[tr_index] = NULL; - } +} + +InternalPowerAttrs::~InternalPowerAttrs() +{ + stringDelete(related_pg_pin_); } InternalPowerModel * @@ -46,6 +47,13 @@ InternalPowerAttrs::setModel(TransRiseFall *tr, models_[tr->index()] = model; } +void +InternalPowerAttrs::setRelatedPgPin(const char *related_pg_pin) +{ + stringDelete(related_pg_pin_); + related_pg_pin_ = stringCopy(related_pg_pin); +} + //////////////////////////////////////////////////////////////// InternalPower::InternalPower(LibertyCell *cell, @@ -54,7 +62,8 @@ InternalPower::InternalPower(LibertyCell *cell, InternalPowerAttrs *attrs) : port_(port), related_port_(related_port), - when_(attrs->when()) + when_(attrs->when()), + related_pg_pin_(stringCopy(attrs->relatedPgPin())) { TransRiseFallIterator tr_iter; while (tr_iter.hasNext()) { @@ -77,6 +86,7 @@ InternalPower::~InternalPower() } if (when_) when_->deleteSubexprs(); + stringDelete(related_pg_pin_); } LibertyCell * diff --git a/liberty/InternalPower.hh b/liberty/InternalPower.hh index e132ccaa..9310d52d 100644 --- a/liberty/InternalPower.hh +++ b/liberty/InternalPower.hh @@ -30,15 +30,19 @@ class InternalPowerAttrs { public: InternalPowerAttrs(); + ~InternalPowerAttrs(); FuncExpr *when() const { return when_; } FuncExpr *&whenRef() { return when_; } void setModel(TransRiseFall *tr, InternalPowerModel *model); InternalPowerModel *model(TransRiseFall *tr) const; + const char *relatedPgPin() const { return related_pg_pin_; } + void setRelatedPgPin(const char *related_pg_pin); protected: FuncExpr *when_; InternalPowerModel *models_[TransRiseFall::index_count]; + const char *related_pg_pin_; private: DISALLOW_COPY_AND_ASSIGN(InternalPowerAttrs); @@ -56,6 +60,7 @@ public: LibertyPort *port() const { return port_; } LibertyPort *relatedPort() const { return related_port_; } FuncExpr *when() const { return when_; } + const char *relatedPgPin() const { return related_pg_pin_; } float power(TransRiseFall *tr, const Pvt *pvt, float in_slew, @@ -65,6 +70,7 @@ protected: LibertyPort *port_; LibertyPort *related_port_; FuncExpr *when_; + const char *related_pg_pin_; InternalPowerModel *models_[TransRiseFall::index_count]; private: diff --git a/liberty/Liberty.cc b/liberty/Liberty.cc index 059a541c..9a1cb5bf 100644 --- a/liberty/Liberty.cc +++ b/liberty/Liberty.cc @@ -815,6 +815,8 @@ LibertyCell::LibertyCell(LibertyLibrary *library, liberty_library_(library), area_(0.0), dont_use_(false), + is_macro_(false), + is_pad_(false), has_internal_ports_(false), interface_timing_(false), clock_gate_type_(clock_gate_none), @@ -833,7 +835,8 @@ LibertyCell::LibertyCell(LibertyLibrary *library, test_cell_(NULL), ocv_arc_depth_(0.0), ocv_derate_(NULL), - is_disabled_constraint_(false) + is_disabled_constraint_(false), + leakage_power_(0.0) { } @@ -1040,6 +1043,18 @@ LibertyCell::setDontUse(bool dont_use) dont_use_ = dont_use; } +void +LibertyCell::setIsMacro(bool is_macro) +{ + is_macro_ = is_macro; +} + +void +LibertyCell::LibertyCell::setIsPad(bool is_pad) +{ + is_pad_ = is_pad; +} + void LibertyCell::setInterfaceTiming(bool value) { @@ -1155,6 +1170,18 @@ LibertyCell::leakagePowerIterator() return new LibertyCellLeakagePowerIterator(leakage_powers_); } +float +LibertyCell::leakagePower() const +{ + return leakage_power_; +} + +void +LibertyCell::setLeakagePower(float leakage) +{ + leakage_power_ = leakage; +} + void LibertyCell::finish(bool infer_latches, Report *report, @@ -1808,6 +1835,7 @@ LibertyPort::LibertyPort(LibertyCell *cell, min_period_(0.0), pulse_clk_trigger_(NULL), pulse_clk_sense_(NULL), + related_power_pin_(NULL), min_pulse_width_exists_(false), min_period_exists_(false), is_clk_(false), @@ -1830,6 +1858,7 @@ LibertyPort::~LibertyPort() if (tristate_enable_) tristate_enable_->deleteSubexprs(); delete scaled_ports_; + stringDelete(related_power_pin_); } void @@ -2197,6 +2226,12 @@ LibertyPort::setCornerPort(LibertyPort *corner_port, corner_ports_[ap_index] = corner_port; } +void +LibertyPort::setRelatedPowerPin(const char *related_power_pin) +{ + related_power_pin_ = stringCopy(related_power_pin); +} + //////////////////////////////////////////////////////////////// void @@ -2686,7 +2721,7 @@ TestCell::setScanOutInv(LibertyPort *port) OcvDerate::OcvDerate(const char *name) : name_(name) { - MinMaxIterator el_iter; + EarlyLateIterator el_iter; while (el_iter.hasNext()) { EarlyLate *early_late = el_iter.next(); int el_index = early_late->index(); @@ -2706,7 +2741,7 @@ OcvDerate::~OcvDerate() // Derating table models can be shared in multiple places in derate_; // Collect them in a set to avoid duplicate deletes. Set models; - MinMaxIterator el_iter; + EarlyLateIterator el_iter; while (el_iter.hasNext()) { EarlyLate *early_late = el_iter.next(); int el_index = early_late->index(); diff --git a/liberty/Liberty.hh b/liberty/Liberty.hh index 2600f639..256b92c6 100644 --- a/liberty/Liberty.hh +++ b/liberty/Liberty.hh @@ -383,6 +383,10 @@ public: void setArea(float area); bool dontUse() const { return dont_use_; } void setDontUse(bool dont_use); + bool isMacro() const { return is_macro_; } + void setIsMacro(bool is_macro); + bool isPad() const { return is_pad_; } + void setIsPad(bool is_pad); bool interfaceTiming() const { return interface_timing_; } void setInterfaceTiming(bool value); bool isClockGateLatchPosedge() const; @@ -409,6 +413,7 @@ public: bool hasTimingArcs(LibertyPort *port) const; LibertyCellInternalPowerIterator *internalPowerIterator(); LibertyCellLeakagePowerIterator *leakagePowerIterator(); + float leakagePower() const; bool hasSequentials() const; CellSequentialIterator *sequentialIterator() const; void makeSequential(int size, @@ -444,6 +449,7 @@ public: LibertyCell *cornerCell(int ap_index); void setCornerCell(LibertyCell *corner_cell, int ap_index); + void setLeakagePower(float leakage); // AOCV float ocvArcDepth() const; @@ -476,6 +482,8 @@ protected: LibertyLibrary *liberty_library_; float area_; bool dont_use_; + bool is_macro_; + bool is_pad_; bool has_internal_ports_; bool interface_timing_; ClockGateType clock_gate_type_; @@ -505,6 +513,7 @@ protected: OcvDerateMap ocv_derate_map_; bool is_disabled_constraint_; Vector corner_cells_; + float leakage_power_; private: DISALLOW_COPY_AND_ASSIGN(LibertyCell); @@ -637,6 +646,8 @@ public: LibertyPort *cornerPort(int ap_index); void setCornerPort(LibertyPort *corner_port, int ap_index); + const char *relatedPowerPin() const { return related_power_pin_; } + void setRelatedPowerPin(const char *related_power_pin); static bool equiv(const LibertyPort *port1, const LibertyPort *port2); @@ -670,6 +681,7 @@ protected: float min_pulse_width_[TransRiseFall::index_count]; TransRiseFall *pulse_clk_trigger_; TransRiseFall *pulse_clk_sense_; + const char *related_power_pin_; Vector corner_ports_; unsigned int min_pulse_width_exists_:TransRiseFall::index_count; diff --git a/liberty/LibertyReader.cc b/liberty/LibertyReader.cc index 35a0fbaf..d0ff2762 100644 --- a/liberty/LibertyReader.cc +++ b/liberty/LibertyReader.cc @@ -287,6 +287,8 @@ LibertyReader::defineVisitors() &LibertyReader::visitClockGatingIntegratedCell); defineAttrVisitor("area", &LibertyReader::visitArea); defineAttrVisitor("dont_use", &LibertyReader::visitDontUse); + defineAttrVisitor("is_macro", &LibertyReader::visitIsMacro); + defineAttrVisitor("is_pad", &LibertyReader::visitIsPad); defineAttrVisitor("interface_timing", &LibertyReader::visitInterfaceTiming); defineAttrVisitor("scaling_factors", &LibertyReader::visitScalingFactors); @@ -399,6 +401,8 @@ LibertyReader::defineVisitors() &LibertyReader::endRiseFallPower); defineGroupVisitor("rise_power", &LibertyReader::beginRisePower, &LibertyReader::endRiseFallPower); + defineAttrVisitor("related_power_pin", &LibertyReader::visitRelatedPowerPin); + defineAttrVisitor("related_pg_pin", &LibertyReader::visitRelatedPgPin); // AOCV attributes. defineAttrVisitor("ocv_arc_depth", &LibertyReader::visitOcvArcDepth); @@ -417,6 +421,20 @@ LibertyReader::defineVisitors() defineAttrVisitor("rf_type", &LibertyReader::visitRfType); defineAttrVisitor("derate_type", &LibertyReader::visitDerateType); defineAttrVisitor("path_type", &LibertyReader::visitPathType); + + // POCV attributes. + defineGroupVisitor("ocv_sigma_cell_rise", &LibertyReader::beginOcvSigmaCellRise, + &LibertyReader::endOcvSigmaCell); + defineGroupVisitor("ocv_sigma_cell_fall", &LibertyReader::beginOcvSigmaCellFall, + &LibertyReader::endOcvSigmaCell); + defineGroupVisitor("ocv_sigma_rise_transition", + &LibertyReader::beginOcvSigmaRiseTransition, + &LibertyReader::endOcvSigmaTransition); + defineGroupVisitor("ocv_sigma_fall_transition", + &LibertyReader::beginOcvSigmaFallTransition, + &LibertyReader::endOcvSigmaTransition); + defineAttrVisitor("sigma_type", &LibertyReader::visitSigmaType); + defineAttrVisitor("cell_leakage_power", &LibertyReader::visitCellLeakagePower); } void @@ -535,6 +553,8 @@ LibertyReader::beginLibrary(LibertyGroup *group) curr_scale_ = 1E-3F; // Default is 1; power_scale_ = 1; + // Default is fJ. + energy_scale_ = 1e-15; library_->units()->timeUnit()->setScale(time_scale_); library_->units()->capacitanceUnit()->setScale(cap_scale_); @@ -1720,7 +1740,11 @@ void LibertyReader::endCell(LibertyGroup *group) { if (cell_) { + // Sequentials and leakage powers reference expressions outside of port definitions + // so they do not require LibertyFunc's. makeCellSequentials(); + // Parse functions defined inside of port groups that reference other ports + // and replace the references with the parsed expressions. parseCellFuncs(); makeLeakagePowers(); finishPortGroups(); @@ -2128,7 +2152,8 @@ TimingGroup::makeTableModels(LibertyReader *visitor) TableModel *constraint = constraint_[tr_index]; TableModel *transition = transition_[tr_index]; if (cell || transition) { - models_[tr_index] = new GateTableModel(cell, transition); + models_[tr_index] = new GateTableModel(cell, delay_sigma_[tr_index], + transition, slew_sigma_[tr_index]); if (timing_type_ == timing_type_clear || timing_type_ == timing_type_combinational || timing_type_ == timing_type_combinational_fall @@ -2142,11 +2167,10 @@ TimingGroup::makeTableModels(LibertyReader *visitor) || timing_type_ == timing_type_three_state_enable || timing_type_ == timing_type_three_state_enable_fall || timing_type_ == timing_type_three_state_enable_rise) { - const char *tr_name = tr == TransRiseFall::rise() ? "rise" : "fall"; if (transition == NULL) - visitor->libWarn(line_, "missing %s_transition.\n", tr_name); + visitor->libWarn(line_, "missing %s_transition.\n", tr->name()); if (cell == NULL) - visitor->libWarn(line_, "missing cell_%s.\n", tr_name); + visitor->libWarn(line_, "missing cell_%s.\n", tr->name()); } } if (constraint) @@ -2354,6 +2378,28 @@ LibertyReader::visitDontUse(LibertyAttr *attr) } } +void +LibertyReader::visitIsMacro(LibertyAttr *attr) +{ + if (cell_) { + bool is_macro, exists; + getAttrBool(attr, is_macro, exists); + if (exists) + cell_->setIsMacro(is_macro); + } +} + +void +LibertyReader::visitIsPad(LibertyAttr *attr) +{ + if (cell_) { + bool is_pad, exists; + getAttrBool(attr, is_pad, exists); + if (exists) + cell_->setIsPad(is_pad); + } +} + void LibertyReader::visitInterfaceTiming(LibertyAttr *attr) { @@ -3705,6 +3751,7 @@ LibertyReader::beginTableModel(LibertyGroup *group, beginTable(group, scale); tr_ = tr; scale_factor_type_ = scale_factor_type; + sigma_type_ = EarlyLateAll::all(); } void @@ -4248,6 +4295,20 @@ LibertyReader::parseFunc(const char *func, return parseFuncExpr(func, cell_, error_msg, report_); } +EarlyLateAll * +LibertyReader::getAttrEarlyLate(LibertyAttr *attr) +{ + const char *value = getAttrString(attr); + if (stringEq(value, "early")) + return EarlyLateAll::min(); + else if (stringEq(value, "late")) + return EarlyLateAll::max(); + else { + libWarn(attr, "unknown early/late value.\n"); + return EarlyLateAll::all(); + } +} + //////////////////////////////////////////////////////////////// void @@ -4356,7 +4417,7 @@ void LibertyReader::beginFallPower(LibertyGroup *group) { if (internal_power_) - beginTableModel(group, TransRiseFall::fall(), power_scale_, + beginTableModel(group, TransRiseFall::fall(), energy_scale_, scale_factor_internal_power); } @@ -4364,7 +4425,7 @@ void LibertyReader::beginRisePower(LibertyGroup *group) { if (internal_power_) - beginTableModel(group, TransRiseFall::rise(), power_scale_, + beginTableModel(group, TransRiseFall::rise(), energy_scale_, scale_factor_internal_power); } @@ -4378,6 +4439,26 @@ LibertyReader::endRiseFallPower(LibertyGroup *) endTableModel(); } +void +LibertyReader::visitRelatedPowerPin(LibertyAttr *attr) +{ + if (ports_) { + const char *related_power_pin = getAttrString(attr); + LibertyPortSeq::Iterator port_iter(ports_); + while (port_iter.hasNext()) { + LibertyPort *port = port_iter.next(); + port->setRelatedPowerPin(stringCopy(related_power_pin)); + } + } +} + +void +LibertyReader::visitRelatedPgPin(LibertyAttr *attr) +{ + if (internal_power_) + internal_power_->setRelatedPgPin(getAttrString(attr)); +} + //////////////////////////////////////////////////////////////// void @@ -4438,7 +4519,7 @@ LibertyReader::beginOcvDerateFactors(LibertyGroup *group) { if (ocv_derate_) { rf_type_ = TransRiseFallBoth::riseFall(); - early_late_ = EarlyLateAll::all(); + derate_type_ = EarlyLateAll::all(); path_type_ = path_type_clk_and_data; beginTable(group, 1.0); } @@ -4448,7 +4529,7 @@ void LibertyReader::endOcvDerateFactors(LibertyGroup *) { if (ocv_derate_) { - MinMaxIterator el_iter(early_late_); + EarlyLateIterator el_iter(derate_type_); while (el_iter.hasNext()) { EarlyLate *early_late = el_iter.next(); TransRiseFallIterator tr_iter(rf_type_); @@ -4483,13 +4564,7 @@ LibertyReader::visitRfType(LibertyAttr *attr) void LibertyReader::visitDerateType(LibertyAttr *attr) { - const char *derate_type = getAttrString(attr); - if (stringEq(derate_type, "early")) - early_late_ = EarlyLateAll::min(); - else if (stringEq(derate_type, "late")) - early_late_ = EarlyLateAll::max(); - else - libWarn(attr, "unknown derate type.\n"); + derate_type_ = getAttrEarlyLate(attr); } void @@ -4508,6 +4583,92 @@ LibertyReader::visitPathType(LibertyAttr *attr) //////////////////////////////////////////////////////////////// +void +LibertyReader::beginOcvSigmaCellRise(LibertyGroup *group) +{ + beginTimingTableModel(group, TransRiseFall::rise(), scale_factor_unknown); +} + +void +LibertyReader::beginOcvSigmaCellFall(LibertyGroup *group) +{ + beginTimingTableModel(group, TransRiseFall::fall(), scale_factor_unknown); +} + +void +LibertyReader::endOcvSigmaCell(LibertyGroup *group) +{ + if (table_) { + if (GateTableModel::checkAxes(table_)) { + TableModel *table_model = new TableModel(table_, scale_factor_type_, tr_); + if (sigma_type_ == EarlyLateAll::all()) { + timing_->setDelaySigma(tr_, EarlyLate::min(), table_model); + timing_->setDelaySigma(tr_, EarlyLate::max(), table_model); + } + else + timing_->setDelaySigma(tr_, sigma_type_->asMinMax(), table_model); + } + else { + libWarn(group, "unsupported model axis.\n"); + delete table_; + } + } + endTableModel(); +} + +void +LibertyReader::beginOcvSigmaRiseTransition(LibertyGroup *group) +{ + beginTimingTableModel(group, TransRiseFall::rise(), scale_factor_unknown); +} + +void +LibertyReader::beginOcvSigmaFallTransition(LibertyGroup *group) +{ + beginTimingTableModel(group, TransRiseFall::fall(), scale_factor_unknown); +} + +void +LibertyReader::endOcvSigmaTransition(LibertyGroup *group) +{ + if (table_) { + if (GateTableModel::checkAxes(table_)) { + TableModel *table_model = new TableModel(table_, scale_factor_type_, tr_); + if (sigma_type_ == EarlyLateAll::all()) { + timing_->setSlewSigma(tr_, EarlyLate::min(), table_model); + timing_->setSlewSigma(tr_, EarlyLate::max(), table_model); + } + else + timing_->setSlewSigma(tr_, sigma_type_->asMinMax(), table_model); + } + else { + libWarn(group, "unsupported model axis.\n"); + delete table_; + } + } + endTableModel(); +} + +void +LibertyReader::visitSigmaType(LibertyAttr *attr) +{ + sigma_type_ = getAttrEarlyLate(attr); +} + +void +LibertyReader::visitCellLeakagePower(LibertyAttr *attr) +{ + if (cell_) { + float value; + bool exists; + getAttrFloat(attr, value, exists); + if (exists) + cell_->setLeakagePower(value * power_scale_); + } +} + +//////////////////////////////////////////////////////////////// + LibertyFunc::LibertyFunc(const char *expr, FuncExpr *&func_ref, bool invert, @@ -4672,6 +4833,14 @@ TimingGroup::TimingGroup(int line) : intrinsic_exists_[tr_index] = false; resistance_[tr_index] = 0.0F; resistance_exists_[tr_index] = false; + + MinMaxIterator el_iter; + while (el_iter.hasNext()) { + EarlyLate *early_late = el_iter.next(); + int el_index = early_late->index(); + delay_sigma_[tr_index][el_index] = NULL; + slew_sigma_[tr_index][el_index] = NULL; + } } } @@ -4766,7 +4935,23 @@ TimingGroup::setTransition(TransRiseFall *tr, transition_[tr->index()] = model; } -//////////////////////////////////////////////////////////////// +void +TimingGroup::setDelaySigma(TransRiseFall *tr, + EarlyLate *early_late, + TableModel *model) +{ + delay_sigma_[tr->index()][early_late->index()] = model; +} + +void +TimingGroup::setSlewSigma(TransRiseFall *tr, + EarlyLate *early_late, + TableModel *model) +{ + slew_sigma_[tr->index()][early_late->index()] = model; +} + + //////////////////////////////////////////////////////////////// InternalPowerGroup::InternalPowerGroup(int line) : InternalPowerAttrs(), diff --git a/liberty/LibertyReaderPvt.hh b/liberty/LibertyReaderPvt.hh index aa5d4393..2a59e29b 100644 --- a/liberty/LibertyReaderPvt.hh +++ b/liberty/LibertyReaderPvt.hh @@ -175,8 +175,11 @@ public: virtual void visitClockGatingIntegratedCell(LibertyAttr *attr); virtual void visitArea(LibertyAttr *attr); virtual void visitDontUse(LibertyAttr *attr); - void visitInterfaceTiming(LibertyAttr *attr); + virtual void visitIsMacro(LibertyAttr *attr); + virtual void visitIsPad(LibertyAttr *attr); + virtual void visitInterfaceTiming(LibertyAttr *attr); virtual void visitScalingFactors(LibertyAttr *attr); + virtual void visitCellLeakagePower(LibertyAttr *attr); virtual void beginPin(LibertyGroup *group); virtual void endPin(LibertyGroup *group); @@ -222,6 +225,7 @@ public: virtual void visitClockGateOutPin(LibertyAttr *attr); void visitIsPllFeedbackPin(LibertyAttr *attr); virtual void visitSignalType(LibertyAttr *attr); + EarlyLateAll *getAttrEarlyLate(LibertyAttr *attr); virtual void visitClock(LibertyAttr *attr); virtual void beginScalingFactors(LibertyGroup *group); @@ -353,6 +357,8 @@ public: virtual void beginFallPower(LibertyGroup *group); virtual void beginRisePower(LibertyGroup *group); virtual void endRiseFallPower(LibertyGroup *group); + virtual void visitRelatedPowerPin(LibertyAttr *attr); + virtual void visitRelatedPgPin(LibertyAttr *attr); virtual void makeInternalPowers(LibertyPort *port, InternalPowerGroup *power_group); virtual void makeInternalPowers(LibertyPort *port, @@ -372,6 +378,15 @@ public: virtual void visitDerateType(LibertyAttr *attr); virtual void visitPathType(LibertyAttr *attr); + // POCV attributes. + virtual void beginOcvSigmaCellRise(LibertyGroup *group); + virtual void beginOcvSigmaCellFall(LibertyGroup *group); + virtual void endOcvSigmaCell(LibertyGroup *group); + virtual void beginOcvSigmaRiseTransition(LibertyGroup *group); + virtual void beginOcvSigmaFallTransition(LibertyGroup *group); + virtual void endOcvSigmaTransition(LibertyGroup *group); + virtual void visitSigmaType(LibertyAttr *attr); + // Visitors for derived classes to overload. virtual void beginGroup1(LibertyGroup *) {} virtual void beginGroup2(LibertyGroup *) {} @@ -513,7 +528,8 @@ protected: TransRiseFall *tr_; OcvDerate *ocv_derate_; TransRiseFallBoth *rf_type_; - EarlyLateAll *early_late_; + EarlyLateAll *derate_type_; + EarlyLateAll *sigma_type_; PathType path_type_; ScaleFactorType scale_factor_type_; TableAxis *axis_[3]; @@ -529,6 +545,7 @@ protected: float volt_scale_; float curr_scale_; float power_scale_; + float energy_scale_; private: DISALLOW_COPY_AND_ASSIGN(LibertyReader); @@ -688,6 +705,12 @@ public: TableModel *model); void makeTimingModels(LibertyLibrary *library, LibertyReader *visitor); + void setDelaySigma(TransRiseFall *tr, + EarlyLate *early_late, + TableModel *model); + void setSlewSigma(TransRiseFall *tr, + EarlyLate *early_late, + TableModel *model); protected: void makeLinearModels(LibertyLibrary *library); @@ -701,6 +724,8 @@ protected: TableModel *cell_[TransRiseFall::index_count]; TableModel *constraint_[TransRiseFall::index_count]; TableModel *transition_[TransRiseFall::index_count]; + TableModel *delay_sigma_[TransRiseFall::index_count][EarlyLate::index_count]; + TableModel *slew_sigma_[TransRiseFall::index_count][EarlyLate::index_count]; private: DISALLOW_COPY_AND_ASSIGN(TimingGroup); diff --git a/liberty/LinearModel.cc b/liberty/LinearModel.cc index 261c8373..7d90e882 100644 --- a/liberty/LinearModel.cc +++ b/liberty/LinearModel.cc @@ -35,8 +35,8 @@ GateLinearModel::gateDelay(const LibertyCell *, float load_cap, float, // return values - float &gate_delay, - float &drvr_slew) const + ArcDelay &gate_delay, + Slew &drvr_slew) const { gate_delay = intrinsic_ + resistance_ * load_cap; drvr_slew = 0.0; @@ -84,14 +84,15 @@ CheckLinearModel::CheckLinearModel(float intrinsic) : { } -float +void CheckLinearModel::checkDelay(const LibertyCell *, const Pvt *, float, float, - float) const + float, + ArcDelay &margin) const { - return intrinsic_; + margin = intrinsic_; } void diff --git a/liberty/LinearModel.hh b/liberty/LinearModel.hh index ce9c1ac8..39d326d8 100644 --- a/liberty/LinearModel.hh +++ b/liberty/LinearModel.hh @@ -32,7 +32,8 @@ public: float load_cap, float in_slew, float related_out_cap, // return values - float &gate_delay, float &drvr_slew) const; + ArcDelay &gate_delay, + Slew &drvr_slew) const; virtual void reportGateDelay(const LibertyCell *cell, const Pvt *pvt, float load_cap, float in_slew, @@ -56,10 +57,11 @@ class CheckLinearModel : public CheckTimingModel public: explicit CheckLinearModel(float intrinsic); // Timing check margin delay calculation. - virtual float checkDelay(const LibertyCell *cell, - const Pvt *pvt, - float from_slew, float to_slew, - float related_out_cap) const; + virtual void checkDelay(const LibertyCell *cell, + const Pvt *pvt, + float from_slew, float to_slew, + float related_out_cap, + ArcDelay &margin) const; virtual void reportCheckDelay(const LibertyCell *cell, const Pvt *pvt, float from_slew, diff --git a/liberty/TableModel.cc b/liberty/TableModel.cc index 76f3a78c..a16deb7c 100644 --- a/liberty/TableModel.cc +++ b/liberty/TableModel.cc @@ -23,21 +23,43 @@ namespace sta { +static void +reportPvt(const LibertyLibrary *library, + const LibertyCell *cell, + const Pvt *pvt, + int digits, + string *result); static void appendSpaces(string *result, int count); GateTableModel::GateTableModel(TableModel *delay_model, - TableModel *slew_model): + TableModel *delay_sigma_models[EarlyLate::index_count], + TableModel *slew_model, + TableModel *slew_sigma_models[EarlyLate::index_count]) : delay_model_(delay_model), slew_model_(slew_model) { + MinMaxIterator el_iter; + while (el_iter.hasNext()) { + EarlyLate *early_late = el_iter.next(); + int el_index = early_late->index(); + slew_sigma_models_[el_index] = slew_sigma_models[el_index]; + delay_sigma_models_[el_index] = delay_sigma_models[el_index]; + } } GateTableModel::~GateTableModel() { delete delay_model_; delete slew_model_; + MinMaxIterator el_iter; + while (el_iter.hasNext()) { + EarlyLate *early_late = el_iter.next(); + int el_index = early_late->index(); + delete slew_sigma_models_[el_index]; + delete delay_sigma_models_[el_index]; + } } void @@ -56,29 +78,40 @@ GateTableModel::gateDelay(const LibertyCell *cell, float load_cap, float related_out_cap, // return values - float &gate_delay, - float &drvr_slew) const + ArcDelay &gate_delay, + Slew &drvr_slew) const { const LibertyLibrary *library = cell->libertyLibrary(); - gate_delay = findValue(library, cell, pvt, delay_model_, in_slew, - load_cap, related_out_cap); - drvr_slew = findValue(library, cell, pvt, slew_model_, in_slew, - load_cap, related_out_cap); - // Clip negative slews to zero. - if (drvr_slew < 0.0) - drvr_slew = 0.0; -} + float delay = findValue(library, cell, pvt, delay_model_, in_slew, + load_cap, related_out_cap); + float sigma_early = 0.0; + float sigma_late = 0.0; + if (delay_sigma_models_[EarlyLate::earlyIndex()]) + sigma_early = findValue(library, cell, pvt, + delay_sigma_models_[EarlyLate::earlyIndex()], + in_slew, load_cap, related_out_cap); + if (delay_sigma_models_[EarlyLate::lateIndex()]) + sigma_late = findValue(library, cell, pvt, + delay_sigma_models_[EarlyLate::earlyIndex()], + in_slew, load_cap, related_out_cap); + gate_delay = makeDelay(delay, sigma_early, sigma_late); -float -GateTableModel::gateDelay(const LibertyCell *cell, - const Pvt *pvt, - float in_slew, - float load_cap, - float related_out_cap) const -{ - const LibertyLibrary *library = cell->libertyLibrary(); - return findValue(library, cell, pvt, delay_model_, in_slew, - load_cap, related_out_cap); + float slew = findValue(library, cell, pvt, slew_model_, in_slew, + load_cap, related_out_cap); + if (slew_sigma_models_[EarlyLate::earlyIndex()]) + sigma_early = findValue(library, cell, pvt, + slew_sigma_models_[EarlyLate::earlyIndex()], + in_slew, load_cap, related_out_cap); + if (slew_sigma_models_[EarlyLate::lateIndex()]) + sigma_late = findValue(library, cell, pvt, + slew_sigma_models_[EarlyLate::earlyIndex()], + in_slew, load_cap, related_out_cap); + sigma_early = 0.0; + sigma_late = 0.0; + // Clip negative slews to zero. + if (slew < 0.0) + slew = 0.0; + drvr_slew = makeDelay(slew, sigma_early, sigma_late); } void @@ -91,11 +124,28 @@ GateTableModel::reportGateDelay(const LibertyCell *cell, string *result) const { const LibertyLibrary *library = cell->libertyLibrary(); + reportPvt(library, cell, pvt, digits, result); reportTableLookup("Delay", library, cell, pvt, delay_model_, in_slew, load_cap, related_out_cap, digits, result); + if (delay_sigma_models_[EarlyLate::earlyIndex()]) + reportTableLookup("Delay sigma(early)", library, cell, pvt, + delay_sigma_models_[EarlyLate::earlyIndex()], + in_slew, load_cap, related_out_cap, digits, result); + if (delay_sigma_models_[EarlyLate::lateIndex()]) + reportTableLookup("Delay sigma(late)", library, cell, pvt, + delay_sigma_models_[EarlyLate::lateIndex()], + in_slew, load_cap, related_out_cap, digits, result); *result += '\n'; reportTableLookup("Slew", library, cell, pvt, slew_model_, in_slew, load_cap, related_out_cap, digits, result); + if (slew_sigma_models_[EarlyLate::earlyIndex()]) + reportTableLookup("Slew sigma(early)", library, cell, pvt, + slew_sigma_models_[EarlyLate::earlyIndex()], + in_slew, load_cap, related_out_cap, digits, result); + if (slew_sigma_models_[EarlyLate::lateIndex()]) + reportTableLookup("Slew sigma(late)", library, cell, pvt, + slew_sigma_models_[EarlyLate::lateIndex()], + in_slew, load_cap, related_out_cap, digits, result); float drvr_slew = findValue(library, cell, pvt, slew_model_, in_slew, load_cap, related_out_cap); if (drvr_slew < 0.0) @@ -300,23 +350,25 @@ CheckTableModel::setIsScaled(bool is_scaled) model_->setIsScaled(is_scaled); } -float +void CheckTableModel::checkDelay(const LibertyCell *cell, const Pvt *pvt, float from_slew, float to_slew, - float related_out_cap) const + float related_out_cap, + // Return values. + ArcDelay &margin) const { if (model_) { float axis_value1, axis_value2, axis_value3; findAxisValues(from_slew, to_slew, related_out_cap, axis_value1, axis_value2, axis_value3); const LibertyLibrary *library = cell->libertyLibrary(); - return model_->findValue(library, cell, pvt, - axis_value1, axis_value2, axis_value3); + margin = model_->findValue(library, cell, pvt, + axis_value1, axis_value2, axis_value3); } else - return 0.0; + margin = 0.0; } void @@ -334,6 +386,7 @@ CheckTableModel::reportCheckDelay(const LibertyCell *cell, findAxisValues(from_slew, to_slew, related_out_cap, axis_value1, axis_value2, axis_value3); const LibertyLibrary *library = cell->libertyLibrary(); + reportPvt(library, cell, pvt, digits, result); model_->reportValue("Check", library, cell, pvt, axis_value1, from_slew_annotation, axis_value2, axis_value3, digits, result); @@ -531,12 +584,12 @@ TableModel::reportValue(const char *result_name, *result += '\n'; } -void -TableModel::reportPvtScaleFactor(const LibertyLibrary *library, - const LibertyCell *cell, - const Pvt *pvt, - int digits, - string *result) const +static void +reportPvt(const LibertyLibrary *library, + const LibertyCell *cell, + const Pvt *pvt, + int digits, + string *result) { if (pvt == NULL) pvt = library->defaultOperatingConditions(); @@ -550,13 +603,26 @@ TableModel::reportPvtScaleFactor(const LibertyLibrary *library, *result += pvt_str; stringDelete(pvt_str); } - const char *scale_str = stringPrint(strlen("PVT scale factor = %.*f\n") - + digits + 10 + 1, - "PVT scale factor = %.*f\n", - digits, - scaleFactor(library, cell, pvt)); - *result += scale_str; - stringDelete(scale_str); +} + +void +TableModel::reportPvtScaleFactor(const LibertyLibrary *library, + const LibertyCell *cell, + const Pvt *pvt, + int digits, + string *result) const +{ + if (pvt == NULL) + pvt = library->defaultOperatingConditions(); + if (pvt) { + const char *scale_str = stringPrint(strlen("PVT scale factor = %.*f\n") + + digits + 10 + 1, + "PVT scale factor = %.*f\n", + digits, + scaleFactor(library, cell, pvt)); + *result += scale_str; + stringDelete(scale_str); + } } //////////////////////////////////////////////////////////////// diff --git a/liberty/TableModel.hh b/liberty/TableModel.hh index 7019e844..7b981352 100644 --- a/liberty/TableModel.hh +++ b/liberty/TableModel.hh @@ -19,6 +19,7 @@ #include #include "DisallowCopyAssign.hh" +#include "MinMax.hh" #include "Vector.hh" #include "Transition.hh" #include "LibertyClass.hh" @@ -45,21 +46,18 @@ class GateTableModel : public GateTimingModel { public: GateTableModel(TableModel *delay_model, - TableModel *slew_model); + TableModel *delay_sigma_models[EarlyLate::index_count], + TableModel *slew_model, + TableModel *slew_sigma_models[EarlyLate::index_count]); virtual ~GateTableModel(); virtual void gateDelay(const LibertyCell *cell, const Pvt *pvt, float in_slew, float load_cap, float related_out_cap, - // return values - float &gate_delay, - float &drvr_slew) const; - float gateDelay(const LibertyCell *cell, - const Pvt *pvt, - float in_slew, - float load_cap, - float related_out_cap) const; + // Return values. + ArcDelay &gate_delay, + Slew &drvr_slew) const; virtual void reportGateDelay(const LibertyCell *cell, const Pvt *pvt, float in_slew, @@ -115,7 +113,9 @@ protected: static bool checkAxis(TableAxis *axis); TableModel *delay_model_; + TableModel *delay_sigma_models_[EarlyLate::index_count]; TableModel *slew_model_; + TableModel *slew_sigma_models_[EarlyLate::index_count]; private: DISALLOW_COPY_AND_ASSIGN(GateTableModel); @@ -126,11 +126,13 @@ class CheckTableModel : public CheckTimingModel public: explicit CheckTableModel(TableModel *model); virtual ~CheckTableModel(); - virtual float checkDelay(const LibertyCell *cell, - const Pvt *pvt, - float from_slew, - float to_slew, - float related_out_cap) const; + virtual void checkDelay(const LibertyCell *cell, + const Pvt *pvt, + float from_slew, + float to_slew, + float related_out_cap, + // Return values. + ArcDelay &margin) const; virtual void reportCheckDelay(const LibertyCell *cell, const Pvt *pvt, float from_slew, diff --git a/liberty/TimingArc.cc b/liberty/TimingArc.cc index e92c69c7..5a07fa7f 100644 --- a/liberty/TimingArc.cc +++ b/liberty/TimingArc.cc @@ -40,29 +40,19 @@ TimingArcAttrs::TimingArcAttrs() : sdf_cond_end_(NULL), mode_name_(NULL), mode_value_(NULL), - ocv_arc_depth_(0.0) + ocv_arc_depth_(0.0), + models_{NULL, NULL}, + model_refs_{false, false} { - TransRiseFallIterator tr_iter; - while (tr_iter.hasNext()) { - TransRiseFall *tr = tr_iter.next(); - int tr_index = tr->index(); - models_[tr_index] = NULL; - model_refs_[tr_index] = false; - } } TimingArcAttrs::~TimingArcAttrs() { - if (sdf_cond_) - stringDelete(sdf_cond_); - if (sdf_cond_start_) - stringDelete(sdf_cond_start_); - if (sdf_cond_end_) - stringDelete(sdf_cond_end_); - if (mode_name_) - stringDelete(mode_name_); - if (mode_value_) - stringDelete(mode_value_); + stringDelete(sdf_cond_); + stringDelete(sdf_cond_start_); + stringDelete(sdf_cond_end_); + stringDelete(mode_name_); + stringDelete(mode_value_); } void @@ -162,8 +152,9 @@ TimingArcSet::TimingArcSet(LibertyCell *cell, is_cond_default_(false), sdf_cond_start_(NULL), sdf_cond_end_(NULL), - mode_name_(NULL), - mode_value_(NULL), + mode_name_(stringCopy(attrs->modeName())), + mode_value_(stringCopy(attrs->modeValue())), + ocv_arc_depth_(attrs->ocvArcDepth()), index_(0), is_disabled_constraint_(false) { @@ -177,14 +168,6 @@ TimingArcSet::TimingArcSet(LibertyCell *cell, if (sdf_cond_end) sdf_cond_end_ = stringCopy(sdf_cond_end); - const char *mode_name = attrs->modeName(); - if (mode_name) - mode_name_ = stringCopy(mode_name); - const char *mode_value = attrs->modeValue(); - if (mode_value) - mode_value_ = stringCopy(mode_value); - ocv_arc_depth_ = attrs->ocvArcDepth(); - init(cell); } diff --git a/liberty/TimingArc.hh b/liberty/TimingArc.hh index dbdd84ac..1519495d 100644 --- a/liberty/TimingArc.hh +++ b/liberty/TimingArc.hh @@ -128,9 +128,9 @@ protected: const char *sdf_cond_end_; const char *mode_name_; const char *mode_value_; + float ocv_arc_depth_; TimingModel *models_[TransRiseFall::index_count]; bool model_refs_[TransRiseFall::index_count]; - float ocv_arc_depth_; private: DISALLOW_COPY_AND_ASSIGN(TimingArcAttrs); diff --git a/liberty/TimingModel.hh b/liberty/TimingModel.hh index c815de83..b60b1547 100644 --- a/liberty/TimingModel.hh +++ b/liberty/TimingModel.hh @@ -18,6 +18,7 @@ #define STA_TIMING_MODEL_H #include +#include "Delay.hh" #include "LibertyClass.hh" namespace sta { @@ -47,8 +48,8 @@ public: float load_cap, float related_out_cap, // Return values. - float &gate_delay, - float &drvr_slew) const = 0; + ArcDelay &gate_delay, + Slew &drvr_slew) const = 0; virtual void reportGateDelay(const LibertyCell *cell, const Pvt *pvt, float in_slew, @@ -65,11 +66,13 @@ class CheckTimingModel : public TimingModel { public: // Timing check margin delay calculation. - virtual float checkDelay(const LibertyCell *cell, - const Pvt *pvt, - float from_slew, - float to_slew, - float related_out_cap) const = 0; + virtual void checkDelay(const LibertyCell *cell, + const Pvt *pvt, + float from_slew, + float to_slew, + float related_out_cap, + // Return values. + ArcDelay &margin) const = 0; virtual void reportCheckDelay(const LibertyCell *cell, const Pvt *pvt, float from_slew, diff --git a/liberty/Units.cc b/liberty/Units.cc index b7b32177..45f102eb 100644 --- a/liberty/Units.cc +++ b/liberty/Units.cc @@ -76,6 +76,12 @@ Unit::setDigits(int digits) digits_ = digits; } +int +Unit::width() const +{ + return digits_ + (suffix_ ? strlen(suffix_) : 0) + 2; +} + const char * Unit::asString(float value) const { diff --git a/liberty/Units.hh b/liberty/Units.hh index 735a8489..2d43d60f 100644 --- a/liberty/Units.hh +++ b/liberty/Units.hh @@ -36,6 +36,7 @@ public: void setSuffix(const char *suffix); int digits() const { return digits_; } void setDigits(int digits); + int width() const; const char *asString(float value) const; const char *asString(double value) const; const char *asString(float value, diff --git a/parasitics/ConcreteParasitics.cc b/parasitics/ConcreteParasitics.cc index 8963dcc6..b542d43c 100644 --- a/parasitics/ConcreteParasitics.cc +++ b/parasitics/ConcreteParasitics.cc @@ -697,8 +697,9 @@ ConcreteCouplingCapExtPin::replaceNode(ConcreteParasiticNode *from_node, //////////////////////////////////////////////////////////////// -ConcreteParasiticNetwork::ConcreteParasiticNetwork() : - max_node_id_(0) +ConcreteParasiticNetwork::ConcreteParasiticNetwork(bool includes_pin_caps) : + max_node_id_(0), + includes_pin_caps_(includes_pin_caps) { } @@ -783,7 +784,7 @@ ConcreteParasiticNetwork::ensureParasiticNode(Net *net, if (node == NULL) { node = new ConcreteParasiticSubNode(net, id); sub_nodes_[new NetId(net, id)] = node; - max_node_id_ = max(max_node_id_, id); + max_node_id_ = max((int) max_node_id_, id); } return node; } @@ -1653,6 +1654,7 @@ ConcreteParasitics::findParasiticNetwork(const Pin *pin, Parasitic * ConcreteParasitics::makeParasiticNetwork(Net *net, + bool includes_pin_caps, const ParasiticAnalysisPt *ap) { int ap_index = parasiticNetworkAnalysisPtIndex(ap); @@ -1674,7 +1676,7 @@ ConcreteParasitics::makeParasiticNetwork(Net *net, ConcreteParasiticNetwork *parasitic = (*parasitic_network_maps_)[ap_index]->findKey(net); if (parasitic == NULL) { - parasitic = new ConcreteParasiticNetwork; + parasitic = new ConcreteParasiticNetwork(includes_pin_caps); (*(*parasitic_network_maps_)[ap_index])[net] = parasitic; } lock_.unlock(); @@ -1698,6 +1700,14 @@ ConcreteParasitics::deleteParasiticNetwork(const Net *net, } } +bool +ConcreteParasitics::includesPinCaps(Parasitic *parasitic) const +{ + ConcreteParasiticNetwork *cparasitic = + static_cast(parasitic); + return cparasitic->includesPinCaps(); +} + ParasiticNode * ConcreteParasitics::ensureParasiticNode(Parasitic *parasitic, Net *net, @@ -1970,7 +1980,8 @@ ConcreteParasitics::reduceToPiPoleResidue2(Parasitic *parasitic, const MinMax *cnst_min_max, const ParasiticAnalysisPt *ap) { - return sta::reduceToPiPoleResidue2(parasitic, drvr_pin,ap->couplingCapFactor(), + return sta::reduceToPiPoleResidue2(parasitic, drvr_pin, + ap->couplingCapFactor(), tr, op_cond, corner, cnst_min_max, ap, this); } diff --git a/parasitics/ConcreteParasitics.hh b/parasitics/ConcreteParasitics.hh index 8d52deb1..8a1b9949 100644 --- a/parasitics/ConcreteParasitics.hh +++ b/parasitics/ConcreteParasitics.hh @@ -136,9 +136,11 @@ public: virtual Parasitic *findParasiticNetwork(const Pin *pin, const ParasiticAnalysisPt *ap) const; virtual Parasitic *makeParasiticNetwork(Net *net, + bool includes_pin_caps, const ParasiticAnalysisPt *ap); virtual void deleteParasiticNetwork(const Net *net, const ParasiticAnalysisPt *ap); + virtual bool includesPinCaps(Parasitic *parasitic) const; virtual ParasiticNode *ensureParasiticNode(Parasitic *parasitic, Net *net, int id); virtual ParasiticNode *ensureParasiticNode(Parasitic *parasitic, @@ -194,7 +196,8 @@ public: const Corner *corner, const MinMax *cnst_min_max, const ParasiticAnalysisPt *ap); - virtual void reduceToPiElmore(Parasitic *parasitic, const Net *net, + virtual void reduceToPiElmore(Parasitic *parasitic, + const Net *net, const TransRiseFall *tr, const OperatingConditions *op_cond, const Corner *corner, @@ -207,7 +210,8 @@ public: const Corner *corner, const MinMax *cnst_min_max, const ParasiticAnalysisPt *ap); - virtual void reduceToPiPoleResidue2(Parasitic *parasitic, const Net *net, + virtual void reduceToPiPoleResidue2(Parasitic *parasitic, + const Net *net, const TransRiseFall *tr, const OperatingConditions *op_cond, const Corner *corner, diff --git a/parasitics/ConcreteParasiticsPvt.hh b/parasitics/ConcreteParasiticsPvt.hh index 97348229..f92a6c47 100644 --- a/parasitics/ConcreteParasiticsPvt.hh +++ b/parasitics/ConcreteParasiticsPvt.hh @@ -403,9 +403,10 @@ class ConcreteParasiticNetwork : public ParasiticNetwork, public ConcreteParasitic { public: - ConcreteParasiticNetwork(); + ConcreteParasiticNetwork(bool includes_pin_caps); virtual ~ConcreteParasiticNetwork(); virtual bool isParasiticNetwork() const { return true; } + bool includesPinCaps() const { return includes_pin_caps_; } ConcreteParasiticNode *ensureParasiticNode(Net *net, int id); ConcreteParasiticNode *findNode(const Pin *pin); @@ -422,7 +423,8 @@ private: ConcreteParasiticSubNodeMap sub_nodes_; ConcreteParasiticPinNodeMap pin_nodes_; - int max_node_id_; + unsigned max_node_id_:31; + bool includes_pin_caps_:1; }; } // namespace diff --git a/parasitics/NullParasitics.cc b/parasitics/NullParasitics.cc index e0ff3746..ef7c56f3 100644 --- a/parasitics/NullParasitics.cc +++ b/parasitics/NullParasitics.cc @@ -45,7 +45,8 @@ NullParasitics::deleteParasitics() } void -NullParasitics::deleteParasitics(const Net *, const ParasiticAnalysisPt *) +NullParasitics::deleteParasitics(const Net *, + const ParasiticAnalysisPt *) { } @@ -69,7 +70,8 @@ NullParasitics::capacitance(Parasitic *) const } bool -NullParasitics::hasLumpedElmore(const Pin *, const TransRiseFall *, +NullParasitics::hasLumpedElmore(const Pin *, + const TransRiseFall *, const ParasiticAnalysisPt *) const { return false; @@ -90,7 +92,8 @@ NullParasitics::isLumpedElmore(Parasitic *) const } Parasitic * -NullParasitics::makeLumpedElmore(const Pin *, float, +NullParasitics::makeLumpedElmore(const Pin *, + float, const TransRiseFall *, const ParasiticAnalysisPt *) { @@ -124,7 +127,9 @@ Parasitic * NullParasitics::makePiElmore(const Pin *, const TransRiseFall *, const ParasiticAnalysisPt *, - float, float, float) + float, + float, + float) { return NULL; } @@ -135,7 +140,8 @@ NullParasitics::deletePiElmore(const Pin *) } void -NullParasitics::deletePiElmore(const Pin *, const TransRiseFall *, +NullParasitics::deletePiElmore(const Pin *, + const TransRiseFall *, const ParasiticAnalysisPt *) { } @@ -153,28 +159,39 @@ NullParasitics::isReducedParasiticNetwork(Parasitic *) const } void -NullParasitics::setIsReducedParasiticNetwork(Parasitic *, bool) +NullParasitics::setIsReducedParasiticNetwork(Parasitic *, + bool) { } void -NullParasitics::piModel(Parasitic *, float &, float &, float &) const +NullParasitics::piModel(Parasitic *, + float &, + float &, + float &) const { } void -NullParasitics::setPiModel(Parasitic *, float, float, float) +NullParasitics::setPiModel(Parasitic *, + float, + float, + float) { } void -NullParasitics::findElmore(Parasitic *, const Pin *, - float &, bool &) const +NullParasitics::findElmore(Parasitic *, + const Pin *, + float &, + bool &) const { } void -NullParasitics::setElmore(Parasitic *, const Pin *, float) +NullParasitics::setElmore(Parasitic *, + const Pin *, + float) { } @@ -210,7 +227,9 @@ Parasitic * NullParasitics::makePiPoleResidue(const Pin *, const TransRiseFall *, const ParasiticAnalysisPt *, - float, float, float) + float, + float, + float) { return NULL; } @@ -223,8 +242,10 @@ NullParasitics::findPoleResidue(const Parasitic *, } void -NullParasitics::setPoleResidue(Parasitic *, const Pin *, - ComplexFloatSeq *, ComplexFloatSeq *) +NullParasitics::setPoleResidue(Parasitic *, + const Pin *, + ComplexFloatSeq *, + ComplexFloatSeq *) { } @@ -241,8 +262,10 @@ NullParasitics::poleResidueCount(const Parasitic *) const } void -NullParasitics::poleResidue(const Parasitic *, int, - ComplexFloat &, ComplexFloat &) const +NullParasitics::poleResidue(const Parasitic *, + int, + ComplexFloat &, + ComplexFloat &) const { } @@ -267,11 +290,19 @@ NullParasitics::isParasiticNetwork(Parasitic *) const } Parasitic * -NullParasitics::makeParasiticNetwork(Net *, const ParasiticAnalysisPt *) +NullParasitics::makeParasiticNetwork(Net *, + bool, + const ParasiticAnalysisPt *) { return NULL; } +bool +NullParasitics::includesPinCaps(Parasitic *) const +{ + return false; +} + void NullParasitics::deleteParasiticNetwork(const Net *, const ParasiticAnalysisPt *) @@ -279,44 +310,59 @@ NullParasitics::deleteParasiticNetwork(const Net *, } ParasiticNode * -NullParasitics::ensureParasiticNode(Parasitic *, Net *, int) +NullParasitics::ensureParasiticNode(Parasitic *, + Net *, + int) { return NULL; } ParasiticNode * -NullParasitics::ensureParasiticNode(Parasitic *, const Pin *) +NullParasitics::ensureParasiticNode(Parasitic *, + const Pin *) { return NULL; } void -NullParasitics::incrCap(ParasiticNode *, float, +NullParasitics::incrCap(ParasiticNode *, + float, const ParasiticAnalysisPt *) { } void -NullParasitics::makeCouplingCap(const char *, ParasiticNode *, - ParasiticNode *, float, +NullParasitics::makeCouplingCap(const char *, + ParasiticNode *, + ParasiticNode *, + float, const ParasiticAnalysisPt *) { } -void NullParasitics::makeCouplingCap(const char *, ParasiticNode *, Net *, - int, float, const ParasiticAnalysisPt *) +void NullParasitics::makeCouplingCap(const char *, + ParasiticNode *, + Net *, + int, + float, + const ParasiticAnalysisPt *) { } void -NullParasitics::makeCouplingCap(const char *, ParasiticNode *, Pin *, - float, const ParasiticAnalysisPt *) +NullParasitics::makeCouplingCap(const char *, + ParasiticNode *, + Pin *, + float, + const ParasiticAnalysisPt *) { } void -NullParasitics::makeResistor(const char *, ParasiticNode *, - ParasiticNode *, float, +NullParasitics::makeResistor(const char *, + ParasiticNode *, + ParasiticNode *, + float, const ParasiticAnalysisPt *) { } @@ -334,7 +380,8 @@ NullParasitics::connectionPin(const ParasiticNode *) const } ParasiticNode * -NullParasitics::findNode(Parasitic *, const Pin *) const +NullParasitics::findNode(Parasitic *, + const Pin *) const { return NULL; } @@ -378,13 +425,15 @@ NullParasitics::value(const ParasiticDevice *, } ParasiticNode * -NullParasitics::otherNode(const ParasiticDevice *, ParasiticNode *) const +NullParasitics::otherNode(const ParasiticDevice *, + ParasiticNode *) const { return NULL; } void -NullParasitics::reduceTo(Parasitic *, const Net *, +NullParasitics::reduceTo(Parasitic *, + const Net *, ReduceParasiticsTo , const TransRiseFall *, const OperatingConditions *, @@ -395,7 +444,8 @@ NullParasitics::reduceTo(Parasitic *, const Net *, } void -NullParasitics::reduceToPiElmore(Parasitic *, const Net *, +NullParasitics::reduceToPiElmore(Parasitic *, + const Net *, const TransRiseFall *, const OperatingConditions *, const Corner *, @@ -418,11 +468,11 @@ NullParasitics::reduceToPiElmore(Parasitic *, void NullParasitics::reduceToPiPoleResidue2(Parasitic *, const Net *, - const TransRiseFall *, - const OperatingConditions *, + const TransRiseFall *, + const OperatingConditions *, const Corner *, - const MinMax *, - const ParasiticAnalysisPt *) + const MinMax *, + const ParasiticAnalysisPt *) { } diff --git a/parasitics/NullParasitics.hh b/parasitics/NullParasitics.hh index 9827a91f..7b826a1d 100644 --- a/parasitics/NullParasitics.hh +++ b/parasitics/NullParasitics.hh @@ -124,7 +124,9 @@ public: virtual bool isParasiticNetwork(Parasitic *parasitic) const; virtual Parasitic * makeParasiticNetwork(Net *net, + bool pin_cap_included, const ParasiticAnalysisPt *ap); + virtual bool includesPinCaps(Parasitic *parasitic) const; virtual void deleteParasiticNetwork(const Net *net, const ParasiticAnalysisPt *ap); virtual ParasiticNode *ensureParasiticNode(Parasitic *parasitic, @@ -165,14 +167,16 @@ public: virtual ParasiticNode *otherNode(const ParasiticDevice *device, ParasiticNode *node) const; // Reduce parasitic network to reduce_to model. - virtual void reduceTo(Parasitic *parasitic, const Net *net, + virtual void reduceTo(Parasitic *parasitic, + const Net *net, ReduceParasiticsTo reduce_to, const TransRiseFall *tr, const OperatingConditions *op_cond, const Corner *corner, const MinMax *cnst_min_max, const ParasiticAnalysisPt *ap); - virtual void reduceToPiElmore(Parasitic *parasitic, const Net *net, + virtual void reduceToPiElmore(Parasitic *parasitic, + const Net *net, const TransRiseFall *tr, const OperatingConditions *op_cond, const Corner *corner, @@ -186,7 +190,8 @@ public: const Corner *corner, const MinMax *cnst_min_max, const ParasiticAnalysisPt *ap); - virtual void reduceToPiPoleResidue2(Parasitic *parasitic, const Net *net, + virtual void reduceToPiPoleResidue2(Parasitic *parasitic, + const Net *net, const TransRiseFall *tr, const OperatingConditions *op_cond, const Corner *corner, diff --git a/parasitics/Parasitics.cc b/parasitics/Parasitics.cc index 0e7db972..d58b62e3 100644 --- a/parasitics/Parasitics.cc +++ b/parasitics/Parasitics.cc @@ -93,7 +93,7 @@ Parasitics::makeWireloadNetwork(const Pin *drvr_pin, const ParasiticAnalysisPt *ap) { Net *net = network_->net(drvr_pin); - Parasitic *parasitic = makeParasiticNetwork(net, ap); + Parasitic *parasitic = makeParasiticNetwork(net, false, ap); float wireload_cap, wireload_res; wireload->findWireload(fanout, op_cond, wireload_cap, wireload_res); diff --git a/parasitics/Parasitics.hh b/parasitics/Parasitics.hh index acf9ce2d..95561550 100644 --- a/parasitics/Parasitics.hh +++ b/parasitics/Parasitics.hh @@ -182,10 +182,13 @@ public: virtual Parasitic *findParasiticNetwork(const Pin *pin, const ParasiticAnalysisPt *ap) const = 0; virtual Parasitic *makeParasiticNetwork(Net *net, + bool pin_cap_included, const ParasiticAnalysisPt *ap) = 0; // Delete parasitic network if it exists. virtual void deleteParasiticNetwork(const Net *net, const ParasiticAnalysisPt *ap) = 0; + // True if the parasitic network caps include pin capacitances. + virtual bool includesPinCaps(Parasitic *parasitic) const = 0; // Parasitic network component builders. // Make a subnode of the parasitic network net. virtual ParasiticNode *ensureParasiticNode(Parasitic *parasitic, diff --git a/parasitics/Parasitics.i b/parasitics/Parasitics.i index 12c84603..8e5f6ca0 100644 --- a/parasitics/Parasitics.i +++ b/parasitics/Parasitics.i @@ -38,6 +38,7 @@ read_parasitics_cmd(const char *filename, Instance *instance, MinMaxAll *min_max, bool increment, + bool pin_cap_included, bool keep_coupling_caps, float coupling_cap_factor, ReduceParasiticsTo reduce_to, @@ -47,9 +48,9 @@ read_parasitics_cmd(const char *filename, { cmdLinkedNetwork(); return Sta::sta()->readParasitics(filename, instance, min_max, - increment, keep_coupling_caps, - coupling_cap_factor, reduce_to, - delete_after_reduce, + increment, pin_cap_included, + keep_coupling_caps, coupling_cap_factor, + reduce_to, delete_after_reduce, save, quiet); } diff --git a/parasitics/Parasitics.tcl b/parasitics/Parasitics.tcl index 25efb09d..69e5740e 100644 --- a/parasitics/Parasitics.tcl +++ b/parasitics/Parasitics.tcl @@ -17,16 +17,26 @@ namespace eval sta { define_cmd_args "read_parasitics" \ - {[-min] [-max] [-elmore] [-path path] [-increment]\ - [-keep_capacitive_coupling] [-coupling_reduction_factor factor]\ - [-reduce_to pi_elmore|pi_pole_residue2] [-delete_after_reduce]\ - [-quiet] [-save] filenames} + {[-min]\ + [-max]\ + [-elmore]\ + [-path path]\ + [-increment]\ + [-pin_cap_included]\ + [-keep_capacitive_coupling]\ + [-coupling_reduction_factor factor]\ + [-reduce_to pi_elmore|pi_pole_residue2]\ + [-delete_after_reduce]\ + [-quiet]\ + [-save]\ + filename} proc_redirect read_parasitics { # The -elmore flag is required by dc. parse_key_args "read_parasitics" args \ keys {-path -coupling_reduction_factor -reduce_to} \ - flags {-min -max -elmore -increment -keep_capacitive_coupling \ + flags {-min -max -elmore -increment -pin_cap_included \ + -keep_capacitive_coupling \ -delete_after_reduce -quiet -save} check_argc_eq1 "report_parasitics" $args @@ -46,6 +56,8 @@ proc_redirect read_parasitics { check_positive_float "-coupling_reduction_factor" $coupling_reduction_factor } set keep_coupling_caps [info exists flags(-keep_capacitive_coupling)] + set pin_cap_included [info exists flags(-pin_cap_included)] + set reduce_to "none" if [info exists keys(-reduce_to)] { set reduce_to $keys(-reduce_to) @@ -56,17 +68,11 @@ proc_redirect read_parasitics { set delete_after_reduce [info exists flags(-delete_after_reduce)] set quiet [info exists flags(-quiet)] set save [info exists flags(-save)] - set filenames $args - set success 1 - foreach filename $filenames { - if { ![read_parasitics_cmd $filename $instance $min_max $increment \ - $keep_coupling_caps $coupling_reduction_factor \ - $reduce_to $delete_after_reduce \ - $save $quiet] } { - set success 0 - } - } - return $success + set filename $args + return [read_parasitics_cmd $filename $instance $min_max $increment \ + $pin_cap_included $keep_coupling_caps $coupling_reduction_factor \ + $reduce_to $delete_after_reduce \ + $save $quiet] } # set_pi_model [-min] [-max] drvr_pin c2 rpi c1 diff --git a/parasitics/ReadParasitics.cc b/parasitics/ReadParasitics.cc index 3c9355f7..c3d361af 100644 --- a/parasitics/ReadParasitics.cc +++ b/parasitics/ReadParasitics.cc @@ -50,6 +50,7 @@ readParasiticsFile(const char *filename, Instance *instance, ParasiticAnalysisPt *ap, bool increment, + bool pin_cap_included, bool keep_coupling_caps, float coupling_cap_factor, ReduceParasiticsTo reduce_to, @@ -72,7 +73,7 @@ readParasiticsFile(const char *filename, switch (file_type) { case parasitics_file_spef: success = readSpefFile(filename, stream, line_num, - instance, ap, increment, + instance, ap, increment, pin_cap_included, keep_coupling_caps, coupling_cap_factor, reduce_to, delete_after_reduce, op_cond, corner, cnst_min_max, @@ -81,14 +82,16 @@ readParasiticsFile(const char *filename, break; case parasitics_file_rspf: success = readSpfFile(filename, stream, line_num, true, instance, ap, - increment, keep_coupling_caps, coupling_cap_factor, + increment, pin_cap_included, + keep_coupling_caps, coupling_cap_factor, reduce_to, delete_after_reduce, op_cond, corner, cnst_min_max, save, quiet, report, network, parasitics); break; case parasitics_file_dspf: success = readSpfFile(filename, stream, line_num, false, instance, ap, - increment, keep_coupling_caps, coupling_cap_factor, + increment, pin_cap_included, + keep_coupling_caps, coupling_cap_factor, reduce_to, delete_after_reduce, op_cond, corner, cnst_min_max, save, quiet, report, diff --git a/parasitics/ReadParasitics.hh b/parasitics/ReadParasitics.hh index 399002cc..f562eaa8 100644 --- a/parasitics/ReadParasitics.hh +++ b/parasitics/ReadParasitics.hh @@ -34,6 +34,7 @@ readParasiticsFile(const char *filename, Instance *instance, ParasiticAnalysisPt *ap, bool increment, + bool pin_cap_included, bool keep_coupling_caps, float coupling_cap_factor, ReduceParasiticsTo reduce_to, diff --git a/parasitics/ReduceParasitics.cc b/parasitics/ReduceParasitics.cc index 0e2a8493..a36fc034 100644 --- a/parasitics/ReduceParasitics.cc +++ b/parasitics/ReduceParasitics.cc @@ -34,29 +34,39 @@ class ReduceToPi : public StaState { public: ReduceToPi(StaState *sta); - void reduceToPi(const Pin *drvr_pin, ParasiticNode *drvr_node, + void reduceToPi(const Pin *drvr_pin, + ParasiticNode *drvr_node, + bool pin_cap_included, float coupling_cap_factor, const TransRiseFall *tr, const OperatingConditions *op_cond, const Corner *corner, const MinMax *cnst_min_max, const ParasiticAnalysisPt *ap, - float &c2, float &rpi, float &c1); + float &c2, + float &rpi, + float &c1); protected: - void reducePiDfs(const Pin *drvr_pin, ParasiticNode *node, + void reducePiDfs(const Pin *drvr_pin, + ParasiticNode *node, ParasiticDevice *from_res, const ParasiticAnalysisPt *ap, - double &y1, double &y2, double &y3, double &dwn_cap); + double &y1, + double &y2, + double &y3, + double &dwn_cap); void visit(ParasiticNode *node); bool isVisited(ParasiticNode *node); void leave(ParasiticNode *node); - void setDownstreamCap(ParasiticNode *node, float cap); + void setDownstreamCap(ParasiticNode *node, + float cap); float downstreamCap(ParasiticNode *node); float pinCapacitance(ParasiticNode *node); bool isLoopResistor(ParasiticDevice *device); void markLoopResistor(ParasiticDevice *device); + bool pin_cap_included_; float coupling_cap_multiplier_; const TransRiseFall *tr_; const OperatingConditions *op_cond_; @@ -82,15 +92,20 @@ ReduceToPi::ReduceToPi(StaState *sta) : // Thomas Savarino, Proceedings of the 1989 Design Automation // Conference. void -ReduceToPi::reduceToPi(const Pin *drvr_pin, ParasiticNode *drvr_node, +ReduceToPi::reduceToPi(const Pin *drvr_pin, + ParasiticNode *drvr_node, + bool pin_cap_included, float coupling_cap_factor, const TransRiseFall *tr, const OperatingConditions *op_cond, const Corner *corner, const MinMax *cnst_min_max, const ParasiticAnalysisPt *ap, - float &c2, float &rpi, float &c1) + float &c2, + float &rpi, + float &c1) { + pin_cap_included_ = pin_cap_included; coupling_cap_multiplier_ = coupling_cap_factor; tr_ = tr; op_cond_ = op_cond; @@ -190,9 +205,11 @@ ReduceToPi::pinCapacitance(ParasiticNode *node) if (pin) { Port *port = network_->port(pin); LibertyPort *lib_port = network_->libertyPort(port); - if (lib_port) - pin_cap = sdc_->pinCapacitance(pin,tr_, op_cond_, - corner_, cnst_min_max_); + if (lib_port) { + if (!pin_cap_included_) + pin_cap = sdc_->pinCapacitance(pin,tr_, op_cond_, corner_, + cnst_min_max_); + } else if (network_->isTopLevelPort(pin)) pin_cap = sdc_->portExtCap(port, tr_, cnst_min_max_); } @@ -230,7 +247,8 @@ ReduceToPi::markLoopResistor(ParasiticDevice *device) } void -ReduceToPi::setDownstreamCap(ParasiticNode *node, float cap) +ReduceToPi::setDownstreamCap(ParasiticNode *node, + float cap) { node_values_[node] = cap; } @@ -261,7 +279,8 @@ ReduceToPiElmore::ReduceToPiElmore(StaState *sta) : } Parasitic * -reduceToPiElmore(Parasitic *parasitic, const Pin *drvr_pin, +reduceToPiElmore(Parasitic *parasitic, + const Pin *drvr_pin, float coupling_cap_factor, const TransRiseFall *tr, const OperatingConditions *op_cond, @@ -278,7 +297,9 @@ reduceToPiElmore(Parasitic *parasitic, const Pin *drvr_pin, if (drvr_node) { ReduceToPiElmore reducer(sta); float c2, rpi, c1; - reducer.reduceToPi(drvr_pin, drvr_node, coupling_cap_factor, + reducer.reduceToPi(drvr_pin, drvr_node, + parasitics->includesPinCaps(parasitic), + coupling_cap_factor, tr, op_cond, corner, cnst_min_max, ap, c2, rpi, c1); Parasitic *pi_elmore = parasitics->makePiElmore(drvr_pin, tr, ap, @@ -339,25 +360,39 @@ public: ReduceToPiPoleResidue2(StaState *sta); ~ReduceToPiPoleResidue2(); void findPolesResidues(Parasitic *parasitic_network, - Parasitic *pi_pole_residue, const Pin *drvr_pin, + Parasitic *pi_pole_residue, + const Pin *drvr_pin, ParasiticNode *drvr_node, const ParasiticAnalysisPt *ap); private: - void findMoments(const Pin *drvr_pin, ParasiticNode *drvr_node, - int moment_count, const ParasiticAnalysisPt *ap); - void findMoments(const Pin *drvr_pin, ParasiticNode *node, double from_volt, - ParasiticDevice *from_res, int moment_index, + void findMoments(const Pin *drvr_pin, + ParasiticNode *drvr_node, + int moment_count, const ParasiticAnalysisPt *ap); - double findBranchCurrents(const Pin *drvr_pin, ParasiticNode *node, + void findMoments(const Pin *drvr_pin, + ParasiticNode *node, + double from_volt, + ParasiticDevice *from_res, + int moment_index, + const ParasiticAnalysisPt *ap); + double findBranchCurrents(const Pin *drvr_pin, + ParasiticNode *node, ParasiticDevice *from_res, - int moment_index, const ParasiticAnalysisPt *ap); - double moment(ParasiticNode *node, int moment_index); - void setMoment(ParasiticNode *node, double moment, int moment_index); + int moment_index, + const ParasiticAnalysisPt *ap); + double moment(ParasiticNode *node, + int moment_index); + void setMoment(ParasiticNode *node, + double moment, + int moment_index); double current(ParasiticDevice *res); - void setCurrent(ParasiticDevice *res, double i); - void findPolesResidues(Parasitic *pi_pole_residue, const Pin *drvr_pin, - const Pin *load_pin, ParasiticNode *load_node); + void setCurrent(ParasiticDevice *res, + double i); + void findPolesResidues(Parasitic *pi_pole_residue, + const Pin *drvr_pin, + const Pin *load_pin, + ParasiticNode *load_node); // Resistor/capacitor currents. ParasiticDeviceValueMap currents_; @@ -381,7 +416,8 @@ ReduceToPiPoleResidue2::ReduceToPiPoleResidue2(StaState *sta) : // Three Moments of the Impulse Response", Proceedings of the 33rd // Design Automation Conference, 1996, pg 611-616. Parasitic * -reduceToPiPoleResidue2(Parasitic *parasitic, const Pin *drvr_pin, +reduceToPiPoleResidue2(Parasitic *parasitic, + const Pin *drvr_pin, float coupling_cap_factor, const TransRiseFall *tr, const OperatingConditions *op_cond, @@ -398,7 +434,9 @@ reduceToPiPoleResidue2(Parasitic *parasitic, const Pin *drvr_pin, if (drvr_node) { ReduceToPiPoleResidue2 reducer(sta); float c2, rpi, c1; - reducer.reduceToPi(drvr_pin, drvr_node, coupling_cap_factor, + reducer.reduceToPi(drvr_pin, drvr_node, + parasitics->includesPinCaps(parasitic), + coupling_cap_factor, tr, op_cond, corner, cnst_min_max, ap, c2, rpi, c1); Parasitic *pi_pole_residue = parasitics->makePiPoleResidue(drvr_pin, @@ -504,7 +542,8 @@ ReduceToPiPoleResidue2::findBranchCurrents(const Pin *drvr_pin, } void -ReduceToPiPoleResidue2::findMoments(const Pin *drvr_pin, ParasiticNode *node, +ReduceToPiPoleResidue2::findMoments(const Pin *drvr_pin, + ParasiticNode *node, double from_volt, ParasiticDevice *from_res, int moment_index, @@ -540,7 +579,8 @@ ReduceToPiPoleResidue2::findMoments(const Pin *drvr_pin, ParasiticNode *node, } double -ReduceToPiPoleResidue2::moment(ParasiticNode *node, int moment_index) +ReduceToPiPoleResidue2::moment(ParasiticNode *node, + int moment_index) { // Zero'th moments are all 1. if (moment_index == 0) @@ -552,7 +592,8 @@ ReduceToPiPoleResidue2::moment(ParasiticNode *node, int moment_index) } void -ReduceToPiPoleResidue2::setMoment(ParasiticNode *node, double moment, +ReduceToPiPoleResidue2::setMoment(ParasiticNode *node, + double moment, int moment_index) { // Zero'th moments are all 1. @@ -569,7 +610,8 @@ ReduceToPiPoleResidue2::current(ParasiticDevice *res) } void -ReduceToPiPoleResidue2::setCurrent(ParasiticDevice *res, double i) +ReduceToPiPoleResidue2::setCurrent(ParasiticDevice *res, + double i) { currents_[res] = i; } diff --git a/parasitics/ReduceParasitics.hh b/parasitics/ReduceParasitics.hh index 24dddd9f..69ba8f18 100644 --- a/parasitics/ReduceParasitics.hh +++ b/parasitics/ReduceParasitics.hh @@ -25,7 +25,8 @@ class StaState; // Reduce parasitic network to pi elmore model for drvr_pin. Parasitic * -reduceToPiElmore(Parasitic *parasitic, const Pin *drvr_pin, +reduceToPiElmore(Parasitic *parasitic, + const Pin *drvr_pin, float coupling_cap_factor, const TransRiseFall *tr, const OperatingConditions *op_cond, @@ -37,7 +38,8 @@ reduceToPiElmore(Parasitic *parasitic, const Pin *drvr_pin, // Reduce parasitic network to pi and 2nd order pole/residue models // for drvr_pin. Parasitic * -reduceToPiPoleResidue2(Parasitic *parasitic, const Pin *drvr_pin, +reduceToPiPoleResidue2(Parasitic *parasitic, + const Pin *drvr_pin, float coupling_cap_factor, const TransRiseFall *tr, const OperatingConditions *op_cond, diff --git a/parasitics/SpefReader.cc b/parasitics/SpefReader.cc index a706d8d2..533a6411 100644 --- a/parasitics/SpefReader.cc +++ b/parasitics/SpefReader.cc @@ -43,6 +43,7 @@ readSpefFile(const char *filename, Instance *instance, ParasiticAnalysisPt *ap, bool increment, + bool pin_cap_included, bool keep_coupling_caps, float coupling_cap_factor, ReduceParasiticsTo reduce_to, @@ -57,7 +58,7 @@ readSpefFile(const char *filename, Parasitics *parasitics) { SpefReader reader(filename, stream, line, instance, ap, increment, - keep_coupling_caps, coupling_cap_factor, + pin_cap_included, keep_coupling_caps, coupling_cap_factor, reduce_to, delete_after_reduce, op_cond, corner, cnst_min_max, quiet, report, network, parasitics); spef_reader = &reader; @@ -75,6 +76,7 @@ SpefReader::SpefReader(const char *filename, Instance *instance, ParasiticAnalysisPt *ap, bool increment, + bool pin_cap_included, bool keep_coupling_caps, float coupling_cap_factor, ReduceParasiticsTo reduce_to, @@ -86,7 +88,8 @@ SpefReader::SpefReader(const char *filename, Report *report, Network *network, Parasitics *parasitics) : - SpfSpefReader(filename, stream, line, instance, ap, increment, + SpfSpefReader(filename, stream, line, instance, ap, + increment, pin_cap_included, keep_coupling_caps, coupling_cap_factor, reduce_to, delete_after_reduce, op_cond, corner, cnst_min_max, quiet, @@ -339,7 +342,9 @@ SpefReader::dspfBegin(Net *net, parasitic_ = 0; else { parasitics_->deleteParasitics(net, ap_); - parasitic_ = parasitics_->makeParasiticNetwork(net, ap_); + parasitic_ = parasitics_->makeParasiticNetwork(net, pin_cap_included_, + ap_); + } net_ = net; } @@ -361,8 +366,8 @@ SpefReader::dspfFinish() TransRiseFallIterator tr_iter; while (tr_iter.hasNext()) { TransRiseFall *tr = tr_iter.next(); - parasitics_->reduceTo(parasitic_, net_, reduce_to_, tr, op_cond_, - corner_, cnst_min_max_, ap_); + parasitics_->reduceTo(parasitic_, net_, reduce_to_, tr, + op_cond_, corner_, cnst_min_max_, ap_); } if (delete_after_reduce_) parasitics_->deleteParasiticNetwork(net_, ap_); diff --git a/parasitics/SpefReader.hh b/parasitics/SpefReader.hh index 1090beaf..2466d0de 100644 --- a/parasitics/SpefReader.hh +++ b/parasitics/SpefReader.hh @@ -38,6 +38,7 @@ readSpefFile(const char *filename, Instance *instance, ParasiticAnalysisPt *ap, bool increment, + bool pin_cap_included, bool keep_coupling_caps, float coupling_cap_factor, ReduceParasiticsTo reduce_to, diff --git a/parasitics/SpefReaderPvt.hh b/parasitics/SpefReaderPvt.hh index 88102d5f..5956ca3b 100644 --- a/parasitics/SpefReaderPvt.hh +++ b/parasitics/SpefReaderPvt.hh @@ -52,6 +52,7 @@ public: Instance *instance, ParasiticAnalysisPt *ap, bool increment, + bool pin_cap_included, bool keep_coupling_caps, float coupling_cap_factor, ReduceParasiticsTo reduce_to, diff --git a/parasitics/SpfReader.cc b/parasitics/SpfReader.cc index 93696cc1..2d63f96e 100644 --- a/parasitics/SpfReader.cc +++ b/parasitics/SpfReader.cc @@ -44,6 +44,7 @@ readSpfFile(const char *filename, Instance *instance, ParasiticAnalysisPt *ap, bool increment, + bool pin_cap_included, bool keep_coupling_caps, float coupling_cap_factor, ReduceParasiticsTo reduce_to, @@ -58,9 +59,9 @@ readSpfFile(const char *filename, Parasitics *parasitics) { SpfReader reader(filename, stream, line, rspf, instance, ap, - increment, keep_coupling_caps, coupling_cap_factor, - reduce_to, delete_after_reduce, op_cond, - corner, cnst_min_max, quiet, + increment, pin_cap_included, keep_coupling_caps, + coupling_cap_factor, reduce_to, delete_after_reduce, + op_cond, corner, cnst_min_max, quiet, report, network, parasitics); spf_reader = &reader; ::spfResetScanner(); @@ -78,6 +79,7 @@ SpfReader::SpfReader(const char *filename, Instance *instance, ParasiticAnalysisPt *ap, bool increment, + bool pin_cap_included, bool keep_coupling_caps, float coupling_cap_factor, ReduceParasiticsTo reduce_to, @@ -90,7 +92,7 @@ SpfReader::SpfReader(const char *filename, Network *network, Parasitics *parasitics): SpfSpefReader(filename, stream, line, instance, ap, increment, - keep_coupling_caps, coupling_cap_factor, + pin_cap_included, keep_coupling_caps, coupling_cap_factor, reduce_to, delete_after_reduce, op_cond, corner, cnst_min_max, quiet, report, network, parasitics), is_rspf_(rspf), @@ -409,7 +411,7 @@ SpfReader::netBegin(const char *net_name) dspf_ = 0; else { parasitics_->deleteParasitics(net_, ap_); - dspf_ = parasitics_->makeParasiticNetwork(net_, ap_); + dspf_ = parasitics_->makeParasiticNetwork(net_,pin_cap_included_,ap_); } } else { diff --git a/parasitics/SpfReader.hh b/parasitics/SpfReader.hh index 84ecccf7..426d7f65 100644 --- a/parasitics/SpfReader.hh +++ b/parasitics/SpfReader.hh @@ -37,6 +37,7 @@ readSpfFile(const char *filename, Instance *instance, ParasiticAnalysisPt *ap, bool increment, + bool pin_cap_included, bool keep_coupling_caps, float coupling_cap_factor, ReduceParasiticsTo reduce_to, diff --git a/parasitics/SpfReaderPvt.hh b/parasitics/SpfReaderPvt.hh index 6df8eacb..988ea132 100644 --- a/parasitics/SpfReaderPvt.hh +++ b/parasitics/SpfReaderPvt.hh @@ -46,6 +46,7 @@ public: Instance *instance, ParasiticAnalysisPt *ap, bool increment, + bool pin_cap_included, bool keep_coupling_caps, float coupling_cap_factor, ReduceParasiticsTo reduce_to, diff --git a/parasitics/SpfSpefReader.cc b/parasitics/SpfSpefReader.cc index a925d472..3d11255f 100644 --- a/parasitics/SpfSpefReader.cc +++ b/parasitics/SpfSpefReader.cc @@ -28,10 +28,13 @@ namespace sta { -SpfSpefReader::SpfSpefReader(const char *filename, gzFile stream, int line, +SpfSpefReader::SpfSpefReader(const char *filename, + gzFile stream, + int line, Instance *instance, ParasiticAnalysisPt *ap, bool increment, + bool pin_cap_included, bool keep_coupling_caps, float coupling_cap_factor, ReduceParasiticsTo reduce_to, @@ -47,6 +50,7 @@ SpfSpefReader::SpfSpefReader(const char *filename, gzFile stream, int line, instance_(instance), ap_(ap), increment_(increment), + pin_cap_included_(pin_cap_included), keep_coupling_caps_(keep_coupling_caps), reduce_to_(reduce_to), delete_after_reduce_(delete_after_reduce), diff --git a/parasitics/SpfSpefReader.hh b/parasitics/SpfSpefReader.hh index ed45115a..eb92044d 100644 --- a/parasitics/SpfSpefReader.hh +++ b/parasitics/SpfSpefReader.hh @@ -40,6 +40,7 @@ public: Instance *instance, ParasiticAnalysisPt *ap, bool increment, + bool pin_cap_included, bool keep_coupling_caps, float coupling_cap_factor, ReduceParasiticsTo reduce_to, @@ -83,6 +84,7 @@ protected: Instance *instance_; const ParasiticAnalysisPt *ap_; bool increment_; + bool pin_cap_included_; bool keep_coupling_caps_; ReduceParasiticsTo reduce_to_; bool delete_after_reduce_; diff --git a/sdc/ClockInsertion.cc b/sdc/ClockInsertion.cc index bcdc7443..8778e969 100644 --- a/sdc/ClockInsertion.cc +++ b/sdc/ClockInsertion.cc @@ -30,7 +30,7 @@ ClockInsertion::setDelay(const TransRiseFallBoth *tr, const MinMaxAll *min_max, const EarlyLateAll *early_late, float delay) { - MinMaxIterator el_iter(early_late); + EarlyLateIterator el_iter(early_late); while (el_iter.hasNext()) { EarlyLate *el = el_iter.next(); delays_[el->index()].setValue(tr, min_max, delay); @@ -73,7 +73,7 @@ ClockInsertion::setDelay(const TransRiseFall *tr, void ClockInsertion::setDelays(RiseFallMinMax *delays) { - MinMaxIterator el_iter; + EarlyLateIterator el_iter; while (el_iter.hasNext()) { EarlyLate *el = el_iter.next(); delays_[el->index()].setValues(delays); diff --git a/sdc/Sdc.cc b/sdc/Sdc.cc index 8fd70e77..a625febb 100644 --- a/sdc/Sdc.cc +++ b/sdc/Sdc.cc @@ -102,6 +102,7 @@ Sdc::Sdc(StaState *sta) : clk_sense_map_(network_), clk_gating_check_(NULL), input_delay_index_(0), + port_cap_map_(NULL), net_wire_cap_map_(NULL), drvr_pin_wire_cap_map_(NULL), first_from_pin_exceptions_(NULL), @@ -314,12 +315,6 @@ Sdc::deleteConstraints() } } - delete net_wire_cap_map_; - net_wire_cap_map_ = NULL; - - delete drvr_pin_wire_cap_map_; - drvr_pin_wire_cap_map_ = NULL; - clk_hpin_disables_.deleteContentsClear(); clk_hpin_disables_valid_ = false; @@ -355,28 +350,22 @@ Sdc::deleteInstancePvts() void Sdc::removeNetLoadCaps() { - // set_load net - delete net_wire_cap_map_; + delete [] net_wire_cap_map_; net_wire_cap_map_ = NULL; - delete drvr_pin_wire_cap_map_; + delete [] drvr_pin_wire_cap_map_; drvr_pin_wire_cap_map_ = NULL; } void Sdc::removeLoadCaps() { - // set_load port - // set_fanout_load port - port_cap_map_.deleteContents(); - port_cap_map_.clear(); - - // set_load net - delete net_wire_cap_map_; - net_wire_cap_map_ = NULL; - - delete drvr_pin_wire_cap_map_; - drvr_pin_wire_cap_map_ = NULL; + if (port_cap_map_) { + port_cap_map_->deleteContents(); + delete port_cap_map_; + port_cap_map_ = NULL; + } + removeNetLoadCaps(); } void @@ -470,7 +459,7 @@ Sdc::isConstrained(const Pin *pin) const || pin_cap_limit_map_.hasKey(pin1) || port_cap_limit_map_.hasKey(port) || port_fanout_limit_map_.hasKey(port) - || port_cap_map_.hasKey(port) + || hasPortExtCap(port) || disabled_pins_.hasKey(pin1) || disabled_ports_.hasKey(port) || disabled_clk_gating_checks_pin_.hasKey(pin1) @@ -515,8 +504,7 @@ Sdc::isConstrained(const Net *net) const Net *net1 = const_cast(net); return (net_derating_factors_ && net_derating_factors_->hasKey(net)) - || (net_wire_cap_map_ - && net_wire_cap_map_->hasKey(net1)) + || hasNetWireCap(net1) || net_res_map_.hasKey(net1) || (first_thru_net_exceptions_ && first_thru_net_exceptions_->hasKey(net)); @@ -3256,9 +3244,9 @@ Sdc::deleteOutputDelay(OutputDelay *output_delay) void Sdc::setPortExtPinCap(Port *port, - const TransRiseFall *tr, - const MinMax *min_max, - float cap) + const TransRiseFall *tr, + const MinMax *min_max, + float cap) { PortExtCap *port_cap = ensurePortExtPinCap(port); port_cap->setPinCap(cap, tr, min_max); @@ -3286,7 +3274,19 @@ Sdc::setPortExtWireCap(Port *port, PortExtCap * Sdc::portExtCap(Port *port) const { - return port_cap_map_.findKey(port); + if (port_cap_map_) + return port_cap_map_->findKey(port); + else + return NULL; +} + +bool +Sdc::hasPortExtCap(Port *port) const +{ + if (port_cap_map_) + return port_cap_map_->hasKey(port); + else + return false; } void @@ -3301,20 +3301,21 @@ Sdc::portExtCap(Port *port, int &fanout, bool &has_fanout) const { - PortExtCap *port_cap = port_cap_map_.findKey(port); - if (port_cap) { - port_cap->pinCap(tr, min_max, pin_cap, has_pin_cap); - port_cap->wireCap(tr, min_max, wire_cap, has_wire_cap); - port_cap->fanout(min_max, fanout, has_fanout); - } - else { - pin_cap = 0.0F; - has_pin_cap = false; - wire_cap = 0.0F; - has_wire_cap = false; - fanout = 0.0F; - has_fanout = false; + if (port_cap_map_) { + PortExtCap *port_cap = port_cap_map_->findKey(port); + if (port_cap) { + port_cap->pinCap(tr, min_max, pin_cap, has_pin_cap); + port_cap->wireCap(tr, min_max, wire_cap, has_wire_cap); + port_cap->fanout(min_max, fanout, has_fanout); + return; + } } + pin_cap = 0.0F; + has_pin_cap = false; + wire_cap = 0.0F; + has_wire_cap = false; + fanout = 0.0F; + has_fanout = false; } float @@ -3346,6 +3347,7 @@ Sdc::drvrPinHasWireCap(const Pin *pin) void Sdc::drvrPinWireCap(const Pin *pin, + const Corner *corner, const MinMax *min_max, // Return values. float &cap, @@ -3353,7 +3355,7 @@ Sdc::drvrPinWireCap(const Pin *pin, { if (drvr_pin_wire_cap_map_) { MinMaxFloatValues *values = - drvr_pin_wire_cap_map_->findKey(const_cast(pin)); + drvr_pin_wire_cap_map_[corner->index()].findKey(const_cast(pin)); if (values) return values->value(min_max, cap, exists); } @@ -3386,20 +3388,36 @@ Sdc::setNetWireCap(Net *net, } } if (net_wire_cap_map_ == NULL) - net_wire_cap_map_ = new NetWireCapMap; - MinMaxFloatValues &values = (*net_wire_cap_map_)[net]; + net_wire_cap_map_ = new NetWireCapMap[corners_->count()]; + bool make_drvr_entry = net_wire_cap_map_[corner->index()].hasKey(net); + MinMaxFloatValues &values = net_wire_cap_map_[corner->index()][net]; values.setValue(min_max, wire_cap); - NetConnectedPinIterator *pin_iter = network_->connectedPinIterator(net); - while (pin_iter->hasNext()) { - Pin *pin = pin_iter->next(); - if (network_->isDriver(pin)) { - if (drvr_pin_wire_cap_map_ == NULL) - drvr_pin_wire_cap_map_ = new PinWireCapMap; - (*drvr_pin_wire_cap_map_)[pin] = &values; + // Only need to do this when there is new net_wire_cap_map_ entry. + if (make_drvr_entry) { + NetConnectedPinIterator *pin_iter = network_->connectedPinIterator(net); + while (pin_iter->hasNext()) { + Pin *pin = pin_iter->next(); + if (network_->isDriver(pin)) { + if (drvr_pin_wire_cap_map_ == NULL) + drvr_pin_wire_cap_map_ = new PinWireCapMap[corners_->count()]; + drvr_pin_wire_cap_map_[corner->index()][pin] = &values; + } + } + delete pin_iter; + } +} + +bool +Sdc::hasNetWireCap(Net *net) const +{ + if (net_wire_cap_map_) { + for (int i = 0; i < corners_->count(); i++) { + if (net_wire_cap_map_[i].hasKey(net)) + return true; } } - delete pin_iter; + return false; } //////////////////////////////////////////////////////////////// @@ -3420,7 +3438,7 @@ Sdc::connectedCap(const Pin *pin, pin_cap, wire_cap, fanout, has_set_load); float net_wire_cap; bool has_net_wire_cap; - drvrPinWireCap(pin, min_max, net_wire_cap, has_net_wire_cap); + drvrPinWireCap(pin, corner, min_max, net_wire_cap, has_net_wire_cap); if (has_net_wire_cap) { wire_cap += net_wire_cap; has_set_load = true; @@ -3496,7 +3514,7 @@ void FindNetCaps::operator()(Pin *pin) { sdc_->pinCaps(pin, tr_, op_cond_, corner_, min_max_, - pin_cap_, wire_cap_, fanout_, has_set_load_); + pin_cap_, wire_cap_, fanout_, has_set_load_); } // Capacitances for all pins connected to drvr_pin's net. @@ -3637,7 +3655,7 @@ Sdc::portExtFanout(Port *port, int &fanout, bool &exists) { - PortExtCap *port_cap = port_cap_map_.findKey(port); + PortExtCap *port_cap = portExtCap(port); if (port_cap) port_cap->fanout(min_max, fanout, exists); else { @@ -3662,10 +3680,12 @@ Sdc::portExtFanout(Port *port, PortExtCap * Sdc::ensurePortExtPinCap(Port *port) { - PortExtCap *port_cap = port_cap_map_.findKey(port); + if (port_cap_map_ == NULL) + port_cap_map_ = new PortExtCapMap; + PortExtCap *port_cap = port_cap_map_->findKey(port); if (port_cap == NULL) { port_cap = new PortExtCap(port); - port_cap_map_[port] = port_cap; + (*port_cap_map_)[port] = port_cap; } return port_cap; } diff --git a/sdc/Sdc.hh b/sdc/Sdc.hh index f99b930e..81e84095 100644 --- a/sdc/Sdc.hh +++ b/sdc/Sdc.hh @@ -582,10 +582,12 @@ public: const Corner *corner, const MinMax *min_max, float cap); + bool hasNetWireCap(Net *net) const; // True if driver pin net has wire capacitance. bool drvrPinHasWireCap(const Pin *pin); // Net wire capacitance (set_load -wire net). void drvrPinWireCap(const Pin *drvr_pin, + const Corner *corner, const MinMax *min_max, // Return values. float &cap, @@ -907,6 +909,7 @@ public: PinOutputDelayIterator *outputDelayVertexIterator(const Pin *vertex_pin)const; bool hasOutputDelay(const Pin *vertex_pin) const; PortExtCap *portExtCap(Port *port) const; + bool hasPortExtCap(Port *port) const; void portExtCap(Port *port, const TransRiseFall *tr, const MinMax *min_max, @@ -1333,9 +1336,15 @@ protected: PinCapLimitMap pin_cap_limit_map_; PortFanoutLimitMap port_fanout_limit_map_; CellFanoutLimitMap cell_fanout_limit_map_; - // External parasitics on top level ports (from set_load). - PortExtCapMap port_cap_map_; + // External parasitics on top level ports. + // set_load port + // set_fanout_load port + // Indexed by corner_index. + PortExtCapMap *port_cap_map_; + // set_load net + // Indexed by corner_index. NetWireCapMap *net_wire_cap_map_; + // Indexed by corner_index. PinWireCapMap *drvr_pin_wire_cap_map_; NetResistanceMap net_res_map_; PinSet disabled_pins_; diff --git a/sdc/WriteSdc.cc b/sdc/WriteSdc.cc index 67b51157..5877bbdc 100644 --- a/sdc/WriteSdc.cc +++ b/sdc/WriteSdc.cc @@ -632,8 +632,8 @@ void WriteSdc::writeClockInsertion(ClockInsertion *insert, WriteSdcObject &write_obj) const { - RiseFallMinMax *early_values = insert->delays(EarlyLate::min()); - RiseFallMinMax *late_values = insert->delays(EarlyLate::max()); + RiseFallMinMax *early_values = insert->delays(EarlyLate::early()); + RiseFallMinMax *late_values = insert->delays(EarlyLate::late()); if (early_values->equal(late_values)) writeRiseFallMinMaxTimeCmd("set_clock_latency -source", late_values, write_obj); diff --git a/sdf/SdfWriter.cc b/sdf/SdfWriter.cc index e43f0b55..f6954fdd 100644 --- a/sdf/SdfWriter.cc +++ b/sdf/SdfWriter.cc @@ -419,10 +419,10 @@ SdfWriter::writeArcDelays(Edge *edge) TimingArc *arc = arc_iter->next(); TransRiseFall *tr = arc->toTrans()->asRiseFall(); ArcDelay min_delay = graph_->arcDelay(edge, arc, arc_delay_min_index_); - delays.setValue(tr, MinMax::min(), min_delay); + delays.setValue(tr, MinMax::min(), delayAsFloat(min_delay)); ArcDelay max_delay = graph_->arcDelay(edge, arc, arc_delay_max_index_); - delays.setValue(tr, MinMax::max(), max_delay); + delays.setValue(tr, MinMax::max(), delayAsFloat(max_delay)); } delete arc_iter; @@ -682,7 +682,7 @@ SdfWriter::writeCheck(Edge *edge, ArcDelay min_delay = graph_->arcDelay(edge, arc, arc_delay_min_index_); ArcDelay max_delay = graph_->arcDelay(edge, arc, arc_delay_max_index_); - writeSdfTuple(min_delay, max_delay); + writeSdfTuple(delayAsFloat(min_delay), delayAsFloat(max_delay)); gzprintf(stream_, ")\n"); } diff --git a/search/ClkSkew.cc b/search/ClkSkew.cc index 8d6e9321..3426f833 100644 --- a/search/ClkSkew.cc +++ b/search/ClkSkew.cc @@ -93,14 +93,14 @@ float ClkSkew::srcLatency(StaState *sta) { Arrival src_arrival = src_path_.arrival(sta); - return src_arrival - src_path_.clkEdge(sta)->time(); + return delayAsFloat(src_arrival) - src_path_.clkEdge(sta)->time(); } float ClkSkew::tgtLatency(StaState *sta) { Arrival tgt_arrival = tgt_path_.arrival(sta); - return tgt_arrival - tgt_path_.clkEdge(sta)->time(); + return delayAsFloat(tgt_arrival) - tgt_path_.clkEdge(sta)->time(); } float diff --git a/search/Corner.hh b/search/Corner.hh index 4186b2ce..fa22aa14 100644 --- a/search/Corner.hh +++ b/search/Corner.hh @@ -101,6 +101,7 @@ public: Corners *corners); ~Corner(); const char *name() const { return name_; } + int index() const { return index_; } ParasiticAnalysisPt *findParasiticAnalysisPt(const MinMax *min_max) const; DcalcAnalysisPt *findDcalcAnalysisPt(const MinMax *min_max) const; PathAnalysisPt *findPathAnalysisPt(const MinMax *min_max) const; diff --git a/search/Latches.cc b/search/Latches.cc index 145ccd51..532dd89b 100644 --- a/search/Latches.cc +++ b/search/Latches.cc @@ -45,7 +45,7 @@ Latches::latchRequired(const Path *data_path, const PathVertex *disable_path, MultiCyclePath *mcp, PathDelay *path_delay, - float src_clk_latency, + Arrival src_clk_latency, const ArcDelay &margin, // Return values. Required &required, @@ -186,7 +186,8 @@ Latches::latchBorrowInfo(const Path *data_path, if (borrow_limit_exists) max_borrow = borrow_limit; else - max_borrow = nom_pulse_width - latency_diff - crpr_diff - margin; + max_borrow = nom_pulse_width - delayAsFloat(latency_diff) + - crpr_diff - delayAsFloat(margin); } void @@ -211,7 +212,7 @@ Latches::latchRequired(const Path *data_path, false); MultiCyclePath *mcp = dynamic_cast(excpt); PathDelay *path_delay = dynamic_cast(excpt); - float src_clk_latency = 0.0; + Arrival src_clk_latency = 0.0; if (path_delay && path_delay->ignoreClkLatency()) src_clk_latency = search_->pathClkPathArrival(data_path); latchRequired(data_path, enable_path, disable_path, mcp, diff --git a/search/Latches.hh b/search/Latches.hh index f15d4843..ac0730e7 100644 --- a/search/Latches.hh +++ b/search/Latches.hh @@ -44,7 +44,7 @@ public: const PathVertex *disable_path, MultiCyclePath *mcp, PathDelay *path_delay, - float src_clk_latency, + Arrival src_clk_latency, const ArcDelay &margin, // Return values. Required &required, diff --git a/search/Makefile.am b/search/Makefile.am index cdb2cb9b..3d3c9309 100644 --- a/search/Makefile.am +++ b/search/Makefile.am @@ -42,6 +42,7 @@ include_HEADERS = \ PathGroup.hh \ PathVertex.hh \ PathVertexRep.hh \ + Power.hh \ ReportPath.hh \ Search.hh \ SearchClass.hh \ @@ -82,6 +83,7 @@ libsearch_la_SOURCES = \ PathRef.cc \ PathVertex.cc \ PathVertexRep.cc \ + Power.cc \ ReportPath.cc \ Search.cc \ SearchPred.cc \ diff --git a/search/PathEnd.cc b/search/PathEnd.cc index f01729e9..c6a5b4d1 100644 --- a/search/PathEnd.cc +++ b/search/PathEnd.cc @@ -68,6 +68,18 @@ PathEnd::minMax(const StaState *sta) const return path_.pathAnalysisPt(sta)->pathMinMax(); } +const EarlyLate * +PathEnd::pathEarlyLate(const StaState *sta) const +{ + return path_.pathAnalysisPt(sta)->pathMinMax(); +} + +const EarlyLate * +PathEnd::clkEarlyLate(const StaState *sta) const +{ + return NULL; +} + const TransRiseFall * PathEnd::transition(const StaState *sta) const { @@ -540,6 +552,15 @@ PathEndClkConstrained::setPath(PathEnumed *path, crpr_valid_ = false; } +const EarlyLate * +PathEndClkConstrained::clkEarlyLate(const StaState *sta) const +{ + if (clk_path_.isNull()) + return NULL; + else + return clk_path_.pathAnalysisPt(sta)->pathMinMax(); +} + float PathEndClkConstrained::sourceClkOffset(const StaState *sta) const { @@ -1565,15 +1586,15 @@ PathEndDataCheck::type() const Arrival PathEndDataCheck::requiredTimeNoCrpr(const StaState *sta) const { - float data_clk_arrival = data_clk_path_.arrival(sta); + Arrival data_clk_arrival = data_clk_path_.arrival(sta); float data_clk_time = data_clk_path_.clkEdge(sta)->time(); - float data_clk_delay = data_clk_arrival - data_clk_time; - float tgt_clk_arrival = targetClkTime(sta) + Arrival data_clk_delay = data_clk_arrival - data_clk_time; + Arrival tgt_clk_arrival = targetClkTime(sta) + data_clk_delay + targetClkUncertainty(sta) + targetClkMcpAdjustment(sta); - float check_margin = margin(sta); + ArcDelay check_margin = margin(sta); if (checkGenericRole(sta) == TimingRole::setup()) return tgt_clk_arrival - check_margin; else @@ -1773,14 +1794,14 @@ PathEndPathDelay::sourceClkOffset(const StaState *sta) const float PathEnd::pathDelaySrcClkOffset(const PathRef &path, PathDelay *path_delay, - float src_clk_arrival, + Arrival src_clk_arrival, const StaState *sta) { float offset = 0.0; ClockEdge *clk_edge = path.clkEdge(sta); if (clk_edge) { if (path_delay->ignoreClkLatency()) - offset = -src_clk_arrival; + offset = -delayAsFloat(src_clk_arrival); else // Arrival includes src clock edge time that is not counted in the // path delay. diff --git a/search/PathEnd.hh b/search/PathEnd.hh index 7d2dfe9d..7e096d5d 100644 --- a/search/PathEnd.hh +++ b/search/PathEnd.hh @@ -75,6 +75,9 @@ public: const StaState *sta); Vertex *vertex(const StaState *sta) const; const MinMax *minMax(const StaState *sta) const; + // Synonym for minMax(). + const EarlyLate *pathEarlyLate(const StaState *sta) const; + virtual const EarlyLate *clkEarlyLate(const StaState *sta) const; const TransRiseFall *transition(const StaState *sta) const; PathAnalysisPt *pathAnalysisPt(const StaState *sta) const; PathAPIndex pathIndex(const StaState *sta) const; @@ -203,7 +206,7 @@ protected: const StaState *sta); static float pathDelaySrcClkOffset(const PathRef &path, PathDelay *path_delay, - float src_clk_arrival, + Arrival src_clk_arrival, const StaState *sta); PathRef path_; @@ -236,6 +239,7 @@ private: class PathEndClkConstrained : public PathEnd { public: + virtual const EarlyLate *clkEarlyLate(const StaState *sta) const; virtual float sourceClkOffset(const StaState *sta) const; virtual Delay sourceClkLatency(const StaState *sta) const; virtual Delay sourceClkInsertionDelay(const StaState *sta) const; @@ -414,7 +418,7 @@ private: PathVertex disable_path_; PathDelay *path_delay_; // Source clk arrival for set_max_delay -ignore_clk_latency. - float src_clk_arrival_; + Arrival src_clk_arrival_; DISALLOW_COPY_AND_ASSIGN(PathEndLatchCheck); }; diff --git a/search/PathEnum.cc b/search/PathEnum.cc index 6592a45a..799c4611 100644 --- a/search/PathEnum.cc +++ b/search/PathEnum.cc @@ -69,15 +69,11 @@ Diversion::Diversion(PathEnd *path_end, // Default constructor required for DiversionQueue template. DiversionGreater::DiversionGreater() : - cmp_slack_(true) + sta_(NULL) { } -DiversionGreater::DiversionGreater(bool cmp_slack, - const MinMax *min_max, - StaState *sta) : - cmp_slack_(cmp_slack), - min_max_(min_max), +DiversionGreater::DiversionGreater(const StaState *sta) : sta_(sta) { } @@ -114,7 +110,7 @@ PathEnum::PathEnum(int group_count, group_count_(group_count), endpoint_count_(endpoint_count), unique_pins_(unique_pins), - div_queue_(DiversionGreater(cmp_slack, min_max, sta->network())), + div_queue_(DiversionGreater(sta)), div_count_(0), inserts_pruned_(false), next_(NULL) diff --git a/search/PathEnum.hh b/search/PathEnum.hh index c75e6efd..88dd970c 100644 --- a/search/PathEnum.hh +++ b/search/PathEnum.hh @@ -41,15 +41,11 @@ class DiversionGreater { public: DiversionGreater(); - DiversionGreater(bool cmp_slack, - const MinMax *min_max, - StaState *sta); + DiversionGreater(const StaState *sta); bool operator()(Diversion *div1, Diversion *div2) const; private: - bool cmp_slack_; - const MinMax *min_max_; const StaState *sta_; }; diff --git a/search/Power.cc b/search/Power.cc new file mode 100644 index 00000000..ac37651b --- /dev/null +++ b/search/Power.cc @@ -0,0 +1,394 @@ +// OpenSTA, Static Timing Analyzer +// Copyright (c) 2018, 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 // max +#include "Machine.hh" +#include "Debug.hh" +#include "Units.hh" +#include "Transition.hh" +#include "MinMax.hh" +#include "Liberty.hh" +#include "InternalPower.hh" +#include "LeakagePower.hh" +#include "InternalPower.hh" +#include "TimingArc.hh" +#include "FuncExpr.hh" +#include "PortDirection.hh" +#include "Network.hh" +#include "Graph.hh" +#include "Sim.hh" +#include "Corner.hh" +#include "DcalcAnalysisPt.hh" +#include "GraphDelayCalc.hh" +#include "PathVertex.hh" +#include "Clock.hh" +#include "Power.hh" + +// Related liberty not supported: +// library +// default_cell_leakage_power : 0; +// output_voltage (default_VDD_VSS_output) { +// leakage_power +// related_pg_pin : VDD; +// internal_power +// input_voltage : default_VDD_VSS_input; +// pin +// output_voltage : default_VDD_VSS_output; + + +namespace sta { + +Power::Power(Sta *sta) : + StaState(sta), + sta_(sta), + default_signal_toggle_rate_(.1) +{ +} + +float +Power::defaultSignalToggleRate() +{ + return default_signal_toggle_rate_; +} + +void +Power::setDefaultSignalToggleRate(float rate) +{ + default_signal_toggle_rate_ = rate; +} + + +void +Power::power(const Corner *corner, + // Return values. + PowerResult &total, + PowerResult &sequential, + PowerResult &combinational, + PowerResult ¯o, + PowerResult &pad) +{ + total.clear(); + sequential.clear(); + combinational.clear(); + macro.clear(); + pad.clear(); + LeafInstanceIterator *inst_iter = network_->leafInstanceIterator(); + while (inst_iter->hasNext()) { + Instance *inst = inst_iter->next(); + LibertyCell *cell = network_->libertyCell(inst); + if (cell) { + PowerResult inst_power; + power(inst, corner, inst_power); + if (cell->isMacro()) + macro.incr(inst_power); + else if (cell->isPad()) + pad.incr(inst_power); + else if (cell->hasSequentials()) + sequential.incr(inst_power); + else + combinational.incr(inst_power); + total.incr(inst_power); + } + } +} + +//////////////////////////////////////////////////////////////// + +void +Power::power(const Instance *inst, + const Corner *corner, + // Return values. + PowerResult &result) +{ + LibertyCell *cell = network_->libertyCell(inst); + if (cell) + power(inst, cell, corner, result); +} + +void +Power::power(const Instance *inst, + LibertyCell *cell, + const Corner *corner, + // Return values. + PowerResult &result) +{ + MinMax *mm = MinMax::max(); + const DcalcAnalysisPt *dcalc_ap = corner->findDcalcAnalysisPt(mm); + InstancePinIterator *pin_iter = network_->pinIterator(inst); + while (pin_iter->hasNext()) { + const Pin *to_pin = pin_iter->next(); + const LibertyPort *to_port = network_->libertyPort(to_pin); + float load_cap = to_port->direction()->isAnyOutput() + ? loadCap(to_pin, dcalc_ap) + : 0.0; + float activity1; + bool is_clk; + activity(to_pin, activity1, is_clk); + if (to_port->direction()->isAnyOutput()) { + findSwitchingPower(inst, cell, to_pin, to_port, activity1, load_cap, + dcalc_ap, result); + } + findInternalPower(inst, cell, to_pin, to_port, activity1, is_clk, + load_cap, dcalc_ap, result); + } + delete pin_iter; + findLeakagePower(inst, cell, result); +} + +void +Power::findInternalPower(const Instance *inst, + LibertyCell *cell, + const Pin *to_pin, + const LibertyPort *to_port, + float activity, + bool is_clk, + float load_cap, + const DcalcAnalysisPt *dcalc_ap, + // Return values. + PowerResult &result) +{ + debugPrint3(debug_, "power", 2, "internal %s/%s %ss\n", + network_->pathName(inst), + to_port->name(), + cell->name()); + float port_internal = 0.0; + const Pvt *pvt = dcalc_ap->operatingConditions(); + float volt = voltage(cell, to_port, dcalc_ap); + const char *pwr_pin = to_port->relatedPowerPin(); + float duty = is_clk ? 1.0 : .5; + debugPrint3(debug_, "power", 2, "cap = %s activity = %.2f/ns duty = %.1f\n", + units_->capacitanceUnit()->asString(load_cap), + activity * 1e-9, + duty); + LibertyCellInternalPowerIterator *pwr_iter = cell->internalPowerIterator(); + while (pwr_iter->hasNext()) { + InternalPower *pwr = pwr_iter->next(); + const char *related_pg_pin = pwr->relatedPgPin(); + if (pwr->port() == to_port + && ((related_pg_pin == NULL || pwr_pin == NULL) + || stringEqual(related_pg_pin, pwr_pin))) { + const LibertyPort *from_port = pwr->relatedPort(); + if (from_port == NULL) + from_port = to_port; + const Pin *from_pin = network_->findPin(inst, from_port); + Vertex *from_vertex = graph_->pinLoadVertex(from_pin); + TransRiseFallIterator tr_iter; + while (tr_iter.hasNext()) { + TransRiseFall *to_tr = tr_iter.next(); + // Need unateness to find from_tr. + float slew = sta_->vertexSlew(from_vertex, to_tr, dcalc_ap); + float energy, tr_internal; + if (from_port) { + float energy1 = pwr->power(to_tr, pvt, slew, load_cap); + // Scale by voltage and rise/fall transition count. + energy = energy1 * volt / 2.0; + } + else { + float energy1 = pwr->power(to_tr, pvt, 0.0, 0.0); + // Scale by voltage and rise/fall transition count. + energy = energy1 * volt / 2.0; + } + tr_internal = energy * activity * duty; + port_internal += tr_internal; + debugPrint5(debug_, "power", 2, " %s -> %s %s %s (%s)\n", + from_port->name(), + to_tr->shortName(), + to_port->name(), + pwr->when() ? pwr->when()->asString() : "", + related_pg_pin ? related_pg_pin : ""); + debugPrint3(debug_, "power", 2, " slew = %s energy = %.5g pwr = %.5g\n", + units_->timeUnit()->asString(slew), + energy, + tr_internal); + } + } + } + delete pwr_iter; + debugPrint1(debug_, "power", 2, " internal = %.5g\n", port_internal); + result.setInternal(result.internal() + port_internal); +} + +float +Power::loadCap(const Pin *to_pin, + const DcalcAnalysisPt *dcalc_ap) +{ + float ceff_sum = 0.0; + int ceff_count = 0; + Vertex *to_vertex = graph_->pinDrvrVertex(to_pin); + VertexInEdgeIterator edge_iter(to_vertex, graph_); + while (edge_iter.hasNext()) { + Edge *edge = edge_iter.next(); + TimingArcSet *arc_set = edge->timingArcSet(); + TimingArcSetArcIterator *arc_iter = arc_set->timingArcIterator(); + while (arc_iter->hasNext()) { + TimingArc *arc = arc_iter->next(); + ceff_sum += graph_delay_calc_->ceff(edge, arc, dcalc_ap); + ceff_count++; + } + delete arc_iter; + } + return ceff_sum / ceff_count; +} + +void +Power::findLeakagePower(const Instance *inst, + LibertyCell *cell, + // Return values. + PowerResult &result) +{ + float leakage = cell->leakagePower(); + LibertyCellLeakagePowerIterator *pwr_iter = cell->leakagePowerIterator(); + while (pwr_iter->hasNext()) { + LeakagePower *leak = pwr_iter->next(); + FuncExpr *when = leak->when(); + if (when) { + LogicValue when_value = sim_->evalExpr(when, inst); + switch (when_value) { + case logic_zero: + case logic_one: + leakage = max(leakage, leak->power()); + break; + case logic_unknown: + default: + break; + } + } + } + delete pwr_iter; + result.setLeakage(leakage); +} + +void +Power::findSwitchingPower(const Instance *inst, + LibertyCell *cell, + const Pin *to_pin, + const LibertyPort *to_port, + float activity, + float load_cap, + const DcalcAnalysisPt *dcalc_ap, + // Return values. + PowerResult &result) +{ + float volt = voltage(cell, to_port, dcalc_ap); + float switching = load_cap * volt * volt * activity / 2.0; + result.setSwitching(switching); +} + +void +Power::activity(const Pin *pin, + // Return values. + float &activity, + bool &is_clk) +{ + const Clock *clk; + findClk(pin, clk, is_clk); + if (clk) { + if (is_clk) + activity = 2.0 / clk->period(); + else + activity = default_signal_toggle_rate_ * 2.0 / clk->period(); + } + else + activity = 0.0; +} + +float +Power::voltage(LibertyCell *cell, + const LibertyPort *port, + const DcalcAnalysisPt *dcalc_ap) +{ + // Should use cell pg_pin voltage name to voltage. + const Pvt *pvt = dcalc_ap->operatingConditions(); + if (pvt == NULL) + pvt = cell->libertyLibrary()->defaultOperatingConditions(); + if (pvt) + return pvt->voltage(); + else + return 0.0; +} + +void +Power::findClk(const Pin *to_pin, + // Return values. + const Clock *&clk, + bool &is_clk) +{ + clk = NULL; + is_clk = false; + Vertex *to_vertex = graph_->pinDrvrVertex(to_pin); + VertexPathIterator path_iter(to_vertex, this); + while (path_iter.hasNext()) { + PathVertex *path = path_iter.next(); + const Clock *path_clk = path->clock(this); + if (path_clk + && (clk == NULL + || path_clk->period() < clk->period())) + clk = path_clk; + if (path->isClock(this)) + is_clk = true; + } +} + +//////////////////////////////////////////////////////////////// + +PowerResult::PowerResult() : + internal_(0.0), + switching_(0.0), + leakage_(0.0) +{ +} + +void +PowerResult::clear() +{ + internal_ = 0.0; + switching_ = 0.0; + leakage_ = 0.0; +} + +float +PowerResult::total() const +{ + return internal_ + switching_ + leakage_; +} + +void +PowerResult::setInternal(float internal) +{ + internal_ = internal; +} + +void +PowerResult::setLeakage(float leakage) +{ + leakage_ = leakage; +} + +void +PowerResult::setSwitching(float switching) +{ + switching_ = switching; +} + +void +PowerResult::incr(PowerResult &result) +{ + internal_ += result.internal_; + switching_ += result.switching_; + leakage_ += result.leakage_; +} + +} // namespace diff --git a/search/Power.hh b/search/Power.hh new file mode 100644 index 00000000..9a69b44e --- /dev/null +++ b/search/Power.hh @@ -0,0 +1,115 @@ +// OpenSTA, Static Timing Analyzer +// Copyright (c) 2018, 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 . + +#ifndef STA_POWER_H +#define STA_POWER_H + +#include "Sta.hh" + +namespace sta { + +class PowerResult; + +// The Power class has access to Sta components directly for +// convenience but also requires access to the Sta class member functions. +class Power : public StaState +{ +public: + Power(Sta *sta); + void power(const Corner *corner, + // Return values. + PowerResult &total, + PowerResult &sequential, + PowerResult &combinational, + PowerResult ¯o, + PowerResult &pad); + void power(const Instance *inst, + const Corner *corner, + // Return values. + PowerResult &result); + float defaultSignalToggleRate(); + void setDefaultSignalToggleRate(float rate); + +protected: + void power(const Instance *inst, + LibertyCell *cell, + const Corner *corner, + // Return values. + PowerResult &result); + void findInternalPower(const Instance *inst, + LibertyCell *cell, + const Pin *to_pin, + const LibertyPort *to_port, + float activity, + bool is_clk, + float load_cap, + const DcalcAnalysisPt *dcalc_ap, + // Return values. + PowerResult &result); + void findLeakagePower(const Instance *inst, + LibertyCell *cell, + // Return values. + PowerResult &result); + void findSwitchingPower(const Instance *inst, + LibertyCell *cell, + const Pin *to_pin, + const LibertyPort *to_port, + float activity, + float load_cap, + const DcalcAnalysisPt *dcalc_ap, + // Return values. + PowerResult &result); + void findClk(const Pin *to_pin, + // Return values. + const Clock *&clk, + bool &is_clk); + float loadCap(const Pin *to_pin, + const DcalcAnalysisPt *dcalc_ap);; + void activity(const Pin *pin, + // Return values. + float &activity, + bool &is_clk); + float voltage(LibertyCell *cell, + const LibertyPort *port, + const DcalcAnalysisPt *dcalc_ap); + +private: + Sta *sta_; + float default_signal_toggle_rate_; +}; + +class PowerResult +{ +public: + PowerResult(); + void clear(); + float internal() const { return internal_; } + void setInternal(float internal); + float switching() const { return switching_; } + void setSwitching(float switching); + float leakage() const { return leakage_; } + void setLeakage(float leakage); + float total() const; + void incr(PowerResult &result); + +private: + float internal_; + float switching_; + float leakage_; +}; + +} // namespace +#endif diff --git a/search/ReportPath.cc b/search/ReportPath.cc index 899963c1..19eb30b5 100644 --- a/search/ReportPath.cc +++ b/search/ReportPath.cc @@ -423,7 +423,8 @@ ReportPath::reportFull(const PathEndUnconstrained *end, reportEndOfLine(result); reportPath(end, expanded, result); - reportLine("data arrival time", end->dataArrivalTimeOffset(this), result); + reportLine("data arrival time", end->dataArrivalTimeOffset(this), + end->pathEarlyLate(this), result); reportDashLine(result); result += "(Path is unconstrained)\n"; } @@ -542,6 +543,7 @@ ReportPath::reportFull(const PathEndLatchCheck *end, string &result) { PathExpanded expanded(end->path(), this); + const EarlyLate *early_late = end->pathEarlyLate(this); reportShort(end, expanded, result); PathDelay *path_delay = end->pathDelay(); reportEndOfLine(result); @@ -554,7 +556,8 @@ ReportPath::reportFull(const PathEndLatchCheck *end, } else reportSrcPath(end, expanded, result); - reportLine("data arrival time", end->dataArrivalTimeOffset(this), result); + reportLine("data arrival time", end->dataArrivalTimeOffset(this), + early_late, result); reportEndOfLine(result); Required req_time; @@ -565,23 +568,27 @@ ReportPath::reportFull(const PathEndLatchCheck *end, req_time += end->sourceClkOffset(this); if (path_delay) { float delay = path_delay->delay(); - reportLine("max_delay", delay, delay, result); + reportLine("max_delay", delay, delay, early_late, result); if (!ignore_clk_latency) { if (reportClkPath() && isPropagated(end->targetClkPath())) reportTgtClk(end, delay, result); - else - reportCommonClkPessimism(end, delay, result); + else { + Delay delay1(delay); + reportCommonClkPessimism(end, delay1, result); + } } } else reportTgtClk(end, result); if (borrow >= 0.0) - reportLine("time borrowed from endpoint", borrow, req_time, result); + reportLine("time borrowed from endpoint", borrow, req_time, + early_late, result); else - reportLine("time given to endpoint", borrow, req_time, result); - reportLine("data required time", req_time, result); + reportLine("time given to endpoint", borrow, req_time, + early_late, result); + reportLine("data required time", req_time, early_late, result); reportDashLine(result); reportSlack(end, result); if (end->checkGenericRole(this) == TimingRole::setup() @@ -629,13 +636,14 @@ ReportPath::reportBorrowing(const PathEndLatchCheck *end, Delay open_latency, latency_diff, max_borrow; float nom_pulse_width, open_uncertainty, open_crpr, crpr_diff; bool borrow_limit_exists; + const EarlyLate *early_late = EarlyLate::late(); end->latchBorrowInfo(this, nom_pulse_width, open_latency, latency_diff, open_uncertainty, open_crpr, crpr_diff, max_borrow, borrow_limit_exists); result += "Time Borrowing Information\n"; reportDashLineTotal(result); if (borrow_limit_exists) - reportLineTotal("user max time borrow", max_borrow, result); + reportLineTotal("user max time borrow", max_borrow, early_late, result); else { const char *tgt_clk_name = tgtClkName(end); Arrival tgt_clk_width = end->targetClkWidth(this); @@ -645,39 +653,41 @@ ReportPath::reportBorrowing(const PathEndLatchCheck *end, + strlen(tgt_clk_name) + 1, "%s nominal pulse width", tgt_clk_name); - reportLineTotal(width_msg, nom_pulse_width, result); + reportLineTotal(width_msg, nom_pulse_width, early_late, result); if (!delayFuzzyZero(latency_diff)) - reportLineTotalMinus("clock latency difference", latency_diff, result); + reportLineTotalMinus("clock latency difference", latency_diff, + early_late, result); } else { const char *width_msg = stringPrintTmp(strlen(" pulse width") + strlen(tgt_clk_name) + 1, "%s pulse width", tgt_clk_name); - reportLineTotal(width_msg, tgt_clk_width, result); + reportLineTotal(width_msg, tgt_clk_width, early_late, result); } ArcDelay margin = end->margin(this); - reportLineTotalMinus("library setup time", margin, result); + reportLineTotalMinus("library setup time", margin, early_late, result); reportDashLineTotal(result); if (!fuzzyZero(crpr_diff)) - reportLineTotalMinus("CRPR difference", crpr_diff, result); - reportLineTotal("max time borrow", max_borrow, result); + reportLineTotalMinus("CRPR difference", crpr_diff, early_late, result); + reportLineTotal("max time borrow", max_borrow, early_late, result); } if (delayFuzzyGreater(borrow, delay_zero) && (!fuzzyZero(open_uncertainty) || !fuzzyZero(open_crpr))) { reportDashLineTotal(result); - reportLineTotal("actual time borrow", borrow, result); + reportLineTotal("actual time borrow", borrow, early_late, result); if (!fuzzyZero(open_uncertainty)) - reportLineTotal("open edge uncertainty", open_uncertainty, result); + reportLineTotal("open edge uncertainty", open_uncertainty, + early_late, result); if (!fuzzyZero(open_crpr)) - reportLineTotal("open edge CRPR", open_crpr, result); + reportLineTotal("open edge CRPR", open_crpr, early_late, result); reportDashLineTotal(result); reportLineTotal("time given to startpoint", time_given_to_startpoint, - result); + early_late, result); } else - reportLineTotal("actual time borrow", borrow, result); + reportLineTotal("actual time borrow", borrow, early_late, result); reportDashLineTotal(result); } @@ -727,6 +737,7 @@ ReportPath::reportFull(const PathEndPathDelay *end, { PathExpanded expanded(end->path(), this); reportShort(end, expanded, result); + const EarlyLate *early_late = end->pathEarlyLate(this); // Based on reportSrcPathArrival. reportEndOfLine(result); @@ -739,7 +750,8 @@ ReportPath::reportFull(const PathEndPathDelay *end, } else reportSrcPath(end, expanded, result); - reportLine("data arrival time", end->dataArrivalTimeOffset(this), result); + reportLine("data arrival time", end->dataArrivalTimeOffset(this), + early_late, result); reportEndOfLine(result); ArcDelay margin = end->margin(this); @@ -758,7 +770,7 @@ ReportPath::reportFull(const PathEndPathDelay *end, "%s_delay", min_max_str); float delay = path_delay->delay(); - reportLine(delay_msg, delay, delay, result); + reportLine(delay_msg, delay, delay, early_late, result); if (!path_delay->ignoreClkLatency()) { const Path *tgt_clk_path = end->targetClkPath(); if (tgt_clk_path) { @@ -773,7 +785,7 @@ ReportPath::reportFull(const PathEndPathDelay *end, Arrival tgt_clk_arrival = delay + tgt_clk_delay; if (!delayFuzzyZero(tgt_clk_delay)) reportLine(clkNetworkDelayIdealProp(isPropagated(tgt_clk_path)), - tgt_clk_delay, tgt_clk_arrival, result); + tgt_clk_delay, tgt_clk_arrival, early_late, result); reportClkUncertainty(end, tgt_clk_arrival, result); reportCommonClkPessimism(end, tgt_clk_arrival, result); } @@ -968,9 +980,9 @@ ReportPath::reportFull(const PathEndDataCheck *end, // Report the path from the clk network to the data check. PathExpanded clk_expanded(data_clk_path, this); float src_offset = end->sourceClkOffset(this); - float clk_delay = end->targetClkDelay(this); - float clk_arrival = end->targetClkArrival(this); - float offset = clk_arrival - clk_delay + src_offset; + Delay clk_delay = end->targetClkDelay(this); + Arrival clk_arrival = end->targetClkArrival(this); + float offset = delayAsFloat(clk_arrival - clk_delay + src_offset); reportPath5(data_clk_path, clk_expanded, clk_expanded.startIndex(), clk_expanded.size() - 1, data_clk_path->clkInfo(search_)->isPropagated(), false, @@ -1030,11 +1042,12 @@ void ReportPath::reportEndLine(PathEnd *end, string &result) { + const EarlyLate *early_late = end->pathEarlyLate(this); reportDescription(pathEndpoint(end), result); - reportSpaceFieldTime(end->requiredTimeOffset(this), result); - reportSpaceFieldTime(end->dataArrivalTimeOffset(this), result); + reportSpaceFieldDelay(end->requiredTimeOffset(this), early_late, result); + reportSpaceFieldDelay(end->dataArrivalTimeOffset(this), early_late, result); Slack slack = end->slack(this); - reportSpaceFieldTime(slack, result); + reportSpaceFieldDelay(slack, early_late, result); result += (slack >= 0.0) ? " (MET)" : " (VIOLATED)"; reportEndOfLine(result); } @@ -1059,12 +1072,13 @@ ReportPath::reportSummaryLine(PathEnd *end, string &result) { PathExpanded expanded(end->path(), this); + const EarlyLate *early_late = end->pathEarlyLate(this); reportDescription(pathStartpoint(end, expanded), result); reportDescription(pathEndpoint(end), result); if (end->isUnconstrained()) - reportSpaceFieldTime(end->dataArrivalTimeOffset(this), result); + reportSpaceFieldDelay(end->dataArrivalTimeOffset(this), early_late, result); else - reportSpaceFieldTime(end->slack(this), result); + reportSpaceFieldDelay(end->slack(this), early_late, result); reportEndOfLine(result); } @@ -1131,11 +1145,12 @@ void ReportPath::reportSlackOnly(PathEnd *end, string &result) { + const EarlyLate *early_late = end->pathEarlyLate(this); reportDescription(search_->pathGroup(end)->name(), result); if (end->isUnconstrained()) - reportSpaceFieldTime(end->dataArrivalTimeOffset(this), result); + reportSpaceFieldDelay(end->dataArrivalTimeOffset(this), early_late, result); else - reportSpaceFieldTime(end->slack(this), result); + reportSpaceFieldDelay(end->slack(this), early_late, result); reportEndOfLine(result); } @@ -1229,9 +1244,9 @@ ReportPath::reportShort(MinPulseWidthCheck *check, hi_low); reportDescription(what, result); reportSpaceFieldTime(check->minWidth(this), result); - reportSpaceFieldTime(check->width(this), result); + reportSpaceFieldDelay(check->width(this), EarlyLate::late(), result); Slack slack = check->slack(this); - reportSpaceFieldTime(slack, result); + reportSpaceFieldDelay(slack, EarlyLate::early(), result); result += (slack >= 0.0) ? " (MET)" : " (VIOLATED)"; reportEndOfLine(result); } @@ -1249,6 +1264,7 @@ ReportPath::reportVerbose(MinPulseWidthCheck *check, reportEndOfLine(result); reportPathHeader(result); + const EarlyLate *open_el = EarlyLate::late(); ClockEdge *open_clk_edge = check->openClkEdge(this); Clock *open_clk = open_clk_edge->clock(); const char *open_clk_name = open_clk->name(); @@ -1260,15 +1276,18 @@ ReportPath::reportVerbose(MinPulseWidthCheck *check, "clock %s (%s edge)", open_clk_name, open_rise_fall); - reportLine(open_clk_msg, open_clk_time, open_clk_time, result); + reportLine(open_clk_msg, open_clk_time, open_clk_time, + open_el, result); Arrival open_arrival = check->openArrival(this); bool is_prop = isPropagated(check->openPath()); const char *clk_ideal_prop = clkNetworkDelayIdealProp(is_prop); - reportLine(clk_ideal_prop, check->openDelay(this), open_arrival, result); - reportLine(pin_name, delay_zero, open_arrival, result); - reportLine("open edge arrival time", open_arrival, result); + reportLine(clk_ideal_prop, check->openDelay(this), open_arrival, + open_el, result); + reportLine(pin_name, delay_zero, open_arrival, open_el, result); + reportLine("open edge arrival time", open_arrival, open_el, result); reportEndOfLine(result); + const EarlyLate *close_el = EarlyLate::late(); ClockEdge *close_clk_edge = check->closeClkEdge(this); Clock *close_clk = close_clk_edge->clock(); const char *close_clk_name = close_clk->name(); @@ -1281,18 +1300,20 @@ ReportPath::reportVerbose(MinPulseWidthCheck *check, "clock %s (%s edge)", close_clk_name, close_rise_fall); - reportLine(close_clk_msg, close_clk_time, close_clk_time, result); + reportLine(close_clk_msg, close_clk_time, close_clk_time, close_el, result); Arrival close_arrival = check->closeArrival(this) + close_offset; - reportLine(clk_ideal_prop, check->closeDelay(this), close_arrival, result); - reportLine(pin_name, delay_zero, close_arrival, result); + reportLine(clk_ideal_prop, check->closeDelay(this), close_arrival, + close_el, result); + reportLine(pin_name, delay_zero, close_arrival, close_el, result); if (sdc_->crprEnabled()) { float pessimism = check->commonClkPessimism(this); close_arrival += pessimism; reportLine("clock reconvergence pessimism", pessimism, close_arrival, - result); + close_el, result); } - reportLine("close edge arrival time", close_arrival, result); + reportLine("close edge arrival time", close_arrival, close_el, result); + reportDashLine(result); float min_width = check->minWidth(this); const char *hi_low = mpwCheckHiLow(check); @@ -1300,8 +1321,9 @@ ReportPath::reportVerbose(MinPulseWidthCheck *check, + strlen(hi_low) + 1, "required pulse width (%s)", hi_low); - reportLine(rpw_msg, min_width, result); - reportLine("actual pulse width", check->width(this), result); + reportLine(rpw_msg, min_width, EarlyLate::early(), result); + reportLine("actual pulse width", check->width(this), + EarlyLate::early(), result); reportDashLine(result); reportSlack(check->slack(this), result); } @@ -1399,10 +1421,10 @@ ReportPath::reportShort(MinPeriodCheck *check, { const char *pin_name = cmd_network_->pathName(check->pin()); reportDescription(pin_name, result); - reportSpaceFieldTime(check->period(), result); - reportSpaceFieldTime(check->minPeriod(this), result); + reportSpaceFieldDelay(check->period(), EarlyLate::early(), result); + reportSpaceFieldDelay(check->minPeriod(this), EarlyLate::early(), result); Slack slack = check->slack(this); - reportSpaceFieldTime(slack, result); + reportSpaceFieldDelay(slack, EarlyLate::early(), result); result += (slack >= 0.0) ? " (MET)" : " (VIOLATED)"; reportEndOfLine(result); } @@ -1415,8 +1437,9 @@ ReportPath::reportVerbose(MinPeriodCheck *check, string &result) result += pin_name; reportEndOfLine(result); - reportLine("Period", check->period(), result); - reportLine("min_period", -check->minPeriod(this), result); + reportLine("Period", check->period(), EarlyLate::early(), result); + reportLine("min_period", -check->minPeriod(this), + EarlyLate::early(), result); reportDashLine(result); reportSlack(check->slack(this), result); } @@ -1513,10 +1536,10 @@ ReportPath::reportShort(MaxSkewCheck *check, check_arc->fromTrans()->asString(), check_arc->toTrans()->asString()); reportDescription(what, result); - reportSpaceFieldTime(check->maxSkew(this), result); - reportSpaceFieldTime(check->skew(this), result); + reportSpaceFieldDelay(check->maxSkew(this), EarlyLate::early(), result); + reportSpaceFieldDelay(check->skew(this), EarlyLate::early(), result); Slack slack = check->slack(this); - reportSpaceFieldTime(slack, result); + reportSpaceFieldDelay(slack, EarlyLate::early(), result); result += (slack >= 0.0) ? " (MET)" : " (VIOLATED)"; reportEndOfLine(result); } @@ -1544,8 +1567,9 @@ ReportPath::reportVerbose(MaxSkewCheck *check, reportSkewClkPath("constrained pin arrival time", check->clkPath(), result); reportDashLine(result); - reportLine("allowable skew", check->maxSkew(this), result); - reportLine("actual skew", check->skew(this), result); + reportLine("allowable skew", check->maxSkew(this), + EarlyLate::early(), result); + reportLine("actual skew", check->skew(this), EarlyLate::late(), result); reportDashLine(result); reportSlack(check->slack(this), result); } @@ -1558,6 +1582,7 @@ ReportPath::reportSkewClkPath(const char *arrival_msg, { ClockEdge *clk_edge = clk_path->clkEdge(this); Clock *clk = clk_edge->clock(); + const EarlyLate *early_late = clk_path->minMax(this); const TransRiseFall *clk_tr = clk_edge->transition(); const TransRiseFall *clk_end_tr = clk_path->transition(this); const char *clk_name = (clk_end_tr == clk_tr) @@ -1580,17 +1605,19 @@ ReportPath::reportSkewClkPath(const char *arrival_msg, Arrival insertion, latency; PathEnd::checkTgtClkDelay(clk_path, clk_edge, TimingRole::skew(), this, insertion, latency); - reportClkSrcLatency(insertion, clk_time, result); + reportClkSrcLatency(insertion, clk_time, early_late, result); PathExpanded clk_expanded(clk_path, this); reportPath2(clk_path, clk_expanded, false, 0.0, result); } } else { reportLine(clkNetworkDelayIdealProp(is_prop), clk_delay, clk_arrival, - result); - reportLine(descriptionField(clk_vertex), clk_arrival, clk_end_tr, result); + early_late, result); + reportLine(descriptionField(clk_vertex), clk_arrival, early_late, + clk_end_tr, result); } - reportLine(arrival_msg, search_->clkPathArrival(clk_path), result); + reportLine(arrival_msg, search_->clkPathArrival(clk_path), + early_late, result); reportEndOfLine(result); } @@ -1642,7 +1669,7 @@ ReportPath::reportSlewLimitShort(Pin *pin, const char *pin_name = cmd_network_->pathName(pin); reportDescription(pin_name, result); reportSpaceFieldTime(limit, result); - reportSpaceFieldTime(slew, result); + reportSpaceFieldDelay(slew, EarlyLate::late(), result); reportSpaceFieldTime(slack, result); result += (slack >= 0.0) ? " (MET)" : " (VIOLATED)"; reportEndOfLine(result); @@ -1977,7 +2004,8 @@ ReportPath::reportSrcPathArrival(const PathEnd *end, { reportEndOfLine(result); reportSrcPath(end, expanded, result); - reportLine("data arrival time", end->dataArrivalTimeOffset(this), result); + reportLine("data arrival time", end->dataArrivalTimeOffset(this), + end->pathEarlyLate(this), result); reportEndOfLine(result); } @@ -2077,33 +2105,33 @@ ReportPath::reportSrcClkAndPath(const Path *path, reportClkLine(clk, clk_name, clk_end_tr, clk_time, min_max, result); ClkInfo *clk_info = path->tag(search_)->clkInfo(); if (clk_info->isPropagated()) - reportClkSrcLatency(clk_insertion, clk_time, result); + reportClkSrcLatency(clk_insertion, clk_time, early_late, result); reportPath1(path, expanded, true, time_offset, result); } else if (is_prop && reportClkPath() && !(path_from_input && !input_has_ref_path)) { - reportClkLine(clk, clk_name, clk_end_tr, clk_time, min_max, result); - reportClkSrcLatency(clk_insertion, clk_time, result); + reportClkLine(clk, clk_name, clk_end_tr, clk_time, early_late, result); + reportClkSrcLatency(clk_insertion, clk_time, early_late, result); reportPath1(path, expanded, false, time_offset, result); } else if (clk_used_as_data) { - reportClkLine(clk, clk_name, clk_end_tr, clk_time, min_max, result); + reportClkLine(clk, clk_name, clk_end_tr, clk_time, early_late, result); if (clk_insertion > 0.0) - reportClkSrcLatency(clk_insertion, clk_time, result); + reportClkSrcLatency(clk_insertion, clk_time, early_late, result); reportPath1(path, expanded, true, time_offset, result); } else { if (is_path_delay) { if (clk_delay > 0.0) reportLine(clkNetworkDelayIdealProp(is_prop), clk_delay, - clk_end_time, result); + clk_end_time, early_late, result); } else { reportClkLine(clk, clk_name, clk_end_tr, clk_time, min_max, result); - float clk_arrival = clk_end_time; + Arrival clk_arrival = clk_end_time; reportLine(clkNetworkDelayIdealProp(is_prop), clk_delay, - clk_arrival, result); + clk_arrival, early_late, result); } reportPath1(path, expanded, false, time_offset, result); } @@ -2170,7 +2198,7 @@ ReportPath::reportTgtClk(const PathEnd *end, else { Arrival insertion = end->targetClkInsertionDelay(this); if (clk_path) { - reportClkSrcLatency(insertion, clk_time, result); + reportClkSrcLatency(insertion, clk_time, early_late, result); PathExpanded clk_expanded(clk_path, this); float insertion_offset = tgtClkInsertionOffet(clk_path, early_late, path_ap); @@ -2182,7 +2210,7 @@ ReportPath::reportTgtClk(const PathEnd *end, // Output departure. Arrival clk_arrival = clk_time + clk_delay; reportLine(clkNetworkDelayIdealProp(clk->isPropagated()), - clk_delay, clk_arrival, result); + clk_delay, clk_arrival, min_max, result); } } reportClkUncertainty(end, clk_arrival, result); @@ -2190,7 +2218,7 @@ ReportPath::reportTgtClk(const PathEnd *end, } else { reportLine(clkNetworkDelayIdealProp(is_prop), clk_delay, clk_arrival, - result); + min_max, result); reportClkUncertainty(end, clk_arrival, result); reportCommonClkPessimism(end, clk_arrival, result); if (clk_path) { @@ -2199,7 +2227,7 @@ ReportPath::reportTgtClk(const PathEnd *end, prev_time + end->targetClkArrival(this) + end->sourceClkOffset(this), - clk_end_tr, result); + min_max, clk_end_tr, result); } } } @@ -2289,11 +2317,12 @@ ReportPath::reportClkLine(const Clock *clk, clk_name, rise_fall); if (clk->isPropagated()) - reportLine(clk_msg, clk_time - prev_time, clk_time, result); + reportLine(clk_msg, clk_time - prev_time, clk_time, min_max, result); else { // Report ideal clock slew. float clk_slew = clk->slew(clk_tr, min_max); - reportLine(clk_msg, clk_slew, clk_time - prev_time, clk_time, result); + reportLine(clk_msg, clk_slew, clk_time - prev_time, clk_time, + min_max, result); } } @@ -2350,6 +2379,7 @@ ReportPath::reportGenClkSrcPath1(Clock *clk, { PathAnalysisPt *insert_ap = path_ap->insertionAnalysisPt(early_late); PathVertex src_path; + const MinMax *min_max = path_ap->pathMinMax(); search_->genclks()->srcPath(clk, clk_pin, clk_tr, insert_ap, src_path); if (!src_path.isNull()) { ClkInfo *src_clk_info = src_path.clkInfo(search_); @@ -2370,13 +2400,12 @@ ReportPath::reportGenClkSrcPath1(Clock *clk, src_clk_tr, path_ap->pathMinMax(), early_late, path_ap); - reportClkSrcLatency(insertion, gclk_time, result); + reportClkSrcLatency(insertion, gclk_time, early_late, result); } PathExpanded src_expanded(&src_path, this); if (clk->pllOut()) { reportPath4(&src_path, src_expanded, skip_first_path, true, clk_used_as_data, gclk_time, result); - const MinMax *min_max = path_ap->pathMinMax(); PathAnalysisPt *pll_ap=path_ap->insertionAnalysisPt(min_max->opposite()); Arrival pll_delay = search_->genclks()->pllDelay(clk, clk_tr, pll_ap); size_t path_length = src_expanded.size(); @@ -2392,13 +2421,14 @@ ReportPath::reportGenClkSrcPath1(Clock *clk, clk_used_as_data, gclk_time, result); if (!clk->isPropagated()) reportLine("clock network delay (ideal)", 0.0, src_path.arrival(this), - result); + min_max, result); } else { if (clk->isPropagated()) - reportClkSrcLatency(0.0, gclk_time, result); + reportClkSrcLatency(0.0, gclk_time, early_late, result); else if (!clk_used_as_data) - reportLine("clock network delay (ideal)", 0.0, gclk_time, result); + reportLine("clock network delay (ideal)", 0.0, gclk_time, + min_max, result); } return !src_path.isNull(); } @@ -2406,9 +2436,11 @@ ReportPath::reportGenClkSrcPath1(Clock *clk, void ReportPath::reportClkSrcLatency(Arrival insertion, float clk_time, + const EarlyLate *early_late, string &result) { - reportLine("clock source latency", insertion, clk_time + insertion, result); + reportLine("clock source latency", insertion, clk_time + insertion, + early_late, result); } void @@ -2423,7 +2455,9 @@ ReportPath::reportPathLine(const Path *path, const char *what = descriptionField(vertex); const TransRiseFall *tr = path->transition(this); bool is_driver = network_->isDriver(pin); - DcalcAnalysisPt *dcalc_ap = path->pathAnalysisPt(this)->dcalcAnalysisPt(); + PathAnalysisPt *path_ap = path->pathAnalysisPt(this); + const EarlyLate *early_late = path_ap->pathMinMax(); + DcalcAnalysisPt *dcalc_ap = path_ap->dcalcAnalysisPt(); DcalcAPIndex ap_index = dcalc_ap->index(); Slew slew = graph_->slew(vertex, tr, ap_index); float cap = field_blank_; @@ -2431,7 +2465,7 @@ ReportPath::reportPathLine(const Path *path, if (is_driver && field_capacitance_->enabled()) cap = loadCap(pin, tr, dcalc_ap); reportLine(what, cap, slew, field_blank_, - incr, time, false, tr, line_case, result); + incr, time, false, early_late, tr, line_case, result); } void @@ -2440,11 +2474,12 @@ ReportPath::reportRequired(const PathEnd *end, string &result) { Required req_time = end->requiredTimeOffset(this); + const EarlyLate *early_late = end->clkEarlyLate(this); ArcDelay margin = end->margin(this); if (end->minMax(this) == MinMax::max()) margin = -margin; - reportLine(margin_msg, margin, req_time, result); - reportLine("data required time", req_time, result); + reportLine(margin_msg, margin, req_time, early_late, result); + reportLine("data required time", req_time, early_late, result); reportDashLine(result); } @@ -2452,9 +2487,11 @@ void ReportPath::reportSlack(const PathEnd *end, string &result) { - reportLine("data required time", end->requiredTimeOffset(this), result); + const EarlyLate *early_late = end->pathEarlyLate(this); + reportLine("data required time", end->requiredTimeOffset(this), + early_late->opposite(), result); reportLineNegative("data arrival time", end->dataArrivalTimeOffset(this), - result); + early_late, result); reportDashLine(result); reportSlack(end->slack(this), result); } @@ -2463,10 +2500,10 @@ void ReportPath::reportSlack(Slack slack, string &result) { - if (slack < 0.0) - reportLine("slack (VIOLATED)", slack, result); - else - reportLine("slack (MET)", slack, result); + const char *msg = (slack < 0.0) + ? "slack (VIOLATED)" + : "slack (MET)"; + reportLine(msg, slack, EarlyLate::early(), result); } void @@ -2477,7 +2514,8 @@ ReportPath::reportCommonClkPessimism(const PathEnd *end, if (sdc_->crprEnabled()) { float pessimism = end->commonClkPessimism(this); clk_arrival += pessimism; - reportLine("clock reconvergence pessimism", pessimism, clk_arrival, result); + reportLine("clock reconvergence pessimism", pessimism, clk_arrival, + end->clkEarlyLate(this), result); } } @@ -2486,15 +2524,17 @@ ReportPath::reportClkUncertainty(const PathEnd *end, Arrival &clk_arrival, string &result) { + const EarlyLate *early_late = end->clkEarlyLate(this); float uncertainty = end->targetNonInterClkUncertainty(this); clk_arrival += uncertainty; if (uncertainty != 0.0) - reportLine("clock uncertainty", uncertainty, clk_arrival, result); + reportLine("clock uncertainty", uncertainty, clk_arrival, + early_late, result); float inter_uncertainty = end->interClkUncertainty(this); clk_arrival += inter_uncertainty; if (inter_uncertainty != 0.0) reportLine("inter-clock uncertainty", inter_uncertainty, clk_arrival, - result); + early_late, result); } //////////////////////////////////////////////////////////////// @@ -2538,6 +2578,7 @@ ReportPath::reportPath1(const Path *path, latch_time_given, latch_enable_path); if (!latch_enable_path.isNull()) { + const EarlyLate *early_late = latch_enable_path.minMax(this); latch_enable_time = search_->clkPathArrival(&latch_enable_path); if (reportClkPath()) { PathExpanded enable_expanded(&latch_enable_path, this); @@ -2548,9 +2589,10 @@ ReportPath::reportPath1(const Path *path, Arrival time = latch_enable_time + latch_time_given; Arrival incr = latch_time_given; if (incr >= 0.0) - reportLine("time given to startpoint", incr, time, result); + reportLine("time given to startpoint", incr, time, early_late, result); else - reportLine("time borrowed from startpoint", incr, time, result); + reportLine("time borrowed from startpoint", incr, time, + early_late, result); // Override latch D arrival with enable + given. reportPathLine(expanded.path(0), delay_zero, time, "latch_D", result); bool propagated_clk = path->clkInfo(search_)->isPropagated(); @@ -2646,7 +2688,7 @@ ReportPath::reportPath5(const Path *path, Vertex *vertex = path1->vertex(this); Pin *pin = vertex->pin(); Arrival time = path1->arrival(this) + time_offset; - Arrival incr(0.0); + float incr = 0.0; const char *line_case = NULL; bool is_clk_start = network_->isRegClkPin(pin); bool is_clk = path1->isClock(search_); @@ -2671,7 +2713,8 @@ ReportPath::reportPath5(const Path *path, // from the input to the loads. Report the wire delay on the // input pin instead. Arrival next_time = next_path->arrival(this) + time_offset; - incr = next_time - time; + incr = delayMeanSigma(next_time, min_max) + - delayMeanSigma(time, min_max); time = next_time; line_case = "input_drive"; } @@ -2718,11 +2761,13 @@ ReportPath::reportPath5(const Path *path, line_case = "clk_ideal"; } else if (is_clk && !is_clk_start) { - incr = time - prev_time; + incr = delayMeanSigma(time, min_max) + - delayMeanSigma(prev_time, min_max); line_case = "clk_prop"; } else { - incr = time - prev_time; + incr = delayMeanSigma(time, min_max) + - delayMeanSigma(prev_time, min_max); line_case = "normal"; } if (report_input_pin_ @@ -2742,7 +2787,7 @@ ReportPath::reportPath5(const Path *path, if (report_net_ && is_driver) { // Capacitance field is reported on the net line. reportLine(what, field_blank_, slew, field_blank_, - incr, time, false, tr, line_case, result); + incr, time, false, min_max, tr, line_case, result); if (network_->isTopLevelPort(pin)) { const char *pin_name = cmd_network_->pathName(pin); what = stringPrintTmp(strlen(" (net)") @@ -2763,12 +2808,12 @@ ReportPath::reportPath5(const Path *path, } float fanout = drvrFanout(vertex, min_max); reportLine(what, cap, field_blank_, fanout, - field_blank_, field_blank_, false, NULL, + field_blank_, field_blank_, false, min_max, NULL, line_case, result); } else reportLine(what, cap, slew, field_blank_, - incr, time, false, tr, line_case, result); + incr, time, false, min_max, tr, line_case, result); prev_time = time; } } @@ -2870,6 +2915,7 @@ ReportPath::reportInputExternalDelay(const Path *first_path, if (!pathFromClkPin(first_path, first_pin)) { const TransRiseFall *tr = first_path->transition(this); Arrival time = first_path->arrival(this) + time_offset; + const EarlyLate *early_late = first_path->minMax(this); InputDelay *input_delay = pathInputDelay(first_path); if (input_delay) { const Pin *ref_pin = input_delay->refPin(); @@ -2890,10 +2936,11 @@ ReportPath::reportInputExternalDelay(const Path *first_path, } float input_arrival = input_delay->delays()->value(tr, first_path->minMax(this)); - reportLine("input external delay", input_arrival, time, tr, result); + reportLine("input external delay", input_arrival, time, + early_late, tr, result); } else if (network_->isTopLevelPort(first_pin)) - reportLine("input external delay", 0.0, time, tr, result); + reportLine("input external delay", 0.0, time, early_late, tr, result); } } @@ -2970,42 +3017,56 @@ ReportPath::reportPathHeader(string &result) void ReportPath::reportLine(const char *what, Delay total, + const EarlyLate *early_late, string &result) { reportLine(what, field_blank_, field_blank_, field_blank_, - field_blank_, total, false, NULL, NULL, result); + field_blank_, total, false, early_late, NULL, NULL, result); } // Report negative total. void ReportPath::reportLineNegative(const char *what, Delay total, + const EarlyLate *early_late, string &result) { reportLine(what, field_blank_, field_blank_, field_blank_, - field_blank_, total, true, NULL, NULL, result); + field_blank_, total, true, early_late, NULL, NULL, result); } // Report total, and transition suffix. void ReportPath::reportLine(const char *what, Delay total, + const EarlyLate *early_late, const TransRiseFall *tr, string &result) { reportLine(what, field_blank_, field_blank_, field_blank_, - field_blank_, total, false, tr, NULL, result); + field_blank_, total, false, early_late, tr, NULL, result); } // Report increment, and total. void ReportPath::reportLine(const char *what, - Delay incr, - Delay total, + float incr, + float total, string &result) { reportLine(what, field_blank_, field_blank_, field_blank_, - incr, total, false, NULL, NULL, result); + incr, total, false, NULL, NULL, NULL, result); +} + +void +ReportPath::reportLine(const char *what, + Delay incr, + Delay total, + const EarlyLate *early_late, + string &result) +{ + reportLine(what, field_blank_, field_blank_, field_blank_, + incr, total, false, early_late, NULL, NULL, result); } // Report increment, total, and transition suffix. @@ -3013,11 +3074,12 @@ void ReportPath::reportLine(const char *what, Delay incr, Delay total, + const EarlyLate *early_late, const TransRiseFall *tr, string &result) { reportLine(what, field_blank_, field_blank_, field_blank_, - incr, total, false, tr, NULL, result); + incr, total, false, early_late, tr, NULL, result); } // Report slew, increment, and total. @@ -3026,10 +3088,11 @@ ReportPath::reportLine(const char *what, Slew slew, Delay incr, Delay total, + const EarlyLate *early_late, string &result) { reportLine(what, field_blank_, slew, field_blank_, - incr, total, false, NULL, NULL, result); + incr, total, false, early_late, NULL, NULL, result); } void @@ -3040,6 +3103,7 @@ ReportPath::reportLine(const char *what, Delay incr, Delay total, bool total_with_minus, + const EarlyLate *early_late, const TransRiseFall *tr, const char *line_case, string &result) @@ -3070,16 +3134,14 @@ ReportPath::reportLine(const char *what, else if (field == field_capacitance_) reportField(cap, field, result); else if (field == field_slew_) - reportField(slew, field, result); + reportFieldDelay(slew, early_late, field, result); else if (field == field_incr_) - reportField(incr, field, result); + reportFieldDelay(incr, early_late, field, result); else if (field == field_total_) { - if (total == field_blank_) - reportFieldBlank(field, result); - else if (total_with_minus) - reportFieldTimeMinus(total, result); + if (total_with_minus) + reportFieldDelayMinus(total, early_late, field, result); else - reportFieldTime(total, result); + reportFieldDelay(total, early_late, field, result); } else if (field == field_edge_) { if (tr) { @@ -3105,32 +3167,35 @@ ReportPath::reportLine(const char *what, void ReportPath::reportLineTotal(const char *what, Delay incr, + const EarlyLate *early_late, string &result) { - reportLineTotal1(what, incr, false, result); + reportLineTotal1(what, incr, false, early_late, result); } // Only the total field and always with leading minus sign. void ReportPath::reportLineTotalMinus(const char *what, Delay decr, + const EarlyLate *early_late, string &result) { - reportLineTotal1(what, decr, true, result); + reportLineTotal1(what, decr, true, early_late, result); } void ReportPath::reportLineTotal1(const char *what, Delay incr, bool incr_with_minus, + const EarlyLate *early_late, string &result) { reportDescription(what, result); result += ' '; if (incr_with_minus) - reportFieldTimeMinus(incr, result); + reportFieldDelayMinus(incr, early_late, field_total_, result); else - reportFieldTime(incr, result); + reportFieldDelay(incr, early_late, field_total_, result); reportEndOfLine(result); } @@ -3162,35 +3227,84 @@ ReportPath::reportDescription(const char *what, } void -ReportPath::reportSpaceFieldTime(Delay value, - string &result) +ReportPath::reportFieldTime(float value, + ReportField *field, + string &result) { - result += ' '; - reportFieldTime(value, result); + if (delayAsFloat(value) == field_blank_) + reportFieldBlank(field, result); + else { + const char *str = units_->timeUnit()->asString(value, digits_); + if (stringEq(str, minus_zero_)) + // Filter "-0.00" fields. + str = plus_zero_; + reportField(str, field, result); + } } void -ReportPath::reportFieldTime(Delay value, - string &result) +ReportPath::reportSpaceFieldTime(float value, + string &result) { - const char *field = units_->timeUnit()->asString(delayAsFloat(value), - digits_); - if (stringEq(field, minus_zero_)) + result += ' '; + reportFieldTime(value, field_total_, result); +} + +void +ReportPath::reportSpaceFieldDelay(Delay value, + const EarlyLate *early_late, + string &result) +{ + result += ' '; + reportTotalDelay(value, early_late, result); +} + +void +ReportPath::reportTotalDelay(Delay value, + const EarlyLate *early_late, + string &result) +{ + const char *str = delayMeanSigmaString(value, early_late, units_, digits_); + if (stringEq(str, minus_zero_)) // Filter "-0.00" fields. - field = plus_zero_; - reportField(field, field_total_, result); + str = plus_zero_; + reportField(str, field_total_, result); } // Total time always with leading minus sign. void -ReportPath::reportFieldTimeMinus(Delay value, - string &result) +ReportPath::reportFieldDelayMinus(Delay value, + const EarlyLate *early_late, + ReportField *field, + string &result) { - const char *field=units_->timeUnit()->asString(-delayAsFloat(value),digits_); - if (stringEq(field, plus_zero_)) - // Force leading minus sign. - field = minus_zero_; - reportField(field, field_total_, result); + if (delayAsFloat(value) == field_blank_) + reportFieldBlank(field, result); + else { + float mean_sigma = delayMeanSigma(value, early_late); + const char *str = units_->timeUnit()->asString(-mean_sigma, digits_); + if (stringEq(str, plus_zero_)) + // Force leading minus sign. + str = minus_zero_; + reportField(str, field, result); + } +} + +void +ReportPath::reportFieldDelay(Delay value, + const EarlyLate *early_late, + ReportField *field, + string &result) +{ + if (delayAsFloat(value) == field_blank_) + reportFieldBlank(field, result); + else { + const char *str = delayMeanSigmaString(value, early_late, units_, digits_); + if (stringEq(str, minus_zero_)) + // Filter "-0.00" fields. + str = plus_zero_; + reportField(str, field, result); + } } void diff --git a/search/ReportPath.hh b/search/ReportPath.hh index 50765c1b..ac878772 100644 --- a/search/ReportPath.hh +++ b/search/ReportPath.hh @@ -307,9 +307,10 @@ protected: string &result); void reportClkSrcLatency(Arrival insertion, float clk_time, + const EarlyLate *early_late, string &result); void reportPathLine(const Path *path, - Arrival incr, + Delay incr, Arrival time, const char *line_case, string &result); @@ -385,27 +386,37 @@ protected: string &result); void reportLine(const char *what, Delay total, + const EarlyLate *early_late, string &result); void reportLineNegative(const char *what, Delay total, + const EarlyLate *early_late, string &result); void reportLine(const char *what, Delay total, + const EarlyLate *early_late, const TransRiseFall *tr, string &result); void reportLine(const char *what, - Delay incr, - Delay total, + float incr, + float total, string &result); void reportLine(const char *what, Delay incr, Delay total, + const EarlyLate *early_late, + string &result); + void reportLine(const char *what, + Delay incr, + Delay total, + const EarlyLate *early_late, const TransRiseFall *tr, string &result); void reportLine(const char *what, Slew slew, Delay incr, Delay total, + const EarlyLate *early_late, string &result); void reportLine(const char *what, float cap, @@ -414,28 +425,45 @@ protected: Delay incr, Delay total, bool total_with_minus, + const EarlyLate *early_late, const TransRiseFall *tr, const char *line_case, string &result); void reportLineTotal(const char *what, Delay incr, + const EarlyLate *early_late, string &result); void reportLineTotalMinus(const char *what, Delay decr, + const EarlyLate *early_late, string &result); void reportLineTotal1(const char *what, Delay incr, bool incr_with_minus, + const EarlyLate *early_late, string &result); void reportDashLineTotal(string &result); void reportDescription(const char *what, string &result); - void reportSpaceFieldTime(Delay value, - string &result); - void reportFieldTime(Delay value, + void reportFieldTime(float value, + ReportField *field, string &result); - void reportFieldTimeMinus(Delay value, + void reportSpaceFieldTime(float value, string &result); + void reportSpaceFieldDelay(Delay value, + const EarlyLate *early_late, + string &result); + void reportFieldDelayMinus(Delay value, + const EarlyLate *early_late, + ReportField *field, + string &result); + void reportTotalDelay(Delay value, + const EarlyLate *early_late, + string &result); + void reportFieldDelay(Delay value, + const EarlyLate *early_late, + ReportField *field, + string &result); void reportField(float value, ReportField *field, string &result); @@ -520,8 +548,8 @@ protected: const char *plus_zero_; const char *minus_zero_; - static const Delay field_blank_; - static const Delay field_skip_; + static const float field_blank_; + static const float field_skip_; private: DISALLOW_COPY_AND_ASSIGN(ReportPath); diff --git a/search/Search.cc b/search/Search.cc index 600ec940..39a6fae7 100644 --- a/search/Search.cc +++ b/search/Search.cc @@ -227,6 +227,7 @@ Search::Search(StaState *sta) : void Search::init(StaState *sta) { + crpr_path_pruning_enabled_ = true; report_unconstrained_paths_ = false; search_adj_ = new SearchThru(NULL, sta); eval_pred_ = new EvalPred(sta); @@ -292,6 +293,7 @@ Search::~Search() void Search::clear() { + crpr_path_pruning_enabled_ = true; clk_arrivals_valid_ = false; arrivals_exist_ = false; arrivals_at_endpoints_exist_ = false; @@ -315,6 +317,18 @@ Search::clear() found_downstream_clk_pins_ = false; } +bool +Search::crprPathPruningEnabled() const +{ + return crpr_path_pruning_enabled_; +} + +void +Search::setCrprpathPruningEnabled(bool enabled) +{ + crpr_path_pruning_enabled_ = enabled; +} + void Search::setReportUnconstrainedPaths(bool report) { @@ -1086,9 +1100,9 @@ ArrivalVisitor::visit(Vertex *vertex) visitFaninPaths(vertex); if (crpr_active_ + && search->crprPathPruningEnabled() && !has_fanin_one_) pruneCrprArrivals(); - // Insert paths that originate here but if (!network->isTopLevelPort(pin) && sdc->hasInputDelay(pin)) @@ -1538,11 +1552,11 @@ Search::seedClkArrival(const Pin *pin, bool latency_exists; // Check for clk pin latency. sdc_->clockLatency(clk, pin, tr, min_max, - latency, latency_exists); + latency, latency_exists); if (!latency_exists) { // Check for clk latency (lower priority). sdc_->clockLatency(clk, tr, min_max, - latency, latency_exists); + latency, latency_exists); if (latency_exists) { // Propagated pin overrides latency on clk. if (sdc_->isPropagatedClock(pin)) { @@ -1712,8 +1726,7 @@ Search::seedInputArrival(const Pin *pin, { bool has_arrival = false; // There can be multiple arrivals for a pin with wrt different clocks. - PinInputDelayIterator *arrival_iter = - sdc_->inputDelayVertexIterator(pin); + PinInputDelayIterator *arrival_iter = sdc_->inputDelayVertexIterator(pin); TagGroupBldr tag_bldr(true, this); tag_bldr.init(vertex); while (arrival_iter->hasNext()) { @@ -1762,8 +1775,7 @@ Search::seedInputArrival1(const Pin *pin, TagGroupBldr *tag_bldr) { // There can be multiple arrivals for a pin with wrt different clocks. - PinInputDelayIterator *arrival_iter= - sdc_->inputDelayVertexIterator(pin); + PinInputDelayIterator *arrival_iter = sdc_->inputDelayVertexIterator(pin); while (arrival_iter->hasNext()) { InputDelay *input_delay = arrival_iter->next(); Clock *input_clk = input_delay->clock(); @@ -1864,8 +1876,7 @@ Search::inputDelayRefPinArrival(Path *ref_path, const EarlyLate *early_late = min_max; // Input delays from ideal clk reference pins include clock // insertion delay but not latency. - ref_insertion = sdc_->clockInsertion(clk, clk_tr, min_max, - early_late); + ref_insertion = sdc_->clockInsertion(clk, clk_tr, min_max, early_late); ref_arrival = clk_edge->time() + ref_insertion; ref_latency = 0.0; } @@ -2311,7 +2322,7 @@ Search::clkPathArrival(const Path *clk_path, return clk_path->arrival(this); } -float +Arrival Search::pathClkPathArrival(const Path *path) const { PathRef src_clk_path; @@ -2392,7 +2403,7 @@ Search::fromRegClkTag(const Pin *from_pin, { ExceptionStateSet *states = NULL; if (sdc_->exceptionFromStates(from_pin, from_tr, clk, clk_tr, - min_max, states)) { + min_max, states)) { // Hack for filter -from reg/Q. sdc_->filterRegQStates(to_pin, to_tr, min_max, states); return findTag(to_tr, path_ap, clk_info, false, NULL, false, states, true); @@ -2535,7 +2546,7 @@ Search::thruClkInfo(PathVertex *from_path, float latency; bool exists; sdc_->clockLatency(from_clk, to_pin, clk_tr, min_max, - latency, exists); + latency, exists); if (exists) { // Latency on pin has precidence over fanin or hierarchical // pin latency. @@ -2546,7 +2557,7 @@ Search::thruClkInfo(PathVertex *from_path, else { // Check for hierarchical pin latency thru edge. sdc_->clockLatency(edge, clk_tr, min_max, - latency, exists); + latency, exists); if (exists) { to_latency = latency; to_clk_prop = false; @@ -2618,8 +2629,7 @@ Search::mutateTag(Tag *from_tag, } } // Get the set of -thru exceptions starting at to_pin/edge. - sdc_->exceptionThruStates(from_pin, to_pin, to_tr, min_max, - new_states); + sdc_->exceptionThruStates(from_pin, to_pin, to_tr, min_max, new_states); if (new_states || state_change) { // Second pass to apply state changes and add updated existing // states to new states. @@ -2659,8 +2669,7 @@ Search::mutateTag(Tag *from_tag, } else // Get the set of -thru exceptions starting at to_pin/edge. - sdc_->exceptionThruStates(from_pin, to_pin, to_tr, min_max, - new_states); + sdc_->exceptionThruStates(from_pin, to_pin, to_tr, min_max, new_states); if (new_states) return findTag(to_tr, path_ap, to_clk_info, to_is_clk, @@ -3058,8 +3067,8 @@ Search::timingDerate(Vertex *from_vertex, const Pin *pin = from_vertex->pin(); if (role->isWire()) { const TransRiseFall *tr = arc->toTrans()->asRiseFall(); - return sdc_->timingDerateNet(pin, derate_clk_data, - tr, path_ap->pathMinMax()); + return sdc_->timingDerateNet(pin, derate_clk_data, tr, + path_ap->pathMinMax()); } else { TimingDerateType derate_type; @@ -3072,9 +3081,8 @@ Search::timingDerate(Vertex *from_vertex, derate_type = timing_derate_cell_delay; tr = arc->fromTrans()->asRiseFall(); } - return sdc_->timingDerateInstance(pin, derate_type, - derate_clk_data, tr, - path_ap->pathMinMax()); + return sdc_->timingDerateInstance(pin, derate_type, derate_clk_data, tr, + path_ap->pathMinMax()); } } @@ -3536,12 +3544,13 @@ RequiredVisitor::visitFromToPath(const Pin *, VertexPathIterator to_iter(to_vertex, to_tr, path_ap, sta_); while (to_iter.hasNext()) { PathVertex *to_path = to_iter.next(); - if (tagMatchNoCrpr(to_path->tag(sta_), to_tag)) { + Tag *to_path_tag = to_path->tag(sta_); + if (tagMatchNoCrpr(to_path_tag, to_tag)) { Required to_required = to_path->required(sta_); Required from_required = to_required - arc_delay; debugPrint2(debug, "search", 3, " to tag %2u: %s\n", - to_tag->index(), - to_tag->asString(sta_)); + to_path_tag->index(), + to_path_tag->asString(sta_)); debugPrint5(debug, "search", 3, " %s - %s = %s %s %s\n", delayAsString(to_required, sta_), delayAsString(arc_delay, sta_), @@ -3676,7 +3685,7 @@ Search::exceptionTo(ExceptionPathType type, if ((type == exception_type_any || exception->type() == type) && sdc_->isCompleteTo(state, pin, tr, clk_edge, min_max, - match_min_max_exactly, require_to_pin) + match_min_max_exactly, require_to_pin) && (hi_priority_exception == NULL || priority > hi_priority || (priority == hi_priority diff --git a/search/Search.hh b/search/Search.hh index 3e58f6eb..7a88ca6a 100644 --- a/search/Search.hh +++ b/search/Search.hh @@ -68,6 +68,11 @@ public: virtual void copyState(const StaState *sta); // Reset to virgin state. void clear(); + // When enabled, non-critical path arrivals are pruned to improve + // run time and reduce memory. The side-effect is that slacks for + // non-critical paths on intermediate pins may be incorrect. + bool crprPathPruningEnabled() const; + void setCrprpathPruningEnabled(bool enabled); // Report unconstrained paths. bool reportUnconstrainedPaths() const { return report_unconstrained_paths_; } void setReportUnconstrainedPaths(bool report); @@ -132,7 +137,7 @@ public: const MinMax *min_max, const PathAnalysisPt *path_ap) const; // Clock arrival at the path source/launch point. - float pathClkPathArrival(const Path *path) const; + Arrival pathClkPathArrival(const Path *path) const; PathGroup *pathGroup(const PathEnd *path_end) const; void deletePathGroups(); @@ -571,6 +576,7 @@ protected: VisitPathEnds *visit_path_ends_; GatedClk *gated_clk_; Crpr *crpr_; + bool crpr_path_pruning_enabled_; Genclks *genclks_; }; diff --git a/search/Sta.cc b/search/Sta.cc index 72fc3de8..6ef6c813 100644 --- a/search/Sta.cc +++ b/search/Sta.cc @@ -67,6 +67,7 @@ #include "VisitPathGroupVertices.hh" #include "SdfWriter.hh" #include "Genclks.hh" +#include "Power.hh" #include "Sta.hh" namespace sta { @@ -272,6 +273,7 @@ Sta::Sta() : check_max_skews_(NULL), clk_skews_(NULL), report_path_(NULL), + power_(NULL), link_make_black_boxes_(true), update_genclks_(false) { @@ -348,6 +350,8 @@ Sta::updateComponentsState() report_path_->copyState(this); if (check_timing_) check_timing_->copyState(this); + if (power_) + power_->copyState(this); } void @@ -466,6 +470,12 @@ Sta::makeReportPath() report_path_ = new ReportPath(this); } +void +Sta::makePower() +{ + power_ = new Power(this); +} + void Sta::setSta(Sta *sta) { @@ -3185,9 +3195,9 @@ Sta::portExtPinCap(Port *port, int fanout; bool pin_exists, wire_exists, fanout_exists; sdc_->portExtCap(port, tr, min_max, - pin_cap, pin_exists, - wire_cap, wire_exists, - fanout, fanout_exists); + pin_cap, pin_exists, + wire_cap, wire_exists, + fanout, fanout_exists); if (pin_exists) return pin_cap; else @@ -3221,9 +3231,9 @@ Sta::portExtWireCap(Port *port, int fanout; bool pin_exists, wire_exists, fanout_exists; sdc_->portExtCap(port, tr, min_max, - pin_cap, pin_exists, - wire_cap, wire_exists, - fanout, fanout_exists); + pin_cap, pin_exists, + wire_cap, wire_exists, + fanout, fanout_exists); if (wire_exists) return wire_cap; else @@ -3244,8 +3254,7 @@ Sta::setPortExtWireCap(Port *port, MinMaxIterator mm_iter(min_max); while (mm_iter.hasNext()) { MinMax *mm = mm_iter.next(); - sdc_->setPortExtWireCap(port, subtract_pin_cap, tr1, - corner, mm, cap); + sdc_->setPortExtWireCap(port, subtract_pin_cap, tr1, corner, mm, cap); } } delaysInvalidFromFanin(port); @@ -3375,6 +3384,7 @@ Sta::readParasitics(const char *filename, Instance *instance, const MinMaxAll *min_max, bool increment, + bool pin_cap_included, bool keep_coupling_caps, float coupling_cap_factor, ReduceParasiticsTo reduce_to, @@ -3398,6 +3408,7 @@ Sta::readParasitics(const char *filename, const OperatingConditions *op_cond = sdc_->operatingConditions(cnst_min_max); bool success = readParasiticsFile(filename, instance, ap, increment, + pin_cap_included, keep_coupling_caps, coupling_cap_factor, reduce_to, delete_after_reduce, op_cond, corner, cnst_min_max, save, quiet, @@ -4860,4 +4871,39 @@ Sta::reportCheck(MaxSkewCheck *check, report_path_->reportCheck(check, verbose); } +//////////////////////////////////////////////////////////////// + +void +Sta::powerPreamble() +{ + // Use arrivals to find clocking info. + searchPreamble(); + search_->findAllArrivals(); + if (power_ == NULL) + makePower(); +} + +void +Sta::power(const Corner *corner, + // Return values. + PowerResult &total, + PowerResult &sequential, + PowerResult &combinational, + PowerResult ¯o, + PowerResult &pad) +{ + powerPreamble(); + power_->power(corner, total, sequential, combinational, macro, pad); +} + +void +Sta::power(const Instance *inst, + const Corner *corner, + // Return values. + PowerResult &result) +{ + powerPreamble(); + power_->power(inst, corner, result); +} + } // namespace diff --git a/search/Sta.hh b/search/Sta.hh index c8eedaca..17e3cf42 100644 --- a/search/Sta.hh +++ b/search/Sta.hh @@ -55,6 +55,8 @@ class SearchPred; class Corner; class ClkSkews; class ReportField; +class Power; +class PowerResult; typedef InstanceSeq::Iterator SlowDrvrIterator; typedef Vector CheckError; @@ -669,6 +671,7 @@ public: void reportCheck(MaxSkewCheck *check, bool verbose); + //////////////////////////////////////////////////////////////// // User visible but non SDC commands. @@ -974,9 +977,11 @@ public: Slack vertexSlack(Vertex *vertex, const TransRiseFall *tr, const PathAnalysisPt *path_ap); + // Slew for one delay calc analysis pt(corner). Slew vertexSlew(Vertex *vertex, const TransRiseFall *tr, const DcalcAnalysisPt *dcalc_ap); + // Slew across all corners. Slew vertexSlew(Vertex *vertex, const TransRiseFall *tr, const MinMax *min_max); @@ -1021,6 +1026,7 @@ public: Instance *instance, const MinMaxAll *min_max, bool increment, + bool pin_cap_included, bool keep_coupling_caps, float coupling_cap_factor, ReduceParasiticsTo reduce_to, @@ -1143,6 +1149,21 @@ public: Vertex *vertex, LibertyCell *to_cell); + // Power API. + Power *power() { return power_; } + const Power *power() const { return power_; } + void power(const Corner *corner, + // Return values. + PowerResult &total, + PowerResult &sequential, + PowerResult &combinational, + PowerResult ¯o, + PowerResult &pad); + void power(const Instance *inst, + const Corner *corner, + // Return values. + PowerResult &result); + protected: // Default constructors that are called by makeComponents in the Sta // constructor. These can be redefined by a derived class to @@ -1168,6 +1189,7 @@ protected: virtual void makeCheckMinPeriods(); virtual void makeCheckMaxSkews(); virtual void makeReportPath(); + virtual void makePower(); virtual void makeObservers(); NetworkEdit *networkCmdEdit(); @@ -1254,6 +1276,7 @@ protected: Corner *corner, const MinMax *min_max); void parasiticsChangedAfter(); + void powerPreamble(); CmdNamespace cmd_namespace_; Instance *current_instance_; @@ -1264,6 +1287,7 @@ protected: CheckMaxSkews *check_max_skews_; ClkSkews *clk_skews_; ReportPath *report_path_; + Power *power_; Tcl_Interp *tcl_interp_; bool link_make_black_boxes_; bool update_genclks_; diff --git a/tcl/Cmds.tcl b/tcl/Cmds.tcl index dc6d1c6c..8ee51ec6 100644 --- a/tcl/Cmds.tcl +++ b/tcl/Cmds.tcl @@ -1246,6 +1246,22 @@ proc parse_corner { keys_var } { } } +proc parse_corner_or_default { keys_var } { + upvar 1 $keys_var keys + + if { [info exists keys(-corner)] } { + set corner_name $keys(-corner) + set corner [find_corner $corner_name] + if { $corner == "NULL" } { + sta_error "$corner_name is not the name of process corner." + } else { + return $corner + } + } else { + return [default_corner] + } +} + proc parse_corner_or_all { keys_var } { upvar 1 $keys_var keys diff --git a/tcl/Graph.tcl b/tcl/Graph.tcl index 0e2084dc..95d09c0a 100644 --- a/tcl/Graph.tcl +++ b/tcl/Graph.tcl @@ -121,8 +121,8 @@ proc report_edge_ { edge vertex_from_name_proc vertex_to_name_proc } { set iter [$edge timing_arc_iterator] while {[$iter has_next]} { set arc [$iter next] - set delays [$edge arc_delays $arc] - set delays_fmt [format_times $delays $sta_report_default_digits] + set delays [$edge arc_delay_strings $arc $sta_report_default_digits] + set delays_fmt [format_delays $delays] set disable_reason "" if { [timing_arc_disabled $edge $arc] } { set disable_reason " disabled" @@ -144,6 +144,18 @@ proc format_times { values digits } { return $result } +# Separate delay list elements with colons. +proc format_delays { values } { + set result "" + foreach value $values { + if { $result != "" } { + append result ":" + } + append result $value + } + return $result +} + proc edge_disable_reason { edge } { set disables "" if [$edge is_disabled_constraint] { diff --git a/tcl/Liberty.tcl b/tcl/Liberty.tcl index 7ef19702..1da2995c 100644 --- a/tcl/Liberty.tcl +++ b/tcl/Liberty.tcl @@ -33,46 +33,5 @@ proc read_liberty { args } { read_liberty_cmd $filename $corner $min_max $infer_latches } -################################################################ - -define_hidden_cmd_args "report_lib_cell_power" {lib_cell} - -proc report_lib_cell_power { args } { - global sta_report_default_digits - - check_argc_eq3 "report_internal_power" $args - set cells [get_lib_cells [lindex $args 0]] - set slew [time_ui_sta [lindex $args 1]] - set cap [capacitance_ui_sta [lindex $args 2]] - - foreach cell $cells { - puts "[$cell name] Leakage Power" - set leakage_iter [$cell leakage_power_iterator] - while {[$leakage_iter has_next]} { - set leakage [$leakage_iter next] - puts "[format_power [$leakage power] 5] [$leakage when]" - } - $leakage_iter finish - - puts "[$cell name] Internal Power" - set internal_iter [$cell internal_power_iterator] - while {[$internal_iter has_next]} { - set internal [$internal_iter next] - set port_name [[$internal port] name] - set related_port [$internal related_port] - if { $related_port != "NULL" } { - set related_port_name [$related_port name] - } else { - set related_port_name "" - } - set digits $sta_report_default_digits - set rise [format_power [$internal power rise $slew $cap] $digits] - set fall [format_power [$internal power fall $slew $cap] $digits] - puts "$port_name $related_port_name [rise_short_name] $rise [fall_short_name] $fall [$internal when]" - } - $internal_iter finish - } -} - # sta namespace end } diff --git a/tcl/Network.tcl b/tcl/Network.tcl index 4a4acb33..a317e577 100644 --- a/tcl/Network.tcl +++ b/tcl/Network.tcl @@ -251,10 +251,11 @@ define_cmd_args "report_net" \ proc_redirect report_net { global sta_report_default_digits - parse_key_args "report_net" args keys {-digits -significant_digits} \ + parse_key_args "report_net" args keys {-corner -digits -significant_digits} \ flags {-connections -verbose -hier_pins} check_argc_eq1 "report_net" $args + set corner [parse_corner keys] set digits $sta_report_default_digits if { [info exists keys(-digits)] } { set digits $keys(-digits) @@ -269,13 +270,13 @@ proc_redirect report_net { set net_path [lindex $args 0] set net [find_net $net_path] if { $net != "NULL" } { - report_net1 $net $connections $verbose $hier_pins $digits + report_net1 $net $connections $verbose $hier_pins $corner $digits } else { set pin [find_pin $net_path] if { $pin != "NULL" } { set net [$pin net] if { $net != "NULL" } { - report_net1 $net $connections $verbose $hier_pins $digits + report_net1 $net $connections $verbose $hier_pins $corner $digits } else { sta_error "net $net_path not found." } @@ -287,27 +288,27 @@ proc_redirect report_net { proc report_net_ { net } { global sta_report_default_digits - report_net1 $net 1 1 1 $sta_report_default_digits + report_net1 $net 1 1 1 $corner $sta_report_default_digits } -proc report_net1 { net connections verbose hier_pins digits } { +proc report_net1 { net connections verbose hier_pins corner digits } { puts "Net [$net path_name]" if {$connections} { set pins [net_connected_pins_sorted $net] if {$verbose} { - report_net_caps $net $pins $digits + report_net_caps $net $pins $corner $digits } puts "Driver pins" - report_net_pins $pins "is_driver" $verbose $digits + report_net_pins $pins "is_driver" $verbose $corner $digits puts "" puts "Load pins" - report_net_pins $pins "is_load" $verbose $digits + report_net_pins $pins "is_load" $verbose $corner $digits if {$hier_pins} { puts "" puts "Hierarchical pins" - report_net_pins $pins "is_hierarchical" $verbose $digits + report_net_pins $pins "is_hierarchical" $verbose $corner $digits } - report_net_other_pins $pins $verbose $digits + report_net_other_pins $pins $verbose $corner $digits } } @@ -323,8 +324,7 @@ proc net_connected_pins_sorted { net } { return $pins } -proc report_net_caps { net pins digits } { - set corner [default_corner] +proc report_net_caps { net pins corner digits } { report_net_cap $net "Pin" "pin_capacitance" $corner $digits report_net_cap $net "Wire" "wire_capacitance" $corner $digits report_net_cap $net "Total" "capacitance" $corner $digits @@ -356,15 +356,15 @@ proc report_net_cap { net caption cap_msg corner digits } { puts " $caption capacitance: [capacitances_str $cap_r_min $cap_r_max $cap_f_min $cap_f_max $digits]" } -proc report_net_pins { pins pin_pred verbose digits } { +proc report_net_pins { pins pin_pred verbose corner digits } { foreach pin $pins { if {[$pin $pin_pred]} { - report_net_pin $pin $verbose $digits + report_net_pin $pin $verbose $corner $digits } } } -proc report_net_other_pins { pins verbose digits } { +proc report_net_other_pins { pins verbose corner digits } { set header 0 foreach pin $pins { if { !([$pin is_driver] || [$pin is_load] || [$pin is_hierarchical]) } { @@ -373,12 +373,12 @@ proc report_net_other_pins { pins verbose digits } { puts "Other pins" set header 1 } - report_net_pin $pin $verbose $digits + report_net_pin $pin $verbose $corner $digits } } } -proc report_net_pin { pin verbose digits } { +proc report_net_pin { pin verbose corner digits } { if [$pin is_leaf] { set cell_name [[[$pin instance] cell] name] puts -nonewline " [$pin path_name] [$pin direction] ($cell_name)" diff --git a/tcl/Power.tcl b/tcl/Power.tcl new file mode 100644 index 00000000..ea2483e7 --- /dev/null +++ b/tcl/Power.tcl @@ -0,0 +1,138 @@ +# OpenSTA, Static Timing Analyzer +# Copyright (c) 2018, 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 . + +################################################################ +# +# Power commands. +# +################################################################ + +namespace eval sta { + +define_cmd_args "report_power" \ + { [-instances instances]\ + [-corner corner_name]]\ + [-digits digits]\ + [> filename] [>> filename] } + +proc_redirect report_power { + global sta_report_default_digits + + parse_key_args "report_power" args keys {-instances -corner -digits} flags {} 1 + + if [info exists keys(-digits)] { + set digits $keys(-digits) + check_positive_integer "-digits" $digits + } else { + set digits $sta_report_default_digits + } + set corner [parse_corner keys] + + if { [info exists keys(-instances)] } { + set insts [get_instance_error "-cell" $keys(-instances)] + foreach inst $insts { + report_power_inst $inst $corner $digits + } + } else { + report_power_design $corner $digits + } +} + +proc report_power_design { corner digits } { + set power_result [design_power $corner] + puts "Group Internal Switching Leakage Total" + puts " Power Power Power Power (mW)" + puts "-------------------------------------------------------------------" + set design_total [lindex $power_result 3] + report_power_row "Sequential" $power_result 4 $design_total $digits + report_power_row "Combinational" $power_result 8 $design_total $digits + report_power_row "Macro" $power_result 12 $design_total $digits + report_power_row "Pad" $power_result 16 $design_total $digits + puts "-------------------------------------------------------------------" + report_power_row "Total" $power_result 0 $design_total $digits + + puts -nonewline " " + report_power_col_percent $power_result 0 + report_power_col_percent $power_result 1 + report_power_col_percent $power_result 2 + puts "" +} + +proc report_power_row { type power_result index design_total digits } { + set internal [lindex $power_result [expr $index + 0]] + set switching [lindex $power_result [expr $index + 1]] + set leakage [lindex $power_result [expr $index + 2]] + set total [lindex $power_result [expr $index + 3]] + set percent [expr $total / $design_total * 100] + puts -nonewline [format "%-20s" $type] + report_power_col $internal $digits + report_power_col $switching $digits + report_power_col $leakage $digits + report_power_col $total $digits + puts [format " %5.1f%%" $percent] +} + +proc report_power_col { pwr digits } { + puts -nonewline [format "%10.${digits}f" [expr $pwr * 1e+3]] +} + +proc report_power_col_percent { power_result index } { + set total [lindex $power_result 3] + set col [lindex $power_result $index] + puts -nonewline [format "%9.1f%%" [expr $col / $total * 100]] +} + +proc report_power_inst { inst corner digits } { + puts "Instance: [$inst path_name]" + puts "Cell: [[$inst liberty_cell] name]" + puts "Liberty file: [[[$inst liberty_cell] liberty_library] filename]" + set power_result [instance_power $inst $corner] + set switching [lindex $power_result 0] + set internal [lindex $power_result 1] + set leakage [lindex $power_result 2] + set total [lindex $power_result 3] + report_power_line "Switching power" $switching $digits + report_power_line "Internal power" $internal $digits + report_power_line "Leakage power" $leakage $digits + report_power_line "Total power" $total $digits +} + +proc report_power_line { type pwr digits } { + puts [format "%-16s %.${digits}fmW" $type [expr $pwr * 1e+3]] +} + +set ::power_default_signal_toggle_rate 0.1 + +trace variable ::power_default_signal_toggle_rate "rw" \ + sta::trace_power_default_signal_toggle_rate + +proc trace_power_default_signal_toggle_rate { name1 name2 op } { + global power_default_signal_toggle_rate + + if { $op == "r" } { + set power_default_signal_toggle_rate [power_default_signal_toggle_rate] + } elseif { $op == "w" } { + if { [string is double $power_default_signal_toggle_rate] \ + && $power_default_signal_toggle_rate >= 0.0 } { + set_power_default_signal_toggle_rate $power_default_signal_toggle_rate + } else { + sta_error "power_default_signal_toggle_rate must be a positive float." + } + } +} + +# sta namespace end. +} diff --git a/tcl/Sdc.tcl b/tcl/Sdc.tcl index 60f95e6a..c70e6932 100644 --- a/tcl/Sdc.tcl +++ b/tcl/Sdc.tcl @@ -2356,20 +2356,19 @@ proc set_input_transition { args } { ################################################################ define_cmd_args "set_load" \ - {[-rise] [-fall] [-max] [-min] [-corner corner] [-subtract_pin_load]\ + {[-rise] [-fall] [-max] [-min] [-subtract_pin_load]\ [-pin_load] [-wire_load] capacitance objects} proc set_load { args } { - parse_key_args "set_load" args keys {} \ - flags {-rise -fall -min -max -corner -subtract_pin_load \ - -pin_load -wire_load} + parse_key_args "set_load" args keys {-corner} \ + flags {-rise -fall -min -max -subtract_pin_load -pin_load -wire_load}\ check_argc_eq2 "set_load" $args set pin_load [info exists flags(-pin_load)] set wire_load [info exists flags(-wire_load)] set subtract_pin_load [info exists flags(-subtract_pin_load)] - set corner [parse_corner keys] + set corner [parse_corner_or_default keys] set min_max [parse_min_max_all_check_flags flags] set tr [parse_rise_fall_flags flags] diff --git a/tcl/Search.tcl b/tcl/Search.tcl index 69e9e0e9..d0a55909 100644 --- a/tcl/Search.tcl +++ b/tcl/Search.tcl @@ -25,7 +25,42 @@ namespace eval sta { define_cmd_args "report_arrival" {pin} proc report_arrival { pin } { - report_wrt_clks $pin "arrivals_clk" + report_delays_wrt_clks $pin "arrivals_clk_delays" +} + +proc report_delays_wrt_clks { pin_arg what } { + set pin [get_port_pin_error "pin" $pin_arg] + foreach vertex [$pin vertices] { + if { $vertex != "NULL" } { + report_delays_wrt_clk $vertex $what "NULL" "rise" + report_delays_wrt_clk $vertex $what [default_arrival_clock] "rise" + set clk_iter [clock_iterator] + while {[$clk_iter has_next]} { + set clk [$clk_iter next] + report_delays_wrt_clk $vertex $what $clk "rise" + report_delays_wrt_clk $vertex $what $clk "fall" + } + $clk_iter finish + } + } +} + +proc report_delays_wrt_clk { vertex what clk clk_tr } { + global sta_report_default_digits + + set rise [$vertex $what rise $clk $clk_tr $sta_report_default_digits] + set fall [$vertex $what fall $clk $clk_tr $sta_report_default_digits] + # Filter INF/-INF arrivals. + if { !([delays_are_inf $rise] && [delays_are_inf $fall]) } { + set rise_fmt [format_delays $rise] + set fall_fmt [format_delays $fall] + if {$clk != "NULL"} { + set clk_str " ([$clk name] [rise_fall_short_name $clk_tr])" + } else { + set clk_str "" + } + puts "$clk_str r $rise_fmt f $fall_fmt" + } } proc report_wrt_clks { pin_arg what } { @@ -82,6 +117,16 @@ proc times_are_inf { times } { return 1 } +proc delays_are_inf { delays } { + foreach delay $delays { + if { !([string match "INF*" $delay] \ + || [string match "-INF*" $delay]) } { + return 0 + } + } + return 1 +} + ################################################################ # Note that -all and -tags are intentionally "hidden". @@ -221,7 +266,7 @@ proc parse_report_path_options { cmd args_var default_format define_cmd_args "report_required" {pin} proc report_required { pin } { - report_wrt_clks $pin "requireds_clk" + report_delays_wrt_clks $pin "requireds_clk_delays" } ################################################################ @@ -229,7 +274,7 @@ proc report_required { pin } { define_cmd_args "report_slack" {pin} proc report_slack { pin } { - report_wrt_clks $pin "slacks_clk" + report_delays_wrt_clks $pin "slacks_clk_delays" } ################################################################ diff --git a/tcl/StaTcl.i b/tcl/StaTcl.i index e1281337..a1d7ead3 100644 --- a/tcl/StaTcl.i +++ b/tcl/StaTcl.i @@ -49,8 +49,6 @@ #include "Transition.hh" #include "TimingRole.hh" #include "TimingArc.hh" -#include "InternalPower.hh" -#include "LeakagePower.hh" #include "EquivCells.hh" #include "Liberty.hh" #include "Network.hh" @@ -77,6 +75,7 @@ #include "SearchPred.hh" #include "PathAnalysisPt.hh" #include "ReportPath.hh" +#include "Power.hh" #include "Sta.hh" namespace sta { @@ -693,7 +692,10 @@ edgeDelayProperty(Edge *edge, DcalcAnalysisPt *dcalc_ap = corner->findDcalcAnalysisPt(min_max); ArcDelay arc_delay = sta->arcDelay(edge, arc, dcalc_ap); if (!delay_exists - || min_max->compare(arc_delay, delay)) + || ((min_max == MinMax::max() + && arc_delay > delay) + || (min_max == MinMax::min() + && arc_delay < delay))) delay = arc_delay; } } @@ -822,6 +824,16 @@ findEndpoints() return pins; } +void +pushPowerResultFloats(PowerResult &power, + TmpFloatSeq *floats) +{ + floats->push_back(power.internal()); + floats->push_back(power.switching()); + floats->push_back(power.leakage()); + floats->push_back(power.total()); +} + //////////////////////////////////////////////////////////////// void @@ -1510,11 +1522,11 @@ using namespace sta; %typemap(in) EarlyLate* { int length; char *arg = Tcl_GetStringFromObj($input, &length); - MinMax *min_max = MinMax::find(arg); - if (min_max) - $1 = min_max; + EarlyLate *early_late = EarlyLate::find(arg); + if (early_late) + $1 = early_late; else { - tclError(interp, "Error: %s not min or max.", arg); + tclError(interp, "Error: %s not early/min or late/max.", arg); return TCL_ERROR; } } @@ -1523,11 +1535,11 @@ using namespace sta; %typemap(in) EarlyLateAll* { int length; char *arg = Tcl_GetStringFromObj($input, &length); - MinMaxAll *min_max = MinMaxAll::find(arg); - if (min_max) - $1 = min_max; + EarlyLateAll *early_late = EarlyLateAll::find(arg); + if (early_late) + $1 = early_late; else { - tclError(interp, "Error: %s not min, max or min_max.", arg); + tclError(interp, "Error: %s not early/min, late/max or early_late/min_max.", arg); return TCL_ERROR; } } @@ -1735,26 +1747,6 @@ using namespace sta; Tcl_SetObjResult(interp, obj); } -%typemap(out) InternalPower* { - Tcl_Obj *obj=SWIG_NewInstanceObj($1, $1_descriptor, false); - Tcl_SetObjResult(interp, obj); -} - -%typemap(out) LibertyCellInternalPowerIterator* { - Tcl_Obj *obj=SWIG_NewInstanceObj($1, $1_descriptor, false); - Tcl_SetObjResult(interp, obj); -} - -%typemap(out) LeakagePower* { - Tcl_Obj *obj=SWIG_NewInstanceObj($1, $1_descriptor, false); - Tcl_SetObjResult(interp, obj); -} - -%typemap(out) LibertyCellLeakagePowerIterator* { - Tcl_Obj *obj=SWIG_NewInstanceObj($1, $1_descriptor, false); - Tcl_SetObjResult(interp, obj); -} - %typemap(out) CellTimingArcSetIterator* { Tcl_Obj *obj=SWIG_NewInstanceObj($1, $1_descriptor, false); Tcl_SetObjResult(interp, obj); @@ -2009,34 +2001,6 @@ private: ~CellTimingArcSetIterator(); }; -class InternalPower -{ -private: - InternalPower(); - ~InternalPower(); -}; - -class LibertyCellInternalPowerIterator -{ -private: - LibertyCellInternalPowerIterator(); - ~LibertyCellInternalPowerIterator(); -}; - -class LeakagePower -{ -private: - LeakagePower(); - ~LeakagePower(); -}; - -class LibertyCellLeakagePowerIterator -{ -private: - LibertyCellLeakagePowerIterator(); - ~LibertyCellLeakagePowerIterator(); -}; - class TimingArcSetArcIterator { private: @@ -4814,6 +4778,50 @@ report_slew_limit_verbose(Pin *pin, Sta::sta()->reportSlewLimitVerbose(pin, corner, min_max); } +//////////////////////////////////////////////////////////////// + +TmpFloatSeq * +design_power(const Corner *corner) +{ + PowerResult total, sequential, combinational, macro, pad; + Sta::sta()->power(corner, total, sequential, combinational, macro, pad); + FloatSeq *floats = new FloatSeq; + pushPowerResultFloats(total, floats); + pushPowerResultFloats(sequential, floats); + pushPowerResultFloats(combinational, floats); + pushPowerResultFloats(macro, floats); + pushPowerResultFloats(pad, floats); + return floats; +} + +TmpFloatSeq * +instance_power(Instance *inst, + const Corner *corner) +{ + PowerResult power; + Sta::sta()->power(inst, corner, power); + FloatSeq *floats = new FloatSeq; + floats->push_back(power.switching()); + floats->push_back(power.internal()); + floats->push_back(power.leakage()); + floats->push_back(power.total()); + return floats; +} + +float +power_default_signal_toggle_rate() +{ + return Sta::sta()->power()->defaultSignalToggleRate(); +} + +void +set_power_default_signal_toggle_rate(float rate) +{ + return Sta::sta()->power()->setDefaultSignalToggleRate(rate); +} + +//////////////////////////////////////////////////////////////// + EdgeSeq * disabled_edges_sorted() { @@ -5339,6 +5347,7 @@ find_cells_matching(const char *pattern, %extend LibertyLibrary { const char *name() { return self->name(); } +const char *filename() { return self->filename(); } const char *object_name() { return self->name(); } LibertyCell * @@ -5456,12 +5465,6 @@ liberty_port_iterator() { return self->libertyPortIterator(); } CellTimingArcSetIterator * timing_arc_set_iterator() { return self->timingArcSetIterator(); } -LibertyCellInternalPowerIterator * -internal_power_iterator() { return self->internalPowerIterator(); } - -LibertyCellLeakagePowerIterator * -leakage_power_iterator() { return self->leakagePowerIterator(); } - } // LibertyCell methods %extend CellPortIterator { @@ -5594,61 +5597,6 @@ TimingArc *next() { return self->next(); } void finish() { delete self; } } -%extend InternalPower { -LibertyPort *port() { return self->port(); } -LibertyPort *related_port() { return self->relatedPort(); } - -const char * -when() -{ - FuncExpr *when = self->when(); - if (when) - return when->asString(); - else - return ""; -} - -float -power(TransRiseFall *tr, - float in_slew, - float load_cap) -{ - Sta *sta = Sta::sta(); - Corner *corner = sta->corners()->defaultCorner(); - DcalcAnalysisPt *dcalc_ap = corner->findDcalcAnalysisPt(MinMax::max()); - const Pvt *pvt = dcalc_ap->operatingConditions(); - return self->power(tr, pvt, in_slew, load_cap); -} - -} // InternalPower methods - -%extend LibertyCellInternalPowerIterator { -bool has_next() { return self->hasNext(); } -InternalPower *next() { return self->next(); } -void finish() { delete self; } -} - -%extend LeakagePower { -float power() { return self->power(); } - -const char * -when() -{ - FuncExpr *when = self->when(); - if (when) - return when->asString(); - else - return ""; -} - -} // LeakagePower methods - -%extend LibertyCellLeakagePowerIterator { -bool has_next() { return self->hasNext(); } -LeakagePower *next() { return self->next(); } -void finish() { delete self; } -} - %extend Instance { const char *name() { return cmdLinkedNetwork()->name(self); } const char *object_name() { return cmdLinkedNetwork()->pathName(self); } @@ -5954,11 +5902,33 @@ arrivals_clk(const TransRiseFall *tr, PathAnalysisPtIterator ap_iter(sta); while (ap_iter.hasNext()) { PathAnalysisPt *path_ap = ap_iter.next(); - floats->push_back(delayAsFloat(sta->vertexArrival(self, tr, clk_edge, path_ap))); + floats->push_back(delayAsFloat(sta->vertexArrival(self, tr, clk_edge, + path_ap))); } return floats; } +StringSeq * +arrivals_clk_delays(const TransRiseFall *tr, + Clock *clk, + const TransRiseFall *clk_tr, + int digits) +{ + Sta *sta = Sta::sta(); + StringSeq *arrivals = new StringSeq; + const ClockEdge *clk_edge = NULL; + if (clk) + clk_edge = clk->edge(clk_tr); + PathAnalysisPtIterator ap_iter(sta); + while (ap_iter.hasNext()) { + PathAnalysisPt *path_ap = ap_iter.next(); + arrivals->push_back(delayAsString(sta->vertexArrival(self, tr, clk_edge, + path_ap), + sta->units(), digits)); + } + return arrivals; +} + TmpFloatSeq * requireds_clk(const TransRiseFall *tr, Clock *clk, @@ -5972,11 +5942,33 @@ requireds_clk(const TransRiseFall *tr, PathAnalysisPtIterator ap_iter(sta); while (ap_iter.hasNext()) { PathAnalysisPt *path_ap = ap_iter.next(); - floats->push_back(delayAsFloat(sta->vertexRequired(self,tr,clk_edge,path_ap))); + floats->push_back(delayAsFloat(sta->vertexRequired(self, tr, clk_edge, + path_ap))); } return floats; } +StringSeq * +requireds_clk_delays(const TransRiseFall *tr, + Clock *clk, + const TransRiseFall *clk_tr, + int digits) +{ + Sta *sta = Sta::sta(); + StringSeq *requireds = new StringSeq; + const ClockEdge *clk_edge = NULL; + if (clk) + clk_edge = clk->edge(clk_tr); + PathAnalysisPtIterator ap_iter(sta); + while (ap_iter.hasNext()) { + PathAnalysisPt *path_ap = ap_iter.next(); + requireds->push_back(delayAsString(sta->vertexRequired(self, tr, clk_edge, + path_ap), + sta->units(), digits)); + } + return requireds; +} + TmpFloatSeq * slacks(TransRiseFall *tr) { @@ -6004,11 +5996,33 @@ slacks_clk(const TransRiseFall *tr, PathAnalysisPtIterator ap_iter(sta); while (ap_iter.hasNext()) { PathAnalysisPt *path_ap = ap_iter.next(); - floats->push_back(delayAsFloat(sta->vertexSlack(self, tr, clk_edge, path_ap))); + floats->push_back(delayAsFloat(sta->vertexSlack(self, tr, clk_edge, + path_ap))); } return floats; } +StringSeq * +slacks_clk_delays(const TransRiseFall *tr, + Clock *clk, + const TransRiseFall *clk_tr, + int digits) +{ + Sta *sta = Sta::sta(); + StringSeq *slacks = new StringSeq; + const ClockEdge *clk_edge = NULL; + if (clk) + clk_edge = clk->edge(clk_tr); + PathAnalysisPtIterator ap_iter(sta); + while (ap_iter.hasNext()) { + PathAnalysisPt *path_ap = ap_iter.next(); + slacks->push_back(delayAsString(sta->vertexSlack(self, tr, clk_edge, + path_ap), + sta->units(), digits)); + } + return slacks; +} + VertexPathIterator * path_iterator(const TransRiseFall *tr, const MinMax *min_max) @@ -6070,6 +6084,21 @@ arc_delays(TimingArc *arc) return floats; } +StringSeq * +arc_delay_strings(TimingArc *arc, + int digits) +{ + Sta *sta = Sta::sta(); + StringSeq *delays = new StringSeq; + DcalcAnalysisPtIterator ap_iter(sta); + while (ap_iter.hasNext()) { + DcalcAnalysisPt *dcalc_ap = ap_iter.next(); + delays->push_back(delayAsString(sta->arcDelay(self, arc, dcalc_ap), + sta->units(), digits)); + } + return delays; +} + bool delay_annotated(TimingArc *arc, const Corner *corner, @@ -6085,7 +6114,7 @@ arc_delay(TimingArc *arc, const MinMax *min_max) { DcalcAnalysisPt *dcalc_ap = corner->findDcalcAnalysisPt(min_max); - return Sta::sta()->arcDelay(self, arc, dcalc_ap); + return delayAsFloat(Sta::sta()->arcDelay(self, arc, dcalc_ap)); } const char * diff --git a/util/MinMax.cc b/util/MinMax.cc index e495d6e0..398ddf29 100644 --- a/util/MinMax.cc +++ b/util/MinMax.cc @@ -90,9 +90,11 @@ MinMax::opposite() const MinMax * MinMax::find(const char *min_max) { - if (stringEq(min_max, "min")) + if (stringEq(min_max, "min") + || stringEq(min_max, "early")) return min(); - else if (stringEq(min_max, "max")) + else if (stringEq(min_max, "max") + || stringEq(min_max, "late")) return max(); else return NULL; @@ -172,9 +174,11 @@ MinMaxAll::matches(const MinMaxAll *min_max) const MinMaxAll * MinMaxAll::find(const char *min_max) { - if (stringEq(min_max, "min")) + if (stringEq(min_max, "min") + || stringEq(min_max, "early")) return min(); - else if (stringEq(min_max, "max")) + else if (stringEq(min_max, "max") + || stringEq(min_max, "late")) return max(); else if (stringEq(min_max, "all") || stringEq(min_max, "min_max") diff --git a/util/MinMax.hh b/util/MinMax.hh index 289fd1aa..763fe405 100644 --- a/util/MinMax.hh +++ b/util/MinMax.hh @@ -24,9 +24,12 @@ namespace sta { class MinMax; class MinMaxAll; +class MinMaxIterator; +// Use typedefs to make early/late functional equivalents to min/max. typedef MinMax EarlyLate; typedef MinMaxAll EarlyLateAll; +typedef MinMaxIterator EarlyLateIterator; // Large value used for min/max initial values. extern const float INF; @@ -39,8 +42,12 @@ public: // Singleton accessors. static MinMax *min() { return min_; } static MinMax *max() { return max_; } + static EarlyLate *early() { return min_; } + static EarlyLate *late() { return max_; } static int minIndex() { return min_->index_; } + static int earlyIndex() { return min_->index_; } static int maxIndex() { return max_->index_; } + static int lateIndex() { return min_->index_; } const char *asString() const { return name_; } int index() const { return index_; } float initValue() const { return init_value_; } @@ -98,7 +105,9 @@ public: static void destroy(); // Singleton accessors. static MinMaxAll *min() { return min_; } + static MinMaxAll *early() { return min_; } static MinMaxAll *max() { return max_; } + static MinMaxAll *late() { return max_; } static MinMaxAll *all() { return all_; } const char *asString() const { return name_; } int index() const { return index_; }