From 4ca98768a2635d27e4b864e7bac458e3d4682890 Mon Sep 17 00:00:00 2001 From: James Cherry Date: Sat, 21 Oct 2023 17:08:58 -0700 Subject: [PATCH 01/19] power vcd used # as symbol Signed-off-by: James Cherry --- doc/OpenSTA.odt | Bin 103372 -> 103320 bytes power/ReadVcdActivities.cc | 7 ++----- power/VcdReader.cc | 2 +- 3 files changed, 3 insertions(+), 6 deletions(-) diff --git a/doc/OpenSTA.odt b/doc/OpenSTA.odt index af4df1f2ac3a83b1997d051c1f3c78be7748447b..95e9a1847169270bae1531b3907d1baae720cc3e 100644 GIT binary patch delta 67724 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 E00V#eAOHXW delta 67856 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 B?=%1a diff --git a/power/ReadVcdActivities.cc b/power/ReadVcdActivities.cc index f89b785e..24b66ec0 100644 --- a/power/ReadVcdActivities.cc +++ b/power/ReadVcdActivities.cc @@ -191,11 +191,8 @@ ReadVcdActivities::setVarActivity(const char *pin_name, duty); if (sdc_->isLeafPinClock(pin)) checkClkPeriod(pin, transition_count); - else { - power_->setUserActivity(pin, activity, duty, - PwrActivityOrigin::user); - annotated_pins_.insert(pin); - } + power_->setUserActivity(pin, activity, duty, PwrActivityOrigin::user); + annotated_pins_.insert(pin); } } diff --git a/power/VcdReader.cc b/power/VcdReader.cc index 7d990dee..e30be19d 100644 --- a/power/VcdReader.cc +++ b/power/VcdReader.cc @@ -238,7 +238,7 @@ VcdReader::parseVarValues() string token = getToken(); while (!token.empty()) { char char0 = toupper(token[0]); - if (char0 == '#') { + if (char0 == '#' && token.size() > 1) { prev_time_ = time_; time_ = stoll(token.substr(1)); if (time_ > prev_time_) From a60fcbac159836421805c92c40d37b4a66d23c44 Mon Sep 17 00:00:00 2001 From: James Cherry Date: Sat, 21 Oct 2023 17:16:39 -0700 Subject: [PATCH 02/19] PwrActivityOrigin vcd Signed-off-by: James Cherry --- include/sta/PowerClass.hh | 1 + power/Power.cc | 1 + power/ReadVcdActivities.cc | 2 +- 3 files changed, 3 insertions(+), 1 deletion(-) diff --git a/include/sta/PowerClass.hh b/include/sta/PowerClass.hh index a46fe5a8..f7946979 100644 --- a/include/sta/PowerClass.hh +++ b/include/sta/PowerClass.hh @@ -23,6 +23,7 @@ enum class PwrActivityOrigin global, input, user, + vcd, propagated, clock, constant, diff --git a/power/Power.cc b/power/Power.cc index 2a516f17..1361cb5d 100644 --- a/power/Power.cc +++ b/power/Power.cc @@ -1276,6 +1276,7 @@ static EnumNameMap pwr_activity_origin_map = {{PwrActivityOrigin::global, "global"}, {PwrActivityOrigin::input, "input"}, {PwrActivityOrigin::user, "user"}, + {PwrActivityOrigin::vcd, "vcd"}, {PwrActivityOrigin::propagated, "propagated"}, {PwrActivityOrigin::clock, "clock"}, {PwrActivityOrigin::constant, "constant"}, diff --git a/power/ReadVcdActivities.cc b/power/ReadVcdActivities.cc index 24b66ec0..8763e0ff 100644 --- a/power/ReadVcdActivities.cc +++ b/power/ReadVcdActivities.cc @@ -191,7 +191,7 @@ ReadVcdActivities::setVarActivity(const char *pin_name, duty); if (sdc_->isLeafPinClock(pin)) checkClkPeriod(pin, transition_count); - power_->setUserActivity(pin, activity, duty, PwrActivityOrigin::user); + power_->setUserActivity(pin, activity, duty, PwrActivityOrigin::vcd); annotated_pins_.insert(pin); } } From 50bd00034b68d0429f50030e814abe54d55028b2 Mon Sep 17 00:00:00 2001 From: James Cherry Date: Sun, 22 Oct 2023 16:51:18 -0700 Subject: [PATCH 03/19] read_power_activities bus transition count Signed-off-by: James Cherry --- power/ReadVcdActivities.cc | 10 ++++------ power/Vcd.cc | 9 +++++++++ power/Vcd.hh | 1 + 3 files changed, 14 insertions(+), 6 deletions(-) diff --git a/power/ReadVcdActivities.cc b/power/ReadVcdActivities.cc index 8763e0ff..853ef7bd 100644 --- a/power/ReadVcdActivities.cc +++ b/power/ReadVcdActivities.cc @@ -180,6 +180,7 @@ ReadVcdActivities::setVarActivity(const char *pin_name, { const Pin *pin = sdc_network_->findPin(pin_name); if (pin) { + debugPrint(debug_, "read_vcd_activities", 3, "%s values", pin_name); double transition_count, activity, duty; findVarActivity(var_values, value_bit, transition_count, activity, duty); @@ -205,16 +206,13 @@ ReadVcdActivities::findVarActivity(const VcdValues &var_values, double &duty) { transition_count = 0.0; - char prev_value = var_values[0].value(); + char prev_value = var_values[0].value(value_bit); VcdTime prev_time = var_values[0].time(); VcdTime high_time = 0; for (const VcdValue &var_value : var_values) { VcdTime time = var_value.time(); - char value = var_value.value(); - if (value == '\0') { - uint64_t bus_value = var_value.busValue(); - value = ((bus_value >> value_bit) & 0x1) ? '1' : '0'; - } + char value = var_value.value(value_bit); + debugPrint(debug_, "read_vcd_activities", 3, " %llu %c", time, value); if (prev_value == '1') high_time += time - prev_time; if (value != prev_value) diff --git a/power/Vcd.cc b/power/Vcd.cc index 1ea9d8c3..a140528c 100644 --- a/power/Vcd.cc +++ b/power/Vcd.cc @@ -201,4 +201,13 @@ VcdValue::VcdValue(VcdTime time, { } +char +VcdValue::value(int value_bit) const +{ + if (value_ == '\0') + return ((bus_value_ >> value_bit) & 0x1) ? '1' : '0'; + else + return value_; +} + } diff --git a/power/Vcd.hh b/power/Vcd.hh index 76d904b5..b61d3935 100644 --- a/power/Vcd.hh +++ b/power/Vcd.hh @@ -146,6 +146,7 @@ public: VcdTime time() const { return time_; } char value() const { return value_; } uint64_t busValue() const { return bus_value_; } + char value(int value_bit) const; private: VcdTime time_; From bcaa96df99bc9295c820b82b0b18b94b25c009db Mon Sep 17 00:00:00 2001 From: James Cherry Date: Tue, 24 Oct 2023 19:19:44 -0700 Subject: [PATCH 04/19] liberty handle missing library group Signed-off-by: James Cherry --- liberty/LibertyReader.cc | 26 +++++++++++++++----------- 1 file changed, 15 insertions(+), 11 deletions(-) diff --git a/liberty/LibertyReader.cc b/liberty/LibertyReader.cc index 8ec9ff1d..0bef2ef2 100644 --- a/liberty/LibertyReader.cc +++ b/liberty/LibertyReader.cc @@ -1670,13 +1670,15 @@ LibertyReader::visitScaleFactor(LibertyAttr *attr) void LibertyReader::beginOpCond(LibertyGroup *group) { - const char *name = group->firstName(); - if (name) { - op_cond_ = new OperatingConditions(name); - library_->addOperatingConditions(op_cond_); + if (library_) { + const char *name = group->firstName(); + if (name) { + op_cond_ = new OperatingConditions(name); + library_->addOperatingConditions(op_cond_); + } + else + libWarn(68, group, "operating_conditions missing name."); } - else - libWarn(68, group, "operating_conditions missing name."); } void @@ -1863,9 +1865,11 @@ LibertyReader::beginCell(LibertyGroup *group) const char *name = group->firstName(); if (name) { debugPrint(debug_, "liberty", 1, "cell %s", name); - cell_ = builder_.makeCell(library_, name, filename_); - in_bus_ = false; - in_bundle_ = false; + if (library_) { + cell_ = builder_.makeCell(library_, name, filename_); + in_bus_ = false; + in_bundle_ = false; + } } else libWarn(78, group, "cell missing name."); @@ -4196,7 +4200,7 @@ LibertyReader::beginTable(LibertyGroup *group, float scale) { const char *template_name = group->firstName(); - if (template_name) { + if (library_ && template_name) { tbl_template_ = library_->findTableTemplate(template_name, type); if (tbl_template_) { axis_[0] = tbl_template_->axis1(); @@ -4385,7 +4389,7 @@ LibertyReader::endLut(LibertyGroup *) void LibertyReader::beginTestCell(LibertyGroup *group) { - if (cell_->testCell()) + if (cell_ && cell_->testCell()) libWarn(169, group, "cell %s test_cell redefinition.", cell_->name()); else { test_cell_ = new TestCell; From 29d97af978b25e6efc4020521f9e9a73a1c97211 Mon Sep 17 00:00:00 2001 From: James Cherry Date: Tue, 24 Oct 2023 19:20:21 -0700 Subject: [PATCH 05/19] power dbg Signed-off-by: James Cherry --- power/Power.cc | 57 +++++++++++++++++++++++++++++--------------------- 1 file changed, 33 insertions(+), 24 deletions(-) diff --git a/power/Power.cc b/power/Power.cc index 1361cb5d..bedc78b4 100644 --- a/power/Power.cc +++ b/power/Power.cc @@ -111,9 +111,9 @@ Power::setInputPortActivity(const Port *input_port, void Power::setUserActivity(const Pin *pin, - float activity, - float duty, - PwrActivityOrigin origin) + float activity, + float duty, + PwrActivityOrigin origin) { user_activity_map_[pin] = {activity, duty, origin}; activities_valid_ = false; @@ -699,26 +699,40 @@ Power::power(const Instance *inst, MinMax *mm = MinMax::max(); const DcalcAnalysisPt *dcalc_ap = corner->findDcalcAnalysisPt(mm); const Clock *inst_clk = findInstClk(inst); - InstancePinIterator *pin_iter = network_->pinIterator(inst); - while (pin_iter->hasNext()) { - const Pin *to_pin = pin_iter->next(); + InstancePinIterator *pin_iter1 = network_->pinIterator(inst); + while (pin_iter1->hasNext()) { + const Pin *to_pin = pin_iter1->next(); const LibertyPort *to_port = network_->libertyPort(to_pin); if (to_port) { float load_cap = to_port->direction()->isAnyOutput() ? graph_delay_calc_->loadCap(to_pin, dcalc_ap) : 0.0; PwrActivity activity = findClkedActivity(to_pin, inst_clk); - if (to_port->direction()->isAnyOutput()) { - findSwitchingPower(cell, to_port, activity, load_cap, corner, result); + if (to_port->direction()->isAnyOutput()) findOutputInternalPower(to_pin, to_port, inst, cell, activity, load_cap, corner, result); - } if (to_port->direction()->isAnyInput()) findInputInternalPower(to_pin, to_port, inst, cell, activity, load_cap, corner, result); } } - delete pin_iter; + delete pin_iter1; + + InstancePinIterator *pin_iter2 = network_->pinIterator(inst); + while (pin_iter2->hasNext()) { + const Pin *to_pin = pin_iter2->next(); + const LibertyPort *to_port = network_->libertyPort(to_pin); + if (to_port) { + float load_cap = to_port->direction()->isAnyOutput() + ? graph_delay_calc_->loadCap(to_pin, dcalc_ap) + : 0.0; + PwrActivity activity = findClkedActivity(to_pin, inst_clk); + if (to_port->direction()->isAnyOutput()) + findSwitchingPower(cell, to_port, activity, load_cap, corner, result); + } + } + delete pin_iter2; + findLeakagePower(inst, cell, corner, result); return result; } @@ -755,16 +769,14 @@ Power::findInputInternalPower(const Pin *pin, if (corner_cell && corner_port) { const InternalPowerSeq &internal_pwrs = corner_cell->internalPowers(corner_port); if (!internal_pwrs.empty()) { - debugPrint(debug_, "power", 2, "internal input %s/%s (%s)", + debugPrint(debug_, "power", 2, "internal input %s/%s cap %s", network_->pathName(inst), port->name(), - corner_cell->name()); + units_->capacitanceUnit()->asString(load_cap)); + debugPrint(debug_, "power", 2, " when act/ns duty energy power"); const DcalcAnalysisPt *dcalc_ap = corner->findDcalcAnalysisPt(MinMax::max()); const Pvt *pvt = dcalc_ap->operatingConditions(); Vertex *vertex = graph_->pinLoadVertex(pin); - debugPrint(debug_, "power", 2, " cap = %s", - units_->capacitanceUnit()->asString(load_cap)); - debugPrint(debug_, "power", 2, " when act/ns duty energy power"); float internal = 0.0; for (InternalPower *pwr : internal_pwrs) { const char *related_pg_pin = pwr->relatedPgPin(); @@ -886,16 +898,14 @@ Power::findOutputInternalPower(const Pin *to_pin, // Return values. PowerResult &result) { - debugPrint(debug_, "power", 2, "internal output %s/%s (%s)", + debugPrint(debug_, "power", 2, "internal output %s/%s cap %s", network_->pathName(inst), to_port->name(), - cell->name()); + units_->capacitanceUnit()->asString(load_cap)); const DcalcAnalysisPt *dcalc_ap = corner->findDcalcAnalysisPt(MinMax::max()); const Pvt *pvt = dcalc_ap->operatingConditions(); LibertyCell *corner_cell = cell->cornerCell(dcalc_ap); const LibertyPort *to_corner_port = to_port->cornerPort(dcalc_ap); - debugPrint(debug_, "power", 2, " cap = %s", - units_->capacitanceUnit()->asString(load_cap)); FuncExpr *func = to_port->function(); map pg_duty_sum; @@ -906,6 +916,8 @@ Power::findOutputInternalPower(const Pin *to_pin, pg_duty_sum[related_pg_pin] += duty; } + debugPrint(debug_, "power", 2, + " when act/ns duty wgt energy power"); float internal = 0.0; for (InternalPower *pwr : corner_cell->internalPowers(to_corner_port)) { FuncExpr *when = pwr->when(); @@ -917,14 +929,11 @@ Power::findOutputInternalPower(const Pin *to_pin, if (from_corner_port) { positive_unate = isPositiveUnate(corner_cell, from_corner_port, to_corner_port); const Pin *from_pin = findLinkPin(inst, from_corner_port); - if (from_pin) { + if (from_pin) from_vertex = graph_->pinLoadVertex(from_pin); - } } float energy = 0.0; int rf_count = 0; - debugPrint(debug_, "power", 2, - " when act/ns duty wgt energy power"); for (RiseFall *to_rf : RiseFall::range()) { // Use unateness to find from_rf. RiseFall *from_rf = positive_unate ? to_rf : to_rf->opposite(); @@ -947,7 +956,7 @@ Power::findOutputInternalPower(const Pin *to_pin, weight = duty / duty_sum; } float port_internal = weight * energy * to_activity.activity(); - debugPrint(debug_, "power", 2, "%3s -> %-3s %6s %.2f %.2f %.2f %9.2e %9.2e %s", + debugPrint(debug_, "power", 2, "%3s -> %-3s %6s %.3f %.2f %.2f %9.2e %9.2e %s", from_corner_port ? from_corner_port->name() : "-" , to_port->name(), when ? when->asString() : "", From 73cd5ca002f845a8b4ac0aad7db9e7c7e1f380c2 Mon Sep 17 00:00:00 2001 From: James Cherry Date: Wed, 25 Oct 2023 10:33:28 -0700 Subject: [PATCH 06/19] power reorg Signed-off-by: James Cherry --- power/Power.cc | 141 ++++++++++++++++++++++++++++--------------------- power/Power.hh | 12 +++++ 2 files changed, 94 insertions(+), 59 deletions(-) diff --git a/power/Power.cc b/power/Power.cc index bedc78b4..158f41ac 100644 --- a/power/Power.cc +++ b/power/Power.cc @@ -696,43 +696,9 @@ Power::power(const Instance *inst, const Corner *corner) { PowerResult result; - MinMax *mm = MinMax::max(); - const DcalcAnalysisPt *dcalc_ap = corner->findDcalcAnalysisPt(mm); const Clock *inst_clk = findInstClk(inst); - InstancePinIterator *pin_iter1 = network_->pinIterator(inst); - while (pin_iter1->hasNext()) { - const Pin *to_pin = pin_iter1->next(); - const LibertyPort *to_port = network_->libertyPort(to_pin); - if (to_port) { - float load_cap = to_port->direction()->isAnyOutput() - ? graph_delay_calc_->loadCap(to_pin, dcalc_ap) - : 0.0; - PwrActivity activity = findClkedActivity(to_pin, inst_clk); - if (to_port->direction()->isAnyOutput()) - findOutputInternalPower(to_pin, to_port, inst, cell, activity, - load_cap, corner, result); - if (to_port->direction()->isAnyInput()) - findInputInternalPower(to_pin, to_port, inst, cell, activity, - load_cap, corner, result); - } - } - delete pin_iter1; - - InstancePinIterator *pin_iter2 = network_->pinIterator(inst); - while (pin_iter2->hasNext()) { - const Pin *to_pin = pin_iter2->next(); - const LibertyPort *to_port = network_->libertyPort(to_pin); - if (to_port) { - float load_cap = to_port->direction()->isAnyOutput() - ? graph_delay_calc_->loadCap(to_pin, dcalc_ap) - : 0.0; - PwrActivity activity = findClkedActivity(to_pin, inst_clk); - if (to_port->direction()->isAnyOutput()) - findSwitchingPower(cell, to_port, activity, load_cap, corner, result); - } - } - delete pin_iter2; - + findInternalPower(inst, cell, corner, inst_clk, result); + findSwitchingPower(inst, cell, corner, inst_clk, result); findLeakagePower(inst, cell, corner, result); return result; } @@ -752,6 +718,35 @@ Power::findInstClk(const Instance *inst) return inst_clk; } +void +Power::findInternalPower(const Instance *inst, + LibertyCell *cell, + const Corner *corner, + const Clock *inst_clk, + // Return values. + PowerResult &result) +{ + const DcalcAnalysisPt *dcalc_ap = corner->findDcalcAnalysisPt(MinMax::max()); + 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); + if (to_port) { + float load_cap = to_port->direction()->isAnyOutput() + ? graph_delay_calc_->loadCap(to_pin, dcalc_ap) + : 0.0; + PwrActivity activity = findClkedActivity(to_pin, inst_clk); + if (to_port->direction()->isAnyOutput()) + findOutputInternalPower(to_pin, to_port, inst, cell, activity, + load_cap, corner, result); + if (to_port->direction()->isAnyInput()) + findInputInternalPower(to_pin, to_port, inst, cell, activity, + load_cap, corner, result); + } + } + delete pin_iter; +} + void Power::findInputInternalPower(const Pin *pin, const LibertyPort *port, @@ -1045,6 +1040,57 @@ isPositiveUnate(const LibertyCell *cell, //////////////////////////////////////////////////////////////// +void +Power::findSwitchingPower(const Instance *inst, + LibertyCell *cell, + const Corner *corner, + const Clock *inst_clk, + // Return values. + PowerResult &result) +{ + const DcalcAnalysisPt *dcalc_ap = corner->findDcalcAnalysisPt(MinMax::max()); + 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); + if (to_port) { + float load_cap = to_port->direction()->isAnyOutput() + ? graph_delay_calc_->loadCap(to_pin, dcalc_ap) + : 0.0; + PwrActivity activity = findClkedActivity(to_pin, inst_clk); + if (to_port->direction()->isAnyOutput()) + findSwitchingPower(cell, to_port, activity, load_cap, corner, result); + } + } + delete pin_iter; +} + +void +Power::findSwitchingPower(LibertyCell *cell, + const LibertyPort *to_port, + PwrActivity &activity, + float load_cap, + const Corner *corner, + // Return values. + PowerResult &result) +{ + MinMax *mm = MinMax::max(); + const DcalcAnalysisPt *dcalc_ap = corner->findDcalcAnalysisPt(mm); + LibertyCell *corner_cell = cell->cornerCell(dcalc_ap); + float volt = portVoltage(corner_cell, to_port, dcalc_ap); + float switching = .5 * load_cap * volt * volt * activity.activity(); + debugPrint(debug_, "power", 2, "switching %s/%s activity = %.2e volt = %.2f %.3e", + cell->name(), + to_port->name(), + activity.activity(), + volt, + switching); + result.switching() += switching; +} + +//////////////////////////////////////////////////////////////// + + void Power::findLeakagePower(const Instance *inst, LibertyCell *cell, @@ -1106,29 +1152,6 @@ Power::findLeakagePower(const Instance *inst, result.leakage() += leakage; } -void -Power::findSwitchingPower(LibertyCell *cell, - const LibertyPort *to_port, - PwrActivity &activity, - float load_cap, - const Corner *corner, - // Return values. - PowerResult &result) -{ - MinMax *mm = MinMax::max(); - const DcalcAnalysisPt *dcalc_ap = corner->findDcalcAnalysisPt(mm); - LibertyCell *corner_cell = cell->cornerCell(dcalc_ap); - float volt = portVoltage(corner_cell, to_port, dcalc_ap); - float switching = .5 * load_cap * volt * volt * activity.activity(); - debugPrint(debug_, "power", 2, "switching %s/%s activity = %.2e volt = %.2f %.3e", - cell->name(), - to_port->name(), - activity.activity(), - volt, - switching); - result.switching() += switching; -} - PwrActivity Power::findClkedActivity(const Pin *pin) { diff --git a/power/Power.hh b/power/Power.hh index 0359d486..29acb34d 100644 --- a/power/Power.hh +++ b/power/Power.hh @@ -109,6 +109,12 @@ protected: PowerResult power(const Instance *inst, LibertyCell *cell, const Corner *corner); + void findInternalPower(const Instance *inst, + LibertyCell *cell, + const Corner *corner, + const Clock *inst_clk, + // Return values. + PowerResult &result); void findInputInternalPower(const Pin *to_pin, const LibertyPort *to_port, const Instance *inst, @@ -132,6 +138,12 @@ protected: const Corner *corner, // Return values. PowerResult &result); + void findSwitchingPower(const Instance *inst, + LibertyCell *cell, + const Corner *corner, + const Clock *inst_clk, + // Return values. + PowerResult &result); void findSwitchingPower(LibertyCell *cell, const LibertyPort *to_port, PwrActivity &activity, From 0968cc8cbf077dacbe4c4ec9f12de6baaa54a0ee Mon Sep 17 00:00:00 2001 From: James Cherry Date: Wed, 25 Oct 2023 12:57:29 -0700 Subject: [PATCH 07/19] power cleanup Signed-off-by: James Cherry --- power/Power.cc | 37 ++++++++++++------------------------- power/Power.hh | 7 ------- 2 files changed, 12 insertions(+), 32 deletions(-) diff --git a/power/Power.cc b/power/Power.cc index 158f41ac..649bc32b 100644 --- a/power/Power.cc +++ b/power/Power.cc @@ -1049,6 +1049,7 @@ Power::findSwitchingPower(const Instance *inst, PowerResult &result) { const DcalcAnalysisPt *dcalc_ap = corner->findDcalcAnalysisPt(MinMax::max()); + LibertyCell *corner_cell = cell->cornerCell(dcalc_ap); InstancePinIterator *pin_iter = network_->pinIterator(inst); while (pin_iter->hasNext()) { const Pin *to_pin = pin_iter->next(); @@ -1058,36 +1059,22 @@ Power::findSwitchingPower(const Instance *inst, ? graph_delay_calc_->loadCap(to_pin, dcalc_ap) : 0.0; PwrActivity activity = findClkedActivity(to_pin, inst_clk); - if (to_port->direction()->isAnyOutput()) - findSwitchingPower(cell, to_port, activity, load_cap, corner, result); + if (to_port->direction()->isAnyOutput()) { + float volt = portVoltage(corner_cell, to_port, dcalc_ap); + float switching = .5 * load_cap * volt * volt * activity.activity(); + debugPrint(debug_, "power", 2, "switching %s/%s activity = %.2e volt = %.2f %.3e", + cell->name(), + to_port->name(), + activity.activity(), + volt, + switching); + result.switching() += switching; + } } } delete pin_iter; } -void -Power::findSwitchingPower(LibertyCell *cell, - const LibertyPort *to_port, - PwrActivity &activity, - float load_cap, - const Corner *corner, - // Return values. - PowerResult &result) -{ - MinMax *mm = MinMax::max(); - const DcalcAnalysisPt *dcalc_ap = corner->findDcalcAnalysisPt(mm); - LibertyCell *corner_cell = cell->cornerCell(dcalc_ap); - float volt = portVoltage(corner_cell, to_port, dcalc_ap); - float switching = .5 * load_cap * volt * volt * activity.activity(); - debugPrint(debug_, "power", 2, "switching %s/%s activity = %.2e volt = %.2f %.3e", - cell->name(), - to_port->name(), - activity.activity(), - volt, - switching); - result.switching() += switching; -} - //////////////////////////////////////////////////////////////// diff --git a/power/Power.hh b/power/Power.hh index 29acb34d..7aef5f31 100644 --- a/power/Power.hh +++ b/power/Power.hh @@ -144,13 +144,6 @@ protected: const Clock *inst_clk, // Return values. PowerResult &result); - void findSwitchingPower(LibertyCell *cell, - const LibertyPort *to_port, - PwrActivity &activity, - float load_cap, - const Corner *corner, - // Return values. - PowerResult &result); float getSlew(Vertex *vertex, const RiseFall *rf, const Corner *corner); From 22b43568effa5cb58954778817087211e198fe6c Mon Sep 17 00:00:00 2001 From: James Cherry Date: Wed, 25 Oct 2023 15:12:47 -0700 Subject: [PATCH 08/19] clock duty Signed-off-by: James Cherry --- power/Power.cc | 29 ++++++++++++++++++++++++++--- power/Power.hh | 1 + sdc/Clock.cc | 4 ++-- 3 files changed, 29 insertions(+), 5 deletions(-) diff --git a/power/Power.cc b/power/Power.cc index 649bc32b..37c79fb2 100644 --- a/power/Power.cc +++ b/power/Power.cc @@ -711,8 +711,10 @@ Power::findInstClk(const Instance *inst) while (pin_iter->hasNext()) { const Pin *pin = pin_iter->next(); const Clock *clk = findClk(pin); - if (clk) + if (clk) { inst_clk = clk; + break; + } } delete pin_iter; return inst_clk; @@ -1178,7 +1180,9 @@ Power::findActivity(const Pin *pin) if (activity.origin() != PwrActivityOrigin::unknown) return activity; } - return PwrActivity(2.0, 0.5, PwrActivityOrigin::clock); + const Clock *clk = findClk(pin); + float duty = clockDuty(clk); + return PwrActivity(2.0, duty, PwrActivityOrigin::clock); } else if (global_activity_.isSet()) return global_activity_; @@ -1190,6 +1194,25 @@ Power::findActivity(const Pin *pin) return PwrActivity(0.0, 0.0, PwrActivityOrigin::unknown); } +float +Power::clockDuty(const Clock *clk) +{ + if (clk->isGenerated()) { + const Clock *master = clk->masterClk(); + if (master == nullptr) + return 0.5; // punt + else + return clockDuty(master); + } + else { + const FloatSeq *waveform = clk->waveform(); + float rise_time = (*waveform)[0]; + float fall_time = (*waveform)[1]; + float duty = (fall_time - rise_time) / clk->period(); + return duty; + } +} + PwrActivity Power::findSeqActivity(const Instance *inst, LibertyPort *port) @@ -1201,7 +1224,7 @@ Power::findSeqActivity(const Instance *inst, if (activity.origin() != PwrActivityOrigin::unknown) return activity; } - return input_activity_; + return PwrActivity(0.0, 0.0, PwrActivityOrigin::unknown); } float diff --git a/power/Power.hh b/power/Power.hh index 7aef5f31..7dd08827 100644 --- a/power/Power.hh +++ b/power/Power.hh @@ -149,6 +149,7 @@ protected: const Corner *corner); const Clock *findInstClk(const Instance *inst); const Clock *findClk(const Pin *to_pin); + float clockDuty(const Clock *clk); PwrActivity findClkedActivity(const Pin *pin, const Clock *inst_clk); PwrActivity findActivity(const Pin *pin); diff --git a/sdc/Clock.cc b/sdc/Clock.cc index dcc06fc3..5e87df91 100644 --- a/sdc/Clock.cc +++ b/sdc/Clock.cc @@ -118,8 +118,8 @@ void Clock::makeClkEdges() { clk_edges_ = new ClockEdge*[RiseFall::index_count]; - for (auto tr : RiseFall::range()) { - clk_edges_[tr->index()] = new ClockEdge(this, tr); + for (auto rf : RiseFall::range()) { + clk_edges_[rf->index()] = new ClockEdge(this, rf); } } From 29c82cc2b48f82a5b257b07f0bad27d97e3b9faa Mon Sep 17 00:00:00 2001 From: James Cherry Date: Mon, 30 Oct 2023 16:55:48 -0700 Subject: [PATCH 09/19] typo Signed-off-by: James Cherry --- power/Power.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/power/Power.cc b/power/Power.cc index 37c79fb2..dee8e206 100644 --- a/power/Power.cc +++ b/power/Power.cc @@ -867,7 +867,7 @@ Power::findExprOutPort(FuncExpr *expr) return nullptr; } -// Eval activity of differenc(expr) wrt cofactor port. +// Eval activity of difference(expr) wrt cofactor port. PwrActivity Power::evalActivityDifference(FuncExpr *expr, const Instance *inst, From f9abae51d82e7712d9d3534063e2f758b7b7cd5d Mon Sep 17 00:00:00 2001 From: James Cherry Date: Tue, 31 Oct 2023 08:53:45 -0700 Subject: [PATCH 10/19] rm Sta::isDisabledClock Signed-off-by: James Cherry --- include/sta/Sta.hh | 2 -- 1 file changed, 2 deletions(-) diff --git a/include/sta/Sta.hh b/include/sta/Sta.hh index 998ca562..56344d09 100644 --- a/include/sta/Sta.hh +++ b/include/sta/Sta.hh @@ -429,8 +429,6 @@ public: bool isDisabledConstant(Edge *edge); // Edge is default cond disabled by timing_disable_cond_default_arcs var. bool isDisabledCondDefault(Edge *edge); - // Edge is disabled to prpath a clock from propagating. - bool isDisabledClock(Edge *edge); // Return a set of constant pins that disabled edge. // Caller owns the returned set. PinSet disabledConstantPins(Edge *edge); From 7845105f4fcfce7f001f9ce55a6bafcd2ea98870 Mon Sep 17 00:00:00 2001 From: James Cherry Date: Fri, 10 Nov 2023 09:59:50 -0700 Subject: [PATCH 11/19] power cudd support commit 872e7f91b4ce2a475063296b85ba99a2c76f665c Author: James Cherry Date: Fri Nov 10 09:54:02 2023 -0700 power cudd eval diff Signed-off-by: James Cherry commit 908dfaa08b165d59a38c25b5f534db4ca02540d8 Author: James Cherry Date: Wed Nov 8 19:13:33 2023 -0700 Power::seqActivity Signed-off-by: James Cherry commit 47a74dd6989dbd7cfe8127aa0be95dcf19a3cff4 Author: James Cherry Date: Wed Nov 8 18:03:01 2023 -0700 cudd default cache init Signed-off-by: James Cherry commit 87890f699280e0f4aea6c5610a2f1949a46a07ae Author: James Cherry Date: Wed Nov 8 13:48:58 2023 -0700 power buffer activity origin Signed-off-by: James Cherry commit 584b8124ab98d3cd42e23383aa35edb33e26e9d2 Author: James Cherry Date: Wed Nov 8 12:26:16 2023 -0700 power use cudd Signed-off-by: James Cherry Signed-off-by: James Cherry --- CMakeLists.txt | 4 +- include/sta/PowerClass.hh | 1 + power/Power.cc | 382 ++++++++++++++++++++++++++++++-------- power/Power.hh | 38 +++- search/Sim.cc | 43 +++-- search/Sim.hh | 4 +- 6 files changed, 354 insertions(+), 118 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 0e5353c0..9900ea38 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -435,14 +435,14 @@ if (USE_CUDD) find_library(CUDD_LIB NAME cudd PATHS ${CUDD_DIR} - PATH_SUFFIXES lib lib/cudd + PATH_SUFFIXES lib lib/cudd cudd/.libs ) if (CUDD_LIB) message(STATUS "CUDD library: ${CUDD_LIB}") get_filename_component(CUDD_LIB_DIR "${CUDD_LIB}" PATH) get_filename_component(CUDD_LIB_PARENT1 "${CUDD_LIB_DIR}" PATH) find_file(CUDD_HEADER cudd.h - PATHS ${CUDD_LIB_PARENT1}/include ${CUDD_LIB_PARENT1}/include/cudd) + PATHS ${CUDD_LIB_PARENT1} ${CUDD_LIB_PARENT1}/include ${CUDD_LIB_PARENT1}/include/cudd) if (CUDD_HEADER) get_filename_component(CUDD_INCLUDE "${CUDD_HEADER}" PATH) message(STATUS "CUDD header: ${CUDD_HEADER}") diff --git a/include/sta/PowerClass.hh b/include/sta/PowerClass.hh index f7946979..57c9ad0a 100644 --- a/include/sta/PowerClass.hh +++ b/include/sta/PowerClass.hh @@ -43,6 +43,7 @@ public: float duty() const { return duty_; } void setDuty(float duty); PwrActivityOrigin origin() { return origin_; } + void setOrigin(PwrActivityOrigin origin); const char *originName() const; void set(float activity, float duty, diff --git a/power/Power.cc b/power/Power.cc index dee8e206..5350a331 100644 --- a/power/Power.cc +++ b/power/Power.cc @@ -17,7 +17,7 @@ #include "Power.hh" #include // max -#include // aps +#include // abs #include "Debug.hh" #include "EnumNameMap.hh" @@ -47,6 +47,13 @@ #include "Bfs.hh" #include "ClkNetwork.hh" +#if CUDD +#include "cudd.h" +#else +#define Cudd_Init(ignore1, ignore2, ignore3, ignore4, ignore5) nullptr +#define Cudd_Quit(ignore1) +#endif + // Related liberty not supported: // library // default_cell_leakage_power : 0; @@ -71,15 +78,32 @@ isPositiveUnate(const LibertyCell *cell, const LibertyPort *from, const LibertyPort *to); +static EnumNameMap pwr_activity_origin_map = + {{PwrActivityOrigin::global, "global"}, + {PwrActivityOrigin::input, "input"}, + {PwrActivityOrigin::user, "user"}, + {PwrActivityOrigin::vcd, "vcd"}, + {PwrActivityOrigin::propagated, "propagated"}, + {PwrActivityOrigin::clock, "clock"}, + {PwrActivityOrigin::constant, "constant"}, + {PwrActivityOrigin::defaulted, "defaulted"}, + {PwrActivityOrigin::unknown, "unknown"}}; + Power::Power(StaState *sta) : StaState(sta), global_activity_{0.0, 0.0, PwrActivityOrigin::unknown}, input_activity_{0.1, 0.5, PwrActivityOrigin::input}, seq_activity_map_(100, SeqPinHash(network_), SeqPinEqual()), - activities_valid_(false) + activities_valid_(false), + cudd_mgr_(Cudd_Init(0, 0, CUDD_UNIQUE_SLOTS, CUDD_CACHE_SLOTS, 0)) { } +Power::~Power() +{ + Cudd_Quit(cudd_mgr_); +} + void Power::setGlobalActivity(float activity, float duty) @@ -135,6 +159,11 @@ void Power::setActivity(const Pin *pin, PwrActivity &activity) { + debugPrint(debug_, "power_activity", 3, "set %s %.2e %.2f %s", + network_->pathName(pin), + activity.activity(), + activity.duty(), + pwr_activity_origin_map.find(activity.origin())); activity_map_[pin] = activity; } @@ -168,7 +197,7 @@ Power::hasSeqActivity(const Instance *reg, return seq_activity_map_.hasKey(SeqPin(reg, output)); } -PwrActivity +PwrActivity & Power::seqActivity(const Instance *reg, LibertyPort *output) { @@ -331,7 +360,7 @@ private: bool setActivityCheck(const Pin *pin, PwrActivity &activity); - static constexpr float change_tolerance_ = .01; + static constexpr float change_tolerance_ = .001; InstanceSet visited_regs_; float max_change_; Power *power_; @@ -370,10 +399,6 @@ PropActivityVisitor::visit(Vertex *vertex) bool changed = false; if (power_->hasUserActivity(pin)) { PwrActivity &activity = power_->userActivity(pin); - debugPrint(debug_, "power_activity", 3, "set %s %.2e %.2f", - vertex->name(network_), - activity.activity(), - activity.duty()); changed = setActivityCheck(pin, activity); } else { @@ -397,12 +422,8 @@ PropActivityVisitor::visit(Vertex *vertex) if (port) { FuncExpr *func = port->function(); if (func) { - PwrActivity activity = power_->evalActivity(func, inst); + PwrActivity activity = power_->evalActivity(func, inst); changed = setActivityCheck(pin, activity); - debugPrint(debug_, "power_activity", 3, "set %s %.2e %.2f", - vertex->name(network_), - activity.activity(), - activity.duty()); } if (port->isClockGateOut()) { const Pin *enable, *clk, *gclk; @@ -491,6 +512,210 @@ Power::clockGatePins(const Instance *inst, delete pin_iter; } +//////////////////////////////////////////////////////////////// + +#if CUDD + +PwrActivity +Power::evalActivity(FuncExpr *expr, + const Instance *inst) +{ + LibertyPort *func_port = expr->port(); + if (func_port && func_port->direction()->isInternal()) + return findSeqActivity(inst, func_port); + else { + DdNode *bdd = funcBdd(expr); + float duty = evalBddDuty(bdd, inst); + float activity = evalBddActivity(bdd, inst); + + Cudd_RecursiveDeref(cudd_mgr_, bdd); + clearVarMap(); + return PwrActivity(activity, duty, PwrActivityOrigin::propagated); + } +} + +// Find duty when from_port is sensitized. +float +Power::evalDiffDuty(FuncExpr *expr, + LibertyPort *from_port, + const Instance *inst) +{ + DdNode *bdd = funcBdd(expr); + DdNode *var_node = bdd_port_var_map_[from_port]; + unsigned var_index = Cudd_NodeReadIndex(var_node); + DdNode *diff = Cudd_bddBooleanDiff(cudd_mgr_, bdd, var_index); + Cudd_Ref(diff); + float duty = evalBddDuty(diff, inst); + + Cudd_RecursiveDeref(cudd_mgr_, diff); + Cudd_RecursiveDeref(cudd_mgr_, bdd); + clearVarMap(); + return duty; +} + +DdNode * +Power::funcBdd(const FuncExpr *expr) +{ + DdNode *left = nullptr; + DdNode *right = nullptr; + DdNode *result = nullptr; + switch (expr->op()) { + case FuncExpr::op_port: { + LibertyPort *port = expr->port(); + result = ensureNode(port); + break; + } + case FuncExpr::op_not: + left = funcBdd(expr->left()); + if (left) + result = Cudd_Not(left); + break; + case FuncExpr::op_or: + left = funcBdd(expr->left()); + right = funcBdd(expr->right()); + if (left && right) + result = Cudd_bddOr(cudd_mgr_, left, right); + else if (left) + result = left; + else if (right) + result = right; + break; + case FuncExpr::op_and: + left = funcBdd(expr->left()); + right = funcBdd(expr->right()); + if (left && right) + result = Cudd_bddAnd(cudd_mgr_, left, right); + else if (left) + result = left; + else if (right) + result = right; + break; + case FuncExpr::op_xor: + left = funcBdd(expr->left()); + right = funcBdd(expr->right()); + if (left && right) + result = Cudd_bddXor(cudd_mgr_, left, right); + else if (left) + result = left; + else if (right) + result = right; + break; + case FuncExpr::op_one: + result = Cudd_ReadOne(cudd_mgr_); + break; + case FuncExpr::op_zero: + result = Cudd_ReadLogicZero(cudd_mgr_); + break; + default: + report_->critical(596, "unknown function operator"); + } + if (result) + Cudd_Ref(result); + if (left) + Cudd_RecursiveDeref(cudd_mgr_, left); + if (right) + Cudd_RecursiveDeref(cudd_mgr_, right); + return result; +} + +DdNode * +Power::ensureNode(LibertyPort *port) +{ + DdNode *bdd = bdd_port_var_map_.findKey(port); + if (bdd == nullptr) { + bdd = Cudd_bddNewVar(cudd_mgr_); + bdd_port_var_map_[port] = bdd; + unsigned var_index = Cudd_NodeReadIndex(bdd); + bdd_var_idx_port_map_[var_index] = port; + Cudd_Ref(bdd); + debugPrint(debug_, "power_activity", 2, "%s var %d", port->name(), var_index); + } + return bdd; +} + +void +Power::clearVarMap() +{ + for (auto port_node : bdd_port_var_map_) { + DdNode *var_node = port_node.second; + Cudd_RecursiveDeref(cudd_mgr_, var_node); + } + bdd_port_var_map_.clear(); + bdd_var_idx_port_map_.clear(); +} + +// As suggested by +// https://stackoverflow.com/questions/63326728/cudd-printminterm-accessing-the-individual-minterms-in-the-sum-of-products +float +Power::evalBddDuty(DdNode *bdd, + const Instance *inst) +{ + if (Cudd_IsConstant(bdd)) { + if (bdd == Cudd_ReadOne(cudd_mgr_)) + return 1.0; + else if (bdd == Cudd_ReadLogicZero(cudd_mgr_)) + return 0.0; + else + criticalError(1100, "unknown cudd constant"); + } + else { + float duty0 = evalBddDuty(Cudd_E(bdd), inst); + float duty1 = evalBddDuty(Cudd_T(bdd), inst); + unsigned int index = Cudd_NodeReadIndex(bdd); + int var_index = Cudd_ReadPerm(cudd_mgr_, index); + LibertyPort *port = bdd_var_idx_port_map_[var_index]; + if (port->direction()->isInternal()) + return findSeqActivity(inst, port).duty(); + else { + const Pin *pin = findLinkPin(inst, port); + if (pin) { + PwrActivity var_activity = findActivity(pin); + float var_duty = var_activity.duty(); + float duty = duty0 * (1.0 - var_duty) + duty1 * var_duty; + if (Cudd_IsComplement(bdd)) + duty = 1.0 - duty; + return duty; + } + } + } + return 0.0; +} + +// https://www.brown.edu/Departments/Engineering/Courses/engn2912/Lectures/LP-02-logic-power-est.pdf +// F(x0, x1, .. ) is sensitized when F(Xi=1) xor F(Xi=0) +// F(Xi=1), F(Xi=0) are the cofactors of F wrt Xi. +float +Power::evalBddActivity(DdNode *bdd, + const Instance *inst) +{ + float activity = 0.0; + for (auto port_var : bdd_port_var_map_) { + LibertyPort *port = port_var.first; + const Pin *pin = findLinkPin(inst, port); + if (pin) { + PwrActivity var_activity = findActivity(pin); + DdNode *var_node = port_var.second; + unsigned int var_index = Cudd_NodeReadIndex(var_node); + DdNode *diff = Cudd_bddBooleanDiff(cudd_mgr_, bdd, var_index); + Cudd_Ref(diff); + float diff_duty = evalBddDuty(diff, inst); + Cudd_RecursiveDeref(cudd_mgr_, diff); + float var_act = var_activity.activity() * diff_duty; + activity += var_act; + const Clock *clk = findClk(pin); + float clk_period = clk ? clk->period() : 1.0; + debugPrint(debug_, "power_activity", 3, "var %s %.3e * %.3f = %.3e", + port->name(), + var_activity.activity() / clk_period, + diff_duty, + var_act / clk_period); + } + } + return activity; +} + +#else + PwrActivity Power::evalActivity(FuncExpr *expr, const Instance *inst) @@ -519,8 +744,11 @@ Power::evalActivity(FuncExpr *expr, return findSeqActivity(inst, port); else { Pin *pin = findLinkPin(inst, port); - if (pin) - return findActivity(pin); + if (pin) { + PwrActivity activity = findActivity(pin); + activity.setOrigin(PwrActivityOrigin::propagated); + return activity; + } } return PwrActivity(0.0, 0.0, PwrActivityOrigin::constant); } @@ -539,7 +767,8 @@ Power::evalActivity(FuncExpr *expr, float p1 = 1.0 - activity1.duty(); float p2 = 1.0 - activity2.duty(); return PwrActivity(activity1.activity() * p2 + activity2.activity() * p1, - 1.0 - p1 * p2, + // d1 + d2 - d1 * d2 + 1.0 - p1 * p2, PwrActivityOrigin::propagated); } case FuncExpr::op_and: { @@ -574,6 +803,23 @@ Power::evalActivity(FuncExpr *expr, return PwrActivity(); } +// Eval activity of difference(expr) wrt cofactor port. +float +Power::evalDiffDuty(FuncExpr *expr, + LibertyPort *cofactor_port, + const Instance *inst) +{ + // Activity of positive/negative cofactors. + PwrActivity pos = evalActivity(expr, inst, cofactor_port, true); + PwrActivity neg = evalActivity(expr, inst, cofactor_port, false); + // difference = xor(pos, neg). + float p1 = pos.duty() * (1.0 - neg.duty()); + float p2 = neg.duty() * (1.0 - pos.duty()); + return p1 + p2; +} + +#endif // CUDD + //////////////////////////////////////////////////////////////// void @@ -684,6 +930,7 @@ Power::seedRegOutputActivities(const Instance *reg, activity.setActivity(1.0); if (invert) activity.setDuty(1.0 - activity.duty()); + activity.setOrigin(PwrActivityOrigin::propagated); setSeqActivity(reg, output, activity); } } @@ -732,14 +979,14 @@ Power::findInternalPower(const Instance *inst, 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); + LibertyPort *to_port = network_->libertyPort(to_pin); if (to_port) { float load_cap = to_port->direction()->isAnyOutput() ? graph_delay_calc_->loadCap(to_pin, dcalc_ap) : 0.0; PwrActivity activity = findClkedActivity(to_pin, inst_clk); if (to_port->direction()->isAnyOutput()) - findOutputInternalPower(to_pin, to_port, inst, cell, activity, + findOutputInternalPower(to_port, inst, cell, activity, load_cap, corner, result); if (to_port->direction()->isAnyInput()) findInputInternalPower(to_pin, to_port, inst, cell, activity, @@ -751,7 +998,7 @@ Power::findInternalPower(const Instance *inst, void Power::findInputInternalPower(const Pin *pin, - const LibertyPort *port, + LibertyPort *port, const Instance *inst, LibertyCell *cell, PwrActivity &activity, @@ -792,13 +1039,13 @@ Power::findInputInternalPower(const Pin *pin, float duty = 1.0; // fallback default FuncExpr *when = pwr->when(); if (when) { - LibertyPort *out_corner_port = findExprOutPort(when); + const LibertyPort *out_corner_port = findExprOutPort(when); if (out_corner_port) { - const LibertyPort *out_port = findLinkPort(cell, out_corner_port); + LibertyPort *out_port = findLinkPort(cell, out_corner_port); if (out_port) { FuncExpr *func = out_port->function(); if (func && func->hasPort(port)) - duty = evalActivityDifference(func, inst, port).duty(); + duty = evalDiffDuty(func, port, inst); else duty = evalActivity(when, inst).duty(); } @@ -867,26 +1114,8 @@ Power::findExprOutPort(FuncExpr *expr) return nullptr; } -// Eval activity of difference(expr) wrt cofactor port. -PwrActivity -Power::evalActivityDifference(FuncExpr *expr, - const Instance *inst, - const LibertyPort *cofactor_port) -{ - // Activity of positive/negative cofactors. - PwrActivity pos = evalActivity(expr, inst, cofactor_port, true); - PwrActivity neg = evalActivity(expr, inst, cofactor_port, false); - // difference = xor(pos, neg). - float p1 = pos.duty() * (1.0 - neg.duty()); - float p2 = neg.duty() * (1.0 - pos.duty()); - return PwrActivity(pos.activity() * p1 + neg.activity() * p2, - p1 + p2, - PwrActivityOrigin::propagated); -} - void -Power::findOutputInternalPower(const Pin *to_pin, - const LibertyPort *to_port, +Power::findOutputInternalPower(const LibertyPort *to_port, const Instance *inst, LibertyCell *cell, PwrActivity &to_activity, @@ -907,25 +1136,31 @@ Power::findOutputInternalPower(const Pin *to_pin, map pg_duty_sum; for (InternalPower *pwr : corner_cell->internalPowers(to_corner_port)) { - float duty = findInputDuty(to_pin, inst, func, pwr); - const char *related_pg_pin = pwr->relatedPgPin(); - // Note related_pg_pin may be null. - pg_duty_sum[related_pg_pin] += duty; + const LibertyPort *from_corner_port = pwr->relatedPort(); + if (from_corner_port) { + const Pin *from_pin = findLinkPin(inst, from_corner_port); + float from_activity = findActivity(from_pin).activity(); + float duty = findInputDuty(inst, func, pwr); + const char *related_pg_pin = pwr->relatedPgPin(); + // Note related_pg_pin may be null. + pg_duty_sum[related_pg_pin] += from_activity * duty; + } } debugPrint(debug_, "power", 2, - " when act/ns duty wgt energy power"); + " when act/ns duty wgt energy power"); float internal = 0.0; for (InternalPower *pwr : corner_cell->internalPowers(to_corner_port)) { FuncExpr *when = pwr->when(); const char *related_pg_pin = pwr->relatedPgPin(); - float duty = findInputDuty(to_pin, inst, func, pwr); + float duty = findInputDuty(inst, func, pwr); Vertex *from_vertex = nullptr; bool positive_unate = true; const LibertyPort *from_corner_port = pwr->relatedPort(); + const Pin *from_pin = nullptr; if (from_corner_port) { positive_unate = isPositiveUnate(corner_cell, from_corner_port, to_corner_port); - const Pin *from_pin = findLinkPin(inst, from_corner_port); + from_pin = findLinkPin(inst, from_corner_port); if (from_pin) from_vertex = graph_->pinLoadVertex(from_pin); } @@ -949,11 +1184,13 @@ Power::findOutputInternalPower(const Pin *to_pin, float weight = 0.0; if (duty_sum_iter != pg_duty_sum.end()) { float duty_sum = duty_sum_iter->second; - if (duty_sum != 0.0) - weight = duty / duty_sum; + if (duty_sum != 0.0 && from_pin) { + float from_activity = findActivity(from_pin).activity(); + weight = from_activity * duty / duty_sum; + } } float port_internal = weight * energy * to_activity.activity(); - debugPrint(debug_, "power", 2, "%3s -> %-3s %6s %.3f %.2f %.2f %9.2e %9.2e %s", + debugPrint(debug_, "power", 2, "%3s -> %-3s %6s %.3f %.3f %.3f %9.2e %9.2e %s", from_corner_port ? from_corner_port->name() : "-" , to_port->name(), when ? when->asString() : "", @@ -969,33 +1206,21 @@ Power::findOutputInternalPower(const Pin *to_pin, } float -Power::findInputDuty(const Pin *to_pin, - const Instance *inst, - FuncExpr *func, - InternalPower *pwr) +Power::findInputDuty(const Instance *inst, + FuncExpr *func, + InternalPower *pwr) { const LibertyPort *from_corner_port = pwr->relatedPort(); if (from_corner_port) { - const LibertyPort *from_port = findLinkPort(network_->libertyCell(inst), - from_corner_port); + LibertyPort *from_port = findLinkPort(network_->libertyCell(inst), + from_corner_port); const Pin *from_pin = network_->findPin(inst, from_port); if (from_pin) { FuncExpr *when = pwr->when(); Vertex *from_vertex = graph_->pinLoadVertex(from_pin); if (func && func->hasPort(from_port)) { - float from_activity = findActivity(from_pin).activity(); - float to_activity = findActivity(to_pin).activity(); - float duty1 = evalActivityDifference(func, inst, from_port).duty(); - float duty = 0.0; - if (to_activity != 0.0F) { - duty = from_activity * duty1 / to_activity; - // Activities can get very small from multiplying probabilities - // through deep chains of logic. Dividing by very close to zero values - // can result in NaN/Inf depending on numerator. - if (!isnormal(duty)) - duty = 0.0; - } + float duty = evalDiffDuty(func, from_port, inst); return duty; } else if (when) @@ -1220,7 +1445,7 @@ Power::findSeqActivity(const Instance *inst, if (global_activity_.isSet()) return global_activity_; else if (hasSeqActivity(inst, port)) { - PwrActivity activity = seqActivity(inst, port); + PwrActivity &activity = seqActivity(inst, port); if (activity.origin() != PwrActivityOrigin::unknown) return activity; } @@ -1314,17 +1539,6 @@ PowerResult::incr(PowerResult &result) //////////////////////////////////////////////////////////////// -static EnumNameMap pwr_activity_origin_map = - {{PwrActivityOrigin::global, "global"}, - {PwrActivityOrigin::input, "input"}, - {PwrActivityOrigin::user, "user"}, - {PwrActivityOrigin::vcd, "vcd"}, - {PwrActivityOrigin::propagated, "propagated"}, - {PwrActivityOrigin::clock, "clock"}, - {PwrActivityOrigin::constant, "constant"}, - {PwrActivityOrigin::defaulted, "defaulted"}, - {PwrActivityOrigin::unknown, "unknown"}}; - PwrActivity::PwrActivity(float activity, float duty, PwrActivityOrigin origin) : @@ -1354,6 +1568,12 @@ PwrActivity::setDuty(float duty) duty_ = duty; } +void +PwrActivity::setOrigin(PwrActivityOrigin origin) +{ + origin_ = origin; +} + void PwrActivity::set(float activity, float duty, diff --git a/power/Power.hh b/power/Power.hh index 7dd08827..f3075fb8 100644 --- a/power/Power.hh +++ b/power/Power.hh @@ -18,12 +18,16 @@ #include +#include "StaConfig.hh" // CUDD #include "UnorderedMap.hh" #include "Network.hh" #include "SdcClass.hh" #include "PowerClass.hh" #include "StaState.hh" +struct DdNode; +struct DdManager; + namespace sta { class Sta; @@ -34,6 +38,8 @@ class BfsFwdIterator; class Vertex; typedef std::pair SeqPin; +typedef Map BddPortVarMap; +typedef Map BddVarIdxPortMap; class SeqPinHash { @@ -62,6 +68,7 @@ class Power : public StaState { public: Power(StaState *sta); + ~Power(); void power(const Corner *corner, // Return values. PowerResult &total, @@ -100,8 +107,8 @@ protected: PwrActivity &activity); bool hasSeqActivity(const Instance *reg, LibertyPort *output); - PwrActivity seqActivity(const Instance *reg, - LibertyPort *output); + PwrActivity &seqActivity(const Instance *reg, + LibertyPort *output); bool hasActivity(const Pin *pin); void setActivity(const Pin *pin, PwrActivity &activity); @@ -116,7 +123,7 @@ protected: // Return values. PowerResult &result); void findInputInternalPower(const Pin *to_pin, - const LibertyPort *to_port, + LibertyPort *to_port, const Instance *inst, LibertyCell *cell, PwrActivity &to_activity, @@ -124,8 +131,7 @@ protected: const Corner *corner, // Return values. PowerResult &result); - void findOutputInternalPower(const Pin *to_pin, - const LibertyPort *to_port, + void findOutputInternalPower(const LibertyPort *to_port, const Instance *inst, LibertyCell *cell, PwrActivity &to_activity, @@ -175,13 +181,12 @@ protected: const LibertyPort *cofactor_port, bool cofactor_positive); LibertyPort *findExprOutPort(FuncExpr *expr); - float findInputDuty(const Pin *to_pin, - const Instance *inst, + float findInputDuty(const Instance *inst, FuncExpr *func, InternalPower *pwr); - PwrActivity evalActivityDifference(FuncExpr *expr, - const Instance *inst, - const LibertyPort *cofactor_port); + float evalDiffDuty(FuncExpr *expr, + LibertyPort *from_port, + const Instance *inst); LibertyPort *findLinkPort(const LibertyCell *cell, const LibertyPort *corner_port); Pin *findLinkPin(const Instance *inst, @@ -192,6 +197,14 @@ protected: const Pin *&clk, const Pin *&gclk) const; + DdNode *funcBdd(const FuncExpr *expr); + DdNode *ensureNode(LibertyPort *port); + void clearVarMap(); + float evalBddActivity(DdNode *bdd, + const Instance *inst); + float evalBddDuty(DdNode *bdd, + const Instance *inst); + private: // Port/pin activities set by set_pin_activity. // set_pin_activity -global @@ -204,6 +217,11 @@ private: PwrActivityMap activity_map_; PwrSeqActivityMap seq_activity_map_; bool activities_valid_; + + DdManager *cudd_mgr_; + BddPortVarMap bdd_port_var_map_; + BddVarIdxPortMap bdd_var_idx_port_map_; + static constexpr int max_activity_passes_ = 100; friend class PropActivityVisitor; diff --git a/search/Sim.cc b/search/Sim.cc index e18bdf08..24c98e2b 100644 --- a/search/Sim.cc +++ b/search/Sim.cc @@ -60,15 +60,14 @@ Sim::Sim(StaState *sta) : invalid_load_pins_(network_), instances_with_const_pins_(network_), instances_to_annotate_(network_), - // cacheSize = 2^15 - cudd_manager_(Cudd_Init(0, 0, CUDD_UNIQUE_SLOTS, 32768, 0)) + cudd_mgr_(Cudd_Init(0, 0, CUDD_UNIQUE_SLOTS, CUDD_CACHE_SLOTS, 0)) { } Sim::~Sim() { delete observer_; - Cudd_Quit(cudd_manager_); + Cudd_Quit(cudd_mgr_); } #if CUDD @@ -88,11 +87,11 @@ Sim::functionSense(const FuncExpr *expr, LibertyPort *input_port = network_->libertyPort(input_pin); DdNode *input_node = ensureNode(input_port); unsigned int input_index = Cudd_NodeReadIndex(input_node); - increasing = (Cudd_Increasing(cudd_manager_, bdd, input_index) - == Cudd_ReadOne(cudd_manager_)); - decreasing = (Cudd_Decreasing(cudd_manager_, bdd, input_index) - == Cudd_ReadOne(cudd_manager_)); - Cudd_RecursiveDeref(cudd_manager_, bdd); + increasing = (Cudd_Increasing(cudd_mgr_, bdd, input_index) + == Cudd_ReadOne(cudd_mgr_)); + decreasing = (Cudd_Decreasing(cudd_mgr_, bdd, input_index) + == Cudd_ReadOne(cudd_mgr_)); + Cudd_RecursiveDeref(cudd_mgr_, bdd); clearSymtab(); } TimingSense sense; @@ -113,7 +112,7 @@ Sim::clearSymtab() const { for (auto name_node : symtab_) { DdNode *sym_node = name_node.second; - Cudd_RecursiveDeref(cudd_manager_, sym_node); + Cudd_RecursiveDeref(cudd_mgr_, sym_node); } symtab_.clear(); } @@ -125,12 +124,12 @@ Sim::evalExpr(const FuncExpr *expr, UniqueLock lock(cudd_lock_); DdNode *bdd = funcBdd(expr, inst); LogicValue value = LogicValue::unknown; - if (bdd == Cudd_ReadLogicZero(cudd_manager_)) + if (bdd == Cudd_ReadLogicZero(cudd_mgr_)) value = LogicValue::zero; - else if (bdd == Cudd_ReadOne(cudd_manager_)) + else if (bdd == Cudd_ReadOne(cudd_mgr_)) value = LogicValue::one; if (bdd) { - Cudd_RecursiveDeref(cudd_manager_, bdd); + Cudd_RecursiveDeref(cudd_mgr_, bdd); clearSymtab(); } return value; @@ -153,10 +152,10 @@ Sim::funcBdd(const FuncExpr *expr, LogicValue value = logicValue(pin); switch (value) { case LogicValue::zero: - result = Cudd_ReadLogicZero(cudd_manager_); + result = Cudd_ReadLogicZero(cudd_mgr_); break; case LogicValue::one: - result = Cudd_ReadOne(cudd_manager_); + result = Cudd_ReadOne(cudd_mgr_); break; default: result = ensureNode(port); @@ -174,7 +173,7 @@ Sim::funcBdd(const FuncExpr *expr, left = funcBdd(expr->left(), inst); right = funcBdd(expr->right(), inst); if (left && right) - result = Cudd_bddOr(cudd_manager_, left, right); + result = Cudd_bddOr(cudd_mgr_, left, right); else if (left) result = left; else if (right) @@ -184,7 +183,7 @@ Sim::funcBdd(const FuncExpr *expr, left = funcBdd(expr->left(), inst); right = funcBdd(expr->right(), inst); if (left && right) - result = Cudd_bddAnd(cudd_manager_, left, right); + result = Cudd_bddAnd(cudd_mgr_, left, right); else if (left) result = left; else if (right) @@ -194,17 +193,17 @@ Sim::funcBdd(const FuncExpr *expr, left = funcBdd(expr->left(), inst); right = funcBdd(expr->right(), inst); if (left && right) - result = Cudd_bddXor(cudd_manager_, left, right); + result = Cudd_bddXor(cudd_mgr_, left, right); else if (left) result = left; else if (right) result = right; break; case FuncExpr::op_one: - result = Cudd_ReadOne(cudd_manager_); + result = Cudd_ReadOne(cudd_mgr_); break; case FuncExpr::op_zero: - result = Cudd_ReadLogicZero(cudd_manager_); + result = Cudd_ReadLogicZero(cudd_mgr_); break; default: report_->critical(596, "unknown function operator"); @@ -212,9 +211,9 @@ Sim::funcBdd(const FuncExpr *expr, if (result) Cudd_Ref(result); if (left) - Cudd_RecursiveDeref(cudd_manager_, left); + Cudd_RecursiveDeref(cudd_mgr_, left); if (right) - Cudd_RecursiveDeref(cudd_manager_, right); + Cudd_RecursiveDeref(cudd_mgr_, right); return result; } @@ -224,7 +223,7 @@ Sim::ensureNode(LibertyPort *port) const const char *port_name = port->name(); DdNode *node = symtab_.findKey(port_name); if (node == nullptr) { - node = Cudd_bddNewVar(cudd_manager_); + node = Cudd_bddNewVar(cudd_mgr_); symtab_[port_name] = node; Cudd_Ref(node); } diff --git a/search/Sim.hh b/search/Sim.hh index 5344e77a..4f0a6690 100644 --- a/search/Sim.hh +++ b/search/Sim.hh @@ -129,16 +129,14 @@ protected: InstanceSet instances_with_const_pins_; InstanceSet instances_to_annotate_; -#ifdef CUDD DdNode *funcBdd(const FuncExpr *expr, const Instance *inst) const; DdNode *ensureNode(LibertyPort *port) const; void clearSymtab() const; - DdManager *cudd_manager_; + DdManager *cudd_mgr_; mutable BddSymbolTable symtab_; mutable std::mutex cudd_lock_; -#endif // CUDD }; // Abstract base class for Sim value change observer. From 042f1f84d108e4f1396f3c5e993556c159e85c21 Mon Sep 17 00:00:00 2001 From: James Cherry Date: Sat, 11 Nov 2023 12:33:55 -0700 Subject: [PATCH 12/19] rm GraphDelayCalc1 Signed-off-by: James Cherry --- CMakeLists.txt | 1 - dcalc/GraphDelayCalc.cc | 1697 +++++++++++++++++++++++++++++++-- dcalc/GraphDelayCalc1.cc | 1690 -------------------------------- dcalc/GraphDelayCalc1.hh | 236 ----- include/sta/GraphDelayCalc.hh | 225 ++++- include/sta/SearchClass.hh | 2 + search/MakeTimingModel.cc | 2 +- search/Sta.cc | 4 +- 8 files changed, 1828 insertions(+), 2029 deletions(-) delete mode 100644 dcalc/GraphDelayCalc1.cc delete mode 100644 dcalc/GraphDelayCalc1.hh diff --git a/CMakeLists.txt b/CMakeLists.txt index 9900ea38..2c2f1615 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -69,7 +69,6 @@ set(STA_SOURCE dcalc/DmpCeff.cc dcalc/DmpDelayCalc.cc dcalc/GraphDelayCalc.cc - dcalc/GraphDelayCalc1.cc dcalc/LumpedCapDelayCalc.cc dcalc/NetCaps.cc dcalc/RCDelayCalc.cc diff --git a/dcalc/GraphDelayCalc.cc b/dcalc/GraphDelayCalc.cc index d559e29d..3af4a330 100644 --- a/dcalc/GraphDelayCalc.cc +++ b/dcalc/GraphDelayCalc.cc @@ -16,100 +16,1677 @@ #include "GraphDelayCalc.hh" +#include "Debug.hh" +#include "Stats.hh" +#include "MinMax.hh" +#include "Mutex.hh" +#include "TimingRole.hh" +#include "TimingArc.hh" #include "Liberty.hh" +#include "PortDirection.hh" #include "Network.hh" -#include "Graph.hh" +#include "InputDrive.hh" #include "Sdc.hh" +#include "Graph.hh" +#include "Parasitics.hh" +#include "search/Levelize.hh" #include "Corner.hh" +#include "SearchPred.hh" +#include "Bfs.hh" +#include "ArcDelayCalc.hh" +#include "DcalcAnalysisPt.hh" +#include "NetCaps.hh" +#include "ClkNetwork.hh" namespace sta { -GraphDelayCalc::GraphDelayCalc(StaState *sta) : - StaState(sta) +using std::abs; + +static const Slew default_slew = 0.0; + +typedef Set MultiDrvrNetSet; + +static bool +isLeafDriver(const Pin *pin, + const Network *network); + +// Cache parallel delay/slew values for nets with multiple drivers. +class MultiDrvrNet { +public: + MultiDrvrNet(VertexSet *drvrs); + ~MultiDrvrNet(); + const VertexSet *drvrs() const { return drvrs_; } + VertexSet *drvrs() { return drvrs_; } + Vertex *dcalcDrvr() const { return dcalc_drvr_; } + void setDcalcDrvr(Vertex *drvr); + void parallelDelaySlew(const RiseFall *drvr_rf, + const DcalcAnalysisPt *dcalc_ap, + ArcDelayCalc *arc_delay_calc, + GraphDelayCalc *dcalc, + // Return values. + ArcDelay ¶llel_delay, + Slew ¶llel_slew); + void netCaps(const RiseFall *rf, + const DcalcAnalysisPt *dcalc_ap, + // Return values. + float &pin_cap, + float &wire_cap, + float &fanout, + bool &has_net_load); + void findCaps(const GraphDelayCalc *dcalc, + const Sdc *sdc); + +private: + void findDelaysSlews(ArcDelayCalc *arc_delay_calc, + GraphDelayCalc *dcalc); + + // Driver that triggers delay calculation for all the drivers on the net. + Vertex *dcalc_drvr_; + VertexSet *drvrs_; + // [drvr_rf->index][dcalc_ap->index] + ArcDelay *parallel_delays_; + // [drvr_rf->index][dcalc_ap->index] + Slew *parallel_slews_; + // [drvr_rf->index][dcalc_ap->index] + NetCaps *net_caps_; + bool delays_valid_:1; +}; + +MultiDrvrNet::MultiDrvrNet(VertexSet *drvrs) : + dcalc_drvr_(nullptr), + drvrs_(drvrs), + parallel_delays_(nullptr), + parallel_slews_(nullptr), + net_caps_(nullptr), + delays_valid_(false) +{ +} + +MultiDrvrNet::~MultiDrvrNet() +{ + delete drvrs_; + if (delays_valid_) { + delete [] parallel_delays_; + delete [] parallel_slews_; + } + delete [] net_caps_; } void -GraphDelayCalc::setObserver(DelayCalcObserver *observer) +MultiDrvrNet::parallelDelaySlew(const RiseFall *drvr_rf, + const DcalcAnalysisPt *dcalc_ap, + ArcDelayCalc *arc_delay_calc, + GraphDelayCalc *dcalc, + // Return values. + ArcDelay ¶llel_delay, + Slew ¶llel_slew) { - // Observer not needed by GraphDelayCalc. - delete observer; + if (!delays_valid_) { + findDelaysSlews(arc_delay_calc, dcalc); + delays_valid_ = true; + } + + int index = dcalc_ap->index() * RiseFall::index_count + + drvr_rf->index(); + parallel_delay = parallel_delays_[index]; + parallel_slew = parallel_slews_[index]; } -string -GraphDelayCalc::reportDelayCalc(Edge *, - TimingArc *, - const Corner *, - const MinMax *, - int) +void +MultiDrvrNet::findDelaysSlews(ArcDelayCalc *arc_delay_calc, + GraphDelayCalc *dcalc) { - return ""; + Corners *corners = dcalc->corners(); + int count = RiseFall::index_count * corners->dcalcAnalysisPtCount(); + parallel_delays_ = new ArcDelay[count]; + parallel_slews_ = new Slew[count]; + for (auto dcalc_ap : corners->dcalcAnalysisPts()) { + DcalcAPIndex ap_index = dcalc_ap->index(); + const Pvt *pvt = dcalc_ap->operatingConditions(); + for (auto drvr_rf : RiseFall::range()) { + int drvr_rf_index = drvr_rf->index(); + int index = ap_index*RiseFall::index_count+drvr_rf_index; + dcalc->findMultiDrvrGateDelay(this, drvr_rf, pvt, dcalc_ap, + arc_delay_calc, + parallel_delays_[index], + parallel_slews_[index]); + } + } +} + +void +MultiDrvrNet::netCaps(const RiseFall *drvr_rf, + const DcalcAnalysisPt *dcalc_ap, + // Return values. + float &pin_cap, + float &wire_cap, + float &fanout, + bool &has_net_load) +{ + int index = dcalc_ap->index() * RiseFall::index_count + + drvr_rf->index(); + NetCaps &net_caps = net_caps_[index]; + pin_cap = net_caps.pinCap(); + wire_cap = net_caps.wireCap(); + fanout = net_caps.fanout(); + has_net_load = net_caps.hasNetLoad(); +} + +void +MultiDrvrNet::findCaps(const GraphDelayCalc *dcalc, + const Sdc *sdc) +{ + Corners *corners = dcalc->corners(); + int count = RiseFall::index_count * corners->dcalcAnalysisPtCount(); + net_caps_ = new NetCaps[count]; + const Pin *drvr_pin = dcalc_drvr_->pin(); + for (auto dcalc_ap : corners->dcalcAnalysisPts()) { + DcalcAPIndex ap_index = dcalc_ap->index(); + const Corner *corner = dcalc_ap->corner(); + const OperatingConditions *op_cond = dcalc_ap->operatingConditions(); + const MinMax *min_max = dcalc_ap->constraintMinMax(); + for (auto drvr_rf : RiseFall::range()) { + int drvr_rf_index = drvr_rf->index(); + int index = ap_index * RiseFall::index_count + drvr_rf_index; + NetCaps &net_caps = net_caps_[index]; + float pin_cap, wire_cap, fanout; + bool has_net_load; + // Find pin and external pin/wire capacitance. + sdc->connectedCap(drvr_pin, drvr_rf, op_cond, corner, min_max, + pin_cap, wire_cap, fanout, has_net_load); + net_caps.init(pin_cap, wire_cap, fanout, has_net_load); + } + } +} + +void +MultiDrvrNet::setDcalcDrvr(Vertex *drvr) +{ + dcalc_drvr_ = drvr; +} + +//////////////////////////////////////////////////////////////// + +GraphDelayCalc::GraphDelayCalc(StaState *sta) : + StaState(sta), + observer_(nullptr), + delays_seeded_(false), + incremental_(false), + delays_exist_(false), + invalid_delays_(new VertexSet(graph_)), + search_pred_(new SearchPred1(sta)), + search_non_latch_pred_(new SearchPredNonLatch2(sta)), + clk_pred_(new ClkTreeSearchPred(sta)), + iter_(new BfsFwdIterator(BfsIndex::dcalc, search_non_latch_pred_, sta)), + multi_drvr_nets_found_(false), + incremental_delay_tolerance_(0.0) +{ +} + +GraphDelayCalc::~GraphDelayCalc() +{ + delete search_pred_; + delete invalid_delays_; + delete search_non_latch_pred_; + delete clk_pred_; + delete iter_; + deleteMultiDrvrNets(); + delete observer_; +} + +void +GraphDelayCalc::deleteMultiDrvrNets() +{ + MultiDrvrNetSet drvr_nets; + MultiDrvrNetMap::Iterator multi_iter(multi_drvr_net_map_); + while (multi_iter.hasNext()) { + MultiDrvrNet *multi_drvr = multi_iter.next(); + // Multiple drvr pins point to the same drvr PinSet, + // so collect them into a set. + drvr_nets.insert(multi_drvr); + } + multi_drvr_net_map_.clear(); + drvr_nets.deleteContents(); +} + +void +GraphDelayCalc::copyState(const StaState *sta) +{ + StaState::copyState(sta); + // Notify sub-components. + iter_->copyState(sta); +} + +void +GraphDelayCalc::clear() +{ + delaysInvalid(); + deleteMultiDrvrNets(); + multi_drvr_nets_found_ = false; } float GraphDelayCalc::incrementalDelayTolerance() { - return 0.0; + return incremental_delay_tolerance_; } void -GraphDelayCalc::loadCap(const Pin *, - const Parasitic *, - const RiseFall *, - const DcalcAnalysisPt *, - // Return values. - float &pin_cap, - float &wire_cap) const +GraphDelayCalc::setIncrementalDelayTolerance(float tol) { - pin_cap = wire_cap = 0.0F; -} - -float -GraphDelayCalc::loadCap(const Pin *, - const RiseFall *, - const DcalcAnalysisPt *) const -{ - return 0.0F; -} - -float -GraphDelayCalc::loadCap(const Pin *, - const Parasitic *, - const RiseFall *, - const DcalcAnalysisPt *) const -{ - return 0.0F; -} - -float -GraphDelayCalc::loadCap(const Pin *, - const DcalcAnalysisPt *) const -{ - return 0.0F; + incremental_delay_tolerance_ = tol; } void -GraphDelayCalc::netCaps(const Pin *, - const RiseFall *, - const DcalcAnalysisPt *, - // Return values. - float &pin_cap, - float &wire_cap, - float &fanout, - bool &has_set_load) const +GraphDelayCalc::setObserver(DelayCalcObserver *observer) { - pin_cap = wire_cap = fanout = 0.0F; - has_set_load = false; + delete observer_; + observer_ = observer; +} + +void +GraphDelayCalc::delaysInvalid() +{ + debugPrint(debug_, "delay_calc", 1, "delays invalid"); + delays_exist_ = false; + delays_seeded_ = false; + incremental_ = false; + iter_->clear(); + // No need to keep track of incremental updates any more. + invalid_delays_->clear(); + invalid_check_edges_.clear(); + invalid_latch_edges_.clear(); +} + +void +GraphDelayCalc::delayInvalid(const Pin *pin) +{ + if (graph_ && incremental_) { + if (network_->isHierarchical(pin)) { + EdgesThruHierPinIterator edge_iter(pin, network_, graph_); + while (edge_iter.hasNext()) { + Edge *edge = edge_iter.next(); + delayInvalid(edge->from(graph_)); + } + } + else { + Vertex *vertex, *bidirect_drvr_vertex; + graph_->pinVertices(pin, vertex, bidirect_drvr_vertex); + if (vertex) + delayInvalid(vertex); + if (bidirect_drvr_vertex) + delayInvalid(bidirect_drvr_vertex); + } + } +} + +void +GraphDelayCalc::delayInvalid(Vertex *vertex) +{ + debugPrint(debug_, "delay_calc", 2, "delay invalid %s", + vertex->name(sdc_network_)); + if (graph_ && incremental_) { + invalid_delays_->insert(vertex); + // Invalidate driver that triggers dcalc for multi-driver nets. + MultiDrvrNet *multi_drvr = multiDrvrNet(vertex); + if (multi_drvr) + invalid_delays_->insert(multi_drvr->dcalcDrvr()); + } +} + +void +GraphDelayCalc::deleteVertexBefore(Vertex *vertex) +{ + iter_->deleteVertexBefore(vertex); + if (incremental_) + invalid_delays_->erase(vertex); + MultiDrvrNet *multi_drvr = multiDrvrNet(vertex); + if (multi_drvr) { + multi_drvr->drvrs()->erase(vertex); + multi_drvr_net_map_.erase(vertex); + } +} + +//////////////////////////////////////////////////////////////// + +class FindVertexDelays : public VertexVisitor +{ +public: + FindVertexDelays(GraphDelayCalc *graph_delay_calc1); + virtual ~FindVertexDelays(); + virtual void visit(Vertex *vertex); + virtual VertexVisitor *copy() const; + +protected: + GraphDelayCalc *graph_delay_calc1_; + ArcDelayCalc *arc_delay_calc_; +}; + +FindVertexDelays::FindVertexDelays(GraphDelayCalc *graph_delay_calc1) : + VertexVisitor(), + graph_delay_calc1_(graph_delay_calc1), + arc_delay_calc_(graph_delay_calc1_->arc_delay_calc_->copy()) +{ +} + +FindVertexDelays::~FindVertexDelays() +{ + delete arc_delay_calc_; +} + +VertexVisitor * +FindVertexDelays::copy() const +{ + // Copy StaState::arc_delay_calc_ because it needs separate state + // for each thread. + return new FindVertexDelays(graph_delay_calc1_); +} + +void +FindVertexDelays::visit(Vertex *vertex) +{ + graph_delay_calc1_->findVertexDelay(vertex, arc_delay_calc_, true); +} + +// The logical structure of incremental delay calculation closely +// resembles the incremental search arrival time algorithm +// (Search::findArrivals). +void +GraphDelayCalc::findDelays(Level level) +{ + if (arc_delay_calc_) { + Stats stats(debug_, report_); + int dcalc_count = 0; + debugPrint(debug_, "delay_calc", 1, "find delays to level %d", level); + if (!delays_seeded_) { + iter_->clear(); + ensureMultiDrvrNetsFound(); + seedRootSlews(); + delays_seeded_ = true; + } + else + iter_->ensureSize(); + if (incremental_) + seedInvalidDelays(); + + FindVertexDelays visitor(this); + dcalc_count += iter_->visitParallel(level, &visitor); + + // Timing checks require slews at both ends of the arc, + // so find their delays after all slews are known. + for (Edge *check_edge : invalid_check_edges_) + findCheckEdgeDelays(check_edge, arc_delay_calc_); + invalid_check_edges_.clear(); + + for (Edge *latch_edge : invalid_latch_edges_) + findLatchEdgeDelays(latch_edge); + invalid_latch_edges_.clear(); + + delays_exist_ = true; + incremental_ = true; + debugPrint(debug_, "delay_calc", 1, "found %d delays", dcalc_count); + stats.report("Delay calc"); + } +} + +void +GraphDelayCalc::seedInvalidDelays() +{ + for (Vertex *vertex : *invalid_delays_) { + if (vertex->isRoot()) + seedRootSlew(vertex, arc_delay_calc_); + else { + if (search_non_latch_pred_->searchFrom(vertex)) + iter_->enqueue(vertex); + } + } + invalid_delays_->clear(); +} + +class FindNetDrvrs : public PinVisitor +{ +public: + FindNetDrvrs(PinSet &drvr_pins, + const Network *network, + const Graph *graph); + virtual void operator()(const Pin *pin); + +protected: + PinSet &drvr_pins_; + const Network *network_; + const Graph *graph_; +}; + +FindNetDrvrs::FindNetDrvrs(PinSet &drvr_pins, + const Network *network, + const Graph *graph) : + drvr_pins_(drvr_pins), + network_(network), + graph_(graph) +{ +} + +void +FindNetDrvrs::operator()(const Pin *pin) +{ + Vertex *vertex = graph_->pinDrvrVertex(pin); + if (isLeafDriver(pin, network_) + && !(vertex && vertex->isRoot())) + drvr_pins_.insert(pin); +} + +void +GraphDelayCalc::ensureMultiDrvrNetsFound() +{ + if (!multi_drvr_nets_found_) { + LeafInstanceIterator *inst_iter = network_->leafInstanceIterator(); + while (inst_iter->hasNext()) { + Instance *inst = inst_iter->next(); + InstancePinIterator *pin_iter = network_->pinIterator(inst); + while (pin_iter->hasNext()) { + Pin *pin = pin_iter->next(); + Vertex *drvr_vertex = graph_->pinDrvrVertex(pin); + if (network_->isDriver(pin) + && !multi_drvr_net_map_.hasKey(drvr_vertex)) { + PinSet drvr_pins(network_); + FindNetDrvrs visitor(drvr_pins, network_, graph_); + network_->visitConnectedPins(pin, visitor); + if (drvr_pins.size() > 1) + makeMultiDrvrNet(drvr_pins); + } + } + delete pin_iter; + } + delete inst_iter; + multi_drvr_nets_found_ = true; + } +} + +void +GraphDelayCalc::makeMultiDrvrNet(PinSet &drvr_pins) +{ + debugPrint(debug_, "delay_calc", 3, "multi-driver net"); + VertexSet *drvr_vertices = new VertexSet(graph_); + MultiDrvrNet *multi_drvr = new MultiDrvrNet(drvr_vertices); + Level max_drvr_level = 0; + Vertex *max_drvr = nullptr; + PinSet::Iterator pin_iter(drvr_pins); + while (pin_iter.hasNext()) { + const Pin *pin = pin_iter.next(); + Vertex *drvr_vertex = graph_->pinDrvrVertex(pin); + debugPrint(debug_, "delay_calc", 3, " %s", + network_->pathName(pin)); + multi_drvr_net_map_[drvr_vertex] = multi_drvr; + drvr_vertices->insert(drvr_vertex); + Level drvr_level = drvr_vertex->level(); + if (max_drvr == nullptr + || drvr_level > max_drvr_level) { + max_drvr = drvr_vertex; + max_drvr_level = drvr_level; + } + } + multi_drvr->setDcalcDrvr(max_drvr); + multi_drvr->findCaps(this, sdc_); +} + +static bool +isLeafDriver(const Pin *pin, + const Network *network) +{ + PortDirection *dir = network->direction(pin); + const Instance *inst = network->instance(pin); + return network->isLeaf(inst) && dir->isAnyOutput(); +} + +MultiDrvrNet * +GraphDelayCalc::multiDrvrNet(const Vertex *drvr_vertex) const +{ + return multi_drvr_net_map_.findKey(drvr_vertex); +} + +void +GraphDelayCalc::seedRootSlews() +{ + for (Vertex *vertex : *levelize_->roots()) + seedRootSlew(vertex, arc_delay_calc_); +} + +void +GraphDelayCalc::seedRootSlew(Vertex *vertex, + ArcDelayCalc *arc_delay_calc) +{ + if (vertex->isDriver(network_)) + seedDrvrSlew(vertex, arc_delay_calc); + else + seedLoadSlew(vertex); + iter_->enqueueAdjacentVertices(vertex); +} + +void +GraphDelayCalc::seedDrvrSlew(Vertex *drvr_vertex, + ArcDelayCalc *arc_delay_calc) +{ + const Pin *drvr_pin = drvr_vertex->pin(); + debugPrint(debug_, "delay_calc", 2, "seed driver slew %s", + drvr_vertex->name(sdc_network_)); + InputDrive *drive = 0; + if (network_->isTopLevelPort(drvr_pin)) { + Port *port = network_->port(drvr_pin); + drive = sdc_->findInputDrive(port); + } + for (auto rf : RiseFall::range()) { + for (auto dcalc_ap : corners_->dcalcAnalysisPts()) { + if (drive) { + const MinMax *cnst_min_max = dcalc_ap->constraintMinMax(); + const LibertyCell *drvr_cell; + const LibertyPort *from_port, *to_port; + float *from_slews; + drive->driveCell(rf, cnst_min_max, drvr_cell, from_port, + from_slews, to_port); + if (drvr_cell) { + if (from_port == nullptr) + from_port = driveCellDefaultFromPort(drvr_cell, to_port); + findInputDriverDelay(drvr_cell, drvr_pin, drvr_vertex, rf, + from_port, from_slews, to_port, dcalc_ap); + } + else + seedNoDrvrCellSlew(drvr_vertex, drvr_pin, rf, drive, dcalc_ap, + arc_delay_calc); + } + else + seedNoDrvrSlew(drvr_vertex, drvr_pin, rf, dcalc_ap, arc_delay_calc); + } + } +} + +void +GraphDelayCalc::seedNoDrvrCellSlew(Vertex *drvr_vertex, + const Pin *drvr_pin, + const RiseFall *rf, + InputDrive *drive, + DcalcAnalysisPt *dcalc_ap, + ArcDelayCalc *arc_delay_calc) +{ + DcalcAPIndex ap_index = dcalc_ap->index(); + const MinMax *cnst_min_max = dcalc_ap->constraintMinMax(); + Slew slew = default_slew; + float drive_slew; + bool exists; + drive->slew(rf, cnst_min_max, drive_slew, exists); + if (exists) + slew = drive_slew; + else { + // Top level bidirect driver uses load slew unless + // bidirect instance paths are disabled. + if (sdc_->bidirectDrvrSlewFromLoad(drvr_pin)) { + Vertex *load_vertex = graph_->pinLoadVertex(drvr_pin); + slew = graph_->slew(load_vertex, rf, ap_index); + } + } + Delay drive_delay = delay_zero; + float drive_res; + drive->driveResistance(rf, cnst_min_max, drive_res, exists); + Parasitic *parasitic = arc_delay_calc->findParasitic(drvr_pin, rf, dcalc_ap); + if (exists) { + float cap = loadCap(drvr_pin, parasitic, rf, dcalc_ap); + drive_delay = cap * drive_res; + slew = cap * drive_res; + } + const MinMax *slew_min_max = dcalc_ap->slewMinMax(); + if (!drvr_vertex->slewAnnotated(rf, slew_min_max)) + graph_->setSlew(drvr_vertex, rf, ap_index, slew); + arc_delay_calc->inputPortDelay(drvr_pin, delayAsFloat(slew), rf, + parasitic, dcalc_ap); + annotateLoadDelays(drvr_vertex, rf, drive_delay, false, dcalc_ap, + arc_delay_calc); + arc_delay_calc->finishDrvrPin(); +} + +void +GraphDelayCalc::seedNoDrvrSlew(Vertex *drvr_vertex, + const Pin *drvr_pin, + const RiseFall *rf, + DcalcAnalysisPt *dcalc_ap, + ArcDelayCalc *arc_delay_calc) +{ + const MinMax *slew_min_max = dcalc_ap->slewMinMax(); + DcalcAPIndex ap_index = dcalc_ap->index(); + Slew slew(default_slew); + // Top level bidirect driver uses load slew unless + // bidirect instance paths are disabled. + if (sdc_->bidirectDrvrSlewFromLoad(drvr_pin)) { + Vertex *load_vertex = graph_->pinLoadVertex(drvr_pin); + slew = graph_->slew(load_vertex, rf, ap_index); + } + if (!drvr_vertex->slewAnnotated(rf, slew_min_max)) + graph_->setSlew(drvr_vertex, rf, ap_index, slew); + Parasitic *parasitic = arc_delay_calc->findParasitic(drvr_pin, rf, dcalc_ap); + arc_delay_calc->inputPortDelay(drvr_pin, delayAsFloat(slew), rf, + parasitic, dcalc_ap); + annotateLoadDelays(drvr_vertex, rf, delay_zero, false, dcalc_ap, + arc_delay_calc); + arc_delay_calc->finishDrvrPin(); +} + +void +GraphDelayCalc::seedLoadSlew(Vertex *vertex) +{ + const Pin *pin = vertex->pin(); + debugPrint(debug_, "delay_calc", 2, "seed load slew %s", + vertex->name(sdc_network_)); + ClockSet *clks = sdc_->findLeafPinClocks(pin); + initSlew(vertex); + for (auto rf : RiseFall::range()) { + for (auto dcalc_ap : corners_->dcalcAnalysisPts()) { + const MinMax *slew_min_max = dcalc_ap->slewMinMax(); + if (!vertex->slewAnnotated(rf, slew_min_max)) { + float slew = 0.0; + if (clks) { + slew = slew_min_max->initValue(); + ClockSet::Iterator clk_iter(clks); + while (clk_iter.hasNext()) { + Clock *clk = clk_iter.next(); + float clk_slew = clk->slew(rf, slew_min_max); + if (slew_min_max->compare(clk_slew, slew)) + slew = clk_slew; + } + } + DcalcAPIndex ap_index = dcalc_ap->index(); + graph_->setSlew(vertex, rf, ap_index, slew); + } + } + } +} + +// If a driving cell does not specify a -from_pin, the first port +// defined in the cell that has a timing group to the output port +// is used. Not exactly reasonable, but it's compatible. +LibertyPort * +GraphDelayCalc::driveCellDefaultFromPort(const LibertyCell *cell, + const LibertyPort *to_port) +{ + LibertyPort *from_port = 0; + int from_port_index = 0; + for (TimingArcSet *arc_set : cell->timingArcSets(nullptr, to_port)) { + LibertyPort *set_from_port = arc_set->from(); + int set_from_port_index = findPortIndex(cell, set_from_port); + if (from_port == nullptr + || set_from_port_index < from_port_index) { + from_port = set_from_port; + from_port_index = set_from_port_index; + } + } + return from_port; +} + +// Find the index that port is defined in cell. +int +GraphDelayCalc::findPortIndex(const LibertyCell *cell, + const LibertyPort *port) +{ + int index = 0; + LibertyCellPortIterator port_iter(cell); + while (port_iter.hasNext()) { + LibertyPort *cell_port = port_iter.next(); + if (cell_port == port) + return index; + index++; + } + report_->critical(207, "port not found in cell"); + return 0; +} + +void +GraphDelayCalc::findInputDriverDelay(const LibertyCell *drvr_cell, + const Pin *drvr_pin, + Vertex *drvr_vertex, + const RiseFall *rf, + const LibertyPort *from_port, + float *from_slews, + const LibertyPort *to_port, + const DcalcAnalysisPt *dcalc_ap) +{ + debugPrint(debug_, "delay_calc", 2, " driver cell %s %s", + drvr_cell->name(), + rf->asString()); + for (TimingArcSet *arc_set : drvr_cell->timingArcSets(from_port, to_port)) { + for (TimingArc *arc : arc_set->arcs()) { + if (arc->toEdge()->asRiseFall() == rf) { + float from_slew = from_slews[arc->fromEdge()->index()]; + findInputArcDelay(drvr_cell, drvr_pin, drvr_vertex, + arc, from_slew, dcalc_ap); + } + } + } +} + +// Driving cell delay is the load dependent delay, which is the gate +// delay minus the intrinsic delay. Driving cell delays are annotated +// to the wire arcs from the input port pin to the load pins. +void +GraphDelayCalc::findInputArcDelay(const LibertyCell *drvr_cell, + const Pin *drvr_pin, + Vertex *drvr_vertex, + const TimingArc *arc, + float from_slew, + const DcalcAnalysisPt *dcalc_ap) +{ + debugPrint(debug_, "delay_calc", 3, " %s %s -> %s %s (%s)", + arc->from()->name(), + arc->fromEdge()->asString(), + arc->to()->name(), + arc->toEdge()->asString(), + arc->role()->asString()); + RiseFall *drvr_rf = arc->toEdge()->asRiseFall(); + if (drvr_rf) { + DcalcAPIndex ap_index = dcalc_ap->index(); + const Pvt *pvt = dcalc_ap->operatingConditions(); + Parasitic *drvr_parasitic = arc_delay_calc_->findParasitic(drvr_pin, drvr_rf, + dcalc_ap); + float load_cap = loadCap(drvr_pin, drvr_parasitic, drvr_rf, dcalc_ap); + + ArcDelay intrinsic_delay; + Slew intrinsic_slew; + arc_delay_calc_->gateDelay(drvr_cell, arc, Slew(from_slew), + 0.0, 0, 0.0, pvt, dcalc_ap, + intrinsic_delay, intrinsic_slew); + + // For input drivers there is no instance to find a related_output_pin. + ArcDelay gate_delay; + Slew gate_slew; + arc_delay_calc_->gateDelay(drvr_cell, arc, + Slew(from_slew), load_cap, + drvr_parasitic, 0.0, pvt, dcalc_ap, + gate_delay, gate_slew); + ArcDelay load_delay = gate_delay - intrinsic_delay; + debugPrint(debug_, "delay_calc", 3, + " gate delay = %s intrinsic = %s slew = %s", + delayAsString(gate_delay, this), + delayAsString(intrinsic_delay, this), + delayAsString(gate_slew, this)); + graph_->setSlew(drvr_vertex, drvr_rf, ap_index, gate_slew); + annotateLoadDelays(drvr_vertex, drvr_rf, load_delay, false, dcalc_ap, + arc_delay_calc_); + } +} + +void +GraphDelayCalc::findDelays(Vertex *drvr_vertex) +{ + findVertexDelay(drvr_vertex, arc_delay_calc_, true); +} + +void +GraphDelayCalc::findVertexDelay(Vertex *vertex, + ArcDelayCalc *arc_delay_calc, + bool propagate) +{ + const Pin *pin = vertex->pin(); + debugPrint(debug_, "delay_calc", 2, "find delays %s (%s)", + vertex->name(sdc_network_), + network_->cellName(network_->instance(pin))); + if (vertex->isRoot()) { + seedRootSlew(vertex, arc_delay_calc); + if (propagate) + iter_->enqueueAdjacentVertices(vertex); + } + else { + if (network_->isLeaf(pin)) { + if (vertex->isDriver(network_)) { + bool delay_changed = findDriverDelays(vertex, arc_delay_calc); + if (propagate) { + if (network_->direction(pin)->isInternal()) + enqueueTimingChecksEdges(vertex); + // Enqueue adjacent vertices even if the delays did not + // change when non-incremental to stride past annotations. + if (delay_changed || !incremental_) + iter_->enqueueAdjacentVertices(vertex); + } + } + else { + // Load vertex. + enqueueTimingChecksEdges(vertex); + // Enqueue driver vertices from this input load. + if (propagate) + iter_->enqueueAdjacentVertices(vertex); + } + } + // Bidirect port drivers are enqueued by their load vertex in + // annotateLoadDelays. + else if (vertex->isBidirectDriver() + && network_->isTopLevelPort(pin)) + seedRootSlew(vertex, arc_delay_calc); + } +} + +void +GraphDelayCalc::enqueueTimingChecksEdges(Vertex *vertex) +{ + if (vertex->hasChecks()) { + VertexInEdgeIterator edge_iter(vertex, graph_); + UniqueLock lock(invalid_edge_lock_); + while (edge_iter.hasNext()) { + Edge *edge = edge_iter.next(); + if (edge->role()->isTimingCheck()) + invalid_check_edges_.insert(edge); + } + } + if (vertex->isCheckClk()) { + VertexOutEdgeIterator edge_iter(vertex, graph_); + UniqueLock lock(invalid_edge_lock_); + while (edge_iter.hasNext()) { + Edge *edge = edge_iter.next(); + if (edge->role()->isTimingCheck()) + invalid_check_edges_.insert(edge); + } + } + if (network_->isLatchData(vertex->pin())) { + // Latch D->Q arcs have to be re-evaled if level(D) > level(E) + // because levelization does not traverse D->Q arcs to break loops. + VertexOutEdgeIterator edge_iter(vertex, graph_); + UniqueLock lock(invalid_edge_lock_); + while (edge_iter.hasNext()) { + Edge *edge = edge_iter.next(); + if (edge->role() == TimingRole::latchDtoQ()) + invalid_latch_edges_.insert(edge); + } + } +} + +bool +GraphDelayCalc::findDriverDelays(Vertex *drvr_vertex, + ArcDelayCalc *arc_delay_calc) +{ + bool delay_changed = false; + MultiDrvrNet *multi_drvr = multiDrvrNet(drvr_vertex); + if (multi_drvr) { + Vertex *dcalc_drvr = multi_drvr->dcalcDrvr(); + if (drvr_vertex == dcalc_drvr) { + bool init_load_slews = true; + for (Vertex *drvr_vertex : *multi_drvr->drvrs()) { + // Only init load slews once so previous driver dcalc results + // aren't clobbered. + delay_changed |= findDriverDelays1(drvr_vertex, init_load_slews, + multi_drvr, arc_delay_calc); + init_load_slews = false; + } + } + } + else + delay_changed = findDriverDelays1(drvr_vertex, true, nullptr, arc_delay_calc); + arc_delay_calc->finishDrvrPin(); + return delay_changed; +} + +bool +GraphDelayCalc::findDriverDelays1(Vertex *drvr_vertex, + bool init_load_slews, + MultiDrvrNet *multi_drvr, + ArcDelayCalc *arc_delay_calc) +{ + const Pin *drvr_pin = drvr_vertex->pin(); + Instance *drvr_inst = network_->instance(drvr_pin); + LibertyCell *drvr_cell = network_->libertyCell(drvr_inst); + initSlew(drvr_vertex); + initWireDelays(drvr_vertex, init_load_slews); + bool delay_changed = false; + bool has_delays = false; + VertexInEdgeIterator edge_iter(drvr_vertex, graph_); + while (edge_iter.hasNext()) { + Edge *edge = edge_iter.next(); + Vertex *from_vertex = edge->from(graph_); + // Don't let disabled edges set slews that influence downstream delays. + if (search_pred_->searchFrom(from_vertex) + && search_pred_->searchThru(edge) + && !edge->role()->isLatchDtoQ()) { + delay_changed |= findDriverEdgeDelays(drvr_cell, drvr_inst, drvr_pin, + drvr_vertex, multi_drvr, edge, + arc_delay_calc); + has_delays = true; + } + } + if (!has_delays) + zeroSlewAndWireDelays(drvr_vertex); + if (delay_changed && observer_) + observer_->delayChangedTo(drvr_vertex); + return delay_changed; +} + +// Init slews to zero on root vertices that are not inputs, such as +// floating input pins. +void +GraphDelayCalc::initRootSlews(Vertex *vertex) +{ + for (auto dcalc_ap : corners_->dcalcAnalysisPts()) { + const MinMax *slew_min_max = dcalc_ap->slewMinMax(); + DcalcAPIndex ap_index = dcalc_ap->index(); + for (auto rf : RiseFall::range()) { + if (!vertex->slewAnnotated(rf, slew_min_max)) + graph_->setSlew(vertex, rf, ap_index, default_slew); + } + } +} + +void +GraphDelayCalc::findLatchEdgeDelays(Edge *edge) +{ + Vertex *drvr_vertex = edge->to(graph_); + const Pin *drvr_pin = drvr_vertex->pin(); + Instance *drvr_inst = network_->instance(drvr_pin); + LibertyCell *drvr_cell = network_->libertyCell(drvr_inst); + debugPrint(debug_, "delay_calc", 2, "find latch D->Q %s", + sdc_network_->pathName(drvr_inst)); + bool delay_changed = findDriverEdgeDelays(drvr_cell, drvr_inst, drvr_pin, + drvr_vertex, nullptr, edge, arc_delay_calc_); + if (delay_changed && observer_) + observer_->delayChangedTo(drvr_vertex); +} + +bool +GraphDelayCalc::findDriverEdgeDelays(LibertyCell *drvr_cell, + Instance *drvr_inst, + const Pin *drvr_pin, + Vertex *drvr_vertex, + MultiDrvrNet *multi_drvr, + Edge *edge, + ArcDelayCalc *arc_delay_calc) +{ + Vertex *in_vertex = edge->from(graph_); + TimingArcSet *arc_set = edge->timingArcSet(); + const LibertyPort *related_out_port = arc_set->relatedOut(); + const Pin *related_out_pin = 0; + bool delay_changed = false; + if (related_out_port) + related_out_pin = network_->findPin(drvr_inst, related_out_port); + for (auto dcalc_ap : corners_->dcalcAnalysisPts()) { + const Pvt *pvt = sdc_->pvt(drvr_inst, dcalc_ap->constraintMinMax()); + if (pvt == nullptr) + pvt = dcalc_ap->operatingConditions(); + for (TimingArc *arc : arc_set->arcs()) { + const RiseFall *rf = arc->toEdge()->asRiseFall(); + Parasitic *parasitic = arc_delay_calc->findParasitic(drvr_pin, rf, + dcalc_ap); + float related_out_cap = 0.0; + if (related_out_pin) { + Parasitic *related_out_parasitic = + arc_delay_calc->findParasitic(related_out_pin, rf, dcalc_ap); + related_out_cap = loadCap(related_out_pin, + related_out_parasitic, + rf, dcalc_ap); + } + delay_changed |= findArcDelay(drvr_cell, drvr_pin, drvr_vertex, + multi_drvr, arc, parasitic, + related_out_cap, + in_vertex, edge, pvt, dcalc_ap, + arc_delay_calc); + } + } + + if (delay_changed && observer_) { + observer_->delayChangedFrom(in_vertex); + observer_->delayChangedFrom(drvr_vertex); + } + return delay_changed; } float -GraphDelayCalc::ceff(Edge *, - TimingArc *, - const DcalcAnalysisPt *) +GraphDelayCalc::loadCap(const Pin *drvr_pin, + const DcalcAnalysisPt *dcalc_ap) const { - return 0.0; + const MinMax *min_max = dcalc_ap->constraintMinMax(); + float load_cap = 0.0; + for (auto drvr_rf : RiseFall::range()) { + Parasitic *drvr_parasitic = arc_delay_calc_->findParasitic(drvr_pin, drvr_rf, + dcalc_ap); + float cap = loadCap(drvr_pin, nullptr, drvr_parasitic, drvr_rf, dcalc_ap); + arc_delay_calc_->finishDrvrPin(); + if (min_max->compare(cap, load_cap)) + load_cap = cap; + } + return load_cap; } +float +GraphDelayCalc::loadCap(const Pin *drvr_pin, + const RiseFall *drvr_rf, + const DcalcAnalysisPt *dcalc_ap) const +{ + Parasitic *drvr_parasitic = arc_delay_calc_->findParasitic(drvr_pin, drvr_rf, + dcalc_ap); + float cap = loadCap(drvr_pin, nullptr, drvr_parasitic, drvr_rf, dcalc_ap); + return cap; +} + +float +GraphDelayCalc::loadCap(const Pin *drvr_pin, + const Parasitic *drvr_parasitic, + const RiseFall *rf, + const DcalcAnalysisPt *dcalc_ap) const +{ + return loadCap(drvr_pin, nullptr, drvr_parasitic, rf, dcalc_ap); +} + +float +GraphDelayCalc::loadCap(const Pin *drvr_pin, + MultiDrvrNet *multi_drvr, + const Parasitic *drvr_parasitic, + const RiseFall *rf, + const DcalcAnalysisPt *dcalc_ap) const +{ + float pin_cap, wire_cap; + bool has_net_load; + float fanout; + if (multi_drvr) + multi_drvr->netCaps(rf, dcalc_ap, + pin_cap, wire_cap, fanout, has_net_load); + else + netCaps(drvr_pin, rf, dcalc_ap, + pin_cap, wire_cap, fanout, has_net_load); + loadCap(drvr_parasitic, has_net_load, pin_cap, wire_cap); + return wire_cap + pin_cap; +} + +void +GraphDelayCalc::loadCap(const Pin *drvr_pin, + const Parasitic *drvr_parasitic, + const RiseFall *rf, + const DcalcAnalysisPt *dcalc_ap, + // Return values. + float &pin_cap, + float &wire_cap) const +{ + bool has_net_load; + float fanout; + // Find pin and external pin/wire capacitance. + netCaps(drvr_pin, rf, dcalc_ap, + pin_cap, wire_cap, fanout, has_net_load); + loadCap(drvr_parasitic, has_net_load, pin_cap, wire_cap); +} + +void +GraphDelayCalc::loadCap(const Parasitic *drvr_parasitic, + bool has_net_load, + // Return values. + float &pin_cap, + float &wire_cap) const +{ + // set_load net has precidence over parasitics. + if (!has_net_load && drvr_parasitic) { + if (parasitics_->isParasiticNetwork(drvr_parasitic)) + wire_cap += parasitics_->capacitance(drvr_parasitic); + else { + // PiModel includes both pin and external caps. + float cap = parasitics_->capacitance(drvr_parasitic); + if (pin_cap > cap) { + pin_cap = 0.0; + wire_cap = cap; + } + else + wire_cap = cap - pin_cap; + } + } +} + +void +GraphDelayCalc::netCaps(const Pin *drvr_pin, + const RiseFall *rf, + const DcalcAnalysisPt *dcalc_ap, + // Return values. + float &pin_cap, + float &wire_cap, + float &fanout, + bool &has_net_load) const +{ + MultiDrvrNet *multi_drvr = nullptr; + if (graph_) { + Vertex *drvr_vertex = graph_->pinDrvrVertex(drvr_pin); + multi_drvr = multiDrvrNet(drvr_vertex); + } + if (multi_drvr) + multi_drvr->netCaps(rf, dcalc_ap, + pin_cap, wire_cap, fanout, has_net_load); + else { + const OperatingConditions *op_cond = dcalc_ap->operatingConditions(); + const Corner *corner = dcalc_ap->corner(); + const MinMax *min_max = dcalc_ap->constraintMinMax(); + // Find pin and external pin/wire capacitance. + sdc_->connectedCap(drvr_pin, rf, op_cond, corner, min_max, + pin_cap, wire_cap, fanout, has_net_load); + } +} + +void +GraphDelayCalc::initSlew(Vertex *vertex) +{ + for (auto rf : RiseFall::range()) { + for (auto dcalc_ap : corners_->dcalcAnalysisPts()) { + const MinMax *slew_min_max = dcalc_ap->slewMinMax(); + if (!vertex->slewAnnotated(rf, slew_min_max)) { + DcalcAPIndex ap_index = dcalc_ap->index(); + graph_->setSlew(vertex, rf, ap_index, slew_min_max->initValue()); + } + } + } +} + +void +GraphDelayCalc::zeroSlewAndWireDelays(Vertex *drvr_vertex) +{ + for (auto dcalc_ap : corners_->dcalcAnalysisPts()) { + DcalcAPIndex ap_index = dcalc_ap->index(); + const MinMax *slew_min_max = dcalc_ap->slewMinMax(); + for (auto rf : RiseFall::range()) { + // Init drvr slew. + if (!drvr_vertex->slewAnnotated(rf, slew_min_max)) { + DcalcAPIndex ap_index = dcalc_ap->index(); + graph_->setSlew(drvr_vertex, rf, ap_index, slew_min_max->initValue()); + } + + // Init wire delays and slews. + VertexOutEdgeIterator edge_iter(drvr_vertex, graph_); + while (edge_iter.hasNext()) { + Edge *wire_edge = edge_iter.next(); + if (wire_edge->isWire()) { + Vertex *load_vertex = wire_edge->to(graph_); + if (!graph_->wireDelayAnnotated(wire_edge, rf, ap_index)) + graph_->setWireArcDelay(wire_edge, rf, ap_index, 0.0); + // Init load vertex slew. + if (!load_vertex->slewAnnotated(rf, slew_min_max)) + graph_->setSlew(load_vertex, rf, ap_index, 0.0); + } + } + } + } +} + +// Init wire delays and load slews. +void +GraphDelayCalc::initWireDelays(Vertex *drvr_vertex, + bool init_load_slews) +{ + VertexOutEdgeIterator edge_iter(drvr_vertex, graph_); + while (edge_iter.hasNext()) { + Edge *wire_edge = edge_iter.next(); + if (wire_edge->isWire()) { + Vertex *load_vertex = wire_edge->to(graph_); + for (auto dcalc_ap : corners_->dcalcAnalysisPts()) { + const MinMax *delay_min_max = dcalc_ap->delayMinMax(); + const MinMax *slew_min_max = dcalc_ap->slewMinMax(); + Delay delay_init_value(delay_min_max->initValue()); + Slew slew_init_value(slew_min_max->initValue()); + DcalcAPIndex ap_index = dcalc_ap->index(); + for (auto rf : RiseFall::range()) { + if (!graph_->wireDelayAnnotated(wire_edge, rf, ap_index)) + graph_->setWireArcDelay(wire_edge, rf, ap_index, delay_init_value); + // Init load vertex slew. + if (init_load_slews + && !load_vertex->slewAnnotated(rf, slew_min_max)) + graph_->setSlew(load_vertex, rf, ap_index, slew_init_value); + } + } + } + } +} + +// Call the arc delay calculator to find the delay thru a single gate +// input to output timing arc, the wire delays from the gate output to +// each load pin, and the slew at each load pin. Annotate the graph +// with the results. +bool +GraphDelayCalc::findArcDelay(LibertyCell *drvr_cell, + const Pin *drvr_pin, + Vertex *drvr_vertex, + MultiDrvrNet *multi_drvr, + TimingArc *arc, + Parasitic *drvr_parasitic, + float related_out_cap, + Vertex *from_vertex, + Edge *edge, + const Pvt *pvt, + const DcalcAnalysisPt *dcalc_ap, + ArcDelayCalc *arc_delay_calc) +{ + bool delay_changed = false; + RiseFall *from_rf = arc->fromEdge()->asRiseFall(); + RiseFall *drvr_rf = arc->toEdge()->asRiseFall(); + if (from_rf && drvr_rf) { + DcalcAPIndex ap_index = dcalc_ap->index(); + debugPrint(debug_, "delay_calc", 3, + " %s %s -> %s %s (%s) corner:%s/%s", + arc->from()->name(), + arc->fromEdge()->asString(), + arc->to()->name(), + arc->toEdge()->asString(), + arc->role()->asString(), + dcalc_ap->corner()->name(), + dcalc_ap->delayMinMax()->asString()); + // Delay calculation is done even when the gate delays/slews are + // annotated because the wire delays may not be annotated. + const Slew from_slew = edgeFromSlew(from_vertex, from_rf, edge, dcalc_ap); + ArcDelay gate_delay; + Slew gate_slew; + if (multi_drvr + && arc->to()->direction()->isOutput()) + parallelGateDelay(multi_drvr, drvr_cell, drvr_pin, arc, + pvt, dcalc_ap, from_slew, drvr_parasitic, + related_out_cap, + arc_delay_calc, + gate_delay, gate_slew); + else { + float load_cap = loadCap(drvr_pin, multi_drvr, drvr_parasitic, + drvr_rf, dcalc_ap); + arc_delay_calc->gateDelay(drvr_cell, arc, + from_slew, load_cap, drvr_parasitic, + related_out_cap, pvt, dcalc_ap, + gate_delay, gate_slew); + } + debugPrint(debug_, "delay_calc", 3, + " gate delay = %s slew = %s", + delayAsString(gate_delay, this), + delayAsString(gate_slew, this)); + // Merge slews. + const Slew &drvr_slew = graph_->slew(drvr_vertex, drvr_rf, ap_index); + const MinMax *slew_min_max = dcalc_ap->slewMinMax(); + if (delayGreater(gate_slew, drvr_slew, dcalc_ap->slewMinMax(), this) + && !drvr_vertex->slewAnnotated(drvr_rf, slew_min_max) + && !edge->role()->isLatchDtoQ()) + graph_->setSlew(drvr_vertex, drvr_rf, ap_index, gate_slew); + if (!graph_->arcDelayAnnotated(edge, arc, ap_index)) { + const ArcDelay &prev_gate_delay = graph_->arcDelay(edge,arc,ap_index); + float gate_delay1 = delayAsFloat(gate_delay); + float prev_gate_delay1 = delayAsFloat(prev_gate_delay); + if (prev_gate_delay1 == 0.0 + || (abs(gate_delay1 - prev_gate_delay1) / prev_gate_delay1 + > incremental_delay_tolerance_)) + delay_changed = true; + graph_->setArcDelay(edge, arc, ap_index, gate_delay); + } + if (!edge->role()->isLatchDtoQ()) + annotateLoadDelays(drvr_vertex, drvr_rf, delay_zero, true, dcalc_ap, + arc_delay_calc); + } + return delay_changed; +} + +void +GraphDelayCalc::parallelGateDelay(MultiDrvrNet *multi_drvr, + LibertyCell *drvr_cell, + const Pin *drvr_pin, + TimingArc *arc, + const Pvt *pvt, + const DcalcAnalysisPt *dcalc_ap, + const Slew from_slew, + Parasitic *drvr_parasitic, + float related_out_cap, + ArcDelayCalc *arc_delay_calc, + // Return values. + ArcDelay &gate_delay, + Slew &gate_slew) +{ + ArcDelay intrinsic_delay; + Slew intrinsic_slew; + arc_delay_calc->gateDelay(drvr_cell, arc, from_slew, + 0.0, 0, 0.0, pvt, dcalc_ap, + intrinsic_delay, intrinsic_slew); + ArcDelay parallel_delay; + Slew parallel_slew; + const RiseFall *drvr_rf = arc->toEdge()->asRiseFall(); + multi_drvr->parallelDelaySlew(drvr_rf, dcalc_ap, arc_delay_calc, this, + parallel_delay, parallel_slew); + + gate_delay = parallel_delay + intrinsic_delay; + gate_slew = parallel_slew; + + float load_cap = loadCap(drvr_pin, multi_drvr, drvr_parasitic, + drvr_rf, dcalc_ap); + Delay gate_delay1; + Slew gate_slew1; + arc_delay_calc->gateDelay(drvr_cell, arc, + from_slew, load_cap, drvr_parasitic, + related_out_cap, pvt, dcalc_ap, + gate_delay1, gate_slew1); + float factor = delayRatio(gate_slew, gate_slew1); + arc_delay_calc->setMultiDrvrSlewFactor(factor); +} + +void +GraphDelayCalc::findMultiDrvrGateDelay(MultiDrvrNet *multi_drvr, + const RiseFall *drvr_rf, + const Pvt *pvt, + const DcalcAnalysisPt *dcalc_ap, + ArcDelayCalc *arc_delay_calc, + // Return values. + ArcDelay ¶llel_delay, + Slew ¶llel_slew) +{ + ArcDelay delay_sum = 1.0; + Slew slew_sum = 1.0; + for (Vertex *drvr_vertex1 : *multi_drvr->drvrs()) { + Pin *drvr_pin1 = drvr_vertex1->pin(); + Instance *drvr_inst1 = network_->instance(drvr_pin1); + LibertyCell *drvr_cell1 = network_->libertyCell(drvr_inst1); + if (network_->isDriver(drvr_pin1)) { + VertexInEdgeIterator edge_iter(drvr_vertex1, graph_); + while (edge_iter.hasNext()) { + Edge *edge1 = edge_iter.next(); + TimingArcSet *arc_set1 = edge1->timingArcSet(); + const LibertyPort *related_out_port = arc_set1->relatedOut(); + for (TimingArc *arc1 : arc_set1->arcs()) { + RiseFall *drvr_rf1 = arc1->toEdge()->asRiseFall(); + if (drvr_rf1 == drvr_rf) { + Vertex *from_vertex1 = edge1->from(graph_); + RiseFall *from_rf1 = arc1->fromEdge()->asRiseFall(); + Slew from_slew1 = edgeFromSlew(from_vertex1, from_rf1, edge1, dcalc_ap); + ArcDelay intrinsic_delay1; + Slew intrinsic_slew1; + arc_delay_calc->gateDelay(drvr_cell1, arc1, from_slew1, + 0.0, 0, 0.0, pvt, dcalc_ap, + intrinsic_delay1, intrinsic_slew1); + Parasitic *parasitic1 = + arc_delay_calc->findParasitic(drvr_pin1, drvr_rf1, dcalc_ap); + const Pin *related_out_pin1 = 0; + float related_out_cap1 = 0.0; + if (related_out_port) { + Instance *inst1 = network_->instance(drvr_pin1); + related_out_pin1 = network_->findPin(inst1, related_out_port); + if (related_out_pin1) { + Parasitic *related_out_parasitic1 = + arc_delay_calc->findParasitic(related_out_pin1, drvr_rf, + dcalc_ap); + related_out_cap1 = loadCap(related_out_pin1, + related_out_parasitic1, + drvr_rf, dcalc_ap); + } + } + float load_cap1 = loadCap(drvr_pin1, parasitic1, + drvr_rf, dcalc_ap); + ArcDelay gate_delay1; + Slew gate_slew1; + arc_delay_calc->gateDelay(drvr_cell1, arc1, + from_slew1, load_cap1, parasitic1, + related_out_cap1, pvt, dcalc_ap, + gate_delay1, gate_slew1); + delay_sum += 1.0F / (gate_delay1 - intrinsic_delay1); + slew_sum += 1.0F / gate_slew1; + } + } + } + } + } + parallel_delay = 1.0F / delay_sum; + parallel_slew = 1.0F / slew_sum; +} + +// Use clock slew for register/latch clk->q edges. +Slew +GraphDelayCalc::edgeFromSlew(const Vertex *from_vertex, + const RiseFall *from_rf, + const Edge *edge, + const DcalcAnalysisPt *dcalc_ap) +{ + const TimingRole *role = edge->role(); + if (role->genericRole() == TimingRole::regClkToQ() + && clk_network_->isIdealClock(from_vertex->pin())) + return clk_network_->idealClkSlew(from_vertex->pin(), from_rf, + dcalc_ap->slewMinMax()); + else + return graph_->slew(from_vertex, from_rf, dcalc_ap->index()); +} + +// Annotate wire arc delays and load pin slews. +// extra_delay is additional wire delay to add to delay returned +// by the delay calculator. +void +GraphDelayCalc::annotateLoadDelays(Vertex *drvr_vertex, + const RiseFall *drvr_rf, + const ArcDelay &extra_delay, + bool merge, + const DcalcAnalysisPt *dcalc_ap, + ArcDelayCalc *arc_delay_calc) +{ + DcalcAPIndex ap_index = dcalc_ap->index(); + const MinMax *slew_min_max = dcalc_ap->slewMinMax(); + VertexOutEdgeIterator edge_iter(drvr_vertex, graph_); + while (edge_iter.hasNext()) { + Edge *wire_edge = edge_iter.next(); + if (wire_edge->isWire()) { + Vertex *load_vertex = wire_edge->to(graph_); + Pin *load_pin = load_vertex->pin(); + ArcDelay wire_delay; + Slew load_slew; + arc_delay_calc->loadDelay(load_pin, wire_delay, load_slew); + debugPrint(debug_, "delay_calc", 3, + " %s load delay = %s slew = %s", + load_vertex->name(sdc_network_), + delayAsString(wire_delay, this), + delayAsString(load_slew, this)); + if (!load_vertex->slewAnnotated(drvr_rf, slew_min_max)) { + if (drvr_vertex->slewAnnotated(drvr_rf, slew_min_max)) { + // Copy the driver slew to the load if it is annotated. + const Slew &drvr_slew = graph_->slew(drvr_vertex,drvr_rf,ap_index); + graph_->setSlew(load_vertex, drvr_rf, ap_index, drvr_slew); + } + else { + const Slew &slew = graph_->slew(load_vertex, drvr_rf, ap_index); + if (!merge + || delayGreater(load_slew, slew, slew_min_max, this)) + graph_->setSlew(load_vertex, drvr_rf, ap_index, load_slew); + } + } + if (!graph_->wireDelayAnnotated(wire_edge, drvr_rf, ap_index)) { + // Multiple timing arcs with the same output transition + // annotate the same wire edges so they must be combined + // rather than set. + const ArcDelay &delay = graph_->wireArcDelay(wire_edge, drvr_rf, + ap_index); + Delay wire_delay_extra = extra_delay + wire_delay; + const MinMax *delay_min_max = dcalc_ap->delayMinMax(); + if (!merge + || delayGreater(wire_delay_extra, delay, delay_min_max, this)) { + graph_->setWireArcDelay(wire_edge, drvr_rf, ap_index, + wire_delay_extra); + if (observer_) + observer_->delayChangedTo(load_vertex); + } + } + // Enqueue bidirect driver from load vertex. + if (sdc_->bidirectDrvrSlewFromLoad(load_pin)) + iter_->enqueue(graph_->pinDrvrVertex(load_pin)); + } + } +} + +void +GraphDelayCalc::findCheckEdgeDelays(Edge *edge, + ArcDelayCalc *arc_delay_calc) +{ + Vertex *from_vertex = edge->from(graph_); + Vertex *to_vertex = edge->to(graph_); + TimingArcSet *arc_set = edge->timingArcSet(); + const Pin *to_pin = to_vertex->pin(); + Instance *inst = network_->instance(to_pin); + const LibertyCell *cell = network_->libertyCell(inst); + debugPrint(debug_, "delay_calc", 2, "find check %s %s -> %s", + sdc_network_->pathName(inst), + network_->portName(from_vertex->pin()), + network_->portName(to_pin)); + bool delay_changed = false; + for (TimingArc *arc : arc_set->arcs()) { + RiseFall *from_rf = arc->fromEdge()->asRiseFall(); + RiseFall *to_rf = arc->toEdge()->asRiseFall(); + if (from_rf && to_rf) { + 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); + for (auto dcalc_ap : corners_->dcalcAnalysisPts()) { + DcalcAPIndex ap_index = dcalc_ap->index(); + if (!graph_->arcDelayAnnotated(edge, arc, ap_index)) { + const Pvt *pvt = sdc_->pvt(inst,dcalc_ap->constraintMinMax()); + if (pvt == nullptr) + pvt = dcalc_ap->operatingConditions(); + const Slew &from_slew = checkEdgeClkSlew(from_vertex, from_rf, + dcalc_ap); + int slew_index = dcalc_ap->checkDataSlewIndex(); + const Slew &to_slew = graph_->slew(to_vertex, to_rf, slew_index); + debugPrint(debug_, "delay_calc", 3, + " %s %s -> %s %s (%s)", + arc_set->from()->name(), + arc->fromEdge()->asString(), + arc_set->to()->name(), + arc->toEdge()->asString(), + arc_set->role()->asString()); + debugPrint(debug_, "delay_calc", 3, + " from_slew = %s to_slew = %s", + delayAsString(from_slew, this), + delayAsString(to_slew, this)); + float related_out_cap = 0.0; + if (related_out_pin) { + Parasitic *related_out_parasitic = + arc_delay_calc->findParasitic(related_out_pin, to_rf, dcalc_ap); + related_out_cap = loadCap(related_out_pin, + related_out_parasitic, + to_rf, dcalc_ap); + } + ArcDelay check_delay; + arc_delay_calc->checkDelay(cell, arc, + from_slew, to_slew, + related_out_cap, + pvt, dcalc_ap, + check_delay); + debugPrint(debug_, "delay_calc", 3, + " check_delay = %s", + delayAsString(check_delay, this)); + graph_->setArcDelay(edge, arc, ap_index, check_delay); + delay_changed = true; + } + } + } + } + + if (delay_changed && observer_) + observer_->checkDelayChangedTo(to_vertex); +} + +// Use clock slew for timing check clock edges. +Slew +GraphDelayCalc::checkEdgeClkSlew(const Vertex *from_vertex, + const RiseFall *from_rf, + const DcalcAnalysisPt *dcalc_ap) +{ + if (clk_network_->isIdealClock(from_vertex->pin())) + return clk_network_->idealClkSlew(from_vertex->pin(), from_rf, + dcalc_ap->checkClkSlewMinMax()); + else + return graph_->slew(from_vertex, from_rf, dcalc_ap->checkClkSlewIndex()); +} + +//////////////////////////////////////////////////////////////// + +float +GraphDelayCalc::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 == nullptr) + pvt = dcalc_ap->operatingConditions(); + RiseFall *from_rf = arc->fromEdge()->asRiseFall(); + RiseFall *to_rf = arc->toEdge()->asRiseFall(); + if (from_rf && to_rf) { + 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 = + arc_delay_calc_->findParasitic(related_out_pin, to_rf, dcalc_ap); + related_out_cap = loadCap(related_out_pin, related_out_parasitic, + to_rf, dcalc_ap); + } + Parasitic *to_parasitic = arc_delay_calc_->findParasitic(to_pin, to_rf, + dcalc_ap); + const Slew &from_slew = edgeFromSlew(from_vertex, from_rf, edge, dcalc_ap); + float load_cap = loadCap(to_pin, to_parasitic, to_rf, dcalc_ap); + ceff = arc_delay_calc_->ceff(cell, arc, + from_slew, load_cap, to_parasitic, + related_out_cap, pvt, dcalc_ap); + arc_delay_calc_->finishDrvrPin(); + } + return ceff; +} + +//////////////////////////////////////////////////////////////// + +string +GraphDelayCalc::reportDelayCalc(Edge *edge, + TimingArc *arc, + const Corner *corner, + const MinMax *min_max, + int digits) +{ + Vertex *from_vertex = edge->from(graph_); + Vertex *to_vertex = edge->to(graph_); + Pin *to_pin = to_vertex->pin(); + TimingRole *role = arc->role(); + Instance *inst = network_->instance(to_pin); + LibertyCell *cell = network_->libertyCell(inst); + TimingArcSet *arc_set = edge->timingArcSet(); + string result; + DcalcAnalysisPt *dcalc_ap = corner->findDcalcAnalysisPt(min_max); + const Pvt *pvt = sdc_->pvt(inst, dcalc_ap->constraintMinMax()); + if (pvt == nullptr) + pvt = dcalc_ap->operatingConditions(); + RiseFall *from_rf = arc->fromEdge()->asRiseFall(); + RiseFall *to_rf = arc->toEdge()->asRiseFall(); + if (from_rf && to_rf) { + 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 = + arc_delay_calc_->findParasitic(related_out_pin, to_rf, dcalc_ap); + related_out_cap = loadCap(related_out_pin, related_out_parasitic, + to_rf, dcalc_ap); + } + if (role->isTimingCheck()) { + const Slew &from_slew = checkEdgeClkSlew(from_vertex, from_rf, dcalc_ap); + int slew_index = dcalc_ap->checkDataSlewIndex(); + const Slew &to_slew = graph_->slew(to_vertex, to_rf, slew_index); + bool from_ideal_clk = clk_network_->isIdealClock(from_vertex->pin()); + const char *from_slew_annotation = from_ideal_clk ? " (ideal clock)" : nullptr; + result = arc_delay_calc_->reportCheckDelay(cell, arc, from_slew, + from_slew_annotation, to_slew, + related_out_cap, pvt, dcalc_ap, + digits); + } + else { + Parasitic *to_parasitic = + arc_delay_calc_->findParasitic(to_pin, to_rf, dcalc_ap); + const Slew &from_slew = edgeFromSlew(from_vertex, from_rf, edge, dcalc_ap); + float load_cap = loadCap(to_pin, to_parasitic, to_rf, dcalc_ap); + result = arc_delay_calc_->reportGateDelay(cell, arc, + from_slew, load_cap, to_parasitic, + related_out_cap, pvt, dcalc_ap, digits); + } + arc_delay_calc_->finishDrvrPin(); + } + return result; +} + +//////////////////////////////////////////////////////////////// + void GraphDelayCalc::minPulseWidth(const Pin *pin, const RiseFall *hi_low, diff --git a/dcalc/GraphDelayCalc1.cc b/dcalc/GraphDelayCalc1.cc deleted file mode 100644 index 53566b45..00000000 --- a/dcalc/GraphDelayCalc1.cc +++ /dev/null @@ -1,1690 +0,0 @@ -// OpenSTA, Static Timing Analyzer -// Copyright (c) 2023, 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 "GraphDelayCalc1.hh" - -#include "Debug.hh" -#include "Stats.hh" -#include "MinMax.hh" -#include "Mutex.hh" -#include "TimingRole.hh" -#include "TimingArc.hh" -#include "Liberty.hh" -#include "PortDirection.hh" -#include "Network.hh" -#include "InputDrive.hh" -#include "Sdc.hh" -#include "Graph.hh" -#include "Parasitics.hh" -#include "search/Levelize.hh" -#include "Corner.hh" -#include "SearchPred.hh" -#include "Bfs.hh" -#include "ArcDelayCalc.hh" -#include "DcalcAnalysisPt.hh" -#include "NetCaps.hh" -#include "ClkNetwork.hh" - -namespace sta { - -using std::abs; - -static const Slew default_slew = 0.0; - -typedef Set MultiDrvrNetSet; - -static bool -isLeafDriver(const Pin *pin, - const Network *network); - -// Cache parallel delay/slew values for nets with multiple drivers. -class MultiDrvrNet -{ -public: - MultiDrvrNet(VertexSet *drvrs); - ~MultiDrvrNet(); - const VertexSet *drvrs() const { return drvrs_; } - VertexSet *drvrs() { return drvrs_; } - Vertex *dcalcDrvr() const { return dcalc_drvr_; } - void setDcalcDrvr(Vertex *drvr); - void parallelDelaySlew(const RiseFall *drvr_rf, - const DcalcAnalysisPt *dcalc_ap, - ArcDelayCalc *arc_delay_calc, - GraphDelayCalc1 *dcalc, - // Return values. - ArcDelay ¶llel_delay, - Slew ¶llel_slew); - void netCaps(const RiseFall *rf, - const DcalcAnalysisPt *dcalc_ap, - // Return values. - float &pin_cap, - float &wire_cap, - float &fanout, - bool &has_net_load); - void findCaps(const GraphDelayCalc1 *dcalc, - const Sdc *sdc); - -private: - void findDelaysSlews(ArcDelayCalc *arc_delay_calc, - GraphDelayCalc1 *dcalc); - - // Driver that triggers delay calculation for all the drivers on the net. - Vertex *dcalc_drvr_; - VertexSet *drvrs_; - // [drvr_rf->index][dcalc_ap->index] - ArcDelay *parallel_delays_; - // [drvr_rf->index][dcalc_ap->index] - Slew *parallel_slews_; - // [drvr_rf->index][dcalc_ap->index] - NetCaps *net_caps_; - bool delays_valid_:1; -}; - -MultiDrvrNet::MultiDrvrNet(VertexSet *drvrs) : - dcalc_drvr_(nullptr), - drvrs_(drvrs), - parallel_delays_(nullptr), - parallel_slews_(nullptr), - net_caps_(nullptr), - delays_valid_(false) -{ -} - -MultiDrvrNet::~MultiDrvrNet() -{ - delete drvrs_; - if (delays_valid_) { - delete [] parallel_delays_; - delete [] parallel_slews_; - } - delete [] net_caps_; -} - -void -MultiDrvrNet::parallelDelaySlew(const RiseFall *drvr_rf, - const DcalcAnalysisPt *dcalc_ap, - ArcDelayCalc *arc_delay_calc, - GraphDelayCalc1 *dcalc, - // Return values. - ArcDelay ¶llel_delay, - Slew ¶llel_slew) -{ - if (!delays_valid_) { - findDelaysSlews(arc_delay_calc, dcalc); - delays_valid_ = true; - } - - int index = dcalc_ap->index() * RiseFall::index_count - + drvr_rf->index(); - parallel_delay = parallel_delays_[index]; - parallel_slew = parallel_slews_[index]; -} - -void -MultiDrvrNet::findDelaysSlews(ArcDelayCalc *arc_delay_calc, - GraphDelayCalc1 *dcalc) -{ - Corners *corners = dcalc->corners(); - int count = RiseFall::index_count * corners->dcalcAnalysisPtCount(); - parallel_delays_ = new ArcDelay[count]; - parallel_slews_ = new Slew[count]; - for (auto dcalc_ap : corners->dcalcAnalysisPts()) { - DcalcAPIndex ap_index = dcalc_ap->index(); - const Pvt *pvt = dcalc_ap->operatingConditions(); - for (auto drvr_rf : RiseFall::range()) { - int drvr_rf_index = drvr_rf->index(); - int index = ap_index*RiseFall::index_count+drvr_rf_index; - dcalc->findMultiDrvrGateDelay(this, drvr_rf, pvt, dcalc_ap, - arc_delay_calc, - parallel_delays_[index], - parallel_slews_[index]); - } - } -} - -void -MultiDrvrNet::netCaps(const RiseFall *drvr_rf, - const DcalcAnalysisPt *dcalc_ap, - // Return values. - float &pin_cap, - float &wire_cap, - float &fanout, - bool &has_net_load) -{ - int index = dcalc_ap->index() * RiseFall::index_count - + drvr_rf->index(); - NetCaps &net_caps = net_caps_[index]; - pin_cap = net_caps.pinCap(); - wire_cap = net_caps.wireCap(); - fanout = net_caps.fanout(); - has_net_load = net_caps.hasNetLoad(); -} - -void -MultiDrvrNet::findCaps(const GraphDelayCalc1 *dcalc, - const Sdc *sdc) -{ - Corners *corners = dcalc->corners(); - int count = RiseFall::index_count * corners->dcalcAnalysisPtCount(); - net_caps_ = new NetCaps[count]; - const Pin *drvr_pin = dcalc_drvr_->pin(); - for (auto dcalc_ap : corners->dcalcAnalysisPts()) { - DcalcAPIndex ap_index = dcalc_ap->index(); - const Corner *corner = dcalc_ap->corner(); - const OperatingConditions *op_cond = dcalc_ap->operatingConditions(); - const MinMax *min_max = dcalc_ap->constraintMinMax(); - for (auto drvr_rf : RiseFall::range()) { - int drvr_rf_index = drvr_rf->index(); - int index = ap_index * RiseFall::index_count + drvr_rf_index; - NetCaps &net_caps = net_caps_[index]; - float pin_cap, wire_cap, fanout; - bool has_net_load; - // Find pin and external pin/wire capacitance. - sdc->connectedCap(drvr_pin, drvr_rf, op_cond, corner, min_max, - pin_cap, wire_cap, fanout, has_net_load); - net_caps.init(pin_cap, wire_cap, fanout, has_net_load); - } - } -} - -void -MultiDrvrNet::setDcalcDrvr(Vertex *drvr) -{ - dcalc_drvr_ = drvr; -} - -//////////////////////////////////////////////////////////////// - - -GraphDelayCalc1::GraphDelayCalc1(StaState *sta) : - GraphDelayCalc(sta), - observer_(nullptr), - delays_seeded_(false), - incremental_(false), - delays_exist_(false), - invalid_delays_(new VertexSet(graph_)), - search_pred_(new SearchPred1(sta)), - search_non_latch_pred_(new SearchPredNonLatch2(sta)), - clk_pred_(new ClkTreeSearchPred(sta)), - iter_(new BfsFwdIterator(BfsIndex::dcalc, search_non_latch_pred_, sta)), - multi_drvr_nets_found_(false), - incremental_delay_tolerance_(0.0) -{ -} - -GraphDelayCalc1::~GraphDelayCalc1() -{ - delete search_pred_; - delete invalid_delays_; - delete search_non_latch_pred_; - delete clk_pred_; - delete iter_; - deleteMultiDrvrNets(); - delete observer_; -} - -void -GraphDelayCalc1::deleteMultiDrvrNets() -{ - MultiDrvrNetSet drvr_nets; - MultiDrvrNetMap::Iterator multi_iter(multi_drvr_net_map_); - while (multi_iter.hasNext()) { - MultiDrvrNet *multi_drvr = multi_iter.next(); - // Multiple drvr pins point to the same drvr PinSet, - // so collect them into a set. - drvr_nets.insert(multi_drvr); - } - multi_drvr_net_map_.clear(); - drvr_nets.deleteContents(); -} - -void -GraphDelayCalc1::copyState(const StaState *sta) -{ - GraphDelayCalc::copyState(sta); - // Notify sub-components. - iter_->copyState(sta); -} - -void -GraphDelayCalc1::clear() -{ - delaysInvalid(); - deleteMultiDrvrNets(); - multi_drvr_nets_found_ = false; - GraphDelayCalc::clear(); -} - -float -GraphDelayCalc1::incrementalDelayTolerance() -{ - return incremental_delay_tolerance_; -} - -void -GraphDelayCalc1::setIncrementalDelayTolerance(float tol) -{ - incremental_delay_tolerance_ = tol; -} - -void -GraphDelayCalc1::setObserver(DelayCalcObserver *observer) -{ - delete observer_; - observer_ = observer; -} - -void -GraphDelayCalc1::delaysInvalid() -{ - debugPrint(debug_, "delay_calc", 1, "delays invalid"); - delays_exist_ = false; - delays_seeded_ = false; - incremental_ = false; - iter_->clear(); - // No need to keep track of incremental updates any more. - invalid_delays_->clear(); - invalid_check_edges_.clear(); - invalid_latch_edges_.clear(); -} - -void -GraphDelayCalc1::delayInvalid(const Pin *pin) -{ - if (graph_ && incremental_) { - if (network_->isHierarchical(pin)) { - EdgesThruHierPinIterator edge_iter(pin, network_, graph_); - while (edge_iter.hasNext()) { - Edge *edge = edge_iter.next(); - delayInvalid(edge->from(graph_)); - } - } - else { - Vertex *vertex, *bidirect_drvr_vertex; - graph_->pinVertices(pin, vertex, bidirect_drvr_vertex); - if (vertex) - delayInvalid(vertex); - if (bidirect_drvr_vertex) - delayInvalid(bidirect_drvr_vertex); - } - } -} - -void -GraphDelayCalc1::delayInvalid(Vertex *vertex) -{ - debugPrint(debug_, "delay_calc", 2, "delay invalid %s", - vertex->name(sdc_network_)); - if (graph_ && incremental_) { - invalid_delays_->insert(vertex); - // Invalidate driver that triggers dcalc for multi-driver nets. - MultiDrvrNet *multi_drvr = multiDrvrNet(vertex); - if (multi_drvr) - invalid_delays_->insert(multi_drvr->dcalcDrvr()); - } -} - -void -GraphDelayCalc1::deleteVertexBefore(Vertex *vertex) -{ - iter_->deleteVertexBefore(vertex); - if (incremental_) - invalid_delays_->erase(vertex); - MultiDrvrNet *multi_drvr = multiDrvrNet(vertex); - if (multi_drvr) { - multi_drvr->drvrs()->erase(vertex); - multi_drvr_net_map_.erase(vertex); - } -} - -//////////////////////////////////////////////////////////////// - -class FindVertexDelays : public VertexVisitor -{ -public: - FindVertexDelays(GraphDelayCalc1 *graph_delay_calc1); - virtual ~FindVertexDelays(); - virtual void visit(Vertex *vertex); - virtual VertexVisitor *copy() const; - -protected: - GraphDelayCalc1 *graph_delay_calc1_; - ArcDelayCalc *arc_delay_calc_; -}; - -FindVertexDelays::FindVertexDelays(GraphDelayCalc1 *graph_delay_calc1) : - VertexVisitor(), - graph_delay_calc1_(graph_delay_calc1), - arc_delay_calc_(graph_delay_calc1_->arc_delay_calc_->copy()) -{ -} - -FindVertexDelays::~FindVertexDelays() -{ - delete arc_delay_calc_; -} - -VertexVisitor * -FindVertexDelays::copy() const -{ - // Copy StaState::arc_delay_calc_ because it needs separate state - // for each thread. - return new FindVertexDelays(graph_delay_calc1_); -} - -void -FindVertexDelays::visit(Vertex *vertex) -{ - graph_delay_calc1_->findVertexDelay(vertex, arc_delay_calc_, true); -} - -// The logical structure of incremental delay calculation closely -// resembles the incremental search arrival time algorithm -// (Search::findArrivals). -void -GraphDelayCalc1::findDelays(Level level) -{ - if (arc_delay_calc_) { - Stats stats(debug_, report_); - int dcalc_count = 0; - debugPrint(debug_, "delay_calc", 1, "find delays to level %d", level); - if (!delays_seeded_) { - iter_->clear(); - ensureMultiDrvrNetsFound(); - seedRootSlews(); - delays_seeded_ = true; - } - else - iter_->ensureSize(); - if (incremental_) - seedInvalidDelays(); - - FindVertexDelays visitor(this); - dcalc_count += iter_->visitParallel(level, &visitor); - - // Timing checks require slews at both ends of the arc, - // so find their delays after all slews are known. - for (Edge *check_edge : invalid_check_edges_) - findCheckEdgeDelays(check_edge, arc_delay_calc_); - invalid_check_edges_.clear(); - - for (Edge *latch_edge : invalid_latch_edges_) - findLatchEdgeDelays(latch_edge); - invalid_latch_edges_.clear(); - - delays_exist_ = true; - incremental_ = true; - debugPrint(debug_, "delay_calc", 1, "found %d delays", dcalc_count); - stats.report("Delay calc"); - } -} - -void -GraphDelayCalc1::seedInvalidDelays() -{ - for (Vertex *vertex : *invalid_delays_) { - if (vertex->isRoot()) - seedRootSlew(vertex, arc_delay_calc_); - else { - if (search_non_latch_pred_->searchFrom(vertex)) - iter_->enqueue(vertex); - } - } - invalid_delays_->clear(); -} - -class FindNetDrvrs : public PinVisitor -{ -public: - FindNetDrvrs(PinSet &drvr_pins, - const Network *network, - const Graph *graph); - virtual void operator()(const Pin *pin); - -protected: - PinSet &drvr_pins_; - const Network *network_; - const Graph *graph_; -}; - -FindNetDrvrs::FindNetDrvrs(PinSet &drvr_pins, - const Network *network, - const Graph *graph) : - drvr_pins_(drvr_pins), - network_(network), - graph_(graph) -{ -} - -void -FindNetDrvrs::operator()(const Pin *pin) -{ - Vertex *vertex = graph_->pinDrvrVertex(pin); - if (isLeafDriver(pin, network_) - && !(vertex && vertex->isRoot())) - drvr_pins_.insert(pin); -} - -void -GraphDelayCalc1::ensureMultiDrvrNetsFound() -{ - if (!multi_drvr_nets_found_) { - LeafInstanceIterator *inst_iter = network_->leafInstanceIterator(); - while (inst_iter->hasNext()) { - Instance *inst = inst_iter->next(); - InstancePinIterator *pin_iter = network_->pinIterator(inst); - while (pin_iter->hasNext()) { - Pin *pin = pin_iter->next(); - Vertex *drvr_vertex = graph_->pinDrvrVertex(pin); - if (network_->isDriver(pin) - && !multi_drvr_net_map_.hasKey(drvr_vertex)) { - PinSet drvr_pins(network_); - FindNetDrvrs visitor(drvr_pins, network_, graph_); - network_->visitConnectedPins(pin, visitor); - if (drvr_pins.size() > 1) - makeMultiDrvrNet(drvr_pins); - } - } - delete pin_iter; - } - delete inst_iter; - multi_drvr_nets_found_ = true; - } -} - -void -GraphDelayCalc1::makeMultiDrvrNet(PinSet &drvr_pins) -{ - debugPrint(debug_, "delay_calc", 3, "multi-driver net"); - VertexSet *drvr_vertices = new VertexSet(graph_); - MultiDrvrNet *multi_drvr = new MultiDrvrNet(drvr_vertices); - Level max_drvr_level = 0; - Vertex *max_drvr = nullptr; - PinSet::Iterator pin_iter(drvr_pins); - while (pin_iter.hasNext()) { - const Pin *pin = pin_iter.next(); - Vertex *drvr_vertex = graph_->pinDrvrVertex(pin); - debugPrint(debug_, "delay_calc", 3, " %s", - network_->pathName(pin)); - multi_drvr_net_map_[drvr_vertex] = multi_drvr; - drvr_vertices->insert(drvr_vertex); - Level drvr_level = drvr_vertex->level(); - if (max_drvr == nullptr - || drvr_level > max_drvr_level) { - max_drvr = drvr_vertex; - max_drvr_level = drvr_level; - } - } - multi_drvr->setDcalcDrvr(max_drvr); - multi_drvr->findCaps(this, sdc_); -} - -static bool -isLeafDriver(const Pin *pin, - const Network *network) -{ - PortDirection *dir = network->direction(pin); - const Instance *inst = network->instance(pin); - return network->isLeaf(inst) && dir->isAnyOutput(); -} - -MultiDrvrNet * -GraphDelayCalc1::multiDrvrNet(const Vertex *drvr_vertex) const -{ - return multi_drvr_net_map_.findKey(drvr_vertex); -} - -void -GraphDelayCalc1::seedRootSlews() -{ - for (Vertex *vertex : *levelize_->roots()) - seedRootSlew(vertex, arc_delay_calc_); -} - -void -GraphDelayCalc1::seedRootSlew(Vertex *vertex, - ArcDelayCalc *arc_delay_calc) -{ - if (vertex->isDriver(network_)) - seedDrvrSlew(vertex, arc_delay_calc); - else - seedLoadSlew(vertex); - iter_->enqueueAdjacentVertices(vertex); -} - -void -GraphDelayCalc1::seedDrvrSlew(Vertex *drvr_vertex, - ArcDelayCalc *arc_delay_calc) -{ - const Pin *drvr_pin = drvr_vertex->pin(); - debugPrint(debug_, "delay_calc", 2, "seed driver slew %s", - drvr_vertex->name(sdc_network_)); - InputDrive *drive = 0; - if (network_->isTopLevelPort(drvr_pin)) { - Port *port = network_->port(drvr_pin); - drive = sdc_->findInputDrive(port); - } - for (auto rf : RiseFall::range()) { - for (auto dcalc_ap : corners_->dcalcAnalysisPts()) { - if (drive) { - const MinMax *cnst_min_max = dcalc_ap->constraintMinMax(); - const LibertyCell *drvr_cell; - const LibertyPort *from_port, *to_port; - float *from_slews; - drive->driveCell(rf, cnst_min_max, drvr_cell, from_port, - from_slews, to_port); - if (drvr_cell) { - if (from_port == nullptr) - from_port = driveCellDefaultFromPort(drvr_cell, to_port); - findInputDriverDelay(drvr_cell, drvr_pin, drvr_vertex, rf, - from_port, from_slews, to_port, dcalc_ap); - } - else - seedNoDrvrCellSlew(drvr_vertex, drvr_pin, rf, drive, dcalc_ap, - arc_delay_calc); - } - else - seedNoDrvrSlew(drvr_vertex, drvr_pin, rf, dcalc_ap, arc_delay_calc); - } - } -} - -void -GraphDelayCalc1::seedNoDrvrCellSlew(Vertex *drvr_vertex, - const Pin *drvr_pin, - const RiseFall *rf, - InputDrive *drive, - DcalcAnalysisPt *dcalc_ap, - ArcDelayCalc *arc_delay_calc) -{ - DcalcAPIndex ap_index = dcalc_ap->index(); - const MinMax *cnst_min_max = dcalc_ap->constraintMinMax(); - Slew slew = default_slew; - float drive_slew; - bool exists; - drive->slew(rf, cnst_min_max, drive_slew, exists); - if (exists) - slew = drive_slew; - else { - // Top level bidirect driver uses load slew unless - // bidirect instance paths are disabled. - if (sdc_->bidirectDrvrSlewFromLoad(drvr_pin)) { - Vertex *load_vertex = graph_->pinLoadVertex(drvr_pin); - slew = graph_->slew(load_vertex, rf, ap_index); - } - } - Delay drive_delay = delay_zero; - float drive_res; - drive->driveResistance(rf, cnst_min_max, drive_res, exists); - Parasitic *parasitic = arc_delay_calc->findParasitic(drvr_pin, rf, dcalc_ap); - if (exists) { - float cap = loadCap(drvr_pin, parasitic, rf, dcalc_ap); - drive_delay = cap * drive_res; - slew = cap * drive_res; - } - const MinMax *slew_min_max = dcalc_ap->slewMinMax(); - if (!drvr_vertex->slewAnnotated(rf, slew_min_max)) - graph_->setSlew(drvr_vertex, rf, ap_index, slew); - arc_delay_calc->inputPortDelay(drvr_pin, delayAsFloat(slew), rf, - parasitic, dcalc_ap); - annotateLoadDelays(drvr_vertex, rf, drive_delay, false, dcalc_ap, - arc_delay_calc); - arc_delay_calc->finishDrvrPin(); -} - -void -GraphDelayCalc1::seedNoDrvrSlew(Vertex *drvr_vertex, - const Pin *drvr_pin, - const RiseFall *rf, - DcalcAnalysisPt *dcalc_ap, - ArcDelayCalc *arc_delay_calc) -{ - const MinMax *slew_min_max = dcalc_ap->slewMinMax(); - DcalcAPIndex ap_index = dcalc_ap->index(); - Slew slew(default_slew); - // Top level bidirect driver uses load slew unless - // bidirect instance paths are disabled. - if (sdc_->bidirectDrvrSlewFromLoad(drvr_pin)) { - Vertex *load_vertex = graph_->pinLoadVertex(drvr_pin); - slew = graph_->slew(load_vertex, rf, ap_index); - } - if (!drvr_vertex->slewAnnotated(rf, slew_min_max)) - graph_->setSlew(drvr_vertex, rf, ap_index, slew); - Parasitic *parasitic = arc_delay_calc->findParasitic(drvr_pin, rf, dcalc_ap); - arc_delay_calc->inputPortDelay(drvr_pin, delayAsFloat(slew), rf, - parasitic, dcalc_ap); - annotateLoadDelays(drvr_vertex, rf, delay_zero, false, dcalc_ap, - arc_delay_calc); - arc_delay_calc->finishDrvrPin(); -} - -void -GraphDelayCalc1::seedLoadSlew(Vertex *vertex) -{ - const Pin *pin = vertex->pin(); - debugPrint(debug_, "delay_calc", 2, "seed load slew %s", - vertex->name(sdc_network_)); - ClockSet *clks = sdc_->findLeafPinClocks(pin); - initSlew(vertex); - for (auto rf : RiseFall::range()) { - for (auto dcalc_ap : corners_->dcalcAnalysisPts()) { - const MinMax *slew_min_max = dcalc_ap->slewMinMax(); - if (!vertex->slewAnnotated(rf, slew_min_max)) { - float slew = 0.0; - if (clks) { - slew = slew_min_max->initValue(); - ClockSet::Iterator clk_iter(clks); - while (clk_iter.hasNext()) { - Clock *clk = clk_iter.next(); - float clk_slew = clk->slew(rf, slew_min_max); - if (slew_min_max->compare(clk_slew, slew)) - slew = clk_slew; - } - } - DcalcAPIndex ap_index = dcalc_ap->index(); - graph_->setSlew(vertex, rf, ap_index, slew); - } - } - } -} - -// If a driving cell does not specify a -from_pin, the first port -// defined in the cell that has a timing group to the output port -// is used. Not exactly reasonable, but it's compatible. -LibertyPort * -GraphDelayCalc1::driveCellDefaultFromPort(const LibertyCell *cell, - const LibertyPort *to_port) -{ - LibertyPort *from_port = 0; - int from_port_index = 0; - for (TimingArcSet *arc_set : cell->timingArcSets(nullptr, to_port)) { - LibertyPort *set_from_port = arc_set->from(); - int set_from_port_index = findPortIndex(cell, set_from_port); - if (from_port == nullptr - || set_from_port_index < from_port_index) { - from_port = set_from_port; - from_port_index = set_from_port_index; - } - } - return from_port; -} - -// Find the index that port is defined in cell. -int -GraphDelayCalc1::findPortIndex(const LibertyCell *cell, - const LibertyPort *port) -{ - int index = 0; - LibertyCellPortIterator port_iter(cell); - while (port_iter.hasNext()) { - LibertyPort *cell_port = port_iter.next(); - if (cell_port == port) - return index; - index++; - } - report_->critical(207, "port not found in cell"); - return 0; -} - -void -GraphDelayCalc1::findInputDriverDelay(const LibertyCell *drvr_cell, - const Pin *drvr_pin, - Vertex *drvr_vertex, - const RiseFall *rf, - const LibertyPort *from_port, - float *from_slews, - const LibertyPort *to_port, - const DcalcAnalysisPt *dcalc_ap) -{ - debugPrint(debug_, "delay_calc", 2, " driver cell %s %s", - drvr_cell->name(), - rf->asString()); - for (TimingArcSet *arc_set : drvr_cell->timingArcSets(from_port, to_port)) { - for (TimingArc *arc : arc_set->arcs()) { - if (arc->toEdge()->asRiseFall() == rf) { - float from_slew = from_slews[arc->fromEdge()->index()]; - findInputArcDelay(drvr_cell, drvr_pin, drvr_vertex, - arc, from_slew, dcalc_ap); - } - } - } -} - -// Driving cell delay is the load dependent delay, which is the gate -// delay minus the intrinsic delay. Driving cell delays are annotated -// to the wire arcs from the input port pin to the load pins. -void -GraphDelayCalc1::findInputArcDelay(const LibertyCell *drvr_cell, - const Pin *drvr_pin, - Vertex *drvr_vertex, - const TimingArc *arc, - float from_slew, - const DcalcAnalysisPt *dcalc_ap) -{ - debugPrint(debug_, "delay_calc", 3, " %s %s -> %s %s (%s)", - arc->from()->name(), - arc->fromEdge()->asString(), - arc->to()->name(), - arc->toEdge()->asString(), - arc->role()->asString()); - RiseFall *drvr_rf = arc->toEdge()->asRiseFall(); - if (drvr_rf) { - DcalcAPIndex ap_index = dcalc_ap->index(); - const Pvt *pvt = dcalc_ap->operatingConditions(); - Parasitic *drvr_parasitic = arc_delay_calc_->findParasitic(drvr_pin, drvr_rf, - dcalc_ap); - float load_cap = loadCap(drvr_pin, drvr_parasitic, drvr_rf, dcalc_ap); - - ArcDelay intrinsic_delay; - Slew intrinsic_slew; - arc_delay_calc_->gateDelay(drvr_cell, arc, Slew(from_slew), - 0.0, 0, 0.0, pvt, dcalc_ap, - intrinsic_delay, intrinsic_slew); - - // For input drivers there is no instance to find a related_output_pin. - ArcDelay gate_delay; - Slew gate_slew; - arc_delay_calc_->gateDelay(drvr_cell, arc, - Slew(from_slew), load_cap, - drvr_parasitic, 0.0, pvt, dcalc_ap, - gate_delay, gate_slew); - ArcDelay load_delay = gate_delay - intrinsic_delay; - debugPrint(debug_, "delay_calc", 3, - " gate delay = %s intrinsic = %s slew = %s", - delayAsString(gate_delay, this), - delayAsString(intrinsic_delay, this), - delayAsString(gate_slew, this)); - graph_->setSlew(drvr_vertex, drvr_rf, ap_index, gate_slew); - annotateLoadDelays(drvr_vertex, drvr_rf, load_delay, false, dcalc_ap, - arc_delay_calc_); - } -} - -void -GraphDelayCalc1::findDelays(Vertex *drvr_vertex) -{ - findVertexDelay(drvr_vertex, arc_delay_calc_, true); -} - -void -GraphDelayCalc1::findVertexDelay(Vertex *vertex, - ArcDelayCalc *arc_delay_calc, - bool propagate) -{ - const Pin *pin = vertex->pin(); - debugPrint(debug_, "delay_calc", 2, "find delays %s (%s)", - vertex->name(sdc_network_), - network_->cellName(network_->instance(pin))); - if (vertex->isRoot()) { - seedRootSlew(vertex, arc_delay_calc); - if (propagate) - iter_->enqueueAdjacentVertices(vertex); - } - else { - if (network_->isLeaf(pin)) { - if (vertex->isDriver(network_)) { - bool delay_changed = findDriverDelays(vertex, arc_delay_calc); - if (propagate) { - if (network_->direction(pin)->isInternal()) - enqueueTimingChecksEdges(vertex); - // Enqueue adjacent vertices even if the delays did not - // change when non-incremental to stride past annotations. - if (delay_changed || !incremental_) - iter_->enqueueAdjacentVertices(vertex); - } - } - else { - // Load vertex. - enqueueTimingChecksEdges(vertex); - // Enqueue driver vertices from this input load. - if (propagate) - iter_->enqueueAdjacentVertices(vertex); - } - } - // Bidirect port drivers are enqueued by their load vertex in - // annotateLoadDelays. - else if (vertex->isBidirectDriver() - && network_->isTopLevelPort(pin)) - seedRootSlew(vertex, arc_delay_calc); - } -} - -void -GraphDelayCalc1::enqueueTimingChecksEdges(Vertex *vertex) -{ - if (vertex->hasChecks()) { - VertexInEdgeIterator edge_iter(vertex, graph_); - UniqueLock lock(invalid_edge_lock_); - while (edge_iter.hasNext()) { - Edge *edge = edge_iter.next(); - if (edge->role()->isTimingCheck()) - invalid_check_edges_.insert(edge); - } - } - if (vertex->isCheckClk()) { - VertexOutEdgeIterator edge_iter(vertex, graph_); - UniqueLock lock(invalid_edge_lock_); - while (edge_iter.hasNext()) { - Edge *edge = edge_iter.next(); - if (edge->role()->isTimingCheck()) - invalid_check_edges_.insert(edge); - } - } - if (network_->isLatchData(vertex->pin())) { - // Latch D->Q arcs have to be re-evaled if level(D) > level(E) - // because levelization does not traverse D->Q arcs to break loops. - VertexOutEdgeIterator edge_iter(vertex, graph_); - UniqueLock lock(invalid_edge_lock_); - while (edge_iter.hasNext()) { - Edge *edge = edge_iter.next(); - if (edge->role() == TimingRole::latchDtoQ()) - invalid_latch_edges_.insert(edge); - } - } -} - -bool -GraphDelayCalc1::findDriverDelays(Vertex *drvr_vertex, - ArcDelayCalc *arc_delay_calc) -{ - bool delay_changed = false; - MultiDrvrNet *multi_drvr = multiDrvrNet(drvr_vertex); - if (multi_drvr) { - Vertex *dcalc_drvr = multi_drvr->dcalcDrvr(); - if (drvr_vertex == dcalc_drvr) { - bool init_load_slews = true; - for (Vertex *drvr_vertex : *multi_drvr->drvrs()) { - // Only init load slews once so previous driver dcalc results - // aren't clobbered. - delay_changed |= findDriverDelays1(drvr_vertex, init_load_slews, - multi_drvr, arc_delay_calc); - init_load_slews = false; - } - } - } - else - delay_changed = findDriverDelays1(drvr_vertex, true, nullptr, arc_delay_calc); - arc_delay_calc->finishDrvrPin(); - return delay_changed; -} - -bool -GraphDelayCalc1::findDriverDelays1(Vertex *drvr_vertex, - bool init_load_slews, - MultiDrvrNet *multi_drvr, - ArcDelayCalc *arc_delay_calc) -{ - const Pin *drvr_pin = drvr_vertex->pin(); - Instance *drvr_inst = network_->instance(drvr_pin); - LibertyCell *drvr_cell = network_->libertyCell(drvr_inst); - initSlew(drvr_vertex); - initWireDelays(drvr_vertex, init_load_slews); - bool delay_changed = false; - bool has_delays = false; - VertexInEdgeIterator edge_iter(drvr_vertex, graph_); - while (edge_iter.hasNext()) { - Edge *edge = edge_iter.next(); - Vertex *from_vertex = edge->from(graph_); - // Don't let disabled edges set slews that influence downstream delays. - if (search_pred_->searchFrom(from_vertex) - && search_pred_->searchThru(edge) - && !edge->role()->isLatchDtoQ()) { - delay_changed |= findDriverEdgeDelays(drvr_cell, drvr_inst, drvr_pin, - drvr_vertex, multi_drvr, edge, - arc_delay_calc); - has_delays = true; - } - } - if (!has_delays) - zeroSlewAndWireDelays(drvr_vertex); - if (delay_changed && observer_) - observer_->delayChangedTo(drvr_vertex); - return delay_changed; -} - -// Init slews to zero on root vertices that are not inputs, such as -// floating input pins. -void -GraphDelayCalc1::initRootSlews(Vertex *vertex) -{ - for (auto dcalc_ap : corners_->dcalcAnalysisPts()) { - const MinMax *slew_min_max = dcalc_ap->slewMinMax(); - DcalcAPIndex ap_index = dcalc_ap->index(); - for (auto rf : RiseFall::range()) { - if (!vertex->slewAnnotated(rf, slew_min_max)) - graph_->setSlew(vertex, rf, ap_index, default_slew); - } - } -} - -void -GraphDelayCalc1::findLatchEdgeDelays(Edge *edge) -{ - Vertex *drvr_vertex = edge->to(graph_); - const Pin *drvr_pin = drvr_vertex->pin(); - Instance *drvr_inst = network_->instance(drvr_pin); - LibertyCell *drvr_cell = network_->libertyCell(drvr_inst); - debugPrint(debug_, "delay_calc", 2, "find latch D->Q %s", - sdc_network_->pathName(drvr_inst)); - bool delay_changed = findDriverEdgeDelays(drvr_cell, drvr_inst, drvr_pin, - drvr_vertex, nullptr, edge, arc_delay_calc_); - if (delay_changed && observer_) - observer_->delayChangedTo(drvr_vertex); -} - -bool -GraphDelayCalc1::findDriverEdgeDelays(LibertyCell *drvr_cell, - Instance *drvr_inst, - const Pin *drvr_pin, - Vertex *drvr_vertex, - MultiDrvrNet *multi_drvr, - Edge *edge, - ArcDelayCalc *arc_delay_calc) -{ - Vertex *in_vertex = edge->from(graph_); - TimingArcSet *arc_set = edge->timingArcSet(); - const LibertyPort *related_out_port = arc_set->relatedOut(); - const Pin *related_out_pin = 0; - bool delay_changed = false; - if (related_out_port) - related_out_pin = network_->findPin(drvr_inst, related_out_port); - for (auto dcalc_ap : corners_->dcalcAnalysisPts()) { - const Pvt *pvt = sdc_->pvt(drvr_inst, dcalc_ap->constraintMinMax()); - if (pvt == nullptr) - pvt = dcalc_ap->operatingConditions(); - for (TimingArc *arc : arc_set->arcs()) { - const RiseFall *rf = arc->toEdge()->asRiseFall(); - Parasitic *parasitic = arc_delay_calc->findParasitic(drvr_pin, rf, - dcalc_ap); - float related_out_cap = 0.0; - if (related_out_pin) { - Parasitic *related_out_parasitic = - arc_delay_calc->findParasitic(related_out_pin, rf, dcalc_ap); - related_out_cap = loadCap(related_out_pin, - related_out_parasitic, - rf, dcalc_ap); - } - delay_changed |= findArcDelay(drvr_cell, drvr_pin, drvr_vertex, - multi_drvr, arc, parasitic, - related_out_cap, - in_vertex, edge, pvt, dcalc_ap, - arc_delay_calc); - } - } - - if (delay_changed && observer_) { - observer_->delayChangedFrom(in_vertex); - observer_->delayChangedFrom(drvr_vertex); - } - return delay_changed; -} - -float -GraphDelayCalc1::loadCap(const Pin *drvr_pin, - const DcalcAnalysisPt *dcalc_ap) const -{ - const MinMax *min_max = dcalc_ap->constraintMinMax(); - float load_cap = 0.0; - for (auto drvr_rf : RiseFall::range()) { - Parasitic *drvr_parasitic = arc_delay_calc_->findParasitic(drvr_pin, drvr_rf, - dcalc_ap); - float cap = loadCap(drvr_pin, nullptr, drvr_parasitic, drvr_rf, dcalc_ap); - arc_delay_calc_->finishDrvrPin(); - if (min_max->compare(cap, load_cap)) - load_cap = cap; - } - return load_cap; -} - -float -GraphDelayCalc1::loadCap(const Pin *drvr_pin, - const RiseFall *drvr_rf, - const DcalcAnalysisPt *dcalc_ap) const -{ - Parasitic *drvr_parasitic = arc_delay_calc_->findParasitic(drvr_pin, drvr_rf, - dcalc_ap); - float cap = loadCap(drvr_pin, nullptr, drvr_parasitic, drvr_rf, dcalc_ap); - return cap; -} - -float -GraphDelayCalc1::loadCap(const Pin *drvr_pin, - const Parasitic *drvr_parasitic, - const RiseFall *rf, - const DcalcAnalysisPt *dcalc_ap) const -{ - return loadCap(drvr_pin, nullptr, drvr_parasitic, rf, dcalc_ap); -} - -float -GraphDelayCalc1::loadCap(const Pin *drvr_pin, - MultiDrvrNet *multi_drvr, - const Parasitic *drvr_parasitic, - const RiseFall *rf, - const DcalcAnalysisPt *dcalc_ap) const -{ - float pin_cap, wire_cap; - bool has_net_load; - float fanout; - if (multi_drvr) - multi_drvr->netCaps(rf, dcalc_ap, - pin_cap, wire_cap, fanout, has_net_load); - else - netCaps(drvr_pin, rf, dcalc_ap, - pin_cap, wire_cap, fanout, has_net_load); - loadCap(drvr_parasitic, has_net_load, pin_cap, wire_cap); - return wire_cap + pin_cap; -} - -void -GraphDelayCalc1::loadCap(const Pin *drvr_pin, - const Parasitic *drvr_parasitic, - const RiseFall *rf, - const DcalcAnalysisPt *dcalc_ap, - // Return values. - float &pin_cap, - float &wire_cap) const -{ - bool has_net_load; - float fanout; - // Find pin and external pin/wire capacitance. - netCaps(drvr_pin, rf, dcalc_ap, - pin_cap, wire_cap, fanout, has_net_load); - loadCap(drvr_parasitic, has_net_load, pin_cap, wire_cap); -} - -void -GraphDelayCalc1::loadCap(const Parasitic *drvr_parasitic, - bool has_net_load, - // Return values. - float &pin_cap, - float &wire_cap) const -{ - // set_load net has precidence over parasitics. - if (!has_net_load && drvr_parasitic) { - if (parasitics_->isParasiticNetwork(drvr_parasitic)) - wire_cap += parasitics_->capacitance(drvr_parasitic); - else { - // PiModel includes both pin and external caps. - float cap = parasitics_->capacitance(drvr_parasitic); - if (pin_cap > cap) { - pin_cap = 0.0; - wire_cap = cap; - } - else - wire_cap = cap - pin_cap; - } - } -} - -void -GraphDelayCalc1::netCaps(const Pin *drvr_pin, - const RiseFall *rf, - const DcalcAnalysisPt *dcalc_ap, - // Return values. - float &pin_cap, - float &wire_cap, - float &fanout, - bool &has_net_load) const -{ - MultiDrvrNet *multi_drvr = nullptr; - if (graph_) { - Vertex *drvr_vertex = graph_->pinDrvrVertex(drvr_pin); - multi_drvr = multiDrvrNet(drvr_vertex); - } - if (multi_drvr) - multi_drvr->netCaps(rf, dcalc_ap, - pin_cap, wire_cap, fanout, has_net_load); - else { - const OperatingConditions *op_cond = dcalc_ap->operatingConditions(); - const Corner *corner = dcalc_ap->corner(); - const MinMax *min_max = dcalc_ap->constraintMinMax(); - // Find pin and external pin/wire capacitance. - sdc_->connectedCap(drvr_pin, rf, op_cond, corner, min_max, - pin_cap, wire_cap, fanout, has_net_load); - } -} - -void -GraphDelayCalc1::initSlew(Vertex *vertex) -{ - for (auto rf : RiseFall::range()) { - for (auto dcalc_ap : corners_->dcalcAnalysisPts()) { - const MinMax *slew_min_max = dcalc_ap->slewMinMax(); - if (!vertex->slewAnnotated(rf, slew_min_max)) { - DcalcAPIndex ap_index = dcalc_ap->index(); - graph_->setSlew(vertex, rf, ap_index, slew_min_max->initValue()); - } - } - } -} - -void -GraphDelayCalc1::zeroSlewAndWireDelays(Vertex *drvr_vertex) -{ - for (auto dcalc_ap : corners_->dcalcAnalysisPts()) { - DcalcAPIndex ap_index = dcalc_ap->index(); - const MinMax *slew_min_max = dcalc_ap->slewMinMax(); - for (auto rf : RiseFall::range()) { - // Init drvr slew. - if (!drvr_vertex->slewAnnotated(rf, slew_min_max)) { - DcalcAPIndex ap_index = dcalc_ap->index(); - graph_->setSlew(drvr_vertex, rf, ap_index, slew_min_max->initValue()); - } - - // Init wire delays and slews. - VertexOutEdgeIterator edge_iter(drvr_vertex, graph_); - while (edge_iter.hasNext()) { - Edge *wire_edge = edge_iter.next(); - if (wire_edge->isWire()) { - Vertex *load_vertex = wire_edge->to(graph_); - if (!graph_->wireDelayAnnotated(wire_edge, rf, ap_index)) - graph_->setWireArcDelay(wire_edge, rf, ap_index, 0.0); - // Init load vertex slew. - if (!load_vertex->slewAnnotated(rf, slew_min_max)) - graph_->setSlew(load_vertex, rf, ap_index, 0.0); - } - } - } - } -} - -// Init wire delays and load slews. -void -GraphDelayCalc1::initWireDelays(Vertex *drvr_vertex, - bool init_load_slews) -{ - VertexOutEdgeIterator edge_iter(drvr_vertex, graph_); - while (edge_iter.hasNext()) { - Edge *wire_edge = edge_iter.next(); - if (wire_edge->isWire()) { - Vertex *load_vertex = wire_edge->to(graph_); - for (auto dcalc_ap : corners_->dcalcAnalysisPts()) { - const MinMax *delay_min_max = dcalc_ap->delayMinMax(); - const MinMax *slew_min_max = dcalc_ap->slewMinMax(); - Delay delay_init_value(delay_min_max->initValue()); - Slew slew_init_value(slew_min_max->initValue()); - DcalcAPIndex ap_index = dcalc_ap->index(); - for (auto rf : RiseFall::range()) { - if (!graph_->wireDelayAnnotated(wire_edge, rf, ap_index)) - graph_->setWireArcDelay(wire_edge, rf, ap_index, delay_init_value); - // Init load vertex slew. - if (init_load_slews - && !load_vertex->slewAnnotated(rf, slew_min_max)) - graph_->setSlew(load_vertex, rf, ap_index, slew_init_value); - } - } - } - } -} - -// Call the arc delay calculator to find the delay thru a single gate -// input to output timing arc, the wire delays from the gate output to -// each load pin, and the slew at each load pin. Annotate the graph -// with the results. -bool -GraphDelayCalc1::findArcDelay(LibertyCell *drvr_cell, - const Pin *drvr_pin, - Vertex *drvr_vertex, - MultiDrvrNet *multi_drvr, - TimingArc *arc, - Parasitic *drvr_parasitic, - float related_out_cap, - Vertex *from_vertex, - Edge *edge, - const Pvt *pvt, - const DcalcAnalysisPt *dcalc_ap, - ArcDelayCalc *arc_delay_calc) -{ - bool delay_changed = false; - RiseFall *from_rf = arc->fromEdge()->asRiseFall(); - RiseFall *drvr_rf = arc->toEdge()->asRiseFall(); - if (from_rf && drvr_rf) { - DcalcAPIndex ap_index = dcalc_ap->index(); - debugPrint(debug_, "delay_calc", 3, - " %s %s -> %s %s (%s) corner:%s/%s", - arc->from()->name(), - arc->fromEdge()->asString(), - arc->to()->name(), - arc->toEdge()->asString(), - arc->role()->asString(), - dcalc_ap->corner()->name(), - dcalc_ap->delayMinMax()->asString()); - // Delay calculation is done even when the gate delays/slews are - // annotated because the wire delays may not be annotated. - const Slew from_slew = edgeFromSlew(from_vertex, from_rf, edge, dcalc_ap); - ArcDelay gate_delay; - Slew gate_slew; - if (multi_drvr - && arc->to()->direction()->isOutput()) - parallelGateDelay(multi_drvr, drvr_cell, drvr_pin, arc, - pvt, dcalc_ap, from_slew, drvr_parasitic, - related_out_cap, - arc_delay_calc, - gate_delay, gate_slew); - else { - float load_cap = loadCap(drvr_pin, multi_drvr, drvr_parasitic, - drvr_rf, dcalc_ap); - arc_delay_calc->gateDelay(drvr_cell, arc, - from_slew, load_cap, drvr_parasitic, - related_out_cap, pvt, dcalc_ap, - gate_delay, gate_slew); - } - debugPrint(debug_, "delay_calc", 3, - " gate delay = %s slew = %s", - delayAsString(gate_delay, this), - delayAsString(gate_slew, this)); - // Merge slews. - const Slew &drvr_slew = graph_->slew(drvr_vertex, drvr_rf, ap_index); - const MinMax *slew_min_max = dcalc_ap->slewMinMax(); - if (delayGreater(gate_slew, drvr_slew, dcalc_ap->slewMinMax(), this) - && !drvr_vertex->slewAnnotated(drvr_rf, slew_min_max) - && !edge->role()->isLatchDtoQ()) - graph_->setSlew(drvr_vertex, drvr_rf, ap_index, gate_slew); - if (!graph_->arcDelayAnnotated(edge, arc, ap_index)) { - const ArcDelay &prev_gate_delay = graph_->arcDelay(edge,arc,ap_index); - float gate_delay1 = delayAsFloat(gate_delay); - float prev_gate_delay1 = delayAsFloat(prev_gate_delay); - if (prev_gate_delay1 == 0.0 - || (abs(gate_delay1 - prev_gate_delay1) / prev_gate_delay1 - > incremental_delay_tolerance_)) - delay_changed = true; - graph_->setArcDelay(edge, arc, ap_index, gate_delay); - } - if (!edge->role()->isLatchDtoQ()) - annotateLoadDelays(drvr_vertex, drvr_rf, delay_zero, true, dcalc_ap, - arc_delay_calc); - } - return delay_changed; -} - -void -GraphDelayCalc1::parallelGateDelay(MultiDrvrNet *multi_drvr, - LibertyCell *drvr_cell, - const Pin *drvr_pin, - TimingArc *arc, - const Pvt *pvt, - const DcalcAnalysisPt *dcalc_ap, - const Slew from_slew, - Parasitic *drvr_parasitic, - float related_out_cap, - ArcDelayCalc *arc_delay_calc, - // Return values. - ArcDelay &gate_delay, - Slew &gate_slew) -{ - ArcDelay intrinsic_delay; - Slew intrinsic_slew; - arc_delay_calc->gateDelay(drvr_cell, arc, from_slew, - 0.0, 0, 0.0, pvt, dcalc_ap, - intrinsic_delay, intrinsic_slew); - ArcDelay parallel_delay; - Slew parallel_slew; - const RiseFall *drvr_rf = arc->toEdge()->asRiseFall(); - multi_drvr->parallelDelaySlew(drvr_rf, dcalc_ap, arc_delay_calc, this, - parallel_delay, parallel_slew); - - gate_delay = parallel_delay + intrinsic_delay; - gate_slew = parallel_slew; - - float load_cap = loadCap(drvr_pin, multi_drvr, drvr_parasitic, - drvr_rf, dcalc_ap); - Delay gate_delay1; - Slew gate_slew1; - arc_delay_calc->gateDelay(drvr_cell, arc, - from_slew, load_cap, drvr_parasitic, - related_out_cap, pvt, dcalc_ap, - gate_delay1, gate_slew1); - float factor = delayRatio(gate_slew, gate_slew1); - arc_delay_calc->setMultiDrvrSlewFactor(factor); -} - -void -GraphDelayCalc1::findMultiDrvrGateDelay(MultiDrvrNet *multi_drvr, - const RiseFall *drvr_rf, - const Pvt *pvt, - const DcalcAnalysisPt *dcalc_ap, - ArcDelayCalc *arc_delay_calc, - // Return values. - ArcDelay ¶llel_delay, - Slew ¶llel_slew) -{ - ArcDelay delay_sum = 1.0; - Slew slew_sum = 1.0; - for (Vertex *drvr_vertex1 : *multi_drvr->drvrs()) { - Pin *drvr_pin1 = drvr_vertex1->pin(); - Instance *drvr_inst1 = network_->instance(drvr_pin1); - LibertyCell *drvr_cell1 = network_->libertyCell(drvr_inst1); - if (network_->isDriver(drvr_pin1)) { - VertexInEdgeIterator edge_iter(drvr_vertex1, graph_); - while (edge_iter.hasNext()) { - Edge *edge1 = edge_iter.next(); - TimingArcSet *arc_set1 = edge1->timingArcSet(); - const LibertyPort *related_out_port = arc_set1->relatedOut(); - for (TimingArc *arc1 : arc_set1->arcs()) { - RiseFall *drvr_rf1 = arc1->toEdge()->asRiseFall(); - if (drvr_rf1 == drvr_rf) { - Vertex *from_vertex1 = edge1->from(graph_); - RiseFall *from_rf1 = arc1->fromEdge()->asRiseFall(); - Slew from_slew1 = edgeFromSlew(from_vertex1, from_rf1, edge1, dcalc_ap); - ArcDelay intrinsic_delay1; - Slew intrinsic_slew1; - arc_delay_calc->gateDelay(drvr_cell1, arc1, from_slew1, - 0.0, 0, 0.0, pvt, dcalc_ap, - intrinsic_delay1, intrinsic_slew1); - Parasitic *parasitic1 = - arc_delay_calc->findParasitic(drvr_pin1, drvr_rf1, dcalc_ap); - const Pin *related_out_pin1 = 0; - float related_out_cap1 = 0.0; - if (related_out_port) { - Instance *inst1 = network_->instance(drvr_pin1); - related_out_pin1 = network_->findPin(inst1, related_out_port); - if (related_out_pin1) { - Parasitic *related_out_parasitic1 = - arc_delay_calc->findParasitic(related_out_pin1, drvr_rf, - dcalc_ap); - related_out_cap1 = loadCap(related_out_pin1, - related_out_parasitic1, - drvr_rf, dcalc_ap); - } - } - float load_cap1 = loadCap(drvr_pin1, parasitic1, - drvr_rf, dcalc_ap); - ArcDelay gate_delay1; - Slew gate_slew1; - arc_delay_calc->gateDelay(drvr_cell1, arc1, - from_slew1, load_cap1, parasitic1, - related_out_cap1, pvt, dcalc_ap, - gate_delay1, gate_slew1); - delay_sum += 1.0F / (gate_delay1 - intrinsic_delay1); - slew_sum += 1.0F / gate_slew1; - } - } - } - } - } - parallel_delay = 1.0F / delay_sum; - parallel_slew = 1.0F / slew_sum; -} - -// Use clock slew for register/latch clk->q edges. -Slew -GraphDelayCalc1::edgeFromSlew(const Vertex *from_vertex, - const RiseFall *from_rf, - const Edge *edge, - const DcalcAnalysisPt *dcalc_ap) -{ - const TimingRole *role = edge->role(); - if (role->genericRole() == TimingRole::regClkToQ() - && clk_network_->isIdealClock(from_vertex->pin())) - return clk_network_->idealClkSlew(from_vertex->pin(), from_rf, - dcalc_ap->slewMinMax()); - else - return graph_->slew(from_vertex, from_rf, dcalc_ap->index()); -} - -// Annotate wire arc delays and load pin slews. -// extra_delay is additional wire delay to add to delay returned -// by the delay calculator. -void -GraphDelayCalc1::annotateLoadDelays(Vertex *drvr_vertex, - const RiseFall *drvr_rf, - const ArcDelay &extra_delay, - bool merge, - const DcalcAnalysisPt *dcalc_ap, - ArcDelayCalc *arc_delay_calc) -{ - DcalcAPIndex ap_index = dcalc_ap->index(); - const MinMax *slew_min_max = dcalc_ap->slewMinMax(); - VertexOutEdgeIterator edge_iter(drvr_vertex, graph_); - while (edge_iter.hasNext()) { - Edge *wire_edge = edge_iter.next(); - if (wire_edge->isWire()) { - Vertex *load_vertex = wire_edge->to(graph_); - Pin *load_pin = load_vertex->pin(); - ArcDelay wire_delay; - Slew load_slew; - arc_delay_calc->loadDelay(load_pin, wire_delay, load_slew); - debugPrint(debug_, "delay_calc", 3, - " %s load delay = %s slew = %s", - load_vertex->name(sdc_network_), - delayAsString(wire_delay, this), - delayAsString(load_slew, this)); - if (!load_vertex->slewAnnotated(drvr_rf, slew_min_max)) { - if (drvr_vertex->slewAnnotated(drvr_rf, slew_min_max)) { - // Copy the driver slew to the load if it is annotated. - const Slew &drvr_slew = graph_->slew(drvr_vertex,drvr_rf,ap_index); - graph_->setSlew(load_vertex, drvr_rf, ap_index, drvr_slew); - } - else { - const Slew &slew = graph_->slew(load_vertex, drvr_rf, ap_index); - if (!merge - || delayGreater(load_slew, slew, slew_min_max, this)) - graph_->setSlew(load_vertex, drvr_rf, ap_index, load_slew); - } - } - if (!graph_->wireDelayAnnotated(wire_edge, drvr_rf, ap_index)) { - // Multiple timing arcs with the same output transition - // annotate the same wire edges so they must be combined - // rather than set. - const ArcDelay &delay = graph_->wireArcDelay(wire_edge, drvr_rf, - ap_index); - Delay wire_delay_extra = extra_delay + wire_delay; - const MinMax *delay_min_max = dcalc_ap->delayMinMax(); - if (!merge - || delayGreater(wire_delay_extra, delay, delay_min_max, this)) { - graph_->setWireArcDelay(wire_edge, drvr_rf, ap_index, - wire_delay_extra); - if (observer_) - observer_->delayChangedTo(load_vertex); - } - } - // Enqueue bidirect driver from load vertex. - if (sdc_->bidirectDrvrSlewFromLoad(load_pin)) - iter_->enqueue(graph_->pinDrvrVertex(load_pin)); - } - } -} - -void -GraphDelayCalc1::findCheckEdgeDelays(Edge *edge, - ArcDelayCalc *arc_delay_calc) -{ - Vertex *from_vertex = edge->from(graph_); - Vertex *to_vertex = edge->to(graph_); - TimingArcSet *arc_set = edge->timingArcSet(); - const Pin *to_pin = to_vertex->pin(); - Instance *inst = network_->instance(to_pin); - const LibertyCell *cell = network_->libertyCell(inst); - debugPrint(debug_, "delay_calc", 2, "find check %s %s -> %s", - sdc_network_->pathName(inst), - network_->portName(from_vertex->pin()), - network_->portName(to_pin)); - bool delay_changed = false; - for (TimingArc *arc : arc_set->arcs()) { - RiseFall *from_rf = arc->fromEdge()->asRiseFall(); - RiseFall *to_rf = arc->toEdge()->asRiseFall(); - if (from_rf && to_rf) { - 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); - for (auto dcalc_ap : corners_->dcalcAnalysisPts()) { - DcalcAPIndex ap_index = dcalc_ap->index(); - if (!graph_->arcDelayAnnotated(edge, arc, ap_index)) { - const Pvt *pvt = sdc_->pvt(inst,dcalc_ap->constraintMinMax()); - if (pvt == nullptr) - pvt = dcalc_ap->operatingConditions(); - const Slew &from_slew = checkEdgeClkSlew(from_vertex, from_rf, - dcalc_ap); - int slew_index = dcalc_ap->checkDataSlewIndex(); - const Slew &to_slew = graph_->slew(to_vertex, to_rf, slew_index); - debugPrint(debug_, "delay_calc", 3, - " %s %s -> %s %s (%s)", - arc_set->from()->name(), - arc->fromEdge()->asString(), - arc_set->to()->name(), - arc->toEdge()->asString(), - arc_set->role()->asString()); - debugPrint(debug_, "delay_calc", 3, - " from_slew = %s to_slew = %s", - delayAsString(from_slew, this), - delayAsString(to_slew, this)); - float related_out_cap = 0.0; - if (related_out_pin) { - Parasitic *related_out_parasitic = - arc_delay_calc->findParasitic(related_out_pin, to_rf, dcalc_ap); - related_out_cap = loadCap(related_out_pin, - related_out_parasitic, - to_rf, dcalc_ap); - } - ArcDelay check_delay; - arc_delay_calc->checkDelay(cell, arc, - from_slew, to_slew, - related_out_cap, - pvt, dcalc_ap, - check_delay); - debugPrint(debug_, "delay_calc", 3, - " check_delay = %s", - delayAsString(check_delay, this)); - graph_->setArcDelay(edge, arc, ap_index, check_delay); - delay_changed = true; - } - } - } - } - - if (delay_changed && observer_) - observer_->checkDelayChangedTo(to_vertex); -} - -// Use clock slew for timing check clock edges. -Slew -GraphDelayCalc1::checkEdgeClkSlew(const Vertex *from_vertex, - const RiseFall *from_rf, - const DcalcAnalysisPt *dcalc_ap) -{ - if (clk_network_->isIdealClock(from_vertex->pin())) - return clk_network_->idealClkSlew(from_vertex->pin(), from_rf, - dcalc_ap->checkClkSlewMinMax()); - else - return graph_->slew(from_vertex, from_rf, dcalc_ap->checkClkSlewIndex()); -} - -//////////////////////////////////////////////////////////////// - -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 == nullptr) - pvt = dcalc_ap->operatingConditions(); - RiseFall *from_rf = arc->fromEdge()->asRiseFall(); - RiseFall *to_rf = arc->toEdge()->asRiseFall(); - if (from_rf && to_rf) { - 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 = - arc_delay_calc_->findParasitic(related_out_pin, to_rf, dcalc_ap); - related_out_cap = loadCap(related_out_pin, related_out_parasitic, - to_rf, dcalc_ap); - } - Parasitic *to_parasitic = arc_delay_calc_->findParasitic(to_pin, to_rf, - dcalc_ap); - const Slew &from_slew = edgeFromSlew(from_vertex, from_rf, edge, dcalc_ap); - float load_cap = loadCap(to_pin, to_parasitic, to_rf, dcalc_ap); - ceff = arc_delay_calc_->ceff(cell, arc, - from_slew, load_cap, to_parasitic, - related_out_cap, pvt, dcalc_ap); - arc_delay_calc_->finishDrvrPin(); - } - return ceff; -} - -//////////////////////////////////////////////////////////////// - -string -GraphDelayCalc1::reportDelayCalc(Edge *edge, - TimingArc *arc, - const Corner *corner, - const MinMax *min_max, - int digits) -{ - Vertex *from_vertex = edge->from(graph_); - Vertex *to_vertex = edge->to(graph_); - Pin *to_pin = to_vertex->pin(); - TimingRole *role = arc->role(); - Instance *inst = network_->instance(to_pin); - LibertyCell *cell = network_->libertyCell(inst); - TimingArcSet *arc_set = edge->timingArcSet(); - string result; - DcalcAnalysisPt *dcalc_ap = corner->findDcalcAnalysisPt(min_max); - const Pvt *pvt = sdc_->pvt(inst, dcalc_ap->constraintMinMax()); - if (pvt == nullptr) - pvt = dcalc_ap->operatingConditions(); - RiseFall *from_rf = arc->fromEdge()->asRiseFall(); - RiseFall *to_rf = arc->toEdge()->asRiseFall(); - if (from_rf && to_rf) { - 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 = - arc_delay_calc_->findParasitic(related_out_pin, to_rf, dcalc_ap); - related_out_cap = loadCap(related_out_pin, related_out_parasitic, - to_rf, dcalc_ap); - } - if (role->isTimingCheck()) { - const Slew &from_slew = checkEdgeClkSlew(from_vertex, from_rf, dcalc_ap); - int slew_index = dcalc_ap->checkDataSlewIndex(); - const Slew &to_slew = graph_->slew(to_vertex, to_rf, slew_index); - bool from_ideal_clk = clk_network_->isIdealClock(from_vertex->pin()); - const char *from_slew_annotation = from_ideal_clk ? " (ideal clock)" : nullptr; - result = arc_delay_calc_->reportCheckDelay(cell, arc, from_slew, - from_slew_annotation, to_slew, - related_out_cap, pvt, dcalc_ap, - digits); - } - else { - Parasitic *to_parasitic = - arc_delay_calc_->findParasitic(to_pin, to_rf, dcalc_ap); - const Slew &from_slew = edgeFromSlew(from_vertex, from_rf, edge, dcalc_ap); - float load_cap = loadCap(to_pin, to_parasitic, to_rf, dcalc_ap); - result = arc_delay_calc_->reportGateDelay(cell, arc, - from_slew, load_cap, to_parasitic, - related_out_cap, pvt, dcalc_ap, digits); - } - arc_delay_calc_->finishDrvrPin(); - } - return result; -} - -} // namespace diff --git a/dcalc/GraphDelayCalc1.hh b/dcalc/GraphDelayCalc1.hh deleted file mode 100644 index 84189dcb..00000000 --- a/dcalc/GraphDelayCalc1.hh +++ /dev/null @@ -1,236 +0,0 @@ -// OpenSTA, Static Timing Analyzer -// Copyright (c) 2023, 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 . - -#pragma once - -#include - -#include "Delay.hh" -#include "GraphDelayCalc.hh" - -namespace sta { - -class MultiDrvrNet; -class FindVertexDelays; -class Corner; - -typedef Map MultiDrvrNetMap; - -// This class traverses the graph calling the arc delay calculator and -// annotating delays on graph edges. -class GraphDelayCalc1 : public GraphDelayCalc -{ -public: - GraphDelayCalc1(StaState *sta); - virtual ~GraphDelayCalc1(); - virtual void copyState(const StaState *sta); - virtual void delaysInvalid(); - virtual void delayInvalid(Vertex *vertex); - virtual void delayInvalid(const Pin *pin); - virtual void deleteVertexBefore(Vertex *vertex); - virtual void clear(); - virtual void findDelays(Level level); - virtual void findDelays(Vertex *drvr_vertex); - virtual string reportDelayCalc(Edge *edge, - TimingArc *arc, - const Corner *corner, - const MinMax *min_max, - int digits); - 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 RiseFall *drvr_rf, - const DcalcAnalysisPt *dcalc_ap) const; - virtual float loadCap(const Pin *drvr_pin, - const DcalcAnalysisPt *dcalc_ap) const; - virtual void loadCap(const Pin *drvr_pin, - const Parasitic *drvr_parasitic, - const RiseFall *rf, - const DcalcAnalysisPt *dcalc_ap, - // Return values. - float &pin_cap, - float &wire_cap) const; - virtual float loadCap(const Pin *drvr_pin, - const Parasitic *drvr_parasitic, - const RiseFall *rf, - const DcalcAnalysisPt *dcalc_ap) const; - virtual void netCaps(const Pin *drvr_pin, - const RiseFall *rf, - const DcalcAnalysisPt *dcalc_ap, - // Return values. - float &pin_cap, - float &wire_cap, - float &fanout, - bool &has_set_load) const; - float ceff(Edge *edge, - TimingArc *arc, - const DcalcAnalysisPt *dcalc_ap); - -protected: - void seedInvalidDelays(); - void ensureMultiDrvrNetsFound(); - void makeMultiDrvrNet(PinSet &drvr_pins); - void initSlew(Vertex *vertex); - void seedRootSlew(Vertex *vertex, - ArcDelayCalc *arc_delay_calc); - void seedRootSlews(); - void seedDrvrSlew(Vertex *vertex, - ArcDelayCalc *arc_delay_calc); - void seedNoDrvrSlew(Vertex *drvr_vertex, - const Pin *drvr_pin, - const RiseFall *rf, - DcalcAnalysisPt *dcalc_ap, - ArcDelayCalc *arc_delay_calc); - void seedNoDrvrCellSlew(Vertex *drvr_vertex, - const Pin *drvr_pin, - const RiseFall *rf, - InputDrive *drive, - DcalcAnalysisPt *dcalc_ap, - ArcDelayCalc *arc_delay_calc); - void seedLoadSlew(Vertex *vertex); - void setInputPortWireDelays(Vertex *vertex); - void findInputDriverDelay(const LibertyCell *drvr_cell, - const Pin *drvr_pin, - Vertex *drvr_vertex, - const RiseFall *rf, - const LibertyPort *from_port, - float *from_slews, - const LibertyPort *to_port, - const DcalcAnalysisPt *dcalc_ap); - LibertyPort *driveCellDefaultFromPort(const LibertyCell *cell, - const LibertyPort *to_port); - int findPortIndex(const LibertyCell *cell, - const LibertyPort *port); - void findInputArcDelay(const LibertyCell *drvr_cell, - const Pin *drvr_pin, - Vertex *drvr_vertex, - const TimingArc *arc, - float from_slew, - const DcalcAnalysisPt *dcalc_ap); - bool findDriverDelays(Vertex *drvr_vertex, - ArcDelayCalc *arc_delay_calc); - bool findDriverDelays1(Vertex *drvr_vertex, - bool init_load_slews, - MultiDrvrNet *multi_drvr, - ArcDelayCalc *arc_delay_calc); - bool findDriverEdgeDelays(LibertyCell *drvr_cell, - Instance *drvr_inst, - const Pin *drvr_pin, - Vertex *drvr_vertex, - MultiDrvrNet *multi_drvr, - Edge *edge, - ArcDelayCalc *arc_delay_calc); - void initWireDelays(Vertex *drvr_vertex, - bool init_load_slews); - void initRootSlews(Vertex *vertex); - void zeroSlewAndWireDelays(Vertex *drvr_vertex); - void findVertexDelay(Vertex *vertex, - ArcDelayCalc *arc_delay_calc, - bool propagate); - void enqueueTimingChecksEdges(Vertex *vertex); - bool findArcDelay(LibertyCell *drvr_cell, - const Pin *drvr_pin, - Vertex *drvr_vertex, - MultiDrvrNet *multi_drvr, - TimingArc *arc, - Parasitic *drvr_parasitic, - float related_out_cap, - Vertex *from_vertex, - Edge *edge, - const Pvt *pvt, - const DcalcAnalysisPt *dcalc_ap, - ArcDelayCalc *arc_delay_calc); - void annotateLoadDelays(Vertex *drvr_vertex, - const RiseFall *drvr_rf, - const ArcDelay &extra_delay, - bool merge, - const DcalcAnalysisPt *dcalc_ap, - ArcDelayCalc *arc_delay_calc); - void findLatchEdgeDelays(Edge *edge); - void findCheckEdgeDelays(Edge *edge, - ArcDelayCalc *arc_delay_calc); - void findMultiDrvrGateDelay(MultiDrvrNet *multi_drvr, - const RiseFall *drvr_rf, - const Pvt *pvt, - const DcalcAnalysisPt *dcalc_ap, - ArcDelayCalc *arc_delay_calc, - // Return values. - ArcDelay ¶llel_delay, - Slew ¶llel_slew); - void parallelGateDelay(MultiDrvrNet *multi_drvr, - LibertyCell *drvr_cell, - const Pin *drvr_pin, - TimingArc *arc, - const Pvt *pvt, - const DcalcAnalysisPt *dcalc_ap, - const Slew from_slew, - Parasitic *drvr_parasitic, - float related_out_cap, - ArcDelayCalc *arc_delay_calc, - // Return values. - ArcDelay &gate_delay, - Slew &gate_slew); - void deleteMultiDrvrNets(); - Slew edgeFromSlew(const Vertex *from_vertex, - const RiseFall *from_rf, - const Edge *edge, - const DcalcAnalysisPt *dcalc_ap); - Slew checkEdgeClkSlew(const Vertex *from_vertex, - const RiseFall *from_rf, - const DcalcAnalysisPt *dcalc_ap); - bool bidirectDrvrSlewFromLoad(const Vertex *vertex) const; - MultiDrvrNet *multiDrvrNet(const Vertex *drvr_vertex) const; - void loadCap(const Parasitic *drvr_parasitic, - bool has_set_load, - // Return values. - float &pin_cap, - float &wire_cap) const; - float loadCap(const Pin *drvr_pin, - MultiDrvrNet *multi_drvr, - const Parasitic *drvr_parasitic, - const RiseFall *rf, - const DcalcAnalysisPt *dcalc_ap) const; - - // Observer for edge delay changes. - DelayCalcObserver *observer_; - bool delays_seeded_; - bool incremental_; - bool delays_exist_; - // Vertices with invalid -to delays. - VertexSet *invalid_delays_; - // Timing check edges with invalid delays. - EdgeSet invalid_check_edges_; - // Latch D->Q edges with invalid delays. - EdgeSet invalid_latch_edges_; - // shared by invalid_check_edges_ and invalid_latch_edges_ - std::mutex invalid_edge_lock_; - SearchPred *search_pred_; - SearchPred *search_non_latch_pred_; - SearchPred *clk_pred_; - BfsFwdIterator *iter_; - MultiDrvrNetMap multi_drvr_net_map_; - bool multi_drvr_nets_found_; - // Percentage (0.0:1.0) change in delay that causes downstream - // delays to be recomputed during incremental delay calculation. - float incremental_delay_tolerance_; - - friend class FindVertexDelays; - friend class MultiDrvrNet; -}; - -} // namespace diff --git a/include/sta/GraphDelayCalc.hh b/include/sta/GraphDelayCalc.hh index 5d795979..d6aa9526 100644 --- a/include/sta/GraphDelayCalc.hh +++ b/include/sta/GraphDelayCalc.hh @@ -16,46 +16,44 @@ #pragma once -#include - +#include "Map.hh" +#include "NetworkClass.hh" #include "GraphClass.hh" +#include "SearchClass.hh" #include "DcalcAnalysisPt.hh" #include "StaState.hh" +#include "Delay.hh" namespace sta { -using std::string; - -class BfsFwdIterator; -class SearchPred; class DelayCalcObserver; -class Parasitic; -class Corner; +class MultiDrvrNet; +class FindVertexDelays; -// Base class for graph delay calculator. -// This class annotates the arc delays and slews on the graph by calling -// the timing arc delay calculation primitive through an implementation -// of the ArcDelayCalc abstract class. -// This class does not traverse the graph or call an arc delay -// calculator. Use it with applications that use an external delay -// calculator and annotate all edge delays. +typedef Map MultiDrvrNetMap; + +// This class traverses the graph calling the arc delay calculator and +// annotating delays on graph edges. class GraphDelayCalc : public StaState { public: - explicit GraphDelayCalc(StaState *sta); - virtual ~GraphDelayCalc() {} - // Find arc delays and vertex slews thru level. - virtual void findDelays(Level /* level */) {}; - // Find and annotate drvr_vertex gate and load delays/slews. - virtual void findDelays(Vertex * /* drvr_vertex */) {}; + GraphDelayCalc(StaState *sta); + virtual ~GraphDelayCalc(); + virtual void copyState(const StaState *sta); + // Set the observer for edge delay changes. + virtual void setObserver(DelayCalcObserver *observer); // Invalidate all delays/slews. - virtual void delaysInvalid() {}; + virtual void delaysInvalid(); // Invalidate vertex and downstream delays/slews. - virtual void delayInvalid(Vertex * /* vertex */) {}; - virtual void delayInvalid(const Pin * /* pin */) {}; - virtual void deleteVertexBefore(Vertex * /* vertex */) {}; + virtual void delayInvalid(Vertex *vertex); + virtual void delayInvalid(const Pin *pin); + virtual void deleteVertexBefore(Vertex *vertex); // Reset to virgin state. - virtual void clear() {} + virtual void clear(); + // Find arc delays and vertex slews thru level. + virtual void findDelays(Level level); + // Find and annotate drvr_vertex gate and load delays/slews. + virtual void findDelays(Vertex *drvr_vertex); // Returned string is owned by the caller. virtual string reportDelayCalc(Edge *edge, TimingArc *arc, @@ -65,9 +63,14 @@ public: // Percentage (0.0:1.0) change in delay that causes downstream // delays to be recomputed during incremental delay calculation. virtual float incrementalDelayTolerance(); - virtual void setIncrementalDelayTolerance(float /* tol */) {} - // Set the observer for edge delay changes. - virtual void setObserver(DelayCalcObserver *observer); + virtual void setIncrementalDelayTolerance(float tol); + // Load pin_cap + wire_cap. + virtual float loadCap(const Pin *drvr_pin, + const RiseFall *drvr_rf, + const DcalcAnalysisPt *dcalc_ap) const; + // Load pin_cap + wire_cap including parasitic min/max for rise/fall. + virtual float loadCap(const Pin *drvr_pin, + const DcalcAnalysisPt *dcalc_ap) const; // pin_cap = net pin capacitances + port external pin capacitance, // wire_cap = annotated net capacitance + port external wire capacitance. virtual void loadCap(const Pin *drvr_pin, @@ -78,13 +81,6 @@ public: float &pin_cap, float &wire_cap) const; // Load pin_cap + wire_cap including parasitic. - virtual float loadCap(const Pin *drvr_pin, - const RiseFall *to_rf, - const DcalcAnalysisPt *dcalc_ap) const; - // Load pin_cap + wire_cap including parasitic min/max for rise/fall. - virtual float loadCap(const Pin *drvr_pin, - const DcalcAnalysisPt *dcalc_ap) const; - // Load pin_cap + wire_cap. virtual float loadCap(const Pin *drvr_pin, const Parasitic *drvr_parasitic, const RiseFall *rf, @@ -97,9 +93,9 @@ public: float &wire_cap, float &fanout, bool &has_set_load) const; - virtual float ceff(Edge *edge, - TimingArc *arc, - const DcalcAnalysisPt *dcalc_ap); + float ceff(Edge *edge, + TimingArc *arc, + const DcalcAnalysisPt *dcalc_ap); // Precedence: // SDF annotation // Liberty library @@ -118,6 +114,157 @@ public: // Return values. float &min_period, bool &exists); + +protected: + void seedInvalidDelays(); + void ensureMultiDrvrNetsFound(); + void makeMultiDrvrNet(PinSet &drvr_pins); + void initSlew(Vertex *vertex); + void seedRootSlew(Vertex *vertex, + ArcDelayCalc *arc_delay_calc); + void seedRootSlews(); + void seedDrvrSlew(Vertex *vertex, + ArcDelayCalc *arc_delay_calc); + void seedNoDrvrSlew(Vertex *drvr_vertex, + const Pin *drvr_pin, + const RiseFall *rf, + DcalcAnalysisPt *dcalc_ap, + ArcDelayCalc *arc_delay_calc); + void seedNoDrvrCellSlew(Vertex *drvr_vertex, + const Pin *drvr_pin, + const RiseFall *rf, + InputDrive *drive, + DcalcAnalysisPt *dcalc_ap, + ArcDelayCalc *arc_delay_calc); + void seedLoadSlew(Vertex *vertex); + void setInputPortWireDelays(Vertex *vertex); + void findInputDriverDelay(const LibertyCell *drvr_cell, + const Pin *drvr_pin, + Vertex *drvr_vertex, + const RiseFall *rf, + const LibertyPort *from_port, + float *from_slews, + const LibertyPort *to_port, + const DcalcAnalysisPt *dcalc_ap); + LibertyPort *driveCellDefaultFromPort(const LibertyCell *cell, + const LibertyPort *to_port); + int findPortIndex(const LibertyCell *cell, + const LibertyPort *port); + void findInputArcDelay(const LibertyCell *drvr_cell, + const Pin *drvr_pin, + Vertex *drvr_vertex, + const TimingArc *arc, + float from_slew, + const DcalcAnalysisPt *dcalc_ap); + bool findDriverDelays(Vertex *drvr_vertex, + ArcDelayCalc *arc_delay_calc); + bool findDriverDelays1(Vertex *drvr_vertex, + bool init_load_slews, + MultiDrvrNet *multi_drvr, + ArcDelayCalc *arc_delay_calc); + bool findDriverEdgeDelays(LibertyCell *drvr_cell, + Instance *drvr_inst, + const Pin *drvr_pin, + Vertex *drvr_vertex, + MultiDrvrNet *multi_drvr, + Edge *edge, + ArcDelayCalc *arc_delay_calc); + void initWireDelays(Vertex *drvr_vertex, + bool init_load_slews); + void initRootSlews(Vertex *vertex); + void zeroSlewAndWireDelays(Vertex *drvr_vertex); + void findVertexDelay(Vertex *vertex, + ArcDelayCalc *arc_delay_calc, + bool propagate); + void enqueueTimingChecksEdges(Vertex *vertex); + bool findArcDelay(LibertyCell *drvr_cell, + const Pin *drvr_pin, + Vertex *drvr_vertex, + MultiDrvrNet *multi_drvr, + TimingArc *arc, + Parasitic *drvr_parasitic, + float related_out_cap, + Vertex *from_vertex, + Edge *edge, + const Pvt *pvt, + const DcalcAnalysisPt *dcalc_ap, + ArcDelayCalc *arc_delay_calc); + void annotateLoadDelays(Vertex *drvr_vertex, + const RiseFall *drvr_rf, + const ArcDelay &extra_delay, + bool merge, + const DcalcAnalysisPt *dcalc_ap, + ArcDelayCalc *arc_delay_calc); + void findLatchEdgeDelays(Edge *edge); + void findCheckEdgeDelays(Edge *edge, + ArcDelayCalc *arc_delay_calc); + void findMultiDrvrGateDelay(MultiDrvrNet *multi_drvr, + const RiseFall *drvr_rf, + const Pvt *pvt, + const DcalcAnalysisPt *dcalc_ap, + ArcDelayCalc *arc_delay_calc, + // Return values. + ArcDelay ¶llel_delay, + Slew ¶llel_slew); + void parallelGateDelay(MultiDrvrNet *multi_drvr, + LibertyCell *drvr_cell, + const Pin *drvr_pin, + TimingArc *arc, + const Pvt *pvt, + const DcalcAnalysisPt *dcalc_ap, + const Slew from_slew, + Parasitic *drvr_parasitic, + float related_out_cap, + ArcDelayCalc *arc_delay_calc, + // Return values. + ArcDelay &gate_delay, + Slew &gate_slew); + void deleteMultiDrvrNets(); + Slew edgeFromSlew(const Vertex *from_vertex, + const RiseFall *from_rf, + const Edge *edge, + const DcalcAnalysisPt *dcalc_ap); + Slew checkEdgeClkSlew(const Vertex *from_vertex, + const RiseFall *from_rf, + const DcalcAnalysisPt *dcalc_ap); + bool bidirectDrvrSlewFromLoad(const Vertex *vertex) const; + MultiDrvrNet *multiDrvrNet(const Vertex *drvr_vertex) const; + void loadCap(const Parasitic *drvr_parasitic, + bool has_set_load, + // Return values. + float &pin_cap, + float &wire_cap) const; + float loadCap(const Pin *drvr_pin, + MultiDrvrNet *multi_drvr, + const Parasitic *drvr_parasitic, + const RiseFall *rf, + const DcalcAnalysisPt *dcalc_ap) const; + + // Observer for edge delay changes. + DelayCalcObserver *observer_; + bool delays_seeded_; + bool incremental_; + bool delays_exist_; + // Vertices with invalid -to delays. + VertexSet *invalid_delays_; + // Timing check edges with invalid delays. + EdgeSet invalid_check_edges_; + // Latch D->Q edges with invalid delays. + EdgeSet invalid_latch_edges_; + // shared by invalid_check_edges_ and invalid_latch_edges_ + std::mutex invalid_edge_lock_; + SearchPred *search_pred_; + SearchPred *search_non_latch_pred_; + SearchPred *clk_pred_; + BfsFwdIterator *iter_; + MultiDrvrNetMap multi_drvr_net_map_; + bool multi_drvr_nets_found_; + // Percentage (0.0:1.0) change in delay that causes downstream + // delays to be recomputed during incremental delay calculation. + float incremental_delay_tolerance_; + + friend class FindVertexDelays; + friend class MultiDrvrNet; }; // Abstract base class for edge delay change observer. diff --git a/include/sta/SearchClass.hh b/include/sta/SearchClass.hh index 6aea164f..52b27f77 100644 --- a/include/sta/SearchClass.hh +++ b/include/sta/SearchClass.hh @@ -59,6 +59,8 @@ class MinPulseWidthCheck; class MinPeriodCheck; class MaxSkewCheck; class CharPtrLess; +class SearchPred; +class BfsFwdIterator; // Tag compare using tag matching (tagMatch) critera. class TagMatchLess diff --git a/search/MakeTimingModel.cc b/search/MakeTimingModel.cc index b73c7b89..6c9d6895 100644 --- a/search/MakeTimingModel.cc +++ b/search/MakeTimingModel.cc @@ -31,7 +31,7 @@ #include "PortDirection.hh" #include "Corner.hh" #include "DcalcAnalysisPt.hh" -#include "dcalc/GraphDelayCalc1.hh" +#include "GraphDelayCalc.hh" #include "Sdc.hh" #include "StaState.hh" #include "Graph.hh" diff --git a/search/Sta.cc b/search/Sta.cc index b886db80..2e7500aa 100644 --- a/search/Sta.cc +++ b/search/Sta.cc @@ -44,7 +44,7 @@ #include "parasitics/ReportParasiticAnnotation.hh" #include "DelayCalc.hh" #include "ArcDelayCalc.hh" -#include "dcalc/GraphDelayCalc1.hh" +#include "GraphDelayCalc.hh" #include "sdf/SdfWriter.hh" #include "Levelize.hh" #include "Sim.hh" @@ -414,7 +414,7 @@ Sta::makeArcDelayCalc() void Sta::makeGraphDelayCalc() { - graph_delay_calc_ = new GraphDelayCalc1(this); + graph_delay_calc_ = new GraphDelayCalc(this); } void From 2bc81c9bdafcb93a41070dd6ec57a166effb2fad Mon Sep 17 00:00:00 2001 From: James Cherry Date: Sun, 12 Nov 2023 18:39:02 -0700 Subject: [PATCH 13/19] GraphDelayCalc::initLoadSlews Signed-off-by: James Cherry --- dcalc/GraphDelayCalc.cc | 47 +++++++++++++++++++++-------------- include/sta/GraphDelayCalc.hh | 5 ++-- 2 files changed, 31 insertions(+), 21 deletions(-) diff --git a/dcalc/GraphDelayCalc.cc b/dcalc/GraphDelayCalc.cc index 3af4a330..beee3dfb 100644 --- a/dcalc/GraphDelayCalc.cc +++ b/dcalc/GraphDelayCalc.cc @@ -903,25 +903,45 @@ GraphDelayCalc::findDriverDelays(Vertex *drvr_vertex, if (multi_drvr) { Vertex *dcalc_drvr = multi_drvr->dcalcDrvr(); if (drvr_vertex == dcalc_drvr) { - bool init_load_slews = true; + initLoadSlews(drvr_vertex); for (Vertex *drvr_vertex : *multi_drvr->drvrs()) { // Only init load slews once so previous driver dcalc results // aren't clobbered. - delay_changed |= findDriverDelays1(drvr_vertex, init_load_slews, - multi_drvr, arc_delay_calc); - init_load_slews = false; + delay_changed |= findDriverDelays1(drvr_vertex, multi_drvr, arc_delay_calc); } } } - else - delay_changed = findDriverDelays1(drvr_vertex, true, nullptr, arc_delay_calc); + else { + initLoadSlews(drvr_vertex); + delay_changed = findDriverDelays1(drvr_vertex, nullptr, arc_delay_calc); + } arc_delay_calc->finishDrvrPin(); return delay_changed; } +void +GraphDelayCalc::initLoadSlews(Vertex *drvr_vertex) +{ + VertexOutEdgeIterator edge_iter(drvr_vertex, graph_); + while (edge_iter.hasNext()) { + Edge *wire_edge = edge_iter.next(); + if (wire_edge->isWire()) { + Vertex *load_vertex = wire_edge->to(graph_); + for (auto dcalc_ap : corners_->dcalcAnalysisPts()) { + const MinMax *slew_min_max = dcalc_ap->slewMinMax(); + Slew slew_init_value(slew_min_max->initValue()); + DcalcAPIndex ap_index = dcalc_ap->index(); + for (auto rf : RiseFall::range()) { + if (!load_vertex->slewAnnotated(rf, slew_min_max)) + graph_->setSlew(load_vertex, rf, ap_index, slew_init_value); + } + } + } + } +} + bool GraphDelayCalc::findDriverDelays1(Vertex *drvr_vertex, - bool init_load_slews, MultiDrvrNet *multi_drvr, ArcDelayCalc *arc_delay_calc) { @@ -929,7 +949,7 @@ GraphDelayCalc::findDriverDelays1(Vertex *drvr_vertex, Instance *drvr_inst = network_->instance(drvr_pin); LibertyCell *drvr_cell = network_->libertyCell(drvr_inst); initSlew(drvr_vertex); - initWireDelays(drvr_vertex, init_load_slews); + initWireDelays(drvr_vertex); bool delay_changed = false; bool has_delays = false; VertexInEdgeIterator edge_iter(drvr_vertex, graph_); @@ -1200,29 +1220,20 @@ GraphDelayCalc::zeroSlewAndWireDelays(Vertex *drvr_vertex) } } -// Init wire delays and load slews. void -GraphDelayCalc::initWireDelays(Vertex *drvr_vertex, - bool init_load_slews) +GraphDelayCalc::initWireDelays(Vertex *drvr_vertex) { VertexOutEdgeIterator edge_iter(drvr_vertex, graph_); while (edge_iter.hasNext()) { Edge *wire_edge = edge_iter.next(); if (wire_edge->isWire()) { - Vertex *load_vertex = wire_edge->to(graph_); for (auto dcalc_ap : corners_->dcalcAnalysisPts()) { const MinMax *delay_min_max = dcalc_ap->delayMinMax(); - const MinMax *slew_min_max = dcalc_ap->slewMinMax(); Delay delay_init_value(delay_min_max->initValue()); - Slew slew_init_value(slew_min_max->initValue()); DcalcAPIndex ap_index = dcalc_ap->index(); for (auto rf : RiseFall::range()) { if (!graph_->wireDelayAnnotated(wire_edge, rf, ap_index)) graph_->setWireArcDelay(wire_edge, rf, ap_index, delay_init_value); - // Init load vertex slew. - if (init_load_slews - && !load_vertex->slewAnnotated(rf, slew_min_max)) - graph_->setSlew(load_vertex, rf, ap_index, slew_init_value); } } } diff --git a/include/sta/GraphDelayCalc.hh b/include/sta/GraphDelayCalc.hh index d6aa9526..67dfc15e 100644 --- a/include/sta/GraphDelayCalc.hh +++ b/include/sta/GraphDelayCalc.hh @@ -159,9 +159,9 @@ protected: bool findDriverDelays(Vertex *drvr_vertex, ArcDelayCalc *arc_delay_calc); bool findDriverDelays1(Vertex *drvr_vertex, - bool init_load_slews, MultiDrvrNet *multi_drvr, ArcDelayCalc *arc_delay_calc); + void initLoadSlews(Vertex *drvr_vertex); bool findDriverEdgeDelays(LibertyCell *drvr_cell, Instance *drvr_inst, const Pin *drvr_pin, @@ -169,8 +169,7 @@ protected: MultiDrvrNet *multi_drvr, Edge *edge, ArcDelayCalc *arc_delay_calc); - void initWireDelays(Vertex *drvr_vertex, - bool init_load_slews); + void initWireDelays(Vertex *drvr_vertex); void initRootSlews(Vertex *vertex); void zeroSlewAndWireDelays(Vertex *drvr_vertex); void findVertexDelay(Vertex *vertex, From f1050e641ca5275eb69f5cceda1c60aeb28a6d62 Mon Sep 17 00:00:00 2001 From: James Cherry Date: Fri, 17 Nov 2023 17:43:39 -0700 Subject: [PATCH 14/19] ssta compile Signed-off-by: James Cherry --- include/sta/SearchClass.hh | 2 +- liberty/Liberty.cc | 2 +- search/MakeTimingModel.cc | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/include/sta/SearchClass.hh b/include/sta/SearchClass.hh index 52b27f77..17d67618 100644 --- a/include/sta/SearchClass.hh +++ b/include/sta/SearchClass.hh @@ -116,7 +116,7 @@ typedef Vector PathVertexSeq; typedef Vector SlackSeq; typedef Delay Crpr; typedef Vector PathRefSeq; -typedef MinMaxValues ClkDelays[RiseFall::index_count][RiseFall::index_count]; +typedef MinMaxValues ClkDelays[RiseFall::index_count][RiseFall::index_count]; enum class ReportPathFormat { full, full_clock, diff --git a/liberty/Liberty.cc b/liberty/Liberty.cc index aedcb5b6..5d5e3535 100644 --- a/liberty/Liberty.cc +++ b/liberty/Liberty.cc @@ -2586,7 +2586,7 @@ LibertyPort::clockTreePathDelays() const MinMax *min_max = (role == TimingRole::clockTreePathMin()) ? MinMax::min() : MinMax::max(); - delays.setValue(rf, min_max, delay); + delays.setValue(rf, min_max, delayAsFloat(delay)); } } } diff --git a/search/MakeTimingModel.cc b/search/MakeTimingModel.cc index 6c9d6895..37128435 100644 --- a/search/MakeTimingModel.cc +++ b/search/MakeTimingModel.cc @@ -536,11 +536,11 @@ MakeTimingModel::findClkInsertionDelays() int clk_rf_index = clk_rf->index(); float delay = min_max->initValue(); for (const int end_rf_index : RiseFall::rangeIndex()) { - float delay1; + Delay delay1; bool exists; delays[clk_rf_index][end_rf_index].value(min_max, delay1, exists); if (exists) - delay = min_max->minMax(delay, delay1); + delay = min_max->minMax(delay, delayAsFloat(delay1)); } TimingModel *model = makeGateModelScalar(delay, clk_rf); if (attrs == nullptr) From 7743f8eb0b0037cd7b639868b7a35b634ba713f0 Mon Sep 17 00:00:00 2001 From: James Cherry Date: Fri, 17 Nov 2023 18:14:41 -0700 Subject: [PATCH 15/19] MakeTimingModel leak Signed-off-by: James Cherry --- search/MakeTimingModel.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/search/MakeTimingModel.cc b/search/MakeTimingModel.cc index 37128435..3788d507 100644 --- a/search/MakeTimingModel.cc +++ b/search/MakeTimingModel.cc @@ -528,10 +528,10 @@ MakeTimingModel::findClkInsertionDelays() size_t clk_count = clks->size(); if (clk_count == 1) { for (const Clock *clk : *clks) { - TimingArcAttrsPtr attrs = nullptr; ClkDelays delays; sta_->findClkDelays(clk, delays); for (const MinMax *min_max : MinMax::range()) { + TimingArcAttrsPtr attrs = nullptr; for (const RiseFall *clk_rf : RiseFall::range()) { int clk_rf_index = clk_rf->index(); float delay = min_max->initValue(); From 19cf68c72a776ac6b39087ce45243d38d6da0d6d Mon Sep 17 00:00:00 2001 From: James Cherry Date: Fri, 17 Nov 2023 18:18:37 -0700 Subject: [PATCH 16/19] TimingRole leak Signed-off-by: James Cherry --- liberty/TimingRole.cc | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/liberty/TimingRole.cc b/liberty/TimingRole.cc index 4747a2e6..ae37ce8c 100644 --- a/liberty/TimingRole.cc +++ b/liberty/TimingRole.cc @@ -176,6 +176,11 @@ TimingRole::destroy() non_seq_setup_ = nullptr; delete non_seq_hold_; non_seq_hold_ = nullptr; + delete clock_tree_path_min_; + clock_tree_path_min_ = nullptr; + delete clock_tree_path_max_; + clock_tree_path_max_ = nullptr; + timing_roles_.clear(); } From 31369dd750befe1305e49eebb26c0d0fac032e21 Mon Sep 17 00:00:00 2001 From: James Cherry Date: Sun, 19 Nov 2023 10:04:45 -0700 Subject: [PATCH 17/19] DelayCalc reorg commit 410ed56c2c2d0d7afb0e84d0c65d5ff75234e9e3 Author: James Cherry Date: Sun Nov 19 08:44:13 2023 -0700 ArcDelayCalcBase -> DelayCalcBase Signed-off-by: James Cherry commit 1fdfebe2838c47f6c1866c8a10b14df6439506e0 Author: James Cherry Date: Sun Nov 19 08:25:36 2023 -0700 LumpedCapDelayCalc::inputPortDelay Signed-off-by: James Cherry commit 3a5e1d01aaff240b2f71d006d620ccd6a70bce6d Author: James Cherry Date: Fri Nov 17 16:32:32 2023 -0700 gateDelayInit cleanup Signed-off-by: James Cherry commit d0133319126ae4a488a7b31679fbf6507c7f6266 Author: James Cherry Date: Fri Nov 17 15:36:12 2023 -0700 mv RCDelayCalc to ArcDelayCalcBase Signed-off-by: James Cherry commit fd028e6ba5e092243a84685eb1756a8e4e4bad76 Author: James Cherry Date: Fri Nov 17 14:32:53 2023 -0700 ArcDelayCalcBase Signed-off-by: James Cherry commit 0ce9cf4c766f7419b998b40aed5af14df97249f1 Author: James Cherry Date: Fri Nov 17 10:57:41 2023 -0700 ParallelArcDelayCalc -> ParallelDelayCalc Signed-off-by: James Cherry commit 7fa7db6b252f1450fa5b546f5d33d8cb8a94d4bb Author: James Cherry Date: Fri Nov 17 08:45:01 2023 -0700 parallelGateDelay args Signed-off-by: James Cherry commit 6b85756774ce049c0f5f123f6d60ebbcd62cdd2b Author: James Cherry Date: Thu Nov 16 19:55:20 2023 -0700 TimingModel cell_ Signed-off-by: James Cherry commit e536d6b0ca0d01e2ad8bd609ad20f9a02497d8f5 Author: James Cherry Date: Thu Nov 16 18:07:11 2023 -0700 TimingModel cell_ Signed-off-by: James Cherry commit d2d622da4206e06d176e4ae741334fde8df35007 Author: James Cherry Date: Thu Nov 16 17:21:15 2023 -0700 rm drvr_cell from arc dcalc funcs Signed-off-by: James Cherry commit 522961e8f58bc1a0f0530a0a5218086280a2bcb0 Author: James Cherry Date: Thu Nov 16 16:24:34 2023 -0700 tr -> rf Signed-off-by: James Cherry commit 29aa0ed40345611b9e3a898342ecc17f6355396f Author: James Cherry Date: Thu Nov 16 13:17:44 2023 -0700 GraphDelayCalc Signed-off-by: James Cherry commit 934d9f19c52c62925b23ae9b457f14d25e818f1a Author: James Cherry Date: Thu Nov 16 12:52:55 2023 -0700 ParallelArcDelayCalc Signed-off-by: James Cherry commit d5687d9482ad0f572b017f0ef806ba8e6ff8b6fa Author: James Cherry Date: Thu Nov 16 12:16:05 2023 -0700 ParallelArcDelayCalc pvt Signed-off-by: James Cherry commit 0de501e5bf2329364b572d1360c18d5aedf3b841 Author: James Cherry Date: Thu Nov 16 10:46:22 2023 -0700 ParallelArcDelayCalc::findMultiDrvrGateDelay Signed-off-by: James Cherry commit d7457b9e335ed5fa583798e0512914aab6524fcc Author: James Cherry Date: Thu Nov 16 10:19:01 2023 -0700 mv multi_drvr_slew_factor_ to ParallelArcDelayCalc Signed-off-by: James Cherry commit afec4daa2ab6dd61a2450f1ac8a8cad1ef015a29 Author: James Cherry Date: Thu Nov 16 08:02:40 2023 -0700 MultiDrvrNet::net_caps vector Signed-off-by: James Cherry commit b450b3a35616ffc8d85610158a91c5d9483b6958 Author: James Cherry Date: Thu Nov 16 07:46:43 2023 -0700 sic Signed-off-by: James Cherry commit 65767403b3b2ab4e6f7552625accf9aa4766628a Author: James Cherry Date: Tue Nov 14 17:49:22 2023 -0700 Sta::connectedCap simplify Signed-off-by: James Cherry commit 85bdb8f3362413e7b05f49447a0383140cbb924f Author: James Cherry Date: Tue Nov 14 16:43:38 2023 -0700 ParallelArcDelayCalc Signed-off-by: James Cherry commit 4feea3ba2277d53697b644d79832e309ce98058a Author: James Cherry Date: Tue Nov 14 15:10:18 2023 -0700 mv parallel dcalc to arc delay calc Signed-off-by: James Cherry commit 915ed28a2c05acce6569c7933366ef94da8bfaeb Author: James Cherry Date: Mon Nov 13 17:47:14 2023 -0700 rm MultiDrvrNet::delays_valid_ Signed-off-by: James Cherry commit 2384eb4e5bdca1410c4bf5e23f35bfb49f013e74 Author: James Cherry Date: Mon Nov 13 16:02:57 2023 -0700 mkae MultiDrvrNets on the fly Signed-off-by: James Cherry Signed-off-by: James Cherry --- CMakeLists.txt | 4 +- dcalc/ArnoldiDelayCalc.cc | 51 +- dcalc/DelayCalcBase.cc | 132 +++++ dcalc/{RCDelayCalc.hh => DelayCalcBase.hh} | 26 +- dcalc/DmpCeff.cc | 40 +- dcalc/DmpCeff.hh | 13 +- dcalc/DmpDelayCalc.cc | 52 +- dcalc/GraphDelayCalc.cc | 641 ++++++--------------- dcalc/LumpedCapDelayCalc.cc | 112 +--- dcalc/LumpedCapDelayCalc.hh | 42 +- dcalc/ParallelDelayCalc.cc | 171 ++++++ dcalc/ParallelDelayCalc.hh | 68 +++ dcalc/RCDelayCalc.cc | 74 --- dcalc/SlewDegradeDelayCalc.cc | 65 +-- dcalc/SlewDegradeDelayCalc.hh | 5 +- dcalc/UnitDelayCalc.cc | 38 +- dcalc/UnitDelayCalc.hh | 30 +- graph/Graph.cc | 6 +- include/sta/ArcDelayCalc.hh | 52 +- include/sta/Graph.hh | 7 +- include/sta/GraphDelayCalc.hh | 99 ++-- include/sta/Liberty.hh | 12 +- include/sta/LinearModel.hh | 22 +- include/sta/Sdc.hh | 3 +- include/sta/TableModel.hh | 51 +- include/sta/TimingModel.hh | 23 +- liberty/InternalPower.cc | 8 +- liberty/Liberty.cc | 71 ++- liberty/LibertyReader.cc | 30 +- liberty/LibertyReaderPvt.hh | 7 +- liberty/LinearModel.cc | 27 +- liberty/TableModel.cc | 187 +++--- liberty/TimingArc.cc | 10 +- liberty/TimingModel.cc | 31 + sdc/Sdc.cc | 2 +- search/MakeTimingModel.cc | 15 +- search/Sta.cc | 13 +- 37 files changed, 1070 insertions(+), 1170 deletions(-) create mode 100644 dcalc/DelayCalcBase.cc rename dcalc/{RCDelayCalc.hh => DelayCalcBase.hh} (58%) create mode 100644 dcalc/ParallelDelayCalc.cc create mode 100644 dcalc/ParallelDelayCalc.hh delete mode 100644 dcalc/RCDelayCalc.cc create mode 100644 liberty/TimingModel.cc diff --git a/CMakeLists.txt b/CMakeLists.txt index 2c2f1615..25573174 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -66,12 +66,13 @@ set(STA_SOURCE dcalc/ArnoldiReduce.cc dcalc/DcalcAnalysisPt.cc dcalc/DelayCalc.cc + dcalc/DelayCalcBase.cc dcalc/DmpCeff.cc dcalc/DmpDelayCalc.cc dcalc/GraphDelayCalc.cc dcalc/LumpedCapDelayCalc.cc dcalc/NetCaps.cc - dcalc/RCDelayCalc.cc + dcalc/ParallelDelayCalc.cc dcalc/SlewDegradeDelayCalc.cc dcalc/UnitDelayCalc.cc @@ -96,6 +97,7 @@ set(STA_SOURCE liberty/Sequential.cc liberty/TableModel.cc liberty/TimingArc.cc + liberty/TimingModel.cc liberty/TimingRole.cc liberty/Units.cc liberty/Wireload.cc diff --git a/dcalc/ArnoldiDelayCalc.cc b/dcalc/ArnoldiDelayCalc.cc index b3205010..15610b41 100644 --- a/dcalc/ArnoldiDelayCalc.cc +++ b/dcalc/ArnoldiDelayCalc.cc @@ -37,7 +37,7 @@ #include "DcalcAnalysisPt.hh" #include "DelayCalc.hh" #include "ArcDelayCalc.hh" -#include "RCDelayCalc.hh" +#include "LumpedCapDelayCalc.hh" #include "GraphDelayCalc.hh" #include "Arnoldi.hh" #include "ArnoldiReduce.hh" @@ -108,7 +108,7 @@ struct delay_work //////////////////////////////////////////////////////////////// -class ArnoldiDelayCalc : public RCDelayCalc +class ArnoldiDelayCalc : public LumpedCapDelayCalc { public: ArnoldiDelayCalc(StaState *sta); @@ -123,8 +123,7 @@ public: const RiseFall *rf, const Parasitic *parasitic, const DcalcAnalysisPt *dcalc_ap) override; - void gateDelay(const LibertyCell *drvr_cell, - const TimingArc *arc, + void gateDelay(const TimingArc *arc, const Slew &in_slew, float load_cap, const Parasitic *drvr_parasitic, @@ -138,8 +137,7 @@ public: // Return values. ArcDelay &wire_delay, Slew &load_slew) override; - string reportGateDelay(const LibertyCell *drvr_cell, - const TimingArc *arc, + string reportGateDelay(const TimingArc *arc, const Slew &in_slew, float load_cap, const Parasitic *drvr_parasitic, @@ -238,7 +236,7 @@ makeArnoldiDelayCalc(StaState *sta) } ArnoldiDelayCalc::ArnoldiDelayCalc(StaState *sta) : - RCDelayCalc(sta), + LumpedCapDelayCalc(sta), reduce_(new ArnoldiReduce(sta)), delay_work_(delay_work_create()) { @@ -321,7 +319,7 @@ ArnoldiDelayCalc::inputPortDelay(const Pin *drvr_pin, const Parasitic *parasitic, const DcalcAnalysisPt *dcalc_ap) { - RCDelayCalc::inputPortDelay(drvr_pin, in_slew, rf, parasitic, dcalc_ap); + LumpedCapDelayCalc::inputPortDelay(drvr_pin, in_slew, rf, parasitic, dcalc_ap); rcmodel_ = nullptr; _delayV[0] = 0.0; _slewV[0] = in_slew; @@ -358,8 +356,7 @@ ArnoldiDelayCalc::inputPortDelay(const Pin *drvr_pin, } void -ArnoldiDelayCalc::gateDelay(const LibertyCell *drvr_cell, - const TimingArc *arc, +ArnoldiDelayCalc::gateDelay(const TimingArc *arc, const Slew &in_slew, float load_cap, const Parasitic *drvr_parasitic, @@ -372,6 +369,7 @@ ArnoldiDelayCalc::gateDelay(const LibertyCell *drvr_cell, { input_port_ = false; drvr_rf_ = arc->toEdge()->asRiseFall(); + const LibertyCell *drvr_cell = arc->from()->libertyCell(); drvr_library_ = drvr_cell->libertyLibrary(); drvr_parasitic_ = drvr_parasitic; ConcreteParasitic *drvr_cparasitic = @@ -384,9 +382,9 @@ ArnoldiDelayCalc::gateDelay(const LibertyCell *drvr_cell, related_out_cap, pvt, gate_delay, drvr_slew); else - LumpedCapDelayCalc::gateDelay(drvr_cell, arc, in_slew, load_cap, - drvr_parasitic, related_out_cap, pvt, - dcalc_ap, gate_delay, drvr_slew); + LumpedCapDelayCalc::gateDelay(arc, in_slew, load_cap, drvr_parasitic, + related_out_cap, pvt, dcalc_ap, + gate_delay, drvr_slew); drvr_slew_ = drvr_slew; multi_drvr_slew_factor_ = 1.0F; } @@ -455,8 +453,7 @@ ArnoldiDelayCalc::loadDelay(const Pin *load_pin, } string -ArnoldiDelayCalc::reportGateDelay(const LibertyCell *, - const TimingArc *, +ArnoldiDelayCalc::reportGateDelay(const TimingArc *, const Slew &, float, const Parasitic *, @@ -1313,9 +1310,7 @@ ArnoldiDelayCalc::ra_get_r(delay_work *D, c1 = ctot; ArcDelay d1; Slew s1; - tab->table->gateDelay(tab->cell, tab->pvt, tab->in_slew, - c1, tab->relcap, pocv_enabled_, - d1, s1); + tab->table->gateDelay(tab->pvt, tab->in_slew, c1, tab->relcap, pocv_enabled_, d1, s1); tlohi = slew_derate*delayAsFloat(s1); r = tlohi/(c_log*c1); if (rdelay>0.0 && r > rdelay) @@ -1337,8 +1332,7 @@ ArnoldiDelayCalc::ra_get_s(delay_work *D, double tlohi,smin,s; ArcDelay d1; Slew s1; - tab->table->gateDelay(tab->cell, tab->pvt, tab->in_slew, - c, tab->relcap, pocv_enabled_, d1, s1); + tab->table->gateDelay(tab->pvt, tab->in_slew, c, tab->relcap, pocv_enabled_, d1, 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) { @@ -1371,10 +1365,8 @@ ArnoldiDelayCalc::ra_rdelay_1(timing_table *tab, return 0.0; ArcDelay d1, d2; Slew s1, s2; - tab->table->gateDelay(tab->cell, tab->pvt, tab->in_slew, - c1, tab->relcap, pocv_enabled_, d1, s1); - tab->table->gateDelay(tab->cell, tab->pvt, tab->in_slew, - c2, tab->relcap, pocv_enabled_, d2, s2); + tab->table->gateDelay(tab->pvt, tab->in_slew, c1, tab->relcap, pocv_enabled_, d1, s1); + tab->table->gateDelay(tab->pvt, tab->in_slew, c2, tab->relcap, pocv_enabled_, d2, s2); double dt50 = delayAsFloat(d1)-delayAsFloat(d2); if (dt50 <= 0.0) return 0.0; @@ -1426,8 +1418,8 @@ ArnoldiDelayCalc::ar1_ceff_delay(delay_work *D, units_->timeUnit()->asString(s)); thix = ra_solve_for_t(p,s,vhi); tlox = ra_solve_for_t(p,s,vlo); - tab->table->gateDelay(tab->cell, tab->pvt,tab->in_slew, - ctot, tab->relcap, pocv_enabled_, df, sf); + tab->table->gateDelay(tab->pvt,tab->in_slew, ctot, tab->relcap, pocv_enabled_, + df, sf); debugPrint(debug_, "arnoldi", 1, "table slew (in_slew %s ctot %s) = %s", units_->timeUnit()->asString(tab->in_slew), units_->capacitanceUnit()->asString(ctot), @@ -1438,8 +1430,8 @@ ArnoldiDelayCalc::ar1_ceff_delay(delay_work *D, units_->timeUnit()->asString(tlox-thix)); } ceff = ctot; - tab->table->gateDelay(tab->cell, tab->pvt, tab->in_slew, - ceff, tab->relcap, pocv_enabled_, df, sf); + tab->table->gateDelay(tab->pvt, tab->in_slew, ceff, tab->relcap, pocv_enabled_, + df, sf); t50_sy = delayAsFloat(df); t50_sr = ra_solve_for_t(1.0/(r*ceff),s,0.5); @@ -1480,8 +1472,7 @@ ArnoldiDelayCalc::ar1_ceff_delay(delay_work *D, units_->timeUnit()->asString(ceff_time), units_->capacitanceUnit()->asString(ceff)); - tab->table->gateDelay(tab->cell, tab->pvt, tab->in_slew, ceff, - tab->relcap, pocv_enabled_, df, sf); + tab->table->gateDelay(tab->pvt, tab->in_slew, ceff, tab->relcap, pocv_enabled_, df, sf); t50_sy = delayAsFloat(df); t50_sr = ra_solve_for_t(1.0/(r*ceff),s,0.5); for (j=0;jn;j++) { diff --git a/dcalc/DelayCalcBase.cc b/dcalc/DelayCalcBase.cc new file mode 100644 index 00000000..c2639664 --- /dev/null +++ b/dcalc/DelayCalcBase.cc @@ -0,0 +1,132 @@ +// OpenSTA, Static Timing Analyzer +// Copyright (c) 2023, 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 "DelayCalcBase.hh" + +#include "Liberty.hh" +#include "TimingArc.hh" +#include "Network.hh" +#include "Parasitics.hh" + +namespace sta { + +DelayCalcBase::DelayCalcBase(StaState *sta) : + ArcDelayCalc(sta) +{ +} + +void +DelayCalcBase::finishDrvrPin() +{ + for (auto parasitic : unsaved_parasitics_) + parasitics_->deleteUnsavedParasitic(parasitic); + unsaved_parasitics_.clear(); + for (auto drvr_pin : reduced_parasitic_drvrs_) + parasitics_->deleteDrvrReducedParasitics(drvr_pin); + reduced_parasitic_drvrs_.clear(); +} + +void +DelayCalcBase::inputPortDelay(const Pin *, + float in_slew, + const RiseFall *rf, + const Parasitic *parasitic, + const DcalcAnalysisPt *) +{ + drvr_cell_ = nullptr; + drvr_library_ = network_->defaultLibertyLibrary(); + drvr_slew_ = in_slew; + drvr_rf_ = rf; + drvr_parasitic_ = parasitic; + input_port_ = true; +} + +void +DelayCalcBase::gateDelayInit(const TimingArc *arc, + const Slew &in_slew, + const Parasitic *drvr_parasitic) +{ + drvr_cell_ = arc->from()->libertyCell(); + drvr_library_ = drvr_cell_->libertyLibrary(); + drvr_rf_ = arc->toEdge()->asRiseFall(); + drvr_slew_ = in_slew; + drvr_parasitic_ = drvr_parasitic; + input_port_ = false; +} + +// For DSPF on an input port the elmore delay is used as the time +// constant of an exponential waveform. The delay to the logic +// threshold and slew are computed for the exponential waveform. +// Note that this uses the driver thresholds and relies on +// thresholdAdjust to convert the delay and slew to the load's thresholds. +void +DelayCalcBase::dspfWireDelaySlew(const Pin *, + float elmore, + ArcDelay &wire_delay, + Slew &load_slew) +{ + float vth = drvr_library_->inputThreshold(drvr_rf_); + float vl = drvr_library_->slewLowerThreshold(drvr_rf_); + float vh = drvr_library_->slewUpperThreshold(drvr_rf_); + float slew_derate = drvr_library_->slewDerateFromLibrary(); + wire_delay = -elmore * log(1.0 - vth); + load_slew = drvr_slew_ + elmore * log((1.0 - vl) / (1.0 - vh)) / slew_derate; +} + +void +DelayCalcBase::thresholdAdjust(const Pin *load_pin, + ArcDelay &load_delay, + Slew &load_slew) +{ + LibertyLibrary *load_library = thresholdLibrary(load_pin); + if (load_library + && drvr_library_ + && load_library != drvr_library_) { + float drvr_vth = drvr_library_->outputThreshold(drvr_rf_); + float load_vth = load_library->inputThreshold(drvr_rf_); + float drvr_slew_delta = drvr_library_->slewUpperThreshold(drvr_rf_) + - drvr_library_->slewLowerThreshold(drvr_rf_); + float load_delay_delta = + delayAsFloat(load_slew) * ((load_vth - drvr_vth) / drvr_slew_delta); + load_delay += (drvr_rf_ == RiseFall::rise()) + ? load_delay_delta + : -load_delay_delta; + float load_slew_delta = load_library->slewUpperThreshold(drvr_rf_) + - load_library->slewLowerThreshold(drvr_rf_); + float drvr_slew_derate = drvr_library_->slewDerateFromLibrary(); + float load_slew_derate = load_library->slewDerateFromLibrary(); + load_slew = load_slew * ((load_slew_delta / load_slew_derate) + / (drvr_slew_delta / drvr_slew_derate)); + } +} + +LibertyLibrary * +DelayCalcBase::thresholdLibrary(const Pin *load_pin) +{ + if (network_->isTopLevelPort(load_pin)) + // Input/output slews use the default (first read) library + // for slew thresholds. + return network_->defaultLibertyLibrary(); + else { + LibertyPort *lib_port = network_->libertyPort(load_pin); + if (lib_port) + return lib_port->libertyCell()->libertyLibrary(); + else + return network_->defaultLibertyLibrary(); + } +} + +} // namespace diff --git a/dcalc/RCDelayCalc.hh b/dcalc/DelayCalcBase.hh similarity index 58% rename from dcalc/RCDelayCalc.hh rename to dcalc/DelayCalcBase.hh index 24530959..248e8e5a 100644 --- a/dcalc/RCDelayCalc.hh +++ b/dcalc/DelayCalcBase.hh @@ -16,32 +16,48 @@ #pragma once -#include "LumpedCapDelayCalc.hh" +#include "ArcDelayCalc.hh" namespace sta { -// Base class for delay calculators with RC wire delay. -class RCDelayCalc : public LumpedCapDelayCalc +class DelayCalcBase : public ArcDelayCalc { public: - RCDelayCalc(StaState *sta); - ArcDelayCalc *copy() override; + explicit DelayCalcBase(StaState *sta); void inputPortDelay(const Pin *port_pin, float in_slew, const RiseFall *rf, const Parasitic *parasitic, const DcalcAnalysisPt *dcalc_ap) override; + void finishDrvrPin() override; protected: + void gateDelayInit(const TimingArc *arc, + const Slew &in_slew, + const Parasitic *drvr_parasitic); + // Find the liberty library to use for logic/slew thresholds. + LibertyLibrary *thresholdLibrary(const Pin *load_pin); + // Adjust load_delay and load_slew from driver thresholds to load thresholds. + void thresholdAdjust(const Pin *load_pin, + ArcDelay &load_delay, + Slew &load_slew); // Helper function for input ports driving dspf parasitic. void dspfWireDelaySlew(const Pin *load_pin, float elmore, ArcDelay &wire_delay, Slew &load_slew); + Slew drvr_slew_; const LibertyCell *drvr_cell_; + const LibertyLibrary *drvr_library_; const Parasitic *drvr_parasitic_; bool input_port_; + const RiseFall *drvr_rf_; + // Parasitics returned by findParasitic that are reduced or estimated + // that can be deleted after delay calculation for the driver pin + // is finished. + Vector unsaved_parasitics_; + Vector reduced_parasitic_drvrs_; }; } // namespace diff --git a/dcalc/DmpCeff.cc b/dcalc/DmpCeff.cc index 7ebdf5a6..0d298a78 100644 --- a/dcalc/DmpCeff.cc +++ b/dcalc/DmpCeff.cc @@ -381,7 +381,7 @@ DmpAlg::gateCapDelaySlew(double ceff, { ArcDelay model_delay; Slew model_slew; - gate_model_->gateDelay(drvr_cell_, pvt_, in_slew_, ceff, related_out_cap_, + gate_model_->gateDelay(pvt_, in_slew_, ceff, related_out_cap_, pocv_enabled_, model_delay, model_slew); delay = delayAsFloat(model_delay); slew = delayAsFloat(model_slew); @@ -1528,7 +1528,7 @@ testLuDecomp2() bool DmpCeffDelayCalc::unsuppored_model_warned_ = false; DmpCeffDelayCalc::DmpCeffDelayCalc(StaState *sta) : - RCDelayCalc(sta), + LumpedCapDelayCalc(sta), dmp_cap_(new DmpCap(sta)), dmp_pi_(new DmpPi(sta)), dmp_zero_c2_(new DmpZeroC2(sta)), @@ -1551,12 +1551,11 @@ DmpCeffDelayCalc::inputPortDelay(const Pin *port_pin, const DcalcAnalysisPt *dcalc_ap) { dmp_alg_ = nullptr; - RCDelayCalc::inputPortDelay(port_pin, in_slew, rf, parasitic, dcalc_ap); + LumpedCapDelayCalc::inputPortDelay(port_pin, in_slew, rf, parasitic, dcalc_ap); } void -DmpCeffDelayCalc::gateDelay(const LibertyCell *drvr_cell, - const TimingArc *arc, +DmpCeffDelayCalc::gateDelay(const TimingArc *arc, const Slew &in_slew, float load_cap, const Parasitic *drvr_parasitic, @@ -1569,8 +1568,10 @@ DmpCeffDelayCalc::gateDelay(const LibertyCell *drvr_cell, { input_port_ = false; drvr_rf_ = arc->toEdge()->asRiseFall(); + const LibertyCell *drvr_cell = arc->from()->libertyCell(); drvr_library_ = drvr_cell->libertyLibrary(); drvr_parasitic_ = drvr_parasitic; + GateTimingModel *model = gateModel(arc, dcalc_ap); GateTableModel *table_model = dynamic_cast(model); if (table_model && drvr_parasitic) { @@ -1588,9 +1589,9 @@ DmpCeffDelayCalc::gateDelay(const LibertyCell *drvr_cell, drvr_slew = dmp_drvr_slew; } else { - LumpedCapDelayCalc::gateDelay(drvr_cell, arc, in_slew, load_cap, - drvr_parasitic, related_out_cap, pvt, - dcalc_ap, gate_delay, drvr_slew); + LumpedCapDelayCalc::gateDelay(arc, in_slew, load_cap, drvr_parasitic, + related_out_cap, pvt, dcalc_ap, + gate_delay, drvr_slew); if (drvr_parasitic && !unsuppored_model_warned_) { unsuppored_model_warned_ = true; @@ -1646,8 +1647,7 @@ DmpCeffDelayCalc::setCeffAlgorithm(const LibertyLibrary *drvr_library, } float -DmpCeffDelayCalc::ceff(const LibertyCell *drvr_cell, - const TimingArc *arc, +DmpCeffDelayCalc::ceff(const TimingArc *arc, const Slew &in_slew, float load_cap, const Parasitic *drvr_parasitic, @@ -1657,8 +1657,7 @@ DmpCeffDelayCalc::ceff(const LibertyCell *drvr_cell, { ArcDelay gate_delay; Slew drvr_slew; - gateDelay(drvr_cell, arc, in_slew, load_cap, - drvr_parasitic, related_out_cap, pvt, dcalc_ap, + gateDelay(arc, in_slew, load_cap, drvr_parasitic, related_out_cap, pvt, dcalc_ap, gate_delay, drvr_slew); if (dmp_alg_) return dmp_alg_->ceff(); @@ -1667,8 +1666,7 @@ DmpCeffDelayCalc::ceff(const LibertyCell *drvr_cell, } string -DmpCeffDelayCalc::reportGateDelay(const LibertyCell *drvr_cell, - const TimingArc *arc, +DmpCeffDelayCalc::reportGateDelay(const TimingArc *arc, const Slew &in_slew, float load_cap, const Parasitic *drvr_parasitic, @@ -1679,14 +1677,14 @@ DmpCeffDelayCalc::reportGateDelay(const LibertyCell *drvr_cell, { ArcDelay gate_delay; Slew drvr_slew; - gateDelay(drvr_cell, arc, in_slew, load_cap, - drvr_parasitic, related_out_cap, pvt, dcalc_ap, + gateDelay(arc, in_slew, load_cap, drvr_parasitic, related_out_cap, pvt, dcalc_ap, gate_delay, drvr_slew); GateTimingModel *model = gateModel(arc, dcalc_ap); float c_eff = 0.0; string result; if (drvr_parasitic_ && dmp_alg_) { c_eff = dmp_alg_->ceff(); + const LibertyCell *drvr_cell = arc->from()->libertyCell(); const LibertyLibrary *drvr_library = drvr_cell->libertyLibrary(); const Units *units = drvr_library->units(); const Unit *cap_unit = units->capacitanceUnit(); @@ -1707,8 +1705,8 @@ DmpCeffDelayCalc::reportGateDelay(const LibertyCell *drvr_cell, c_eff = load_cap; if (model) { float in_slew1 = delayAsFloat(in_slew); - result += model->reportGateDelay(drvr_cell, pvt, in_slew1, c_eff, - related_out_cap, pocv_enabled_, digits); + result += model->reportGateDelay(pvt, in_slew1, c_eff, related_out_cap, + pocv_enabled_, digits); } return result; } @@ -1728,10 +1726,8 @@ gateModelRd(const LibertyCell *cell, float cap2 = cap1 + 1e-15; ArcDelay d1, d2; Slew s1, s2; - gate_model->gateDelay(cell, pvt, in_slew, cap1, related_out_cap, pocv_enabled, - d1, s1); - gate_model->gateDelay(cell, pvt, in_slew, cap2, related_out_cap, pocv_enabled, - d2, s2); + gate_model->gateDelay(pvt, in_slew, cap1, related_out_cap, pocv_enabled, d1, s1); + gate_model->gateDelay(pvt, in_slew, cap2, related_out_cap, pocv_enabled, d2, s2); double vth = cell->libertyLibrary()->outputThreshold(rf); float rd = -log(vth) * abs(delayAsFloat(d1) - delayAsFloat(d2)) / (cap2 - cap1); return rd; diff --git a/dcalc/DmpCeff.hh b/dcalc/DmpCeff.hh index 5f8360d2..b79dbdc6 100644 --- a/dcalc/DmpCeff.hh +++ b/dcalc/DmpCeff.hh @@ -17,7 +17,7 @@ #pragma once #include "LibertyClass.hh" -#include "RCDelayCalc.hh" +#include "LumpedCapDelayCalc.hh" namespace sta { @@ -29,7 +29,7 @@ class GateTableModel; // Delay calculator using Dartu/Menezes/Pileggi effective capacitance // algorithm for RSPF loads. -class DmpCeffDelayCalc : public RCDelayCalc +class DmpCeffDelayCalc : public LumpedCapDelayCalc { public: DmpCeffDelayCalc(StaState *sta); @@ -39,8 +39,7 @@ public: const RiseFall *rf, const Parasitic *parasitic, const DcalcAnalysisPt *dcalc_ap); - virtual void gateDelay(const LibertyCell *drvr_cell, - const TimingArc *arc, + virtual void gateDelay(const TimingArc *arc, const Slew &in_slew, float load_cap, const Parasitic *drvr_parasitic, @@ -50,16 +49,14 @@ public: // return values ArcDelay &gate_delay, Slew &drvr_slew); - virtual float ceff(const LibertyCell *drvr_cell, - const TimingArc *arc, + virtual float ceff(const TimingArc *arc, const Slew &in_slew, float load_cap, const Parasitic *drvr_parasitic, float related_out_cap, const Pvt *pvt, const DcalcAnalysisPt *dcalc_ap); - virtual string reportGateDelay(const LibertyCell *drvr_cell, - const TimingArc *arc, + virtual string reportGateDelay(const TimingArc *arc, const Slew &in_slew, float load_cap, const Parasitic *drvr_parasitic, diff --git a/dcalc/DmpDelayCalc.cc b/dcalc/DmpDelayCalc.cc index 169b1051..ed97468a 100644 --- a/dcalc/DmpDelayCalc.cc +++ b/dcalc/DmpDelayCalc.cc @@ -35,17 +35,6 @@ class DmpCeffElmoreDelayCalc : public DmpCeffDelayCalc public: DmpCeffElmoreDelayCalc(StaState *sta); ArcDelayCalc *copy() override; - void gateDelay(const LibertyCell *drvr_cell, - const TimingArc *arc, - const Slew &in_slew, - float load_cap, - const Parasitic *drvr_parasitic, - float related_out_cap, - const Pvt *pvt, - const DcalcAnalysisPt *dcalc_ap, - // Return values. - ArcDelay &gate_, - Slew &drvr_slew) override; void loadDelay(const Pin *load_pin, ArcDelay &wire_delay, Slew &load_slew) override; @@ -68,25 +57,6 @@ DmpCeffElmoreDelayCalc::copy() return new DmpCeffElmoreDelayCalc(this); } -void -DmpCeffElmoreDelayCalc::gateDelay(const LibertyCell *drvr_cell, - const TimingArc *arc, - const Slew &in_slew, - float load_cap, - const Parasitic *drvr_parasitic, - float related_out_cap, - const Pvt *pvt, - const DcalcAnalysisPt *dcalc_ap, - // Return values. - ArcDelay &gate_delay, - Slew &drvr_slew) -{ - DmpCeffDelayCalc::gateDelay(drvr_cell, arc, in_slew, - load_cap, drvr_parasitic, related_out_cap, - pvt, dcalc_ap, - gate_delay, drvr_slew); -} - void DmpCeffElmoreDelayCalc::loadDelay(const Pin *load_pin, ArcDelay &wire_delay, @@ -127,8 +97,7 @@ public: const RiseFall *rf, const Parasitic *parasitic, const DcalcAnalysisPt *dcalc_ap) override; - void gateDelay(const LibertyCell *drvr_cell, - const TimingArc *arc, + void gateDelay(const TimingArc *arc, const Slew &in_slew, float load_cap, const Parasitic *drvr_parasitic, @@ -233,7 +202,7 @@ DmpCeffTwoPoleDelayCalc::findParasitic(const Pin *drvr_pin, cnst_min_max, parasitic_ap); // Estimated parasitics are not recorded in the "database", so - // it for deletion after the drvr pin delay calc is finished. + // save it for deletion after the drvr pin delay calc is finished. if (parasitic) unsaved_parasitics_.push_back(parasitic); } @@ -260,8 +229,7 @@ DmpCeffTwoPoleDelayCalc::inputPortDelay(const Pin *port_pin, } void -DmpCeffTwoPoleDelayCalc::gateDelay(const LibertyCell *drvr_cell, - const TimingArc *arc, +DmpCeffTwoPoleDelayCalc::gateDelay(const TimingArc *arc, const Slew &in_slew, float load_cap, const Parasitic *drvr_parasitic, @@ -272,15 +240,13 @@ DmpCeffTwoPoleDelayCalc::gateDelay(const LibertyCell *drvr_cell, ArcDelay &gate_delay, Slew &drvr_slew) { + gateDelayInit(arc, in_slew, drvr_parasitic); parasitic_is_pole_residue_ = parasitics_->isPiPoleResidue(drvr_parasitic); - const LibertyLibrary *drvr_library = drvr_cell->libertyLibrary(); - const RiseFall *rf = arc->toEdge()->asRiseFall(); - vth_ = drvr_library->outputThreshold(rf); - vl_ = drvr_library->slewLowerThreshold(rf); - vh_ = drvr_library->slewUpperThreshold(rf); - slew_derate_ = drvr_library->slewDerateFromLibrary(); - DmpCeffDelayCalc::gateDelay(drvr_cell, arc, in_slew, - load_cap, drvr_parasitic, + vth_ = drvr_library_->outputThreshold(drvr_rf_); + vl_ = drvr_library_->slewLowerThreshold(drvr_rf_); + vh_ = drvr_library_->slewUpperThreshold(drvr_rf_); + slew_derate_ = drvr_library_->slewDerateFromLibrary(); + DmpCeffDelayCalc::gateDelay(arc, in_slew, load_cap, drvr_parasitic, related_out_cap, pvt, dcalc_ap, gate_delay, drvr_slew); } diff --git a/dcalc/GraphDelayCalc.cc b/dcalc/GraphDelayCalc.cc index beee3dfb..ef8735db 100644 --- a/dcalc/GraphDelayCalc.cc +++ b/dcalc/GraphDelayCalc.cc @@ -44,170 +44,10 @@ using std::abs; static const Slew default_slew = 0.0; -typedef Set MultiDrvrNetSet; - static bool isLeafDriver(const Pin *pin, const Network *network); -// Cache parallel delay/slew values for nets with multiple drivers. -class MultiDrvrNet -{ -public: - MultiDrvrNet(VertexSet *drvrs); - ~MultiDrvrNet(); - const VertexSet *drvrs() const { return drvrs_; } - VertexSet *drvrs() { return drvrs_; } - Vertex *dcalcDrvr() const { return dcalc_drvr_; } - void setDcalcDrvr(Vertex *drvr); - void parallelDelaySlew(const RiseFall *drvr_rf, - const DcalcAnalysisPt *dcalc_ap, - ArcDelayCalc *arc_delay_calc, - GraphDelayCalc *dcalc, - // Return values. - ArcDelay ¶llel_delay, - Slew ¶llel_slew); - void netCaps(const RiseFall *rf, - const DcalcAnalysisPt *dcalc_ap, - // Return values. - float &pin_cap, - float &wire_cap, - float &fanout, - bool &has_net_load); - void findCaps(const GraphDelayCalc *dcalc, - const Sdc *sdc); - -private: - void findDelaysSlews(ArcDelayCalc *arc_delay_calc, - GraphDelayCalc *dcalc); - - // Driver that triggers delay calculation for all the drivers on the net. - Vertex *dcalc_drvr_; - VertexSet *drvrs_; - // [drvr_rf->index][dcalc_ap->index] - ArcDelay *parallel_delays_; - // [drvr_rf->index][dcalc_ap->index] - Slew *parallel_slews_; - // [drvr_rf->index][dcalc_ap->index] - NetCaps *net_caps_; - bool delays_valid_:1; -}; - -MultiDrvrNet::MultiDrvrNet(VertexSet *drvrs) : - dcalc_drvr_(nullptr), - drvrs_(drvrs), - parallel_delays_(nullptr), - parallel_slews_(nullptr), - net_caps_(nullptr), - delays_valid_(false) -{ -} - -MultiDrvrNet::~MultiDrvrNet() -{ - delete drvrs_; - if (delays_valid_) { - delete [] parallel_delays_; - delete [] parallel_slews_; - } - delete [] net_caps_; -} - -void -MultiDrvrNet::parallelDelaySlew(const RiseFall *drvr_rf, - const DcalcAnalysisPt *dcalc_ap, - ArcDelayCalc *arc_delay_calc, - GraphDelayCalc *dcalc, - // Return values. - ArcDelay ¶llel_delay, - Slew ¶llel_slew) -{ - if (!delays_valid_) { - findDelaysSlews(arc_delay_calc, dcalc); - delays_valid_ = true; - } - - int index = dcalc_ap->index() * RiseFall::index_count - + drvr_rf->index(); - parallel_delay = parallel_delays_[index]; - parallel_slew = parallel_slews_[index]; -} - -void -MultiDrvrNet::findDelaysSlews(ArcDelayCalc *arc_delay_calc, - GraphDelayCalc *dcalc) -{ - Corners *corners = dcalc->corners(); - int count = RiseFall::index_count * corners->dcalcAnalysisPtCount(); - parallel_delays_ = new ArcDelay[count]; - parallel_slews_ = new Slew[count]; - for (auto dcalc_ap : corners->dcalcAnalysisPts()) { - DcalcAPIndex ap_index = dcalc_ap->index(); - const Pvt *pvt = dcalc_ap->operatingConditions(); - for (auto drvr_rf : RiseFall::range()) { - int drvr_rf_index = drvr_rf->index(); - int index = ap_index*RiseFall::index_count+drvr_rf_index; - dcalc->findMultiDrvrGateDelay(this, drvr_rf, pvt, dcalc_ap, - arc_delay_calc, - parallel_delays_[index], - parallel_slews_[index]); - } - } -} - -void -MultiDrvrNet::netCaps(const RiseFall *drvr_rf, - const DcalcAnalysisPt *dcalc_ap, - // Return values. - float &pin_cap, - float &wire_cap, - float &fanout, - bool &has_net_load) -{ - int index = dcalc_ap->index() * RiseFall::index_count - + drvr_rf->index(); - NetCaps &net_caps = net_caps_[index]; - pin_cap = net_caps.pinCap(); - wire_cap = net_caps.wireCap(); - fanout = net_caps.fanout(); - has_net_load = net_caps.hasNetLoad(); -} - -void -MultiDrvrNet::findCaps(const GraphDelayCalc *dcalc, - const Sdc *sdc) -{ - Corners *corners = dcalc->corners(); - int count = RiseFall::index_count * corners->dcalcAnalysisPtCount(); - net_caps_ = new NetCaps[count]; - const Pin *drvr_pin = dcalc_drvr_->pin(); - for (auto dcalc_ap : corners->dcalcAnalysisPts()) { - DcalcAPIndex ap_index = dcalc_ap->index(); - const Corner *corner = dcalc_ap->corner(); - const OperatingConditions *op_cond = dcalc_ap->operatingConditions(); - const MinMax *min_max = dcalc_ap->constraintMinMax(); - for (auto drvr_rf : RiseFall::range()) { - int drvr_rf_index = drvr_rf->index(); - int index = ap_index * RiseFall::index_count + drvr_rf_index; - NetCaps &net_caps = net_caps_[index]; - float pin_cap, wire_cap, fanout; - bool has_net_load; - // Find pin and external pin/wire capacitance. - sdc->connectedCap(drvr_pin, drvr_rf, op_cond, corner, min_max, - pin_cap, wire_cap, fanout, has_net_load); - net_caps.init(pin_cap, wire_cap, fanout, has_net_load); - } - } -} - -void -MultiDrvrNet::setDcalcDrvr(Vertex *drvr) -{ - dcalc_drvr_ = drvr; -} - -//////////////////////////////////////////////////////////////// - GraphDelayCalc::GraphDelayCalc(StaState *sta) : StaState(sta), observer_(nullptr), @@ -238,7 +78,7 @@ GraphDelayCalc::~GraphDelayCalc() void GraphDelayCalc::deleteMultiDrvrNets() { - MultiDrvrNetSet drvr_nets; + Set drvr_nets; MultiDrvrNetMap::Iterator multi_iter(multi_drvr_net_map_); while (multi_iter.hasNext()) { MultiDrvrNet *multi_drvr = multi_iter.next(); @@ -401,7 +241,6 @@ GraphDelayCalc::findDelays(Level level) debugPrint(debug_, "delay_calc", 1, "find delays to level %d", level); if (!delays_seeded_) { iter_->clear(); - ensureMultiDrvrNetsFound(); seedRootSlews(); delays_seeded_ = true; } @@ -444,107 +283,6 @@ GraphDelayCalc::seedInvalidDelays() invalid_delays_->clear(); } -class FindNetDrvrs : public PinVisitor -{ -public: - FindNetDrvrs(PinSet &drvr_pins, - const Network *network, - const Graph *graph); - virtual void operator()(const Pin *pin); - -protected: - PinSet &drvr_pins_; - const Network *network_; - const Graph *graph_; -}; - -FindNetDrvrs::FindNetDrvrs(PinSet &drvr_pins, - const Network *network, - const Graph *graph) : - drvr_pins_(drvr_pins), - network_(network), - graph_(graph) -{ -} - -void -FindNetDrvrs::operator()(const Pin *pin) -{ - Vertex *vertex = graph_->pinDrvrVertex(pin); - if (isLeafDriver(pin, network_) - && !(vertex && vertex->isRoot())) - drvr_pins_.insert(pin); -} - -void -GraphDelayCalc::ensureMultiDrvrNetsFound() -{ - if (!multi_drvr_nets_found_) { - LeafInstanceIterator *inst_iter = network_->leafInstanceIterator(); - while (inst_iter->hasNext()) { - Instance *inst = inst_iter->next(); - InstancePinIterator *pin_iter = network_->pinIterator(inst); - while (pin_iter->hasNext()) { - Pin *pin = pin_iter->next(); - Vertex *drvr_vertex = graph_->pinDrvrVertex(pin); - if (network_->isDriver(pin) - && !multi_drvr_net_map_.hasKey(drvr_vertex)) { - PinSet drvr_pins(network_); - FindNetDrvrs visitor(drvr_pins, network_, graph_); - network_->visitConnectedPins(pin, visitor); - if (drvr_pins.size() > 1) - makeMultiDrvrNet(drvr_pins); - } - } - delete pin_iter; - } - delete inst_iter; - multi_drvr_nets_found_ = true; - } -} - -void -GraphDelayCalc::makeMultiDrvrNet(PinSet &drvr_pins) -{ - debugPrint(debug_, "delay_calc", 3, "multi-driver net"); - VertexSet *drvr_vertices = new VertexSet(graph_); - MultiDrvrNet *multi_drvr = new MultiDrvrNet(drvr_vertices); - Level max_drvr_level = 0; - Vertex *max_drvr = nullptr; - PinSet::Iterator pin_iter(drvr_pins); - while (pin_iter.hasNext()) { - const Pin *pin = pin_iter.next(); - Vertex *drvr_vertex = graph_->pinDrvrVertex(pin); - debugPrint(debug_, "delay_calc", 3, " %s", - network_->pathName(pin)); - multi_drvr_net_map_[drvr_vertex] = multi_drvr; - drvr_vertices->insert(drvr_vertex); - Level drvr_level = drvr_vertex->level(); - if (max_drvr == nullptr - || drvr_level > max_drvr_level) { - max_drvr = drvr_vertex; - max_drvr_level = drvr_level; - } - } - multi_drvr->setDcalcDrvr(max_drvr); - multi_drvr->findCaps(this, sdc_); -} - -static bool -isLeafDriver(const Pin *pin, - const Network *network) -{ - PortDirection *dir = network->direction(pin); - const Instance *inst = network->instance(pin); - return network->isLeaf(inst) && dir->isAnyOutput(); -} - -MultiDrvrNet * -GraphDelayCalc::multiDrvrNet(const Vertex *drvr_vertex) const -{ - return multi_drvr_net_map_.findKey(drvr_vertex); -} - void GraphDelayCalc::seedRootSlews() { @@ -754,8 +492,7 @@ GraphDelayCalc::findInputDriverDelay(const LibertyCell *drvr_cell, for (TimingArc *arc : arc_set->arcs()) { if (arc->toEdge()->asRiseFall() == rf) { float from_slew = from_slews[arc->fromEdge()->index()]; - findInputArcDelay(drvr_cell, drvr_pin, drvr_vertex, - arc, from_slew, dcalc_ap); + findInputArcDelay(drvr_pin, drvr_vertex, arc, from_slew, dcalc_ap); } } } @@ -765,8 +502,7 @@ GraphDelayCalc::findInputDriverDelay(const LibertyCell *drvr_cell, // delay minus the intrinsic delay. Driving cell delays are annotated // to the wire arcs from the input port pin to the load pins. void -GraphDelayCalc::findInputArcDelay(const LibertyCell *drvr_cell, - const Pin *drvr_pin, +GraphDelayCalc::findInputArcDelay(const Pin *drvr_pin, Vertex *drvr_vertex, const TimingArc *arc, float from_slew, @@ -788,15 +524,13 @@ GraphDelayCalc::findInputArcDelay(const LibertyCell *drvr_cell, ArcDelay intrinsic_delay; Slew intrinsic_slew; - arc_delay_calc_->gateDelay(drvr_cell, arc, Slew(from_slew), - 0.0, 0, 0.0, pvt, dcalc_ap, + arc_delay_calc_->gateDelay(arc, Slew(from_slew), 0.0, 0, 0.0, pvt, dcalc_ap, intrinsic_delay, intrinsic_slew); // For input drivers there is no instance to find a related_output_pin. ArcDelay gate_delay; Slew gate_slew; - arc_delay_calc_->gateDelay(drvr_cell, arc, - Slew(from_slew), load_cap, + arc_delay_calc_->gateDelay(arc, Slew(from_slew), load_cap, drvr_parasitic, 0.0, pvt, dcalc_ap, gate_delay, gate_slew); ArcDelay load_delay = gate_delay - intrinsic_delay; @@ -899,16 +633,15 @@ GraphDelayCalc::findDriverDelays(Vertex *drvr_vertex, ArcDelayCalc *arc_delay_calc) { bool delay_changed = false; - MultiDrvrNet *multi_drvr = multiDrvrNet(drvr_vertex); - if (multi_drvr) { + MultiDrvrNet *multi_drvr = findMultiDrvrNet(drvr_vertex); + if (multi_drvr + && multi_drvr->parallelGates(network_)) { Vertex *dcalc_drvr = multi_drvr->dcalcDrvr(); if (drvr_vertex == dcalc_drvr) { initLoadSlews(drvr_vertex); - for (Vertex *drvr_vertex : *multi_drvr->drvrs()) { - // Only init load slews once so previous driver dcalc results - // aren't clobbered. + arc_delay_calc->findParallelGateDelays(multi_drvr, this); + for (Vertex *drvr_vertex : *multi_drvr->drvrs()) delay_changed |= findDriverDelays1(drvr_vertex, multi_drvr, arc_delay_calc); - } } } else { @@ -919,6 +652,74 @@ GraphDelayCalc::findDriverDelays(Vertex *drvr_vertex, return delay_changed; } +MultiDrvrNet * +GraphDelayCalc::findMultiDrvrNet(Vertex *drvr_vertex) +{ + MultiDrvrNet *multi_drvr = multiDrvrNet(drvr_vertex); + if (multi_drvr) + return multi_drvr; + else { + const PinSet *drvrs = network_->drivers(drvr_vertex->pin()); + if (drvrs && drvrs->size() > 1) { + PinSet drvrs1(network_); + // Filter input ports and non-leaf drivers. + for (const Pin *pin : *drvrs) { + if (isLeafDriver(pin, network_)) + drvrs1.insert(pin); + } + MultiDrvrNet *multi_drvr = nullptr; + if (drvrs1.size() > 1) + multi_drvr = makeMultiDrvrNet(drvrs1); + return multi_drvr; + } + else + return nullptr; + } +} + +static bool +isLeafDriver(const Pin *pin, + const Network *network) +{ + PortDirection *dir = network->direction(pin); + const Instance *inst = network->instance(pin); + return network->isLeaf(inst) && dir->isAnyOutput(); +} + +MultiDrvrNet * +GraphDelayCalc::multiDrvrNet(const Vertex *drvr_vertex) const +{ + return multi_drvr_net_map_.findKey(drvr_vertex); +} + +MultiDrvrNet * +GraphDelayCalc::makeMultiDrvrNet(PinSet &drvr_pins) +{ + debugPrint(debug_, "delay_calc", 3, "multi-driver net"); + VertexSet *drvr_vertices = new VertexSet(graph_); + MultiDrvrNet *multi_drvr = new MultiDrvrNet(drvr_vertices); + Level max_drvr_level = 0; + Vertex *max_drvr = nullptr; + PinSet::Iterator pin_iter(drvr_pins); + while (pin_iter.hasNext()) { + const Pin *pin = pin_iter.next(); + Vertex *drvr_vertex = graph_->pinDrvrVertex(pin); + debugPrint(debug_, "delay_calc", 3, " %s", + network_->pathName(pin)); + multi_drvr_net_map_[drvr_vertex] = multi_drvr; + drvr_vertices->insert(drvr_vertex); + Level drvr_level = drvr_vertex->level(); + if (max_drvr == nullptr + || drvr_level > max_drvr_level) { + max_drvr = drvr_vertex; + max_drvr_level = drvr_level; + } + } + multi_drvr->setDcalcDrvr(max_drvr); + multi_drvr->findCaps(sdc_); + return multi_drvr; +} + void GraphDelayCalc::initLoadSlews(Vertex *drvr_vertex) { @@ -947,7 +748,6 @@ GraphDelayCalc::findDriverDelays1(Vertex *drvr_vertex, { const Pin *drvr_pin = drvr_vertex->pin(); Instance *drvr_inst = network_->instance(drvr_pin); - LibertyCell *drvr_cell = network_->libertyCell(drvr_inst); initSlew(drvr_vertex); initWireDelays(drvr_vertex); bool delay_changed = false; @@ -960,9 +760,8 @@ GraphDelayCalc::findDriverDelays1(Vertex *drvr_vertex, if (search_pred_->searchFrom(from_vertex) && search_pred_->searchThru(edge) && !edge->role()->isLatchDtoQ()) { - delay_changed |= findDriverEdgeDelays(drvr_cell, drvr_inst, drvr_pin, - drvr_vertex, multi_drvr, edge, - arc_delay_calc); + delay_changed |= findDriverEdgeDelays(drvr_inst, drvr_pin, drvr_vertex, + multi_drvr, edge, arc_delay_calc); has_delays = true; } } @@ -994,26 +793,24 @@ GraphDelayCalc::findLatchEdgeDelays(Edge *edge) Vertex *drvr_vertex = edge->to(graph_); const Pin *drvr_pin = drvr_vertex->pin(); Instance *drvr_inst = network_->instance(drvr_pin); - LibertyCell *drvr_cell = network_->libertyCell(drvr_inst); debugPrint(debug_, "delay_calc", 2, "find latch D->Q %s", sdc_network_->pathName(drvr_inst)); - bool delay_changed = findDriverEdgeDelays(drvr_cell, drvr_inst, drvr_pin, - drvr_vertex, nullptr, edge, arc_delay_calc_); + bool delay_changed = findDriverEdgeDelays(drvr_inst, drvr_pin, drvr_vertex, + nullptr, edge, arc_delay_calc_); if (delay_changed && observer_) observer_->delayChangedTo(drvr_vertex); } bool -GraphDelayCalc::findDriverEdgeDelays(LibertyCell *drvr_cell, - Instance *drvr_inst, +GraphDelayCalc::findDriverEdgeDelays(const Instance *drvr_inst, const Pin *drvr_pin, Vertex *drvr_vertex, - MultiDrvrNet *multi_drvr, + const MultiDrvrNet *multi_drvr, Edge *edge, ArcDelayCalc *arc_delay_calc) { Vertex *in_vertex = edge->from(graph_); - TimingArcSet *arc_set = edge->timingArcSet(); + const TimingArcSet *arc_set = edge->timingArcSet(); const LibertyPort *related_out_port = arc_set->relatedOut(); const Pin *related_out_pin = 0; bool delay_changed = false; @@ -1025,21 +822,16 @@ GraphDelayCalc::findDriverEdgeDelays(LibertyCell *drvr_cell, pvt = dcalc_ap->operatingConditions(); for (TimingArc *arc : arc_set->arcs()) { const RiseFall *rf = arc->toEdge()->asRiseFall(); - Parasitic *parasitic = arc_delay_calc->findParasitic(drvr_pin, rf, - dcalc_ap); + Parasitic *parasitic = arc_delay_calc->findParasitic(drvr_pin, rf, dcalc_ap); float related_out_cap = 0.0; if (related_out_pin) { Parasitic *related_out_parasitic = arc_delay_calc->findParasitic(related_out_pin, rf, dcalc_ap); - related_out_cap = loadCap(related_out_pin, - related_out_parasitic, - rf, dcalc_ap); + related_out_cap = loadCap(related_out_pin, related_out_parasitic, rf, dcalc_ap); } - delay_changed |= findArcDelay(drvr_cell, drvr_pin, drvr_vertex, - multi_drvr, arc, parasitic, - related_out_cap, - in_vertex, edge, pvt, dcalc_ap, - arc_delay_calc); + delay_changed |= findArcDelay(drvr_pin, drvr_vertex, arc, parasitic, + related_out_cap, in_vertex, edge, pvt, dcalc_ap, + multi_drvr, arc_delay_calc); } } @@ -1059,7 +851,7 @@ GraphDelayCalc::loadCap(const Pin *drvr_pin, for (auto drvr_rf : RiseFall::range()) { Parasitic *drvr_parasitic = arc_delay_calc_->findParasitic(drvr_pin, drvr_rf, dcalc_ap); - float cap = loadCap(drvr_pin, nullptr, drvr_parasitic, drvr_rf, dcalc_ap); + float cap = loadCap(drvr_pin, drvr_parasitic, drvr_rf, dcalc_ap, nullptr); arc_delay_calc_->finishDrvrPin(); if (min_max->compare(cap, load_cap)) load_cap = cap; @@ -1074,7 +866,7 @@ GraphDelayCalc::loadCap(const Pin *drvr_pin, { Parasitic *drvr_parasitic = arc_delay_calc_->findParasitic(drvr_pin, drvr_rf, dcalc_ap); - float cap = loadCap(drvr_pin, nullptr, drvr_parasitic, drvr_rf, dcalc_ap); + float cap = loadCap(drvr_pin, drvr_parasitic, drvr_rf, dcalc_ap, nullptr); return cap; } @@ -1084,15 +876,15 @@ GraphDelayCalc::loadCap(const Pin *drvr_pin, const RiseFall *rf, const DcalcAnalysisPt *dcalc_ap) const { - return loadCap(drvr_pin, nullptr, drvr_parasitic, rf, dcalc_ap); + return loadCap(drvr_pin, drvr_parasitic, rf, dcalc_ap, nullptr); } float GraphDelayCalc::loadCap(const Pin *drvr_pin, - MultiDrvrNet *multi_drvr, const Parasitic *drvr_parasitic, const RiseFall *rf, - const DcalcAnalysisPt *dcalc_ap) const + const DcalcAnalysisPt *dcalc_ap, + const MultiDrvrNet *multi_drvr) const { float pin_cap, wire_cap; bool has_net_load; @@ -1241,21 +1033,20 @@ GraphDelayCalc::initWireDelays(Vertex *drvr_vertex) } // Call the arc delay calculator to find the delay thru a single gate -// input to output timing arc, the wire delays from the gate output to -// each load pin, and the slew at each load pin. Annotate the graph +// input to output timing arc, The wire delays from the gate output to +// each load pin, and the slew at each load pin. Annotate the graph // with the results. bool -GraphDelayCalc::findArcDelay(LibertyCell *drvr_cell, - const Pin *drvr_pin, +GraphDelayCalc::findArcDelay(const Pin *drvr_pin, Vertex *drvr_vertex, - MultiDrvrNet *multi_drvr, - TimingArc *arc, - Parasitic *drvr_parasitic, + const TimingArc *arc, + const Parasitic *drvr_parasitic, float related_out_cap, Vertex *from_vertex, Edge *edge, const Pvt *pvt, const DcalcAnalysisPt *dcalc_ap, + const MultiDrvrNet *multi_drvr, ArcDelayCalc *arc_delay_calc) { bool delay_changed = false; @@ -1277,21 +1068,16 @@ GraphDelayCalc::findArcDelay(LibertyCell *drvr_cell, const Slew from_slew = edgeFromSlew(from_vertex, from_rf, edge, dcalc_ap); ArcDelay gate_delay; Slew gate_slew; + float load_cap = loadCap(drvr_pin, drvr_parasitic, drvr_rf, dcalc_ap, multi_drvr); if (multi_drvr - && arc->to()->direction()->isOutput()) - parallelGateDelay(multi_drvr, drvr_cell, drvr_pin, arc, - pvt, dcalc_ap, from_slew, drvr_parasitic, - related_out_cap, - arc_delay_calc, - gate_delay, gate_slew); - else { - float load_cap = loadCap(drvr_pin, multi_drvr, drvr_parasitic, - drvr_rf, dcalc_ap); - arc_delay_calc->gateDelay(drvr_cell, arc, - from_slew, load_cap, drvr_parasitic, + && multi_drvr->parallelGates(network_)) + arc_delay_calc->parallelGateDelay(drvr_pin, arc, from_slew, load_cap, + drvr_parasitic, related_out_cap, pvt, dcalc_ap, + gate_delay, gate_slew); + else + arc_delay_calc->gateDelay(arc, from_slew, load_cap, drvr_parasitic, related_out_cap, pvt, dcalc_ap, gate_delay, gate_slew); - } debugPrint(debug_, "delay_calc", 3, " gate delay = %s slew = %s", delayAsString(gate_delay, this), @@ -1320,115 +1106,6 @@ GraphDelayCalc::findArcDelay(LibertyCell *drvr_cell, return delay_changed; } -void -GraphDelayCalc::parallelGateDelay(MultiDrvrNet *multi_drvr, - LibertyCell *drvr_cell, - const Pin *drvr_pin, - TimingArc *arc, - const Pvt *pvt, - const DcalcAnalysisPt *dcalc_ap, - const Slew from_slew, - Parasitic *drvr_parasitic, - float related_out_cap, - ArcDelayCalc *arc_delay_calc, - // Return values. - ArcDelay &gate_delay, - Slew &gate_slew) -{ - ArcDelay intrinsic_delay; - Slew intrinsic_slew; - arc_delay_calc->gateDelay(drvr_cell, arc, from_slew, - 0.0, 0, 0.0, pvt, dcalc_ap, - intrinsic_delay, intrinsic_slew); - ArcDelay parallel_delay; - Slew parallel_slew; - const RiseFall *drvr_rf = arc->toEdge()->asRiseFall(); - multi_drvr->parallelDelaySlew(drvr_rf, dcalc_ap, arc_delay_calc, this, - parallel_delay, parallel_slew); - - gate_delay = parallel_delay + intrinsic_delay; - gate_slew = parallel_slew; - - float load_cap = loadCap(drvr_pin, multi_drvr, drvr_parasitic, - drvr_rf, dcalc_ap); - Delay gate_delay1; - Slew gate_slew1; - arc_delay_calc->gateDelay(drvr_cell, arc, - from_slew, load_cap, drvr_parasitic, - related_out_cap, pvt, dcalc_ap, - gate_delay1, gate_slew1); - float factor = delayRatio(gate_slew, gate_slew1); - arc_delay_calc->setMultiDrvrSlewFactor(factor); -} - -void -GraphDelayCalc::findMultiDrvrGateDelay(MultiDrvrNet *multi_drvr, - const RiseFall *drvr_rf, - const Pvt *pvt, - const DcalcAnalysisPt *dcalc_ap, - ArcDelayCalc *arc_delay_calc, - // Return values. - ArcDelay ¶llel_delay, - Slew ¶llel_slew) -{ - ArcDelay delay_sum = 1.0; - Slew slew_sum = 1.0; - for (Vertex *drvr_vertex1 : *multi_drvr->drvrs()) { - Pin *drvr_pin1 = drvr_vertex1->pin(); - Instance *drvr_inst1 = network_->instance(drvr_pin1); - LibertyCell *drvr_cell1 = network_->libertyCell(drvr_inst1); - if (network_->isDriver(drvr_pin1)) { - VertexInEdgeIterator edge_iter(drvr_vertex1, graph_); - while (edge_iter.hasNext()) { - Edge *edge1 = edge_iter.next(); - TimingArcSet *arc_set1 = edge1->timingArcSet(); - const LibertyPort *related_out_port = arc_set1->relatedOut(); - for (TimingArc *arc1 : arc_set1->arcs()) { - RiseFall *drvr_rf1 = arc1->toEdge()->asRiseFall(); - if (drvr_rf1 == drvr_rf) { - Vertex *from_vertex1 = edge1->from(graph_); - RiseFall *from_rf1 = arc1->fromEdge()->asRiseFall(); - Slew from_slew1 = edgeFromSlew(from_vertex1, from_rf1, edge1, dcalc_ap); - ArcDelay intrinsic_delay1; - Slew intrinsic_slew1; - arc_delay_calc->gateDelay(drvr_cell1, arc1, from_slew1, - 0.0, 0, 0.0, pvt, dcalc_ap, - intrinsic_delay1, intrinsic_slew1); - Parasitic *parasitic1 = - arc_delay_calc->findParasitic(drvr_pin1, drvr_rf1, dcalc_ap); - const Pin *related_out_pin1 = 0; - float related_out_cap1 = 0.0; - if (related_out_port) { - Instance *inst1 = network_->instance(drvr_pin1); - related_out_pin1 = network_->findPin(inst1, related_out_port); - if (related_out_pin1) { - Parasitic *related_out_parasitic1 = - arc_delay_calc->findParasitic(related_out_pin1, drvr_rf, - dcalc_ap); - related_out_cap1 = loadCap(related_out_pin1, - related_out_parasitic1, - drvr_rf, dcalc_ap); - } - } - float load_cap1 = loadCap(drvr_pin1, parasitic1, - drvr_rf, dcalc_ap); - ArcDelay gate_delay1; - Slew gate_slew1; - arc_delay_calc->gateDelay(drvr_cell1, arc1, - from_slew1, load_cap1, parasitic1, - related_out_cap1, pvt, dcalc_ap, - gate_delay1, gate_slew1); - delay_sum += 1.0F / (gate_delay1 - intrinsic_delay1); - slew_sum += 1.0F / gate_slew1; - } - } - } - } - } - parallel_delay = 1.0F / delay_sum; - parallel_slew = 1.0F / slew_sum; -} - // Use clock slew for register/latch clk->q edges. Slew GraphDelayCalc::edgeFromSlew(const Vertex *from_vertex, @@ -1517,7 +1194,6 @@ GraphDelayCalc::findCheckEdgeDelays(Edge *edge, TimingArcSet *arc_set = edge->timingArcSet(); const Pin *to_pin = to_vertex->pin(); Instance *inst = network_->instance(to_pin); - const LibertyCell *cell = network_->libertyCell(inst); debugPrint(debug_, "delay_calc", 2, "find check %s %s -> %s", sdc_network_->pathName(inst), network_->portName(from_vertex->pin()), @@ -1561,11 +1237,8 @@ GraphDelayCalc::findCheckEdgeDelays(Edge *edge, to_rf, dcalc_ap); } ArcDelay check_delay; - arc_delay_calc->checkDelay(cell, arc, - from_slew, to_slew, - related_out_cap, - pvt, dcalc_ap, - check_delay); + arc_delay_calc->checkDelay(arc, from_slew, to_slew, related_out_cap, + pvt, dcalc_ap, check_delay); debugPrint(debug_, "delay_calc", 3, " check_delay = %s", delayAsString(check_delay, this)); @@ -1604,8 +1277,7 @@ GraphDelayCalc::ceff(Edge *edge, 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(); + const TimingArcSet *arc_set = edge->timingArcSet(); float ceff = 0.0; const Pvt *pvt = sdc_->pvt(inst, dcalc_ap->constraintMinMax()); if (pvt == nullptr) @@ -1628,8 +1300,7 @@ GraphDelayCalc::ceff(Edge *edge, dcalc_ap); const Slew &from_slew = edgeFromSlew(from_vertex, from_rf, edge, dcalc_ap); float load_cap = loadCap(to_pin, to_parasitic, to_rf, dcalc_ap); - ceff = arc_delay_calc_->ceff(cell, arc, - from_slew, load_cap, to_parasitic, + ceff = arc_delay_calc_->ceff(arc, from_slew, load_cap, to_parasitic, related_out_cap, pvt, dcalc_ap); arc_delay_calc_->finishDrvrPin(); } @@ -1639,8 +1310,8 @@ GraphDelayCalc::ceff(Edge *edge, //////////////////////////////////////////////////////////////// string -GraphDelayCalc::reportDelayCalc(Edge *edge, - TimingArc *arc, +GraphDelayCalc::reportDelayCalc(const Edge *edge, + const TimingArc *arc, const Corner *corner, const MinMax *min_max, int digits) @@ -1649,9 +1320,8 @@ GraphDelayCalc::reportDelayCalc(Edge *edge, Vertex *to_vertex = edge->to(graph_); Pin *to_pin = to_vertex->pin(); TimingRole *role = arc->role(); - Instance *inst = network_->instance(to_pin); - LibertyCell *cell = network_->libertyCell(inst); - TimingArcSet *arc_set = edge->timingArcSet(); + const Instance *inst = network_->instance(to_pin); + const TimingArcSet *arc_set = edge->timingArcSet(); string result; DcalcAnalysisPt *dcalc_ap = corner->findDcalcAnalysisPt(min_max); const Pvt *pvt = sdc_->pvt(inst, dcalc_ap->constraintMinMax()); @@ -1677,18 +1347,16 @@ GraphDelayCalc::reportDelayCalc(Edge *edge, const Slew &to_slew = graph_->slew(to_vertex, to_rf, slew_index); bool from_ideal_clk = clk_network_->isIdealClock(from_vertex->pin()); const char *from_slew_annotation = from_ideal_clk ? " (ideal clock)" : nullptr; - result = arc_delay_calc_->reportCheckDelay(cell, arc, from_slew, - from_slew_annotation, to_slew, - related_out_cap, pvt, dcalc_ap, - digits); + result = arc_delay_calc_->reportCheckDelay(arc, from_slew, from_slew_annotation, + to_slew, related_out_cap, pvt, + dcalc_ap, digits); } else { Parasitic *to_parasitic = arc_delay_calc_->findParasitic(to_pin, to_rf, dcalc_ap); const Slew &from_slew = edgeFromSlew(from_vertex, from_rf, edge, dcalc_ap); float load_cap = loadCap(to_pin, to_parasitic, to_rf, dcalc_ap); - result = arc_delay_calc_->reportGateDelay(cell, arc, - from_slew, load_cap, to_parasitic, + result = arc_delay_calc_->reportGateDelay(arc, from_slew, load_cap, to_parasitic, related_out_cap, pvt, dcalc_ap, digits); } arc_delay_calc_->finishDrvrPin(); @@ -1754,4 +1422,73 @@ GraphDelayCalc::minPeriod(const Pin *pin, } } +//////////////////////////////////////////////////////////////// + +MultiDrvrNet::MultiDrvrNet(VertexSet *drvrs) : + dcalc_drvr_(nullptr), + drvrs_(drvrs) +{ +} + +MultiDrvrNet::~MultiDrvrNet() +{ + delete drvrs_; +} + +void +MultiDrvrNet::netCaps(const RiseFall *drvr_rf, + const DcalcAnalysisPt *dcalc_ap, + // Return values. + float &pin_cap, + float &wire_cap, + float &fanout, + bool &has_net_load) const +{ + int index = dcalc_ap->index() * RiseFall::index_count + + drvr_rf->index(); + const NetCaps &net_caps = net_caps_[index]; + pin_cap = net_caps.pinCap(); + wire_cap = net_caps.wireCap(); + fanout = net_caps.fanout(); + has_net_load = net_caps.hasNetLoad(); +} + +void +MultiDrvrNet::findCaps(const Sdc *sdc) +{ + Corners *corners = sdc->corners(); + int count = RiseFall::index_count * corners->dcalcAnalysisPtCount(); + net_caps_.resize(count); + const Pin *drvr_pin = dcalc_drvr_->pin(); + for (auto dcalc_ap : corners->dcalcAnalysisPts()) { + DcalcAPIndex ap_index = dcalc_ap->index(); + const Corner *corner = dcalc_ap->corner(); + const OperatingConditions *op_cond = dcalc_ap->operatingConditions(); + const MinMax *min_max = dcalc_ap->constraintMinMax(); + for (auto drvr_rf : RiseFall::range()) { + int drvr_rf_index = drvr_rf->index(); + int index = ap_index * RiseFall::index_count + drvr_rf_index; + NetCaps &net_caps = net_caps_[index]; + float pin_cap, wire_cap, fanout; + bool has_net_load; + // Find pin and external pin/wire capacitance. + sdc->connectedCap(drvr_pin, drvr_rf, op_cond, corner, min_max, + pin_cap, wire_cap, fanout, has_net_load); + net_caps.init(pin_cap, wire_cap, fanout, has_net_load); + } + } +} + +void +MultiDrvrNet::setDcalcDrvr(Vertex *drvr) +{ + dcalc_drvr_ = drvr; +} + +bool +MultiDrvrNet::parallelGates(const Network *network) const +{ + return network->direction(dcalc_drvr_->pin())->isOutput(); +} + } // namespace diff --git a/dcalc/LumpedCapDelayCalc.cc b/dcalc/LumpedCapDelayCalc.cc index 0309c2c5..5e99dd7a 100644 --- a/dcalc/LumpedCapDelayCalc.cc +++ b/dcalc/LumpedCapDelayCalc.cc @@ -40,7 +40,7 @@ makeLumpedCapDelayCalc(StaState *sta) } LumpedCapDelayCalc::LumpedCapDelayCalc(StaState *sta) : - ArcDelayCalc(sta) + ParallelDelayCalc(sta) { } @@ -82,9 +82,9 @@ LumpedCapDelayCalc::findParasitic(const Pin *drvr_pin, Wireload *wireload = sdc_->wireload(cnst_min_max); if (wireload) { float pin_cap, wire_cap, fanout; - bool has_wire_cap; + bool has_net_load; graph_delay_calc_->netCaps(drvr_pin, rf, dcalc_ap, - pin_cap, wire_cap, fanout, has_wire_cap); + pin_cap, wire_cap, fanout, has_net_load); parasitic = parasitics_->estimatePiElmore(drvr_pin, rf, wireload, fanout, pin_cap, dcalc_ap->operatingConditions(), @@ -107,32 +107,8 @@ LumpedCapDelayCalc::reducedParasiticType() const return ReducedParasiticType::pi_elmore; } -void -LumpedCapDelayCalc::finishDrvrPin() -{ - for (auto parasitic : unsaved_parasitics_) - parasitics_->deleteUnsavedParasitic(parasitic); - unsaved_parasitics_.clear(); - for (auto drvr_pin : reduced_parasitic_drvrs_) - parasitics_->deleteDrvrReducedParasitics(drvr_pin); - reduced_parasitic_drvrs_.clear(); -} - -void -LumpedCapDelayCalc::inputPortDelay(const Pin *, float in_slew, - const RiseFall *rf, - const Parasitic *, - const DcalcAnalysisPt *) -{ - drvr_slew_ = in_slew; - drvr_rf_ = rf; - drvr_library_ = network_->defaultLibertyLibrary(); - multi_drvr_slew_factor_ = 1.0F; -} - float -LumpedCapDelayCalc::ceff(const LibertyCell *, - const TimingArc *, +LumpedCapDelayCalc::ceff(const TimingArc *, const Slew &, float load_cap, const Parasitic *, @@ -144,11 +120,10 @@ LumpedCapDelayCalc::ceff(const LibertyCell *, } void -LumpedCapDelayCalc::gateDelay(const LibertyCell *drvr_cell, - const TimingArc *arc, +LumpedCapDelayCalc::gateDelay(const TimingArc *arc, const Slew &in_slew, float load_cap, - const Parasitic *, + const Parasitic *drvr_parasitic, float related_out_cap, const Pvt *pvt, const DcalcAnalysisPt *dcalc_ap, @@ -156,6 +131,7 @@ LumpedCapDelayCalc::gateDelay(const LibertyCell *drvr_cell, ArcDelay &gate_delay, Slew &drvr_slew) { + gateDelayInit(arc, in_slew, drvr_parasitic); GateTimingModel *model = gateModel(arc, dcalc_ap); debugPrint(debug_, "delay_calc", 3, " in_slew = %s load_cap = %s related_load_cap = %s lumped", @@ -169,7 +145,7 @@ LumpedCapDelayCalc::gateDelay(const LibertyCell *drvr_cell, // NaNs cause seg faults during table lookup. if (isnan(load_cap) || isnan(related_out_cap) || isnan(delayAsFloat(in_slew))) report_->error(710, "gate delay input variable is NaN"); - model->gateDelay(drvr_cell, pvt, in_slew1, load_cap, related_out_cap, + model->gateDelay(pvt, in_slew1, load_cap, related_out_cap, pocv_enabled_, gate_delay1, drvr_slew1); gate_delay = gate_delay1; drvr_slew = drvr_slew1; @@ -180,9 +156,6 @@ LumpedCapDelayCalc::gateDelay(const LibertyCell *drvr_cell, drvr_slew = delay_zero; drvr_slew_ = 0.0; } - drvr_rf_ = arc->toEdge()->asRiseFall(); - drvr_library_ = drvr_cell->libertyLibrary(); - multi_drvr_slew_factor_ = 1.0F; } void @@ -197,52 +170,8 @@ LumpedCapDelayCalc::loadDelay(const Pin *load_pin, load_slew = load_slew1; } -void -LumpedCapDelayCalc::thresholdAdjust(const Pin *load_pin, - ArcDelay &load_delay, - Slew &load_slew) -{ - LibertyLibrary *load_library = thresholdLibrary(load_pin); - if (load_library - && drvr_library_ - && load_library != drvr_library_) { - float drvr_vth = drvr_library_->outputThreshold(drvr_rf_); - float load_vth = load_library->inputThreshold(drvr_rf_); - float drvr_slew_delta = drvr_library_->slewUpperThreshold(drvr_rf_) - - drvr_library_->slewLowerThreshold(drvr_rf_); - float load_delay_delta = - delayAsFloat(load_slew) * ((load_vth - drvr_vth) / drvr_slew_delta); - load_delay += (drvr_rf_ == RiseFall::rise()) - ? load_delay_delta - : -load_delay_delta; - float load_slew_delta = load_library->slewUpperThreshold(drvr_rf_) - - load_library->slewLowerThreshold(drvr_rf_); - float drvr_slew_derate = drvr_library_->slewDerateFromLibrary(); - float load_slew_derate = load_library->slewDerateFromLibrary(); - load_slew = load_slew * ((load_slew_delta / load_slew_derate) - / (drvr_slew_delta / drvr_slew_derate)); - } -} - -LibertyLibrary * -LumpedCapDelayCalc::thresholdLibrary(const Pin *load_pin) -{ - if (network_->isTopLevelPort(load_pin)) - // Input/output slews use the default (first read) library - // for slew thresholds. - return network_->defaultLibertyLibrary(); - else { - LibertyPort *lib_port = network_->libertyPort(load_pin); - if (lib_port) - return lib_port->libertyCell()->libertyLibrary(); - else - return network_->defaultLibertyLibrary(); - } -} - string -LumpedCapDelayCalc::reportGateDelay(const LibertyCell *drvr_cell, - const TimingArc *arc, +LumpedCapDelayCalc::reportGateDelay(const TimingArc *arc, const Slew &in_slew, float load_cap, const Parasitic *, @@ -254,15 +183,14 @@ LumpedCapDelayCalc::reportGateDelay(const LibertyCell *drvr_cell, GateTimingModel *model = gateModel(arc, dcalc_ap); if (model) { float in_slew1 = delayAsFloat(in_slew); - return model->reportGateDelay(drvr_cell, pvt, in_slew1, load_cap, - related_out_cap, false, digits); + return model->reportGateDelay(pvt, in_slew1, load_cap, related_out_cap, + false, digits); } return ""; } void -LumpedCapDelayCalc::checkDelay(const LibertyCell *cell, - const TimingArc *arc, +LumpedCapDelayCalc::checkDelay(const TimingArc *arc, const Slew &from_slew, const Slew &to_slew, float related_out_cap, @@ -275,16 +203,14 @@ LumpedCapDelayCalc::checkDelay(const LibertyCell *cell, if (model) { float from_slew1 = delayAsFloat(from_slew); float to_slew1 = delayAsFloat(to_slew); - model->checkDelay(cell, pvt, from_slew1, to_slew1, related_out_cap, - pocv_enabled_, margin); + model->checkDelay(pvt, from_slew1, to_slew1, related_out_cap, pocv_enabled_, margin); } else margin = delay_zero; } string -LumpedCapDelayCalc::reportCheckDelay(const LibertyCell *cell, - const TimingArc *arc, +LumpedCapDelayCalc::reportCheckDelay(const TimingArc *arc, const Slew &from_slew, const char *from_slew_annotation, const Slew &to_slew, @@ -297,16 +223,10 @@ LumpedCapDelayCalc::reportCheckDelay(const LibertyCell *cell, if (model) { float from_slew1 = delayAsFloat(from_slew); float to_slew1 = delayAsFloat(to_slew); - return model->reportCheckDelay(cell, pvt, from_slew1, from_slew_annotation, to_slew1, - related_out_cap, false, digits); + return model->reportCheckDelay(pvt, from_slew1, from_slew_annotation, + to_slew1, related_out_cap, false, digits); } return ""; } -void -LumpedCapDelayCalc::setMultiDrvrSlewFactor(float factor) -{ - multi_drvr_slew_factor_ = factor; -} - } // namespace diff --git a/dcalc/LumpedCapDelayCalc.hh b/dcalc/LumpedCapDelayCalc.hh index ff92dea4..db7473c0 100644 --- a/dcalc/LumpedCapDelayCalc.hh +++ b/dcalc/LumpedCapDelayCalc.hh @@ -16,13 +16,13 @@ #pragma once -#include "ArcDelayCalc.hh" +#include "ParallelDelayCalc.hh" namespace sta { // Liberty table model lumped capacitance arc delay calculator. // Wire delays are zero. -class LumpedCapDelayCalc : public ArcDelayCalc +class LumpedCapDelayCalc : public ParallelDelayCalc { public: LumpedCapDelayCalc(StaState *sta); @@ -31,13 +31,7 @@ public: const RiseFall *rf, const DcalcAnalysisPt *dcalc_ap) override; ReducedParasiticType reducedParasiticType() const override; - void inputPortDelay(const Pin *port_pin, - float in_slew, - const RiseFall *rf, - const Parasitic *parasitic, - const DcalcAnalysisPt *dcalc_ap) override; - void gateDelay(const LibertyCell *drvr_cell, - const TimingArc *arc, + void gateDelay(const TimingArc *arc, const Slew &in_slew, float load_cap, const Parasitic *drvr_parasitic, @@ -47,9 +41,7 @@ public: // Return values. ArcDelay &gate_delay, Slew &drvr_slew) override; - void setMultiDrvrSlewFactor(float factor) override; - float ceff(const LibertyCell *drvr_cell, - const TimingArc *arc, + float ceff(const TimingArc *arc, const Slew &in_slew, float load_cap, const Parasitic *drvr_parasitic, @@ -60,8 +52,7 @@ public: // Return values. ArcDelay &wire_delay, Slew &load_slew) override; - void checkDelay(const LibertyCell *cell, - const TimingArc *arc, + void checkDelay(const TimingArc *arc, const Slew &from_slew, const Slew &to_slew, float related_out_cap, @@ -69,8 +60,7 @@ public: const DcalcAnalysisPt *dcalc_ap, // Return values. ArcDelay &margin) override; - string reportGateDelay(const LibertyCell *drvr_cell, - const TimingArc *arc, + string reportGateDelay(const TimingArc *arc, const Slew &in_slew, float load_cap, const Parasitic *drvr_parasitic, @@ -78,8 +68,7 @@ public: const Pvt *pvt, const DcalcAnalysisPt *dcalc_ap, int digits) override; - string reportCheckDelay(const LibertyCell *cell, - const TimingArc *arc, + string reportCheckDelay(const TimingArc *arc, const Slew &from_slew, const char *from_slew_annotation, const Slew &to_slew, @@ -87,25 +76,8 @@ public: const Pvt *pvt, const DcalcAnalysisPt *dcalc_ap, int digits) override; - void finishDrvrPin() override; protected: - // Find the liberty library to use for logic/slew thresholds. - LibertyLibrary *thresholdLibrary(const Pin *load_pin); - // Adjust load_delay and load_slew from driver thresholds to load thresholds. - void thresholdAdjust(const Pin *load_pin, - ArcDelay &load_delay, - Slew &load_slew); - - Slew drvr_slew_; - float multi_drvr_slew_factor_; - const LibertyLibrary *drvr_library_; - const RiseFall *drvr_rf_; - // Parasitics returned by findParasitic that are reduced or estimated - // that can be deleted after delay calculation for the driver pin - // is finished. - Vector unsaved_parasitics_; - Vector reduced_parasitic_drvrs_; }; ArcDelayCalc * diff --git a/dcalc/ParallelDelayCalc.cc b/dcalc/ParallelDelayCalc.cc new file mode 100644 index 00000000..62fd90a6 --- /dev/null +++ b/dcalc/ParallelDelayCalc.cc @@ -0,0 +1,171 @@ +// OpenSTA, Static Timing Analyzer +// Copyright (c) 2023, 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 "ParallelDelayCalc.hh" + +#include "TimingArc.hh" +#include "Corner.hh" +#include "Network.hh" +#include "Graph.hh" +#include "Sdc.hh" +#include "Liberty.hh" +#include "GraphDelayCalc.hh" + +namespace sta { + +ParallelDelayCalc::ParallelDelayCalc(StaState *sta): + DelayCalcBase(sta) +{ +} + +void +ParallelDelayCalc::inputPortDelay(const Pin *drvr_pin, + float in_slew, + const RiseFall *rf, + const Parasitic *parasitic, + const DcalcAnalysisPt *dcalc_ap) +{ + DelayCalcBase::inputPortDelay(drvr_pin, in_slew, rf, parasitic, dcalc_ap); + multi_drvr_slew_factor_ = 1.0; +} + +void +ParallelDelayCalc::gateDelayInit(const TimingArc *arc, + const Slew &in_slew, + const Parasitic *drvr_parasitic) +{ + DelayCalcBase::gateDelayInit(arc, in_slew, drvr_parasitic); + multi_drvr_slew_factor_ = 1.0F; +} + +void +ParallelDelayCalc::findParallelGateDelays(const MultiDrvrNet *multi_drvr, + GraphDelayCalc *dcalc) +{ + int count = RiseFall::index_count * corners_->dcalcAnalysisPtCount(); + parallel_delays_.resize(count); + parallel_slews_.resize(count); + for (auto dcalc_ap : corners_->dcalcAnalysisPts()) { + for (auto drvr_rf : RiseFall::range()) { + DcalcAPIndex ap_index = dcalc_ap->index(); + int drvr_rf_index = drvr_rf->index(); + int index = ap_index * RiseFall::index_count + drvr_rf_index; + findMultiDrvrGateDelay(multi_drvr, drvr_rf, dcalc_ap, dcalc, + parallel_delays_[index], + parallel_slews_[index]); + } + } +} + +void +ParallelDelayCalc::findMultiDrvrGateDelay(const MultiDrvrNet *multi_drvr, + const RiseFall *drvr_rf, + const DcalcAnalysisPt *dcalc_ap, + GraphDelayCalc *dcalc, + // Return values. + ArcDelay ¶llel_delay, + Slew ¶llel_slew) +{ + ArcDelay delay_sum = 0.0; + Slew slew_sum = 0.0; + for (Vertex *drvr_vertex : *multi_drvr->drvrs()) { + Pin *drvr_pin = drvr_vertex->pin(); + Instance *drvr_inst = network_->instance(drvr_pin); + const Pvt *pvt = sdc_->pvt(drvr_inst, dcalc_ap->constraintMinMax()); + if (pvt == nullptr) + pvt = dcalc_ap->operatingConditions(); + VertexInEdgeIterator edge_iter(drvr_vertex, graph_); + while (edge_iter.hasNext()) { + Edge *edge = edge_iter.next(); + TimingArcSet *arc_set = edge->timingArcSet(); + const LibertyPort *related_out_port = arc_set->relatedOut(); + for (TimingArc *arc : arc_set->arcs()) { + RiseFall *arc_rf = arc->toEdge()->asRiseFall(); + if (arc_rf == drvr_rf) { + Vertex *from_vertex = edge->from(graph_); + RiseFall *from_rf = arc->fromEdge()->asRiseFall(); + Slew from_slew = dcalc->edgeFromSlew(from_vertex, from_rf, + edge, dcalc_ap); + ArcDelay intrinsic_delay; + Slew intrinsic_slew; + gateDelay(arc, from_slew, 0.0, 0, 0.0, pvt, dcalc_ap, + intrinsic_delay, intrinsic_slew); + Parasitic *parasitic = findParasitic(drvr_pin, drvr_rf, dcalc_ap); + const Pin *related_out_pin = 0; + float related_out_cap = 0.0; + if (related_out_port) { + Instance *inst = network_->instance(drvr_pin); + related_out_pin = network_->findPin(inst, related_out_port); + if (related_out_pin) { + Parasitic *related_out_parasitic = findParasitic(related_out_pin, + drvr_rf, + dcalc_ap); + related_out_cap = dcalc->loadCap(related_out_pin, + related_out_parasitic, + drvr_rf, dcalc_ap); + } + } + float load_cap = dcalc->loadCap(drvr_pin, parasitic, + drvr_rf, dcalc_ap); + ArcDelay gate_delay; + Slew gate_slew; + gateDelay(arc, from_slew, load_cap, parasitic, + related_out_cap, pvt, dcalc_ap, + gate_delay, gate_slew); + delay_sum += 1.0F / (gate_delay - intrinsic_delay); + slew_sum += 1.0F / gate_slew; + } + } + } + } + parallel_delay = 1.0F / delay_sum; + parallel_slew = 1.0F / slew_sum; +} + +void +ParallelDelayCalc::parallelGateDelay(const Pin *, + const TimingArc *arc, + const Slew &from_slew, + float load_cap, + const Parasitic *drvr_parasitic, + float related_out_cap, + const Pvt *pvt, + const DcalcAnalysisPt *dcalc_ap, + // Return values. + ArcDelay &gate_delay, + Slew &gate_slew) +{ + ArcDelay intrinsic_delay; + Slew intrinsic_slew; + gateDelay(arc, from_slew, 0.0, 0, 0.0, pvt, dcalc_ap, + intrinsic_delay, intrinsic_slew); + const RiseFall *drvr_rf = arc->toEdge()->asRiseFall(); + int index = dcalc_ap->index() * RiseFall::index_count + drvr_rf->index(); + ArcDelay parallel_delay = parallel_delays_[index]; + Slew parallel_slew = parallel_slews_[index]; + gate_delay = parallel_delay + intrinsic_delay; + gate_slew = parallel_slew; + + Delay gate_delay1; + Slew gate_slew1; + gateDelay(arc, from_slew, load_cap, drvr_parasitic, + related_out_cap, pvt, dcalc_ap, + gate_delay1, gate_slew1); + float factor = delayRatio(gate_slew, gate_slew1); + multi_drvr_slew_factor_ = factor; +} + +} // namespace diff --git a/dcalc/ParallelDelayCalc.hh b/dcalc/ParallelDelayCalc.hh new file mode 100644 index 00000000..68510e99 --- /dev/null +++ b/dcalc/ParallelDelayCalc.hh @@ -0,0 +1,68 @@ +// OpenSTA, Static Timing Analyzer +// Copyright (c) 2023, 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 . + +#pragma once + +#include + +#include "DelayCalcBase.hh" + +namespace sta { + +// Delay calculation for parallel gates based on using parallel drive resistance. +class ParallelDelayCalc : public DelayCalcBase +{ +public: + explicit ParallelDelayCalc(StaState *sta); + void inputPortDelay(const Pin *port_pin, + float in_slew, + const RiseFall *rf, + const Parasitic *parasitic, + const DcalcAnalysisPt *dcalc_ap) override; + void gateDelayInit(const TimingArc *arc, + const Slew &in_slew, + const Parasitic *drvr_parasitic); + void findParallelGateDelays(const MultiDrvrNet *multi_drvr, + GraphDelayCalc *dcalc) override; + void parallelGateDelay(const Pin *drvr_pin, + const TimingArc *arc, + const Slew &from_slew, + float load_cap, + const Parasitic *drvr_parasitic, + float related_out_cap, + const Pvt *pvt, + const DcalcAnalysisPt *dcalc_ap, + // Return values. + ArcDelay &gate_delay, + Slew &gate_slew) override; + +protected: + void findMultiDrvrGateDelay(const MultiDrvrNet *multi_drvr, + const RiseFall *drvr_rf, + const DcalcAnalysisPt *dcalc_ap, + GraphDelayCalc *dcalc, + // Return values. + ArcDelay ¶llel_delay, + Slew ¶llel_slew); + + // [drvr_rf->index][dcalc_ap->index] + vector parallel_delays_; + // [drvr_rf->index][dcalc_ap->index] + vector parallel_slews_; + float multi_drvr_slew_factor_; +}; + +} // namespace diff --git a/dcalc/RCDelayCalc.cc b/dcalc/RCDelayCalc.cc deleted file mode 100644 index 900df3f0..00000000 --- a/dcalc/RCDelayCalc.cc +++ /dev/null @@ -1,74 +0,0 @@ -// OpenSTA, Static Timing Analyzer -// Copyright (c) 2023, 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 "RCDelayCalc.hh" - -#include "Liberty.hh" -#include "Network.hh" -#include "Sdc.hh" -#include "Parasitics.hh" -#include "GraphDelayCalc.hh" - -namespace sta { - -RCDelayCalc::RCDelayCalc(StaState *sta) : - LumpedCapDelayCalc(sta) -{ -} - -ArcDelayCalc * -RCDelayCalc::copy() -{ - return new RCDelayCalc(this); -} - -void -RCDelayCalc::inputPortDelay(const Pin *, - float in_slew, - const RiseFall *rf, - const Parasitic *parasitic, - const DcalcAnalysisPt *) -{ - drvr_parasitic_ = parasitic; - drvr_slew_ = in_slew; - drvr_rf_ = rf; - drvr_cell_ = nullptr; - drvr_library_ = network_->defaultLibertyLibrary(); - multi_drvr_slew_factor_ = 1.0F; - input_port_ = true; -} - -// For DSPF on an input port the elmore delay is used as the time -// constant of an exponential waveform. The delay to the logic -// threshold and slew are computed for the exponential waveform. -// Note that this uses the driver thresholds and relies on -// thresholdAdjust to convert the delay and slew to the load's thresholds. -void -RCDelayCalc::dspfWireDelaySlew(const Pin *, - float elmore, - ArcDelay &wire_delay, - Slew &load_slew) -{ - float vth = drvr_library_->inputThreshold(drvr_rf_); - float vl = drvr_library_->slewLowerThreshold(drvr_rf_); - float vh = drvr_library_->slewUpperThreshold(drvr_rf_); - float slew_derate = drvr_library_->slewDerateFromLibrary(); - wire_delay = -elmore * log(1.0 - vth); - load_slew = (drvr_slew_ + elmore * log((1.0 - vl) / (1.0 - vh)) - / slew_derate) * multi_drvr_slew_factor_; -} - -} // namespace diff --git a/dcalc/SlewDegradeDelayCalc.cc b/dcalc/SlewDegradeDelayCalc.cc index 5e9a9f20..3e00e164 100644 --- a/dcalc/SlewDegradeDelayCalc.cc +++ b/dcalc/SlewDegradeDelayCalc.cc @@ -22,6 +22,7 @@ #include "Sdc.hh" #include "Parasitics.hh" #include "DcalcAnalysisPt.hh" +#include "LumpedCapDelayCalc.hh" namespace sta { @@ -30,33 +31,32 @@ namespace sta { // Wire delays are elmore delays. // Driver slews are degraded to loads by rise/fall transition_degradation // tables. -class SlewDegradeDelayCalc : public RCDelayCalc +class SlewDegradeDelayCalc : public LumpedCapDelayCalc { public: SlewDegradeDelayCalc(StaState *sta); - virtual ArcDelayCalc *copy(); - virtual void inputPortDelay(const Pin *port_pin, - float in_slew, - const RiseFall *rf, - const Parasitic *parasitic, - const DcalcAnalysisPt *dcalc_ap); - virtual void gateDelay(const LibertyCell *drvr_cell, - const TimingArc *arc, - const Slew &in_slew, - float load_cap, - const Parasitic *drvr_parasitic, - float related_out_cap, - const Pvt *pvt, - const DcalcAnalysisPt *dcalc_ap, - // Return values. - ArcDelay &gate_delay, - Slew &drvr_slew); - virtual void loadDelay(const Pin *load_pin, - ArcDelay &wire_delay, - Slew &load_slew); + ArcDelayCalc *copy() override; + void inputPortDelay(const Pin *port_pin, + float in_slew, + const RiseFall *rf, + const Parasitic *parasitic, + const DcalcAnalysisPt *dcalc_ap) override; + void gateDelay(const TimingArc *arc, + const Slew &in_slew, + float load_cap, + const Parasitic *drvr_parasitic, + float related_out_cap, + const Pvt *pvt, + const DcalcAnalysisPt *dcalc_ap, + // Return values. + ArcDelay &gate_delay, + Slew &drvr_slew) override; + void loadDelay(const Pin *load_pin, + ArcDelay &wire_delay, + Slew &load_slew) override; - using RCDelayCalc::gateDelay; - using RCDelayCalc::reportGateDelay; + using LumpedCapDelayCalc::gateDelay; + using LumpedCapDelayCalc::reportGateDelay; private: const Pvt *pvt_; @@ -69,7 +69,7 @@ makeSlewDegradeDelayCalc(StaState *sta) } SlewDegradeDelayCalc::SlewDegradeDelayCalc(StaState *sta) : - RCDelayCalc(sta) + LumpedCapDelayCalc(sta) { } @@ -87,12 +87,11 @@ SlewDegradeDelayCalc::inputPortDelay(const Pin *port_pin, const DcalcAnalysisPt *dcalc_ap) { pvt_ = dcalc_ap->operatingConditions(); - RCDelayCalc::inputPortDelay(port_pin, in_slew, rf, parasitic, dcalc_ap); + LumpedCapDelayCalc::inputPortDelay(port_pin, in_slew, rf, parasitic, dcalc_ap); } void -SlewDegradeDelayCalc::gateDelay(const LibertyCell *drvr_cell, - const TimingArc *arc, +SlewDegradeDelayCalc::gateDelay(const TimingArc *arc, const Slew &in_slew, float load_cap, const Parasitic *drvr_parasitic, @@ -106,12 +105,11 @@ SlewDegradeDelayCalc::gateDelay(const LibertyCell *drvr_cell, input_port_ = false; drvr_parasitic_ = drvr_parasitic; drvr_rf_ = arc->toEdge()->asRiseFall(); - drvr_cell_ = drvr_cell; - drvr_library_ = drvr_cell->libertyLibrary(); + drvr_cell_ = arc->from()->libertyCell(); + drvr_library_ = drvr_cell_->libertyLibrary(); pvt_ = pvt; - LumpedCapDelayCalc::gateDelay(drvr_cell, arc, in_slew, - load_cap, drvr_parasitic, related_out_cap, - pvt, dcalc_ap, + LumpedCapDelayCalc::gateDelay(arc, in_slew, load_cap, drvr_parasitic, + related_out_cap, pvt, dcalc_ap, gate_delay, drvr_slew); } @@ -129,8 +127,7 @@ SlewDegradeDelayCalc::loadDelay(const Pin *load_pin, if (elmore_exists) { if (drvr_library_ && drvr_library_->wireSlewDegradationTable(drvr_rf_)) { wire_delay1 = elmore; - load_slew1 = drvr_library_->degradeWireSlew(drvr_cell_, drvr_rf_, - pvt_, + load_slew1 = drvr_library_->degradeWireSlew(drvr_rf_, delayAsFloat(drvr_slew_), delayAsFloat(wire_delay1)); } diff --git a/dcalc/SlewDegradeDelayCalc.hh b/dcalc/SlewDegradeDelayCalc.hh index 97b39c5f..7195985d 100644 --- a/dcalc/SlewDegradeDelayCalc.hh +++ b/dcalc/SlewDegradeDelayCalc.hh @@ -16,10 +16,11 @@ #pragma once -#include "RCDelayCalc.hh" - namespace sta { +class ArcDelayCalc; +class StaState; + ArcDelayCalc * makeSlewDegradeDelayCalc(StaState *sta); diff --git a/dcalc/UnitDelayCalc.cc b/dcalc/UnitDelayCalc.cc index 20ddaf14..cc629ba5 100644 --- a/dcalc/UnitDelayCalc.cc +++ b/dcalc/UnitDelayCalc.cc @@ -61,8 +61,7 @@ UnitDelayCalc::inputPortDelay(const Pin *, } void -UnitDelayCalc::gateDelay(const LibertyCell *, - const TimingArc *, +UnitDelayCalc::gateDelay(const TimingArc *, const Slew &, float, const Parasitic *, @@ -75,6 +74,29 @@ UnitDelayCalc::gateDelay(const LibertyCell *, drvr_slew = 0.0; } +void +UnitDelayCalc::findParallelGateDelays(const MultiDrvrNet *, + GraphDelayCalc *) +{ +} + +void +UnitDelayCalc::parallelGateDelay(const Pin *, + const TimingArc *, + const Slew &, + float, + const Parasitic *, + float, + const Pvt *, + const DcalcAnalysisPt *, + // Return values. + ArcDelay &gate_delay, + Slew &gate_slew) +{ + gate_delay = units_->timeUnit()->scale(); + gate_slew = 0.0; +} + void UnitDelayCalc::loadDelay(const Pin *, ArcDelay &wire_delay, @@ -85,8 +107,7 @@ UnitDelayCalc::loadDelay(const Pin *, } float -UnitDelayCalc::ceff(const LibertyCell *, - const TimingArc *, +UnitDelayCalc::ceff(const TimingArc *, const Slew &, float, const Parasitic *, @@ -98,8 +119,7 @@ UnitDelayCalc::ceff(const LibertyCell *, } string -UnitDelayCalc::reportGateDelay(const LibertyCell *, - const TimingArc *, +UnitDelayCalc::reportGateDelay(const TimingArc *, const Slew &, float, const Parasitic *, @@ -114,8 +134,7 @@ UnitDelayCalc::reportGateDelay(const LibertyCell *, } void -UnitDelayCalc::checkDelay(const LibertyCell *, - const TimingArc *, +UnitDelayCalc::checkDelay(const TimingArc *, const Slew &, const Slew &, float, @@ -128,8 +147,7 @@ UnitDelayCalc::checkDelay(const LibertyCell *, } string -UnitDelayCalc::reportCheckDelay(const LibertyCell *, - const TimingArc *, +UnitDelayCalc::reportCheckDelay(const TimingArc *, const Slew &, const char *, const Slew &, diff --git a/dcalc/UnitDelayCalc.hh b/dcalc/UnitDelayCalc.hh index 1b7b22b9..6ecae7cf 100644 --- a/dcalc/UnitDelayCalc.hh +++ b/dcalc/UnitDelayCalc.hh @@ -35,8 +35,7 @@ public: const RiseFall *rf, const Parasitic *parasitic, const DcalcAnalysisPt *dcalc_ap) override; - void gateDelay(const LibertyCell *drvr_cell, - const TimingArc *arc, + void gateDelay(const TimingArc *arc, const Slew &in_slew, float load_cap, const Parasitic *drvr_parasitic, @@ -46,21 +45,32 @@ public: // Return values. ArcDelay &gate_delay, Slew &drvr_slew) override; + void findParallelGateDelays(const MultiDrvrNet *multi_drvr, + GraphDelayCalc *dcalc) override; + // Retrieve the delay and slew for one parallel gate. + void parallelGateDelay(const Pin *drvr_pin, + const TimingArc *arc, + const Slew &from_slew, + float load_cap, + const Parasitic *drvr_parasitic, + float related_out_cap, + const Pvt *pvt, + const DcalcAnalysisPt *dcalc_ap, + // Return values. + ArcDelay &gate_delay, + Slew &gate_slew) override; void loadDelay(const Pin *load_pin, // Return values. ArcDelay &wire_delay, Slew &load_slew) override; - void setMultiDrvrSlewFactor(float) override {} - float ceff(const LibertyCell *drvr_cell, - const TimingArc *arc, + float ceff(const TimingArc *arc, const Slew &in_slew, float load_cap, const Parasitic *drvr_parasitic, float related_out_cap, const Pvt *pvt, const DcalcAnalysisPt *dcalc_ap) override; - void checkDelay(const LibertyCell *cell, - const TimingArc *arc, + void checkDelay(const TimingArc *arc, const Slew &from_slew, const Slew &to_slew, float related_out_cap, @@ -68,8 +78,7 @@ public: const DcalcAnalysisPt *dcalc_ap, // Return values. ArcDelay &margin) override; - string reportGateDelay(const LibertyCell *drvr_cell, - const TimingArc *arc, + string reportGateDelay(const TimingArc *arc, const Slew &in_slew, float load_cap, const Parasitic *drvr_parasitic, @@ -77,8 +86,7 @@ public: const Pvt *pvt, const DcalcAnalysisPt *dcalc_ap, int digits) override; - string reportCheckDelay(const LibertyCell *cell, - const TimingArc *arc, + string reportCheckDelay(const TimingArc *arc, const Slew &from_slew, const char *from_slew_annotation, const Slew &to_slew, diff --git a/graph/Graph.cc b/graph/Graph.cc index 8d5bc4ee..29af550a 100644 --- a/graph/Graph.cc +++ b/graph/Graph.cc @@ -783,8 +783,8 @@ Graph::setWireArcDelay(Edge *edge, } bool -Graph::arcDelayAnnotated(Edge *edge, - TimingArc *arc, +Graph::arcDelayAnnotated(const Edge *edge, + const TimingArc *arc, DcalcAPIndex ap_index) const { if (arc_delay_annotated_.size()) { @@ -799,7 +799,7 @@ Graph::arcDelayAnnotated(Edge *edge, void Graph::setArcDelayAnnotated(Edge *edge, - TimingArc *arc, + const TimingArc *arc, DcalcAPIndex ap_index, bool annotated) { diff --git a/include/sta/ArcDelayCalc.hh b/include/sta/ArcDelayCalc.hh index 37ed4baf..c1c6617b 100644 --- a/include/sta/ArcDelayCalc.hh +++ b/include/sta/ArcDelayCalc.hh @@ -17,6 +17,7 @@ #pragma once #include +#include #include "MinMax.hh" #include "LibertyClass.hh" @@ -28,22 +29,26 @@ namespace sta { using std::string; +using std::vector; class Parasitic; class DcalcAnalysisPt; +class MultiDrvrNet; // Delay calculator class hierarchy. // ArcDelayCalc // UnitDelayCalc -// LumpedCapDelayCalc -// RCDelayCalc -// SlewDegradeDelayCalc -// DmpCeffDelayCalc -// DmpCeffElmoreDelayCalc -// DmpCeffTwoPoleDelayCalc -// ArnoldiDelayCalc +// DelayCalcBase +// ParallelDelayCalc +// LumpedCapDelayCalc +// SlewDegradeDelayCalc +// DmpCeffDelayCalc +// DmpCeffElmoreDelayCalc +// DmpCeffTwoPoleDelayCalc +// ArnoldiDelayCalc -// Abstract class to interface to a delay calculator primitive. +// Abstract class for the graph delay calculator traversal to interface +// to a delay calculator primitive. class ArcDelayCalc : public StaState { public: @@ -66,8 +71,7 @@ public: const DcalcAnalysisPt *dcalc_ap) = 0; // Find the delay and slew for arc driving drvr_pin. - virtual void gateDelay(const LibertyCell *drvr_cell, - const TimingArc *arc, + virtual void gateDelay(const TimingArc *arc, const Slew &in_slew, // Pass in load_cap or drvr_parasitic. float load_cap, @@ -78,16 +82,29 @@ public: // Return values. ArcDelay &gate_delay, Slew &drvr_slew) = 0; + // Find gate delays and slews for parallel gates. + virtual void findParallelGateDelays(const MultiDrvrNet *multi_drvr, + GraphDelayCalc *dcalc) = 0; + // Retrieve the delay and slew for one parallel gate. + virtual void parallelGateDelay(const Pin *drvr_pin, + const TimingArc *arc, + const Slew &from_slew, + float load_cap, + const Parasitic *drvr_parasitic, + float related_out_cap, + const Pvt *pvt, + const DcalcAnalysisPt *dcalc_ap, + // Return values. + ArcDelay &gate_delay, + Slew &gate_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, // Return values. 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, - const TimingArc *arc, + virtual float ceff(const TimingArc *arc, const Slew &in_slew, float load_cap, const Parasitic *drvr_parasitic, @@ -97,8 +114,7 @@ public: // Find the delay for a timing check arc given the arc's // from/clock, to/data slews and related output pin parasitic. - virtual void checkDelay(const LibertyCell *drvr_cell, - const TimingArc *arc, + virtual void checkDelay(const TimingArc *arc, const Slew &from_slew, const Slew &to_slew, float related_out_cap, @@ -107,8 +123,7 @@ public: // Return values. ArcDelay &margin) = 0; // Report delay and slew calculation. - virtual string reportGateDelay(const LibertyCell *drvr_cell, - const TimingArc *arc, + virtual string reportGateDelay(const TimingArc *arc, const Slew &in_slew, // Pass in load_cap or drvr_parasitic. float load_cap, @@ -118,8 +133,7 @@ public: const DcalcAnalysisPt *dcalc_ap, int digits) = 0; // Report timing check delay calculation. - virtual string reportCheckDelay(const LibertyCell *cell, - const TimingArc *arc, + virtual string reportCheckDelay(const TimingArc *arc, const Slew &from_slew, const char *from_slew_annotation, const Slew &to_slew, diff --git a/include/sta/Graph.hh b/include/sta/Graph.hh index 247b5bcf..d2dea625 100644 --- a/include/sta/Graph.hh +++ b/include/sta/Graph.hh @@ -157,11 +157,11 @@ public: DcalcAPIndex ap_index, const ArcDelay &delay); // Is timing arc delay annotated. - bool arcDelayAnnotated(Edge *edge, - TimingArc *arc, + bool arcDelayAnnotated(const Edge *edge, + const TimingArc *arc, DcalcAPIndex ap_index) const; void setArcDelayAnnotated(Edge *edge, - TimingArc *arc, + const TimingArc *arc, DcalcAPIndex ap_index, bool annotated); bool wireDelayAnnotated(Edge *edge, @@ -539,7 +539,6 @@ class VertexSet : public Set { public: VertexSet(Graph *&graph); - VertexSet(const VertexSet &set); }; } // namespace diff --git a/include/sta/GraphDelayCalc.hh b/include/sta/GraphDelayCalc.hh index 67dfc15e..da47b31e 100644 --- a/include/sta/GraphDelayCalc.hh +++ b/include/sta/GraphDelayCalc.hh @@ -16,6 +16,8 @@ #pragma once +#include + #include "Map.hh" #include "NetworkClass.hh" #include "GraphClass.hh" @@ -26,9 +28,12 @@ namespace sta { +using std::vector; + class DelayCalcObserver; class MultiDrvrNet; class FindVertexDelays; +class NetCaps; typedef Map MultiDrvrNetMap; @@ -55,8 +60,8 @@ public: // Find and annotate drvr_vertex gate and load delays/slews. virtual void findDelays(Vertex *drvr_vertex); // Returned string is owned by the caller. - virtual string reportDelayCalc(Edge *edge, - TimingArc *arc, + virtual string reportDelayCalc(const Edge *edge, + const TimingArc *arc, const Corner *corner, const MinMax *min_max, int digits); @@ -85,6 +90,11 @@ public: const Parasitic *drvr_parasitic, const RiseFall *rf, const DcalcAnalysisPt *dcalc_ap) const; + float loadCap(const Pin *drvr_pin, + const Parasitic *drvr_parasitic, + const RiseFall *rf, + const DcalcAnalysisPt *dcalc_ap, + const MultiDrvrNet *multi_drvr) const; virtual void netCaps(const Pin *drvr_pin, const RiseFall *rf, const DcalcAnalysisPt *dcalc_ap, @@ -115,10 +125,13 @@ public: float &min_period, bool &exists); + Slew edgeFromSlew(const Vertex *from_vertex, + const RiseFall *from_rf, + const Edge *edge, + const DcalcAnalysisPt *dcalc_ap); + protected: void seedInvalidDelays(); - void ensureMultiDrvrNetsFound(); - void makeMultiDrvrNet(PinSet &drvr_pins); void initSlew(Vertex *vertex); void seedRootSlew(Vertex *vertex, ArcDelayCalc *arc_delay_calc); @@ -150,23 +163,23 @@ protected: const LibertyPort *to_port); int findPortIndex(const LibertyCell *cell, const LibertyPort *port); - void findInputArcDelay(const LibertyCell *drvr_cell, - const Pin *drvr_pin, + void findInputArcDelay(const Pin *drvr_pin, Vertex *drvr_vertex, const TimingArc *arc, float from_slew, const DcalcAnalysisPt *dcalc_ap); bool findDriverDelays(Vertex *drvr_vertex, ArcDelayCalc *arc_delay_calc); + MultiDrvrNet *findMultiDrvrNet(Vertex *drvr_pin); + MultiDrvrNet *makeMultiDrvrNet(PinSet &drvr_pins); bool findDriverDelays1(Vertex *drvr_vertex, MultiDrvrNet *multi_drvr, ArcDelayCalc *arc_delay_calc); void initLoadSlews(Vertex *drvr_vertex); - bool findDriverEdgeDelays(LibertyCell *drvr_cell, - Instance *drvr_inst, + bool findDriverEdgeDelays(const Instance *drvr_inst, const Pin *drvr_pin, Vertex *drvr_vertex, - MultiDrvrNet *multi_drvr, + const MultiDrvrNet *multi_drvr, Edge *edge, ArcDelayCalc *arc_delay_calc); void initWireDelays(Vertex *drvr_vertex); @@ -176,17 +189,16 @@ protected: ArcDelayCalc *arc_delay_calc, bool propagate); void enqueueTimingChecksEdges(Vertex *vertex); - bool findArcDelay(LibertyCell *drvr_cell, - const Pin *drvr_pin, + bool findArcDelay(const Pin *drvr_pin, Vertex *drvr_vertex, - MultiDrvrNet *multi_drvr, - TimingArc *arc, - Parasitic *drvr_parasitic, + const TimingArc *arc, + const Parasitic *drvr_parasitic, float related_out_cap, Vertex *from_vertex, Edge *edge, const Pvt *pvt, const DcalcAnalysisPt *dcalc_ap, + const MultiDrvrNet *multi_drvr, ArcDelayCalc *arc_delay_calc); void annotateLoadDelays(Vertex *drvr_vertex, const RiseFall *drvr_rf, @@ -197,32 +209,7 @@ protected: void findLatchEdgeDelays(Edge *edge); void findCheckEdgeDelays(Edge *edge, ArcDelayCalc *arc_delay_calc); - void findMultiDrvrGateDelay(MultiDrvrNet *multi_drvr, - const RiseFall *drvr_rf, - const Pvt *pvt, - const DcalcAnalysisPt *dcalc_ap, - ArcDelayCalc *arc_delay_calc, - // Return values. - ArcDelay ¶llel_delay, - Slew ¶llel_slew); - void parallelGateDelay(MultiDrvrNet *multi_drvr, - LibertyCell *drvr_cell, - const Pin *drvr_pin, - TimingArc *arc, - const Pvt *pvt, - const DcalcAnalysisPt *dcalc_ap, - const Slew from_slew, - Parasitic *drvr_parasitic, - float related_out_cap, - ArcDelayCalc *arc_delay_calc, - // Return values. - ArcDelay &gate_delay, - Slew &gate_slew); void deleteMultiDrvrNets(); - Slew edgeFromSlew(const Vertex *from_vertex, - const RiseFall *from_rf, - const Edge *edge, - const DcalcAnalysisPt *dcalc_ap); Slew checkEdgeClkSlew(const Vertex *from_vertex, const RiseFall *from_rf, const DcalcAnalysisPt *dcalc_ap); @@ -233,11 +220,6 @@ protected: // Return values. float &pin_cap, float &wire_cap) const; - float loadCap(const Pin *drvr_pin, - MultiDrvrNet *multi_drvr, - const Parasitic *drvr_parasitic, - const RiseFall *rf, - const DcalcAnalysisPt *dcalc_ap) const; // Observer for edge delay changes. DelayCalcObserver *observer_; @@ -277,4 +259,33 @@ public: virtual void checkDelayChangedTo(Vertex *vertex) = 0; }; +// Nets with multiple drivers (tristate, bidirect or output). +// Cache net caps to prevent N^2 net pin walk. +class MultiDrvrNet +{ +public: + MultiDrvrNet(VertexSet *drvrs); + ~MultiDrvrNet(); + const VertexSet *drvrs() const { return drvrs_; } + VertexSet *drvrs() { return drvrs_; } + bool parallelGates(const Network *network) const; + Vertex *dcalcDrvr() const { return dcalc_drvr_; } + void setDcalcDrvr(Vertex *drvr); + void netCaps(const RiseFall *rf, + const DcalcAnalysisPt *dcalc_ap, + // Return values. + float &pin_cap, + float &wire_cap, + float &fanout, + bool &has_net_load) const; + void findCaps(const Sdc *sdc); + +private: + // Driver that triggers delay calculation for all the drivers on the net. + Vertex *dcalc_drvr_; + VertexSet *drvrs_; + // [drvr_rf->index][dcalc_ap->index] + vector net_caps_; +}; + } // namespace diff --git a/include/sta/Liberty.hh b/include/sta/Liberty.hh index 0924ebd9..14dab380 100644 --- a/include/sta/Liberty.hh +++ b/include/sta/Liberty.hh @@ -167,16 +167,14 @@ public: const LibertyCell *cell, const Pvt *pvt) const; float scaleFactor(ScaleFactorType type, - int tr_index, + int rf_index, const LibertyCell *cell, const Pvt *pvt) const; void setWireSlewDegradationTable(TableModel *model, RiseFall *rf); TableModel *wireSlewDegradationTable(const RiseFall *rf) const; - float degradeWireSlew(const LibertyCell *cell, - const RiseFall *rf, - const Pvt *pvt, + float degradeWireSlew(const RiseFall *rf, float in_slew, float wire_delay) const; // Check for supported axis variables. @@ -323,9 +321,7 @@ public: void addDriverWaveform(DriverWaveform *driver_waveform); protected: - float degradeWireSlew(const LibertyCell *cell, - const Pvt *pvt, - const TableModel *model, + float degradeWireSlew(const TableModel *model, float in_slew, float wire_delay) const; @@ -925,7 +921,7 @@ public: RiseFall *rf); float scale(ScaleFactorType type, ScaleFactorPvt pvt, - int tr_index); + int rf_index); float scale(ScaleFactorType type, ScaleFactorPvt pvt); void setScale(ScaleFactorType type, diff --git a/include/sta/LinearModel.hh b/include/sta/LinearModel.hh index f06558e0..25f1988c 100644 --- a/include/sta/LinearModel.hh +++ b/include/sta/LinearModel.hh @@ -23,9 +23,10 @@ namespace sta { class GateLinearModel : public GateTimingModel { public: - GateLinearModel(float intrinsic, float resistance); - void gateDelay(const LibertyCell *cell, - const Pvt *pvt, + GateLinearModel(LibertyCell *cell, + float intrinsic, + float resistance); + void gateDelay(const Pvt *pvt, float in_slew, float load_cap, float related_out_cap, @@ -33,15 +34,13 @@ public: // Return values. ArcDelay &gate_delay, Slew &drvr_slew) const override; - string reportGateDelay(const LibertyCell *cell, - const Pvt *pvt, + string reportGateDelay(const Pvt *pvt, float in_slew, float load_cap, float related_out_cap, bool pocv_enabled, int digits) const override; - float driveResistance(const LibertyCell *cell, - const Pvt *pvt) const override; + float driveResistance(const Pvt *pvt) const override; protected: void setIsScaled(bool is_scaled) override; @@ -53,17 +52,16 @@ protected: class CheckLinearModel : public CheckTimingModel { public: - explicit CheckLinearModel(float intrinsic); - void checkDelay(const LibertyCell *cell, - const Pvt *pvt, + explicit CheckLinearModel(LibertyCell *cell, + float intrinsic); + void checkDelay(const Pvt *pvt, float from_slew, float to_slew, float related_out_cap, bool pocv_enabled, // Return values. ArcDelay &margin) const override; - string reportCheckDelay(const LibertyCell *cell, - const Pvt *pvt, + string reportCheckDelay(const Pvt *pvt, float from_slew, const char *from_slew_annotation, float to_slew, diff --git a/include/sta/Sdc.hh b/include/sta/Sdc.hh index c6ddeaef..eb471f20 100644 --- a/include/sta/Sdc.hh +++ b/include/sta/Sdc.hh @@ -827,7 +827,8 @@ public: // Returns nullptr if set_operating_conditions has not been called. OperatingConditions *operatingConditions(const MinMax *min_max); // Instance specific process/voltage/temperature. - const Pvt *pvt(Instance *inst, const MinMax *min_max) const; + const Pvt *pvt(const Instance *inst, + const MinMax *min_max) const; // Pvt may be shared among multiple instances. void setPvt(const Instance *inst, const MinMaxAll *min_max, diff --git a/include/sta/TableModel.hh b/include/sta/TableModel.hh index 0ec14588..81b03895 100644 --- a/include/sta/TableModel.hh +++ b/include/sta/TableModel.hh @@ -51,15 +51,15 @@ tableVariableUnit(TableAxisVariable variable, class GateTableModel : public GateTimingModel { public: - GateTableModel(TableModel *delay_model, + GateTableModel(LibertyCell *cell, + TableModel *delay_model, TableModel *delay_sigma_models[EarlyLate::index_count], TableModel *slew_model, TableModel *slew_sigma_models[EarlyLate::index_count], ReceiverModelPtr receiver_model, OutputWaveforms *output_waveforms); virtual ~GateTableModel(); - void gateDelay(const LibertyCell *cell, - const Pvt *pvt, + void gateDelay(const Pvt *pvt, float in_slew, float load_cap, float related_out_cap, @@ -67,15 +67,13 @@ public: // Return values. ArcDelay &gate_delay, Slew &drvr_slew) const override; - string reportGateDelay(const LibertyCell *cell, - const Pvt *pvt, + string reportGateDelay(const Pvt *pvt, float in_slew, float load_cap, float related_out_cap, bool pocv_enabled, int digits) const override; - float driveResistance(const LibertyCell *cell, - const Pvt *pvt) const override; + float driveResistance(const Pvt *pvt) const override; const TableModel *delayModel() const { return delay_model_; } const TableModel *slewModel() const { return slew_model_; } @@ -86,8 +84,7 @@ public: static bool checkAxes(const TablePtr table); protected: - void maxCapSlew(const LibertyCell *cell, - float in_slew, + void maxCapSlew(float in_slew, const Pvt *pvt, float &slew, float &cap) const; @@ -96,16 +93,12 @@ protected: float load_cap, float in_slew, float related_out_cap) const; - float findValue(const LibertyLibrary *library, - const LibertyCell *cell, - const Pvt *pvt, + float findValue(const Pvt *pvt, const TableModel *model, float in_slew, float load_cap, float related_out_cap) const; string reportTableLookup(const char *result_name, - const LibertyLibrary *library, - const LibertyCell *cell, const Pvt *pvt, const TableModel *model, float in_slew, @@ -133,19 +126,18 @@ protected: class CheckTableModel : public CheckTimingModel { public: - explicit CheckTableModel(TableModel *model, + explicit CheckTableModel(LibertyCell *cell, + TableModel *model, TableModel *sigma_models[EarlyLate::index_count]); virtual ~CheckTableModel(); - void checkDelay(const LibertyCell *cell, - const Pvt *pvt, + void checkDelay(const Pvt *pvt, float from_slew, float to_slew, float related_out_cap, bool pocv_enabled, // Return values. ArcDelay &margin) const override; - string reportCheckDelay(const LibertyCell *cell, - const Pvt *pvt, + string reportCheckDelay(const Pvt *pvt, float from_slew, const char *from_slew_annotation, float to_slew, @@ -160,9 +152,7 @@ public: protected: void setIsScaled(bool is_scaled) override; - float findValue(const LibertyLibrary *library, - const LibertyCell *cell, - const Pvt *pvt, + float findValue(const Pvt *pvt, const TableModel *model, float from_slew, float to_slew, @@ -179,8 +169,6 @@ protected: float in_slew, float related_out_cap) const; string reportTableDelay(const char *result_name, - const LibertyLibrary *library, - const LibertyCell *cell, const Pvt *pvt, const TableModel *model, float from_slew, @@ -217,14 +205,12 @@ public: float value2, float value3) const; // Table interpolated lookup with scale factor. - float findValue(const LibertyLibrary *library, - const LibertyCell *cell, + float findValue(const LibertyCell *cell, const Pvt *pvt, float value1, float value2, float value3) const; string reportValue(const char *result_name, - const LibertyLibrary *library, const LibertyCell *cell, const Pvt *pvt, float value1, @@ -237,11 +223,9 @@ public: Report *report) const; protected: - float scaleFactor(const LibertyLibrary *library, - const LibertyCell *cell, + float scaleFactor(const LibertyCell *cell, const Pvt *pvt) const; - string reportPvtScaleFactor(const LibertyLibrary *library, - const LibertyCell *cell, + string reportPvtScaleFactor(const LibertyCell *cell, const Pvt *pvt, int digits) const; @@ -280,7 +264,6 @@ public: float axis_value2, float axis_value3) const; virtual string reportValue(const char *result_name, - const LibertyLibrary *library, const LibertyCell *cell, const Pvt *pvt, float value1, @@ -306,7 +289,6 @@ public: float axis_value2, float axis_value3) const override; string reportValue(const char *result_name, - const LibertyLibrary *library, const LibertyCell *cell, const Pvt *pvt, float value1, @@ -342,7 +324,6 @@ public: float value2, float value3) const override; string reportValue(const char *result_name, - const LibertyLibrary *library, const LibertyCell *cell, const Pvt *pvt, float value1, @@ -389,7 +370,6 @@ public: float value2, float value3) const override; string reportValue(const char *result_name, - const LibertyLibrary *library, const LibertyCell *cell, const Pvt *pvt, float value1, @@ -436,7 +416,6 @@ public: float value2, float value3) const override; string reportValue(const char *result_name, - const LibertyLibrary *library, const LibertyCell *cell, const Pvt *pvt, float value1, diff --git a/include/sta/TimingModel.hh b/include/sta/TimingModel.hh index 94604d78..e6b2fd04 100644 --- a/include/sta/TimingModel.hh +++ b/include/sta/TimingModel.hh @@ -29,21 +29,21 @@ using std::string; class TimingModel { public: + TimingModel(LibertyCell *cell); virtual ~TimingModel() {} - -protected: virtual void setIsScaled(bool is_scaled) = 0; - friend class LibertyCell; +protected: + LibertyCell *cell_; }; // Abstract base class for LinearModel and TableModel. class GateTimingModel : public TimingModel { public: + GateTimingModel(LibertyCell *cell); // Gate delay calculation. - virtual void gateDelay(const LibertyCell *cell, - const Pvt *pvt, + virtual void gateDelay(const Pvt *pvt, float in_slew, float load_cap, float related_out_cap, @@ -51,32 +51,29 @@ public: // Return values. ArcDelay &gate_delay, Slew &drvr_slew) const = 0; - virtual string reportGateDelay(const LibertyCell *cell, - const Pvt *pvt, + virtual string reportGateDelay(const Pvt *pvt, float in_slew, float load_cap, float related_out_cap, bool pocv_enabled, int digits) const = 0; - virtual float driveResistance(const LibertyCell *cell, - const Pvt *pvt) const = 0; + virtual float driveResistance(const Pvt *pvt) const = 0; }; // Abstract base class for timing check timing models. class CheckTimingModel : public TimingModel { public: + CheckTimingModel(LibertyCell *cell); // Timing check margin delay calculation. - virtual void checkDelay(const LibertyCell *cell, - const Pvt *pvt, + virtual void checkDelay(const Pvt *pvt, float from_slew, float to_slew, float related_out_cap, bool pocv_enabled, // Return values. ArcDelay &margin) const = 0; - virtual string reportCheckDelay(const LibertyCell *cell, - const Pvt *pvt, + virtual string reportCheckDelay(const Pvt *pvt, float from_slew, const char *from_slew_annotation, float to_slew, diff --git a/liberty/InternalPower.cc b/liberty/InternalPower.cc index efa6afce..4c251eb4 100644 --- a/liberty/InternalPower.cc +++ b/liberty/InternalPower.cc @@ -131,9 +131,7 @@ InternalPowerModel::power(const LibertyCell *cell, float axis_value1, axis_value2, axis_value3; findAxisValues(in_slew, load_cap, axis_value1, axis_value2, axis_value3); - const LibertyLibrary *library = cell->libertyLibrary(); - return model_->findValue(library, cell, pvt, - axis_value1, axis_value2, axis_value3); + return model_->findValue(cell, pvt, axis_value1, axis_value2, axis_value3); } else return 0.0; @@ -151,8 +149,8 @@ InternalPowerModel::reportPower(const LibertyCell *cell, findAxisValues(in_slew, load_cap, axis_value1, axis_value2, axis_value3); const LibertyLibrary *library = cell->libertyLibrary(); - return model_->reportValue("Power", library, cell, pvt, - axis_value1, nullptr, axis_value2, axis_value3, + return model_->reportValue("Power", cell, pvt, axis_value1, nullptr, + axis_value2, axis_value3, library->units()->powerUnit(), digits); } return ""; diff --git a/liberty/Liberty.cc b/liberty/Liberty.cc index 5d5e3535..6e8a91c0 100644 --- a/liberty/Liberty.cc +++ b/liberty/Liberty.cc @@ -94,12 +94,12 @@ LibertyLibrary::LibertyLibrary(const char *name, addTableTemplate(scalar_template, type); } - for (auto tr_index : RiseFall::rangeIndex()) { - wire_slew_degradation_tbls_[tr_index] = nullptr; - input_threshold_[tr_index] = input_threshold_default_; - output_threshold_[tr_index] = output_threshold_default_; - slew_lower_threshold_[tr_index] = slew_lower_threshold_default_; - slew_upper_threshold_[tr_index] = slew_upper_threshold_default_; + for (auto rf_index : RiseFall::rangeIndex()) { + wire_slew_degradation_tbls_[rf_index] = nullptr; + input_threshold_[rf_index] = input_threshold_default_; + output_threshold_[rf_index] = output_threshold_default_; + slew_lower_threshold_[rf_index] = slew_lower_threshold_default_; + slew_upper_threshold_[rf_index] = slew_upper_threshold_default_; } } @@ -111,8 +111,8 @@ LibertyLibrary::~LibertyLibrary() scale_factors_map_.deleteContents(); delete scale_factors_; - for (auto tr_index : RiseFall::rangeIndex()) { - TableModel *model = wire_slew_degradation_tbls_[tr_index]; + for (auto rf_index : RiseFall::rangeIndex()) { + TableModel *model = wire_slew_degradation_tbls_[rf_index]; delete model; } operating_conditions_.deleteContents(); @@ -289,7 +289,7 @@ LibertyLibrary::scaleFactor(ScaleFactorType type, float LibertyLibrary::scaleFactor(ScaleFactorType type, - int tr_index, + int rf_index, const LibertyCell *cell, const Pvt *pvt) const { @@ -299,18 +299,18 @@ LibertyLibrary::scaleFactor(ScaleFactorType type, // All scale factors are unity for nominal pvt. if (pvt) { ScaleFactors *scale_factors = nullptr; - // Cell level scale factors have precidence over library scale factors. + // Cell level scale factors have precedence over library scale factors. if (cell) scale_factors = cell->scaleFactors(); if (scale_factors == nullptr) scale_factors = scale_factors_; if (scale_factors) { float process_scale = 1.0F + (pvt->process() - nominal_process_) - * scale_factors->scale(type, ScaleFactorPvt::process, tr_index); + * scale_factors->scale(type, ScaleFactorPvt::process, rf_index); float temp_scale = 1.0F + (pvt->temperature() - nominal_temperature_) - * scale_factors->scale(type, ScaleFactorPvt::temp, tr_index); + * scale_factors->scale(type, ScaleFactorPvt::temp, rf_index); float volt_scale = 1.0F + (pvt->voltage() - nominal_voltage_) - * scale_factors->scale(type, ScaleFactorPvt::volt, tr_index); + * scale_factors->scale(type, ScaleFactorPvt::volt, rf_index); float scale = process_scale * temp_scale * volt_scale; return scale; } @@ -322,10 +322,10 @@ void LibertyLibrary::setWireSlewDegradationTable(TableModel *model, RiseFall *rf) { - int tr_index = rf->index(); - if (wire_slew_degradation_tbls_[tr_index]) - delete wire_slew_degradation_tbls_[tr_index]; - wire_slew_degradation_tbls_[tr_index] = model; + int rf_index = rf->index(); + if (wire_slew_degradation_tbls_[rf_index]) + delete wire_slew_degradation_tbls_[rf_index]; + wire_slew_degradation_tbls_[rf_index] = model; } TableModel * @@ -335,36 +335,32 @@ LibertyLibrary::wireSlewDegradationTable(const RiseFall *rf) const } float -LibertyLibrary::degradeWireSlew(const LibertyCell *cell, - const RiseFall *rf, - const Pvt *pvt, +LibertyLibrary::degradeWireSlew(const RiseFall *rf, float in_slew, float wire_delay) const { const TableModel *model = wireSlewDegradationTable(rf); if (model) - return degradeWireSlew(cell, pvt, model, in_slew, wire_delay); + return degradeWireSlew(model, in_slew, wire_delay); else return in_slew; } float -LibertyLibrary::degradeWireSlew(const LibertyCell *cell, - const Pvt *pvt, - const TableModel *model, +LibertyLibrary::degradeWireSlew(const TableModel *model, float in_slew, float wire_delay) const { switch (model->order()) { case 0: - return model->findValue(this, cell, pvt, 0.0, 0.0, 0.0); + return model->findValue(0.0, 0.0, 0.0); case 1: { TableAxisPtr axis1 = model->axis1(); TableAxisVariable var1 = axis1->variable(); if (var1 == TableAxisVariable::output_pin_transition) - return model->findValue(this, cell, pvt, in_slew, 0.0, 0.0); + return model->findValue(in_slew, 0.0, 0.0); else if (var1 == TableAxisVariable::connect_delay) - return model->findValue(this, cell, pvt, wire_delay, 0.0, 0.0); + return model->findValue(wire_delay, 0.0, 0.0); else { criticalError(231, "unsupported slew degradation table axes"); return 0.0; @@ -377,10 +373,10 @@ LibertyLibrary::degradeWireSlew(const LibertyCell *cell, TableAxisVariable var2 = axis2->variable(); if (var1 == TableAxisVariable::output_pin_transition && var2 == TableAxisVariable::connect_delay) - return model->findValue(this, cell, pvt, in_slew, wire_delay, 0.0); + return model->findValue(in_slew, wire_delay, 0.0); else if (var1 == TableAxisVariable::connect_delay && var2 == TableAxisVariable::output_pin_transition) - return model->findValue(this, cell, pvt, wire_delay, in_slew, 0.0); + return model->findValue(wire_delay, in_slew, 0.0); else { criticalError(232, "unsupported slew degradation table axes"); return 0.0; @@ -2580,8 +2576,7 @@ LibertyPort::clockTreePathDelays() GateTimingModel *gate_model = dynamic_cast(model); ArcDelay delay; Slew slew; - gate_model->gateDelay(liberty_cell_, nullptr, 0.0, 0.0, 0.0, false, - delay, slew); + gate_model->gateDelay(nullptr, 0.0, 0.0, 0.0, false, delay, slew); const RiseFall *rf = arc->toEdge()->asRiseFall(); const MinMax *min_max = (role == TimingRole::clockTreePathMin()) ? MinMax::min() @@ -2911,8 +2906,8 @@ ScaleFactors::ScaleFactors(const char *name) : { for (int type = 0; type < scale_factor_type_count; type++) { for (int pvt = 0; pvt < scale_factor_pvt_count; pvt++) { - for (auto tr_index : RiseFall::rangeIndex()) { - scales_[type][pvt][tr_index] = 0.0; + for (auto rf_index : RiseFall::rangeIndex()) { + scales_[type][pvt][rf_index] = 0.0; } } } @@ -2951,9 +2946,9 @@ ScaleFactors::scale(ScaleFactorType type, float ScaleFactors::scale(ScaleFactorType type, ScaleFactorPvt pvt, - int tr_index) + int rf_index) { - return scales_[int(type)][int(pvt)][tr_index]; + return scales_[int(type)][int(pvt)][rf_index]; } float @@ -3050,9 +3045,9 @@ OcvDerate::OcvDerate(const char *name) : name_(name) { for (auto el_index : EarlyLate::rangeIndex()) { - for (auto tr_index : RiseFall::rangeIndex()) { - derate_[tr_index][el_index][int(PathType::clk)] = nullptr; - derate_[tr_index][el_index][int(PathType::data)] = nullptr; + for (auto rf_index : RiseFall::rangeIndex()) { + derate_[rf_index][el_index][int(PathType::clk)] = nullptr; + derate_[rf_index][el_index][int(PathType::data)] = nullptr; } } } diff --git a/liberty/LibertyReader.cc b/liberty/LibertyReader.cc index 0bef2ef2..049d345c 100644 --- a/liberty/LibertyReader.cc +++ b/liberty/LibertyReader.cc @@ -1939,7 +1939,7 @@ void LibertyReader::makeTimingArcs(PortGroup *port_group) { for (TimingGroup *timing : port_group->timingGroups()) { - timing->makeTimingModels(library_, this); + timing->makeTimingModels(cell_, this); for (LibertyPort *port : *port_group->ports()) makeTimingArcs(port, timing); @@ -2204,15 +2204,15 @@ LibertyReader::makeTimingArcs(LibertyPort *to_port, } void -TimingGroup::makeTimingModels(LibertyLibrary *library, +TimingGroup::makeTimingModels(LibertyCell *cell, LibertyReader *visitor) { - switch (library->delayModelType()) { + switch (cell->libertyLibrary()->delayModelType()) { case DelayModelType::cmos_linear: - makeLinearModels(library); + makeLinearModels(cell); break; case DelayModelType::table: - makeTableModels(visitor); + makeTableModels(cell, visitor); break; case DelayModelType::cmos_pwl: case DelayModelType::cmos2: @@ -2223,8 +2223,9 @@ TimingGroup::makeTimingModels(LibertyLibrary *library, } void -TimingGroup::makeLinearModels(LibertyLibrary *library) +TimingGroup::makeLinearModels(LibertyCell *cell) { + LibertyLibrary *library = cell->libertyLibrary(); for (auto rf : RiseFall::range()) { int rf_index = rf->index(); float intr = intrinsic_[rf_index]; @@ -2234,7 +2235,7 @@ TimingGroup::makeLinearModels(LibertyLibrary *library) TimingModel *model = nullptr; if (timingTypeIsCheck(attrs_->timingType())) { if (intr_exists) - model = new CheckLinearModel(intr); + model = new CheckLinearModel(cell, intr); } else { float res = resistance_[rf_index]; @@ -2245,22 +2246,23 @@ TimingGroup::makeLinearModels(LibertyLibrary *library) if (!res_exists) res = 0.0F; if (intr_exists) - model = new GateLinearModel(intr, res); + model = new GateLinearModel(cell, intr, res); } attrs_->setModel(rf, model); } } void -TimingGroup::makeTableModels(LibertyReader *reader) +TimingGroup::makeTableModels(LibertyCell *cell, + LibertyReader *reader) { for (auto rf : RiseFall::range()) { int rf_index = rf->index(); - TableModel *cell = cell_[rf_index]; + TableModel *delay = cell_[rf_index]; TableModel *transition = transition_[rf_index]; TableModel *constraint = constraint_[rf_index]; - if (cell || transition) { - attrs_->setModel(rf, new GateTableModel(cell, delay_sigma_[rf_index], + if (delay || transition) { + attrs_->setModel(rf, new GateTableModel(cell, delay, delay_sigma_[rf_index], transition, slew_sigma_[rf_index], receiver_model_, @@ -2281,11 +2283,11 @@ TimingGroup::makeTableModels(LibertyReader *reader) || timing_type == TimingType::three_state_enable_rise) { if (transition == nullptr) reader->libWarn(95, line_, "missing %s_transition.", rf->name()); - if (cell == nullptr) + if (delay == nullptr) reader->libWarn(96, line_, "missing cell_%s.", rf->name()); } } else if (constraint) - attrs_->setModel(rf, new CheckTableModel(constraint, + attrs_->setModel(rf, new CheckTableModel(cell, constraint, constraint_sigma_[rf_index])); } } diff --git a/liberty/LibertyReaderPvt.hh b/liberty/LibertyReaderPvt.hh index 4a32067d..f99fa1f1 100644 --- a/liberty/LibertyReaderPvt.hh +++ b/liberty/LibertyReaderPvt.hh @@ -784,7 +784,7 @@ public: TableModel *transition(RiseFall *rf); void setTransition(RiseFall *rf, TableModel *model); - void makeTimingModels(LibertyLibrary *library, + void makeTimingModels(LibertyCell *cell, LibertyReader *visitor); void setDelaySigma(RiseFall *rf, EarlyLate *early_late, @@ -801,8 +801,9 @@ public: OutputWaveforms *output_current); protected: - void makeLinearModels(LibertyLibrary *library); - void makeTableModels(LibertyReader *reader); + void makeLinearModels(LibertyCell *cell); + void makeTableModels(LibertyCell *cell, + LibertyReader *reader); TimingArcAttrsPtr attrs_; const char *related_output_port_name_; diff --git a/liberty/LinearModel.cc b/liberty/LinearModel.cc index 6baa5671..1ba8ff8b 100644 --- a/liberty/LinearModel.cc +++ b/liberty/LinearModel.cc @@ -21,16 +21,17 @@ namespace sta { -GateLinearModel::GateLinearModel(float intrinsic, +GateLinearModel::GateLinearModel(LibertyCell *cell, + float intrinsic, float resistance) : + GateTimingModel(cell), intrinsic_(intrinsic), resistance_(resistance) { } void -GateLinearModel::gateDelay(const LibertyCell *, - const Pvt *, +GateLinearModel::gateDelay(const Pvt *, float, float load_cap, float, @@ -44,15 +45,14 @@ GateLinearModel::gateDelay(const LibertyCell *, } string -GateLinearModel::reportGateDelay(const LibertyCell *cell, - const Pvt *, +GateLinearModel::reportGateDelay(const Pvt *, float, float load_cap, float, bool, int digits) const { - const LibertyLibrary *library = cell->libertyLibrary(); + const LibertyLibrary *library = cell_->libertyLibrary(); const Units *units = library->units(); const Unit *time_unit = units->timeUnit(); const Unit *res_unit = units->resistanceUnit(); @@ -70,8 +70,7 @@ GateLinearModel::reportGateDelay(const LibertyCell *cell, } float -GateLinearModel::driveResistance(const LibertyCell *, - const Pvt *) const +GateLinearModel::driveResistance(const Pvt *) const { return resistance_; } @@ -81,14 +80,15 @@ GateLinearModel::setIsScaled(bool) { } -CheckLinearModel::CheckLinearModel(float intrinsic) : +CheckLinearModel::CheckLinearModel(LibertyCell *cell, + float intrinsic) : + CheckTimingModel(cell), intrinsic_(intrinsic) { } void -CheckLinearModel::checkDelay(const LibertyCell *, - const Pvt *, +CheckLinearModel::checkDelay(const Pvt *, float, float, float, @@ -99,8 +99,7 @@ CheckLinearModel::checkDelay(const LibertyCell *, } string -CheckLinearModel::reportCheckDelay(const LibertyCell *cell, - const Pvt *, +CheckLinearModel::reportCheckDelay(const Pvt *, float, const char *, float, @@ -108,7 +107,7 @@ CheckLinearModel::reportCheckDelay(const LibertyCell *cell, bool, int digits) const { - const LibertyLibrary *library = cell->libertyLibrary(); + const LibertyLibrary *library = cell_->libertyLibrary(); const Units *units = library->units(); const Unit *time_unit = units->timeUnit(); string result = "Check = "; diff --git a/liberty/TableModel.cc b/liberty/TableModel.cc index 57e9865a..7ec97126 100644 --- a/liberty/TableModel.cc +++ b/liberty/TableModel.cc @@ -34,19 +34,26 @@ using std::make_shared; static void deleteSigmaModels(TableModel *models[EarlyLate::index_count]); static string -reportPvt(const LibertyLibrary *library, - const Pvt *pvt, +reportPvt(const LibertyCell *cell, + const Pvt *pvt, int digits); static void appendSpaces(string &result, int count); -GateTableModel::GateTableModel(TableModel *delay_model, +TimingModel::TimingModel(LibertyCell *cell) : + cell_(cell) +{ +} + +GateTableModel::GateTableModel(LibertyCell *cell, + TableModel *delay_model, TableModel *delay_sigma_models[EarlyLate::index_count], TableModel *slew_model, TableModel *slew_sigma_models[EarlyLate::index_count], ReceiverModelPtr receiver_model, OutputWaveforms *output_waveforms) : + GateTimingModel(cell), delay_model_(delay_model), slew_model_(slew_model), receiver_model_(receiver_model), @@ -94,8 +101,7 @@ GateTableModel::setIsScaled(bool is_scaled) } void -GateTableModel::gateDelay(const LibertyCell *cell, - const Pvt *pvt, +GateTableModel::gateDelay(const Pvt *pvt, float in_slew, float load_cap, float related_out_cap, @@ -104,30 +110,23 @@ GateTableModel::gateDelay(const LibertyCell *cell, ArcDelay &gate_delay, Slew &drvr_slew) const { - const LibertyLibrary *library = cell->libertyLibrary(); - float delay = findValue(library, cell, pvt, delay_model_, in_slew, - load_cap, related_out_cap); + float delay = findValue(pvt, delay_model_, in_slew, load_cap, related_out_cap); float sigma_early = 0.0; float sigma_late = 0.0; if (pocv_enabled && delay_sigma_models_[EarlyLate::earlyIndex()]) - sigma_early = findValue(library, cell, pvt, - delay_sigma_models_[EarlyLate::earlyIndex()], + sigma_early = findValue(pvt, delay_sigma_models_[EarlyLate::earlyIndex()], in_slew, load_cap, related_out_cap); if (pocv_enabled && delay_sigma_models_[EarlyLate::lateIndex()]) - sigma_late = findValue(library, cell, pvt, - delay_sigma_models_[EarlyLate::lateIndex()], + sigma_late = findValue(pvt, delay_sigma_models_[EarlyLate::lateIndex()], in_slew, load_cap, related_out_cap); gate_delay = makeDelay(delay, sigma_early, sigma_late); - float slew = findValue(library, cell, pvt, slew_model_, in_slew, - load_cap, related_out_cap); + float slew = findValue(pvt, slew_model_, in_slew, load_cap, related_out_cap); if (pocv_enabled && slew_sigma_models_[EarlyLate::earlyIndex()]) - sigma_early = findValue(library, cell, pvt, - slew_sigma_models_[EarlyLate::earlyIndex()], + sigma_early = findValue(pvt, slew_sigma_models_[EarlyLate::earlyIndex()], in_slew, load_cap, related_out_cap); if (pocv_enabled && slew_sigma_models_[EarlyLate::lateIndex()]) - sigma_late = findValue(library, cell, pvt, - slew_sigma_models_[EarlyLate::lateIndex()], + sigma_late = findValue(pvt, slew_sigma_models_[EarlyLate::lateIndex()], in_slew, load_cap, related_out_cap); // Clip negative slews to zero. if (slew < 0.0) @@ -136,39 +135,36 @@ GateTableModel::gateDelay(const LibertyCell *cell, } string -GateTableModel::reportGateDelay(const LibertyCell *cell, - const Pvt *pvt, +GateTableModel::reportGateDelay(const Pvt *pvt, float in_slew, float load_cap, float related_out_cap, bool pocv_enabled, int digits) const { - const LibertyLibrary *library = cell->libertyLibrary(); - string result = reportPvt(library, pvt, digits); - result += reportTableLookup("Delay", library, cell, pvt, delay_model_, in_slew, + string result = reportPvt(cell_, pvt, digits); + result += reportTableLookup("Delay", pvt, delay_model_, in_slew, load_cap, related_out_cap, digits); if (pocv_enabled && delay_sigma_models_[EarlyLate::earlyIndex()]) - result += reportTableLookup("Delay sigma(early)", library, cell, pvt, + result += reportTableLookup("Delay sigma(early)", pvt, delay_sigma_models_[EarlyLate::earlyIndex()], in_slew, load_cap, related_out_cap, digits); if (pocv_enabled && delay_sigma_models_[EarlyLate::lateIndex()]) - result += reportTableLookup("Delay sigma(late)", library, cell, pvt, + result += reportTableLookup("Delay sigma(late)", pvt, delay_sigma_models_[EarlyLate::lateIndex()], in_slew, load_cap, related_out_cap, digits); result += '\n'; - result += reportTableLookup("Slew", library, cell, pvt, slew_model_, in_slew, + result += reportTableLookup("Slew", pvt, slew_model_, in_slew, load_cap, related_out_cap, digits); if (pocv_enabled && slew_sigma_models_[EarlyLate::earlyIndex()]) - result += reportTableLookup("Slew sigma(early)", library, cell, pvt, + result += reportTableLookup("Slew sigma(early)", pvt, slew_sigma_models_[EarlyLate::earlyIndex()], in_slew, load_cap, related_out_cap, digits); if (pocv_enabled && slew_sigma_models_[EarlyLate::lateIndex()]) - result += reportTableLookup("Slew sigma(late)", library, cell, pvt, + result += reportTableLookup("Slew sigma(late)", pvt, slew_sigma_models_[EarlyLate::lateIndex()], in_slew, load_cap, related_out_cap, digits); - float drvr_slew = findValue(library, cell, pvt, slew_model_, in_slew, - load_cap, related_out_cap); + float drvr_slew = findValue(pvt, slew_model_, in_slew, load_cap, related_out_cap); if (drvr_slew < 0.0) result += "Negative slew clipped to 0.0\n"; return result; @@ -176,8 +172,6 @@ GateTableModel::reportGateDelay(const LibertyCell *cell, string GateTableModel::reportTableLookup(const char *result_name, - const LibertyLibrary *library, - const LibertyCell *cell, const Pvt *pvt, const TableModel *model, float in_slew, @@ -189,17 +183,16 @@ GateTableModel::reportTableLookup(const char *result_name, float axis_value1, axis_value2, axis_value3; findAxisValues(model, in_slew, load_cap, related_out_cap, axis_value1, axis_value2, axis_value3); - return model->reportValue(result_name, library, cell, pvt, - axis_value1, nullptr, axis_value2, axis_value3, + const LibertyLibrary *library = cell_->libertyLibrary(); + return model->reportValue(result_name, cell_, pvt, axis_value1, nullptr, + axis_value2, axis_value3, library->units()->timeUnit(), digits); } return ""; } float -GateTableModel::findValue(const LibertyLibrary *library, - const LibertyCell *cell, - const Pvt *pvt, +GateTableModel::findValue(const Pvt *pvt, const TableModel *model, float in_slew, float load_cap, @@ -209,8 +202,7 @@ GateTableModel::findValue(const LibertyLibrary *library, float axis_value1, axis_value2, axis_value3; findAxisValues(model, in_slew, load_cap, related_out_cap, axis_value1, axis_value2, axis_value3); - return model->findValue(library, cell, pvt, - axis_value1, axis_value2, axis_value3); + return model->findValue(cell_, pvt, axis_value1, axis_value2, axis_value3); } else return 0.0; @@ -264,42 +256,36 @@ GateTableModel::findAxisValues(const TableModel *model, // Use slew/Cload for the highest Cload, which approximates output // admittance as the "drive". float -GateTableModel::driveResistance(const LibertyCell *cell, - const Pvt *pvt) const +GateTableModel::driveResistance(const Pvt *pvt) const { float slew, cap; - maxCapSlew(cell, 0.0, pvt, slew, cap); + maxCapSlew(0.0, pvt, slew, cap); return slew / cap; } void -GateTableModel::maxCapSlew(const LibertyCell *cell, - float in_slew, +GateTableModel::maxCapSlew(float in_slew, const Pvt *pvt, float &slew, float &cap) const { - const LibertyLibrary *library = cell->libertyLibrary(); TableAxisPtr axis1 = slew_model_->axis1(); TableAxisPtr axis2 = slew_model_->axis2(); TableAxisPtr axis3 = slew_model_->axis3(); if (axis1 && axis1->variable() == TableAxisVariable::total_output_net_capacitance) { cap = axis1->axisValue(axis1->size() - 1); - slew = findValue(library, cell, pvt, slew_model_, - in_slew, cap, 0.0); + slew = findValue(pvt, slew_model_, in_slew, cap, 0.0); } else if (axis2 && axis2->variable()==TableAxisVariable::total_output_net_capacitance) { cap = axis2->axisValue(axis2->size() - 1); - slew = findValue(library, cell, pvt, slew_model_, - in_slew, cap, 0.0); + slew = findValue(pvt, slew_model_, in_slew, cap, 0.0); } else if (axis3 && axis3->variable()==TableAxisVariable::total_output_net_capacitance) { cap = axis3->axisValue(axis3->size() - 1); - slew = findValue(library, cell, pvt, slew_model_, - in_slew, cap, 0.0); + slew = findValue(pvt, slew_model_, in_slew, cap, 0.0); } else { // Table not dependent on capacitance. @@ -399,8 +385,10 @@ ReceiverModel::checkAxes(TablePtr table) //////////////////////////////////////////////////////////////// -CheckTableModel::CheckTableModel(TableModel *model, +CheckTableModel::CheckTableModel(LibertyCell *cell, + TableModel *model, TableModel *sigma_models[EarlyLate::index_count]) : + CheckTimingModel(cell), model_(model) { for (auto el_index : EarlyLate::rangeIndex()) @@ -420,8 +408,7 @@ CheckTableModel::setIsScaled(bool is_scaled) } void -CheckTableModel::checkDelay(const LibertyCell *cell, - const Pvt *pvt, +CheckTableModel::checkDelay(const Pvt *pvt, float from_slew, float to_slew, float related_out_cap, @@ -430,18 +417,14 @@ CheckTableModel::checkDelay(const LibertyCell *cell, ArcDelay &margin) const { if (model_) { - const LibertyLibrary *library = cell->libertyLibrary(); - float mean = findValue(library, cell, pvt, model_, - from_slew, to_slew, related_out_cap); + float mean = findValue(pvt, model_, from_slew, to_slew, related_out_cap); float sigma_early = 0.0; float sigma_late = 0.0; if (pocv_enabled && sigma_models_[EarlyLate::earlyIndex()]) - sigma_early = findValue(library, cell, pvt, - sigma_models_[EarlyLate::earlyIndex()], + sigma_early = findValue(pvt, sigma_models_[EarlyLate::earlyIndex()], from_slew, to_slew, related_out_cap); if (pocv_enabled && sigma_models_[EarlyLate::lateIndex()]) - sigma_late = findValue(library, cell, pvt, - sigma_models_[EarlyLate::lateIndex()], + sigma_late = findValue(pvt, sigma_models_[EarlyLate::lateIndex()], from_slew, to_slew, related_out_cap); margin = makeDelay(mean, sigma_early, sigma_late); } @@ -450,9 +433,7 @@ CheckTableModel::checkDelay(const LibertyCell *cell, } float -CheckTableModel::findValue(const LibertyLibrary *library, - const LibertyCell *cell, - const Pvt *pvt, +CheckTableModel::findValue(const Pvt *pvt, const TableModel *model, float from_slew, float to_slew, @@ -462,16 +443,14 @@ CheckTableModel::findValue(const LibertyLibrary *library, float axis_value1, axis_value2, axis_value3; findAxisValues(from_slew, to_slew, related_out_cap, axis_value1, axis_value2, axis_value3); - return model->findValue(library, cell, pvt, - axis_value1, axis_value2, axis_value3); + return model->findValue(cell_, pvt, axis_value1, axis_value2, axis_value3); } else return 0.0; } string -CheckTableModel::reportCheckDelay(const LibertyCell *cell, - const Pvt *pvt, +CheckTableModel::reportCheckDelay(const Pvt *pvt, float from_slew, const char *from_slew_annotation, float to_slew, @@ -479,17 +458,16 @@ CheckTableModel::reportCheckDelay(const LibertyCell *cell, bool pocv_enabled, int digits) const { - const LibertyLibrary *library = cell->libertyLibrary(); - string result = reportTableDelay("Check", library, cell, pvt, model_, + string result = reportTableDelay("Check", pvt, model_, from_slew, from_slew_annotation, to_slew, related_out_cap, digits); if (pocv_enabled && sigma_models_[EarlyLate::earlyIndex()]) - result += reportTableDelay("Check sigma early", library, cell, pvt, + result += reportTableDelay("Check sigma early", pvt, sigma_models_[EarlyLate::earlyIndex()], from_slew, from_slew_annotation, to_slew, related_out_cap, digits); if (pocv_enabled && sigma_models_[EarlyLate::lateIndex()]) - result += reportTableDelay("Check sigma late", library, cell, pvt, + result += reportTableDelay("Check sigma late", pvt, sigma_models_[EarlyLate::lateIndex()], from_slew, from_slew_annotation, to_slew, related_out_cap, digits); @@ -498,8 +476,6 @@ CheckTableModel::reportCheckDelay(const LibertyCell *cell, string CheckTableModel::reportTableDelay(const char *result_name, - const LibertyLibrary *library, - const LibertyCell *cell, const Pvt *pvt, const TableModel *model, float from_slew, @@ -512,10 +488,11 @@ CheckTableModel::reportTableDelay(const char *result_name, float axis_value1, axis_value2, axis_value3; findAxisValues(from_slew, to_slew, related_out_cap, axis_value1, axis_value2, axis_value3); - string result = reportPvt(library, pvt, digits); - result += model_->reportValue(result_name, library, cell, pvt, + string result = reportPvt(cell_, pvt, digits); + result += model_->reportValue(result_name, cell_, pvt, axis_value1, from_slew_annotation, axis_value2, - axis_value3, library->units()->timeUnit(), digits); + axis_value3, + cell_->libertyLibrary()->units()->timeUnit(), digits); return result; } return ""; @@ -665,20 +642,26 @@ TableModel::value(size_t axis_index1, } float -TableModel::findValue(const LibertyLibrary *library, - const LibertyCell *cell, +TableModel::findValue(float axis_value1, + float axis_value2, + float axis_value3) const +{ + return table_->findValue(axis_value1, axis_value2, axis_value3); +} + +float +TableModel::findValue(const LibertyCell *cell, const Pvt *pvt, float axis_value1, float axis_value2, float axis_value3) const { return table_->findValue(axis_value1, axis_value2, axis_value3) - * scaleFactor(library, cell, pvt); + * scaleFactor(cell, pvt); } float -TableModel::scaleFactor(const LibertyLibrary *library, - const LibertyCell *cell, +TableModel::scaleFactor(const LibertyCell *cell, const Pvt *pvt) const { if (is_scaled_) @@ -686,13 +669,12 @@ TableModel::scaleFactor(const LibertyLibrary *library, // nominal pvt. return 1.0F; else - return library->scaleFactor(static_cast(scale_factor_type_), - rf_index_, cell, pvt); + return cell->libertyLibrary()->scaleFactor(static_cast(scale_factor_type_), + rf_index_, cell, pvt); } string TableModel::reportValue(const char *result_name, - const LibertyLibrary *library, const LibertyCell *cell, const Pvt *pvt, float value1, @@ -702,24 +684,24 @@ TableModel::reportValue(const char *result_name, const Unit *table_unit, int digits) const { - string result = table_->reportValue("Table value", library, cell, pvt, value1, + string result = table_->reportValue("Table value", cell, pvt, value1, comment1, value2, value3, table_unit, digits); - result += reportPvtScaleFactor(library, cell, pvt, digits); + result += reportPvtScaleFactor(cell, pvt, digits); result += result_name; result += " = "; - result += table_unit->asString(findValue(library, cell, pvt, - value1, value2, value3), digits); + result += table_unit->asString(findValue(cell, pvt, value1, value2, value3), digits); result += '\n'; return result; } static string -reportPvt(const LibertyLibrary *library, +reportPvt(const LibertyCell *cell, const Pvt *pvt, int digits) { + const LibertyLibrary *library = cell->libertyLibrary(); if (pvt == nullptr) pvt = library->defaultOperatingConditions(); if (pvt) { @@ -734,18 +716,17 @@ reportPvt(const LibertyLibrary *library, } string -TableModel::reportPvtScaleFactor(const LibertyLibrary *library, - const LibertyCell *cell, +TableModel::reportPvtScaleFactor(const LibertyCell *cell, const Pvt *pvt, int digits) const { if (pvt == nullptr) - pvt = library->defaultOperatingConditions(); + pvt = cell->libertyLibrary()->defaultOperatingConditions(); if (pvt) { string result; stringPrint(result, "PVT scale factor = %.*f\n", digits, - scaleFactor(library, cell, pvt)); + scaleFactor(cell, pvt)); return result; } return ""; @@ -777,7 +758,6 @@ Table0::findValue(float, string Table0::reportValue(const char *result_name, - const LibertyLibrary *, const LibertyCell *, const Pvt *, float value1, @@ -930,9 +910,8 @@ Table1::findValueClipZero(float axis_value1) const } string -Table1::reportValue(const char *result_name, const - LibertyLibrary *library, - const LibertyCell *, +Table1::reportValue(const char *result_name, + const LibertyCell *cell, const Pvt *, float value1, const char *comment1, @@ -941,7 +920,7 @@ Table1::reportValue(const char *result_name, const const Unit *table_unit, int digits) const { - const Units *units = library->units(); + const Units *units = cell->libertyLibrary()->units(); const Unit *unit1 = axis1_->unit(units); string result = "Table is indexed by\n "; result += axis1_->variableString(); @@ -1098,8 +1077,7 @@ Table2::findValue(float axis_value1, string Table2::reportValue(const char *result_name, - const LibertyLibrary *library, - const LibertyCell *, + const LibertyCell *cell, const Pvt *, float value1, const char *comment1, @@ -1108,7 +1086,7 @@ Table2::reportValue(const char *result_name, const Unit *table_unit, int digits) const { - const Units *units = library->units(); + const Units *units = cell->libertyLibrary()->units(); const Unit *unit1 = axis1_->unit(units); const Unit *unit2 = axis2_->unit(units); string result = "------- "; @@ -1289,8 +1267,7 @@ Table3::findValue(float axis_value1, // 0.40 | 0.20 0.30 string Table3::reportValue(const char *result_name, - const LibertyLibrary *library, - const LibertyCell *, + const LibertyCell *cell, const Pvt *, float value1, const char *comment1, @@ -1299,7 +1276,7 @@ Table3::reportValue(const char *result_name, const Unit *table_unit, int digits) const { - const Units *units = library->units(); + const Units *units = cell->libertyLibrary()->units(); const Unit *unit1 = axis1_->unit(units); const Unit *unit2 = axis2_->unit(units); const Unit *unit3 = axis3_->unit(units); diff --git a/liberty/TimingArc.cc b/liberty/TimingArc.cc index ba7301c7..0f1532e3 100644 --- a/liberty/TimingArc.cc +++ b/liberty/TimingArc.cc @@ -149,10 +149,8 @@ float TimingArc::driveResistance() const { GateTimingModel *model = dynamic_cast(model_); - if (model) { - LibertyCell *cell = set_->libertyCell(); - return model->driveResistance(cell, nullptr); - } + if (model) + return model->driveResistance(nullptr); else return 0.0; } @@ -162,11 +160,9 @@ TimingArc::intrinsicDelay() const { GateTimingModel *model = dynamic_cast(model_); if (model) { - LibertyCell *cell = set_->libertyCell(); ArcDelay arc_delay; Slew slew; - model->gateDelay(cell, nullptr, 0.0, 0.0, 0.0, false, - arc_delay, slew); + model->gateDelay(nullptr, 0.0, 0.0, 0.0, false, arc_delay, slew); return arc_delay; } else diff --git a/liberty/TimingModel.cc b/liberty/TimingModel.cc new file mode 100644 index 00000000..efb73cf6 --- /dev/null +++ b/liberty/TimingModel.cc @@ -0,0 +1,31 @@ +// OpenSTA, Static Timing Analyzer +// Copyright (c) 2023, 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 "TimingModel.hh" + +namespace sta { + +GateTimingModel::GateTimingModel(LibertyCell *cell) : + TimingModel(cell) +{ +} + +CheckTimingModel::CheckTimingModel(LibertyCell *cell) : + TimingModel(cell) +{ +} + +} // namespace diff --git a/sdc/Sdc.cc b/sdc/Sdc.cc index 51e2e20e..c4c071d8 100644 --- a/sdc/Sdc.cc +++ b/sdc/Sdc.cc @@ -472,7 +472,7 @@ Sdc::operatingConditions(const MinMax *min_max) } const Pvt * -Sdc::pvt(Instance *inst, +Sdc::pvt(const Instance *inst, const MinMax *min_max) const { const InstancePvtMap &pvt_map = instance_pvt_maps_[min_max->index()]; diff --git a/search/MakeTimingModel.cc b/search/MakeTimingModel.cc index 3788d507..58932034 100644 --- a/search/MakeTimingModel.cc +++ b/search/MakeTimingModel.cc @@ -581,7 +581,7 @@ MakeTimingModel::makeScalarCheckModel(float value, library_->findTableTemplate("scalar", TableTemplateType::delay); TableModel *table_model = new TableModel(table, tbl_template, scale_factor_type, rf); - CheckTableModel *check_model = new CheckTableModel(table_model, nullptr); + CheckTableModel *check_model = new CheckTableModel(cell_, table_model, nullptr); return check_model; } @@ -598,7 +598,7 @@ MakeTimingModel::makeGateModelScalar(Delay delay, ScaleFactorType::cell, rf); TableModel *slew_model = new TableModel(slew_table, tbl_template, ScaleFactorType::cell, rf); - GateTableModel *gate_model = new GateTableModel(delay_model, nullptr, + GateTableModel *gate_model = new GateTableModel(cell_, delay_model, nullptr, slew_model, nullptr, nullptr, nullptr); return gate_model; @@ -613,7 +613,7 @@ MakeTimingModel::makeGateModelScalar(Delay delay, library_->findTableTemplate("scalar", TableTemplateType::delay); TableModel *delay_model = new TableModel(delay_table, tbl_template, ScaleFactorType::cell, rf); - GateTableModel *gate_model = new GateTableModel(delay_model, nullptr, + GateTableModel *gate_model = new GateTableModel(cell_, delay_model, nullptr, nullptr, nullptr, nullptr, nullptr); return gate_model; @@ -655,8 +655,7 @@ MakeTimingModel::makeGateModelTable(const Pin *output_pin, float output_load_cap = graph_delay_calc_->loadCap(output_pin, dcalc_ap); ArcDelay drvr_self_delay; Slew drvr_self_slew; - drvr_gate_model->gateDelay(drvr_cell, pvt, in_slew1, - output_load_cap, 0.0, false, + drvr_gate_model->gateDelay(pvt, in_slew1, output_load_cap, 0.0, false, drvr_self_delay, drvr_self_slew); const TableModel *drvr_table = drvr_gate_model->delayModel(); @@ -671,8 +670,7 @@ MakeTimingModel::makeGateModelTable(const Pin *output_pin, // get slew from driver input pin ArcDelay gate_delay; Slew gate_slew; - drvr_gate_model->gateDelay(drvr_cell, pvt, in_slew1, - load_cap, 0.0, false, + drvr_gate_model->gateDelay(pvt, in_slew1, load_cap, 0.0, false, gate_delay, gate_slew); // Remove the self delay driving the output pin net load cap. load_values->push_back(delayAsFloat(delay + gate_delay @@ -694,7 +692,8 @@ MakeTimingModel::makeGateModelTable(const Pin *output_pin, ScaleFactorType::cell, rf); TableModel *slew_model = new TableModel(slew_table, model_template, ScaleFactorType::cell, rf); - GateTableModel *gate_model = new GateTableModel(delay_model, nullptr, + GateTableModel *gate_model = new GateTableModel(cell_, + delay_model, nullptr, slew_model, nullptr, nullptr, nullptr); return gate_model; diff --git a/search/Sta.cc b/search/Sta.cc index 2e7500aa..bb437451 100644 --- a/search/Sta.cc +++ b/search/Sta.cc @@ -3789,22 +3789,11 @@ Sta::connectedCap(const Pin *drvr_pin, float &pin_cap, float &wire_cap) const { - pin_cap = 0.0; - wire_cap = 0.0; - bool cap_exists = false; const DcalcAnalysisPt *dcalc_ap = corner->findDcalcAnalysisPt(min_max); Parasitic *parasitic = arc_delay_calc_->findParasitic(drvr_pin, rf, dcalc_ap); - float ap_pin_cap = 0.0; - float ap_wire_cap = 0.0; graph_delay_calc_->loadCap(drvr_pin, parasitic, rf, dcalc_ap, - ap_pin_cap, ap_wire_cap); + pin_cap, wire_cap); arc_delay_calc_->finishDrvrPin(); - if (!cap_exists - || min_max->compare(ap_pin_cap, pin_cap)) { - pin_cap = ap_pin_cap; - wire_cap = ap_wire_cap; - cap_exists = true; - } } void From 2b4fccbf59e56aa177d81add9d9de686f771757d Mon Sep 17 00:00:00 2001 From: James Cherry Date: Thu, 23 Nov 2023 07:39:18 -0800 Subject: [PATCH 18/19] ccs table indices Signed-off-by: James Cherry --- liberty/LibertyReader.cc | 2 +- liberty/TableModel.cc | 30 +++++++++++++++--------------- 2 files changed, 16 insertions(+), 16 deletions(-) diff --git a/liberty/LibertyReader.cc b/liberty/LibertyReader.cc index 049d345c..7cf3491d 100644 --- a/liberty/LibertyReader.cc +++ b/liberty/LibertyReader.cc @@ -2526,7 +2526,7 @@ LibertyReader::endOutputCurrentRiseFall(LibertyGroup *group) slew_axis->findAxisIndex(waveform->slew(), slew_index, slew_exists); cap_axis->findAxisIndex(waveform->cap(), cap_index, cap_exists); if (slew_exists && cap_exists) { - size_t index = slew_index * slew_axis->size() + cap_index; + size_t index = slew_index * cap_axis->size() + cap_index; current_waveforms[index] = waveform->stealCurrents(); (*ref_times)[slew_index] = waveform->referenceTime(); } diff --git a/liberty/TableModel.cc b/liberty/TableModel.cc index 7ec97126..8400bc2f 100644 --- a/liberty/TableModel.cc +++ b/liberty/TableModel.cc @@ -1639,11 +1639,11 @@ OutputWaveforms::timeCurrent(float slew, { size_t slew_index = slew_axis_->findAxisIndex(slew); size_t cap_index = cap_axis_->findAxisIndex(cap); - size_t slew_count = slew_axis_->size(); - size_t wave_index00 = slew_index * slew_count + cap_index; - size_t wave_index01 = slew_index * slew_count + (cap_index + 1); - size_t wave_index10 = (slew_index + 1) * slew_count + cap_index; - size_t wave_index11 = (slew_index + 1) * slew_count + (cap_index + 1); + size_t cap_count = cap_axis_->size(); + size_t wave_index00 = slew_index * cap_count + cap_index; + size_t wave_index01 = slew_index * cap_count + (cap_index + 1); + size_t wave_index10 = (slew_index + 1) * cap_count + cap_index; + size_t wave_index11 = (slew_index + 1) * cap_count + (cap_index + 1); const Table1 *waveform00 = current_waveforms_[wave_index00]; const Table1 *waveform01 = current_waveforms_[wave_index01]; @@ -1710,11 +1710,11 @@ OutputWaveforms::voltageTime(float slew, { size_t slew_index = slew_axis_->findAxisIndex(slew); size_t cap_index = cap_axis_->findAxisIndex(cap); - size_t slew_count = slew_axis_->size(); - size_t wave_index00 = slew_index * slew_count + cap_index; - size_t wave_index01 = slew_index * slew_count + (cap_index + 1); - size_t wave_index10 = (slew_index + 1) * slew_count + cap_index; - size_t wave_index11 = (slew_index + 1) * slew_count + (cap_index + 1); + size_t cap_count = cap_axis_->size(); + size_t wave_index00 = slew_index * cap_count + cap_index; + size_t wave_index01 = slew_index * cap_count + (cap_index + 1); + size_t wave_index10 = (slew_index + 1) * cap_count + cap_index; + size_t wave_index11 = (slew_index + 1) * cap_count + (cap_index + 1); float cap0 = cap_axis_->axisValue(cap_index); float cap1 = cap_axis_->axisValue(cap_index + 1); @@ -1835,11 +1835,11 @@ OutputWaveforms::voltageCurrent(float slew, { size_t slew_index = slew_axis_->findAxisIndex(slew); size_t cap_index = cap_axis_->findAxisIndex(cap); - size_t slew_count = slew_axis_->size(); - size_t wave_index00 = slew_index * slew_count + cap_index; - size_t wave_index01 = slew_index * slew_count + (cap_index + 1); - size_t wave_index10 = (slew_index + 1) * slew_count + cap_index; - size_t wave_index11 = (slew_index + 1) * slew_count + (cap_index + 1); + size_t cap_count = cap_axis_->size(); + size_t wave_index00 = slew_index * cap_count + cap_index; + size_t wave_index01 = slew_index * cap_count + (cap_index + 1); + size_t wave_index10 = (slew_index + 1) * cap_count + cap_index; + size_t wave_index11 = (slew_index + 1) * cap_count + (cap_index + 1); float cap0 = cap_axis_->axisValue(cap_index); float cap1 = cap_axis_->axisValue(cap_index + 1); From 4f870fca7c363764d80e668c3a326040a9a8497e Mon Sep 17 00:00:00 2001 From: James Cherry Date: Thu, 23 Nov 2023 16:27:13 -0800 Subject: [PATCH 19/19] set_input_delay -reference_pin seg fault Signed-off-by: James Cherry --- search/Bfs.cc | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/search/Bfs.cc b/search/Bfs.cc index 8daf1d0c..d634cf2f 100644 --- a/search/Bfs.cc +++ b/search/Bfs.cc @@ -143,17 +143,19 @@ BfsIterator::visit(Level to_level, && levelLessOrEqual(first_level_, to_level)) { VertexSeq &level_vertices = queue_[first_level_]; incrLevel(first_level_); - if (!level_vertices.empty()) { - for (auto vertex : level_vertices) { - if (vertex) { - vertex->setBfsInQueue(bfs_index_, false); - visitor->visit(vertex); - visit_count++; - } + // Note that ArrivalVisitor::enqueueRefPinInputDelays may enqueue + // vertices at this level so range iteration fails if the vector grows. + while (!level_vertices.empty()) { + Vertex *vertex = level_vertices.back(); + level_vertices.pop_back(); + if (vertex) { + vertex->setBfsInQueue(bfs_index_, false); + visitor->visit(vertex); + visit_count++; } - level_vertices.clear(); - visitor->levelFinished(); } + level_vertices.clear(); + visitor->levelFinished(); } return visit_count; }