diff --git a/CMakeLists.txt b/CMakeLists.txt index eaa3c353..5fe12ab8 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -31,6 +31,7 @@ project(STA VERSION 2.5.0 option(USE_CUDD "Use CUDD BDD package") option(CUDD_DIR "CUDD BDD package directory") +# Turn on to debug compiler args. set(CMAKE_VERBOSE_MAKEFILE OFF) set(STA_HOME ${PROJECT_SOURCE_DIR}) @@ -65,6 +66,7 @@ set(STA_SOURCE dcalc/ArcDcalcWaveforms.cc dcalc/ArnoldiDelayCalc.cc dcalc/ArnoldiReduce.cc + dcalc/CcsCeffDelayCalc.cc dcalc/DcalcAnalysisPt.cc dcalc/DelayCalc.cc dcalc/DelayCalcBase.cc @@ -145,6 +147,7 @@ set(STA_SOURCE sdf/SdfReader.cc sdf/SdfWriter.cc + search/Bdd.cc search/Bfs.cc search/CheckMaxSkews.cc search/CheckMinPeriods.cc @@ -154,6 +157,7 @@ set(STA_SOURCE search/CheckSlewLimits.cc search/CheckTiming.cc search/ClkInfo.cc + search/ClkLatency.cc search/ClkNetwork.cc search/ClkSkew.cc search/Corner.cc @@ -188,6 +192,7 @@ set(STA_SOURCE search/VisitPathGroupVertices.cc search/WorstSlack.cc search/WritePathSpice.cc + search/WriteSpice.cc power/Power.cc power/ReadVcdActivities.cc @@ -300,77 +305,6 @@ bison_target(SdfParser ${STA_HOME}/sdf/SdfParse.yy ${CMAKE_CURRENT_BINARY_DIR}/S add_flex_bison_dependency(SdfLex SdfParser) -################################################################ - -find_package(SWIG 3.0 REQUIRED) -include(UseSWIG) - -set(STA_SWIG_FILE app/StaApp.i) - -set_property(SOURCE ${STA_SWIG_FILE} - PROPERTY CPLUSPLUS ON -) -# Ubuntu 18.04 cmake version 3.10.2 does not support the -# COMPILE_OPTIONS and INCLUDE_DIRECTORIES properties, so cram -# them into SWIG_FLAGS for the time being. -set_property(SOURCE ${STA_SWIG_FILE} - PROPERTY SWIG_FLAGS - -module sta - -namespace -prefix sta - -I${STA_HOME}/tcl - -I${STA_HOME}/sdf - -I${STA_HOME}/dcalc - -I${STA_HOME}/parasitics - -I${STA_HOME}/power - -I${STA_HOME}/verilog -) - -set(SWIG_FILES - ${STA_HOME}/dcalc/DelayCalc.i - ${STA_HOME}/parasitics/Parasitics.i - ${STA_HOME}/power/Power.i - ${STA_HOME}/sdf/Sdf.i - ${STA_HOME}/tcl/Exception.i - ${STA_HOME}/tcl/StaTcl.i - ${STA_HOME}/tcl/StaTclTypes.i - ${STA_HOME}/tcl/NetworkEdit.i - ${STA_HOME}/verilog/Verilog.i - ) - -set_property(SOURCE ${STA_SWIG_FILE} - PROPERTY DEPENDS ${SWIG_FILES} -) - -swig_add_library(sta_swig - LANGUAGE tcl - TYPE STATIC - SOURCES ${STA_SWIG_FILE} -) - -get_target_property(STA_SWIG_CXX_FILE sta_swig SOURCES) - -set_source_files_properties(${STA_SWIG_CXX_FILE} - PROPERTIES - # No simple way to modify the swig template that emits code full of warnings - # so suppress them. - COMPILE_OPTIONS "-Wno-cast-qual;-Wno-missing-braces;-Wno-deprecated-declarations" - ) - -target_link_libraries(sta_swig - PUBLIC - OpenSTA -) - -# result build/CMakeFiles/sta_swig.dir/StaAppTCL_wrap.cxx -target_include_directories(sta_swig - PUBLIC - include/sta - - PRIVATE - ${STA_HOME} - ${TCL_INCLUDE_PATH} -) - ################################################################ set(STA_TCL_INIT ${CMAKE_CURRENT_BINARY_DIR}/StaTclInitVar.cc) @@ -469,6 +403,80 @@ configure_file(${STA_HOME}/util/StaConfig.hh.cmake ${STA_HOME}/include/sta/StaConfig.hh ) +########################################################### +# Swig +########################################################### + +find_package(SWIG 3.0 REQUIRED) +include(UseSWIG) + +set(STA_SWIG_FILE app/StaApp.i) + +set_property(SOURCE ${STA_SWIG_FILE} + PROPERTY CPLUSPLUS ON +) +# Ubuntu 18.04 cmake version 3.10.2 does not support the +# COMPILE_OPTIONS and INCLUDE_DIRECTORIES properties, so cram +# them into SWIG_FLAGS for the time being. +set_property(SOURCE ${STA_SWIG_FILE} + PROPERTY SWIG_FLAGS + -module sta + -namespace -prefix sta + -I${STA_HOME}/tcl + -I${STA_HOME}/sdf + -I${STA_HOME}/dcalc + -I${STA_HOME}/parasitics + -I${STA_HOME}/power + -I${STA_HOME}/verilog +) + +set(SWIG_FILES + ${STA_HOME}/dcalc/DelayCalc.i + ${STA_HOME}/parasitics/Parasitics.i + ${STA_HOME}/power/Power.i + ${STA_HOME}/sdf/Sdf.i + ${STA_HOME}/tcl/Exception.i + ${STA_HOME}/tcl/StaTcl.i + ${STA_HOME}/tcl/StaTclTypes.i + ${STA_HOME}/tcl/NetworkEdit.i + ${STA_HOME}/verilog/Verilog.i + ) + +set_property(SOURCE ${STA_SWIG_FILE} + PROPERTY DEPENDS ${SWIG_FILES} +) + +swig_add_library(sta_swig + LANGUAGE tcl + TYPE STATIC + SOURCES ${STA_SWIG_FILE} +) + +get_target_property(STA_SWIG_CXX_FILE sta_swig SOURCES) + +set_source_files_properties(${STA_SWIG_CXX_FILE} + PROPERTIES + # No simple way to modify the swig template that emits code full of warnings + # so suppress them. + COMPILE_OPTIONS "-Wno-cast-qual;-Wno-missing-braces;-Wno-deprecated-declarations" + INCLUDE_DIRECTORIES "${CUDD_INCLUDE}" + ) + +target_link_libraries(sta_swig + PUBLIC + OpenSTA +) + +# result build/CMakeFiles/sta_swig.dir/StaAppTCL_wrap.cxx +target_include_directories(sta_swig + PUBLIC + include/sta + + PRIVATE + ${STA_HOME} + ${TCL_INCLUDE_PATH} +) + ########################################################### # Library ########################################################### diff --git a/dcalc/ArcDelayCalc.cc b/dcalc/ArcDelayCalc.cc index 4b5a3cb7..bb7ea5c1 100644 --- a/dcalc/ArcDelayCalc.cc +++ b/dcalc/ArcDelayCalc.cc @@ -16,6 +16,10 @@ #include "ArcDelayCalc.hh" +#include "Liberty.hh" +#include "TimingArc.hh" +#include "Network.hh" + namespace sta { ArcDelayCalc::ArcDelayCalc(StaState *sta): @@ -53,19 +57,85 @@ ArcDcalcArg::ArcDcalcArg() : { } -ArcDcalcArg::ArcDcalcArg(const Pin *drvr_pin, +ArcDcalcArg::ArcDcalcArg(const Pin *in_pin, + const Pin *drvr_pin, Edge *edge, const TimingArc *arc, const Slew in_slew, const Parasitic *parasitic) : + in_pin_(in_pin), drvr_pin_(drvr_pin), edge_(edge), arc_(arc), in_slew_(in_slew), - parasitic_(parasitic) + parasitic_(parasitic), + input_delay_(0.0) { } +ArcDcalcArg::ArcDcalcArg(const Pin *in_pin, + const Pin *drvr_pin, + Edge *edge, + const TimingArc *arc, + float input_delay) : + in_pin_(in_pin), + drvr_pin_(drvr_pin), + edge_(edge), + arc_(arc), + in_slew_(0.0), + parasitic_(nullptr), + input_delay_(input_delay) +{ +} + +ArcDcalcArg::ArcDcalcArg(const ArcDcalcArg &arg) : + in_pin_(arg.in_pin_), + drvr_pin_(arg.drvr_pin_), + edge_(arg.edge_), + arc_(arg.arc_), + in_slew_(arg.in_slew_), + parasitic_(arg.parasitic_), + input_delay_(arg.input_delay_) +{ +} + +const RiseFall * +ArcDcalcArg::inEdge() const +{ + return arc_->fromEdge()->asRiseFall(); +} + +LibertyCell * +ArcDcalcArg::drvrCell() const +{ + + return arc_->to()->libertyCell(); +} + +const LibertyLibrary * +ArcDcalcArg::drvrLibrary() const +{ + return arc_->to()->libertyLibrary(); +} + +const RiseFall * +ArcDcalcArg::drvrEdge() const +{ + return arc_->toEdge()->asRiseFall(); +} + +const Net * +ArcDcalcArg::drvrNet(const Network *network) const +{ + return network->net(drvr_pin_); +} + +void +ArcDcalcArg::setInSlew(Slew in_slew) +{ + in_slew_ = in_slew; +} + void ArcDcalcArg::setParasitic(const Parasitic *parasitic) { diff --git a/dcalc/ArnoldiReduce.cc b/dcalc/ArnoldiReduce.cc index 5c80c382..91a36412 100644 --- a/dcalc/ArnoldiReduce.cc +++ b/dcalc/ArnoldiReduce.cc @@ -314,7 +314,8 @@ ArnoldiReduce::findPt(ParasiticNode *node) rcmodel * ArnoldiReduce::makeRcmodelDrv() { - ParasiticNode *drv_node = parasitics_->findNode(parasitic_network_, drvr_pin_); + ParasiticNode *drv_node = + parasitics_->findParasiticNode(parasitic_network_, drvr_pin_); ts_point *pdrv = findPt(drv_node); makeRcmodelDfs(pdrv); getRC(); diff --git a/dcalc/CcsCeffDelayCalc.cc b/dcalc/CcsCeffDelayCalc.cc new file mode 100644 index 00000000..7c11eec7 --- /dev/null +++ b/dcalc/CcsCeffDelayCalc.cc @@ -0,0 +1,678 @@ +// OpenSTA, Static Timing Analyzer +// Copyright (c) 2024, 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 "CcsCeffDelayCalc.hh" + +#include "Debug.hh" +#include "Units.hh" +#include "Liberty.hh" +#include "TimingArc.hh" +#include "Network.hh" +#include "Graph.hh" +#include "Corner.hh" +#include "DcalcAnalysisPt.hh" +#include "Parasitics.hh" +#include "GraphDelayCalc.hh" +#include "DmpDelayCalc.hh" +#include "FindRoot.hh" + +namespace sta { + +// Implementaion based on: +// "Gate Delay Estimation with Library Compatible Current Source Models +// and Effective Capacitance", D. Garyfallou et al, +// IEEE Transactions on Very Large Scale Integration (VLSI) Systems, March 2021 + +using std::abs; +using std::exp; +using std::make_shared; + +ArcDelayCalc * +makeCcsCeffDelayCalc(StaState *sta) +{ + return new CcsCeffDelayCalc(sta); +} + +CcsCeffDelayCalc::CcsCeffDelayCalc(StaState *sta) : + LumpedCapDelayCalc(sta), + output_waveforms_(nullptr), + // Includes the Vh:Vdd region. + region_count_(0), + vl_fail_(false), + capacitance_unit_(units_->capacitanceUnit()), + table_dcalc_(makeDmpCeffElmoreDelayCalc(sta)) +{ +} + +CcsCeffDelayCalc::~CcsCeffDelayCalc() +{ + delete table_dcalc_; +} + +ArcDelayCalc * +CcsCeffDelayCalc::copy() +{ + return new CcsCeffDelayCalc(this); +} + +ArcDcalcResult +CcsCeffDelayCalc::gateDelay(const Pin *drvr_pin, + const TimingArc *arc, + const Slew &in_slew, + float load_cap, + const Parasitic *parasitic, + const LoadPinIndexMap &load_pin_index_map, + const DcalcAnalysisPt *dcalc_ap) +{ + bool vdd_exists; + LibertyCell *drvr_cell = arc->to()->libertyCell(); + const LibertyLibrary *drvr_library = drvr_cell->libertyLibrary(); + const RiseFall *rf = arc->toEdge()->asRiseFall(); + drvr_library->supplyVoltage("VDD", vdd_, vdd_exists); + if (!vdd_exists) + report_->error(1700, "VDD not defined in library %s", drvr_library->name()); + vth_ = drvr_library->outputThreshold(rf) * vdd_; + vl_ = drvr_library->slewLowerThreshold(rf) * vdd_; + vh_ = drvr_library->slewUpperThreshold(rf) * vdd_; + in_slew_ = in_slew; + load_cap_ = load_cap; + parasitic_ = parasitic; + drvr_cell->ensureVoltageWaveforms(); + output_waveforms_ = nullptr; + + GateTableModel *table_model = gateTableModel(arc, dcalc_ap); + if (table_model && parasitic) { + OutputWaveforms *output_waveforms = table_model->outputWaveforms(); + parasitics_->piModel(parasitic, c2_, rpi_, c1_); + if (output_waveforms + && rpi_ > 0.0 && c1_ > 0.0 + // Bounds check because extrapolating waveforms does not work for shit. + && output_waveforms->slewAxis()->inBounds(in_slew_) + && output_waveforms->capAxis()->inBounds(c2_) + && output_waveforms->capAxis()->inBounds(load_cap_)) { + in_slew_ = delayAsFloat(in_slew); + output_waveforms_ = output_waveforms; + ref_time_ = output_waveforms_->referenceTime(in_slew_); + debugPrint(debug_, "ccs_dcalc", 1, "%s %s", + drvr_cell->name(), + rf->asString()); + ArcDelay gate_delay; + Slew drvr_slew; + gateDelaySlew(drvr_library, rf, gate_delay, drvr_slew); + return makeResult(drvr_library, rf, gate_delay, drvr_slew, load_pin_index_map); + } + } + return table_dcalc_->gateDelay(drvr_pin, arc, in_slew, load_cap, parasitic, + load_pin_index_map, dcalc_ap); +} + +void +CcsCeffDelayCalc::gateDelaySlew(const LibertyLibrary *drvr_library, + const RiseFall *rf, + // Return values. + ArcDelay &gate_delay, + Slew &drvr_slew) +{ + initRegions(drvr_library, rf); + findCsmWaveform(); + ref_time_ = output_waveforms_->referenceTime(in_slew_); + gate_delay = region_times_[region_vth_idx_] - ref_time_; + drvr_slew = abs(region_times_[region_vh_idx_] - region_times_[region_vl_idx_]); + debugPrint(debug_, "ccs_dcalc", 2, + "gate_delay %s drvr_slew %s (initial)", + delayAsString(gate_delay, this), + delayAsString(drvr_slew, this)); + float prev_drvr_slew = delayAsFloat(drvr_slew); + constexpr int max_iterations = 5; + for (int iter = 0; iter < max_iterations; iter++) { + debugPrint(debug_, "ccs_dcalc", 2, "iteration %d", iter); + // Init drvr ramp model for vl. + for (size_t i = 0; i <= region_count_; i++) { + region_ramp_times_[i] = region_times_[i]; + if (i < region_count_) + region_ramp_slopes_[i] = (region_volts_[i + 1] - region_volts_[i]) + / (region_times_[i + 1] - region_times_[i]); + } + + for (size_t i = 0; i < region_count_; i++) { + double v1 = region_volts_[i]; + double v2 = region_volts_[i + 1]; + double t1 = region_times_[i]; + double t2 = region_times_[i + 1]; + + // Receiver cap Cp(l) assumed constant and included in c1. + // Note that eqn 8 in the ref'd paper does not properly account + // for the charge on c1 from previous segments so it does not + // work well. + double c1_v1, c1_v2, ignore; + vl(t1, rpi_ * c1_, c1_v1, ignore); + vl(t2, rpi_ * c1_, c1_v2, ignore); + double q1 = v1 * c2_ + c1_v1 * c1_; + double q2 = v2 * c2_ + c1_v2 * c1_; + double ceff = (q2 - q1) / (v2 - v1); + + debugPrint(debug_, "ccs_dcalc", 2, "ceff %s", + capacitance_unit_->asString(ceff)); + region_ceff_[i] = ceff; + } + findCsmWaveform(); + gate_delay = region_times_[region_vth_idx_] - ref_time_; + drvr_slew = abs(region_times_[region_vh_idx_] - region_times_[region_vl_idx_]); + debugPrint(debug_, "ccs_dcalc", 2, + "gate_delay %s drvr_slew %s", + delayAsString(gate_delay, this), + delayAsString(drvr_slew, this)); + if (abs(delayAsFloat(drvr_slew) - prev_drvr_slew) < .01 * prev_drvr_slew) + break; + prev_drvr_slew = delayAsFloat(drvr_slew); + } +} + +void +CcsCeffDelayCalc::initRegions(const LibertyLibrary *drvr_library, + const RiseFall *rf) +{ + // Falling waveforms are treated as rising to simplify the conditionals. + if (rf == RiseFall::fall()) { + vl_ = (1.0 - drvr_library->slewUpperThreshold(rf)) * vdd_; + vh_ = (1.0 - drvr_library->slewLowerThreshold(rf)) * vdd_; + } + + // Includes the Vh:Vdd region. + region_count_ = 7; + region_volts_.resize(region_count_ + 1); + region_ceff_.resize(region_count_ + 1); + region_times_.resize(region_count_ + 1); + region_begin_times_.resize(region_count_ + 1); + region_end_times_.resize(region_count_ + 1); + region_time_offsets_.resize(region_count_ + 1); + region_ramp_times_.resize(region_count_ + 1); + region_ramp_slopes_.resize(region_count_ + 1); + + region_vl_idx_ = 1; + region_vh_idx_ = region_count_ - 1; + + double vth_vh = (vh_ - vth_); + switch (region_count_) { + case 4: + region_vth_idx_ = 2; + region_volts_ = {0.0, vl_, vth_, vh_, vdd_}; + break; + case 5: { + region_vth_idx_ = 2; + double v1 = vth_ + .7 * vth_vh; + region_volts_ = {0.0, vl_, vth_, v1, vh_, vdd_}; + break; + } + case 6: { + region_vth_idx_ = 2; + double v1 = vth_ + .3 * vth_vh; + double v2 = vth_ + .6 * vth_vh; + region_volts_ = {0.0, vl_, vth_, v1, v2, vh_, vdd_}; + break; + } + case 7: { + region_vth_idx_ = 2; + region_vh_idx_ = 5; + double v1 = vth_ + .3 * vth_vh; + double v2 = vth_ + .6 * vth_vh; + double v3 = vh_ + .5 * (vdd_ - vh_); + region_volts_ = {0.0, vl_, vth_, v1, v2, vh_, v3, vdd_}; + break; + } + case 8: { + region_vth_idx_ = 2; + region_vh_idx_ = 6; + double v1 = vth_ + .25 * vth_vh; + double v2 = vth_ + .50 * vth_vh; + double v3 = vth_ + .75 * vth_vh; + double v4 = vh_ + .5 * (vdd_ - vh_); + region_volts_ = {0.0, vl_, vth_, v1, v2, v3, vh_, v4, vdd_}; + break; + } + case 9: { + region_vth_idx_ = 2; + region_vh_idx_ = 7; + double v1 = vth_ + .2 * vth_vh; + double v2 = vth_ + .4 * vth_vh; + double v3 = vth_ + .6 * vth_vh; + double v4 = vth_ + .8 * vth_vh; + double v5 = vh_ + .5 * (vdd_ - vh_); + region_volts_ = {0.0, vl_, vth_, v1, v2, v3, v4, vh_, v5, vdd_}; + break; + } + case 10: { + region_vth_idx_ = 2; + region_vh_idx_ = 7; + double v1 = vth_ + .2 * vth_vh; + double v2 = vth_ + .4 * vth_vh; + double v3 = vth_ + .6 * vth_vh; + double v4 = vth_ + .8 * vth_vh; + double v5 = vh_ + .3 * (vdd_ - vh_); + double v6 = vh_ + .6 * (vdd_ - vh_); + region_volts_ = {0.0, vl_, vth_, v1, v2, v3, v4, vh_, v5, v6, vdd_}; + break; + } + default: + report_->error(1701, "unsupported ccs region count."); + break; + } + fill(region_ceff_.begin(), region_ceff_.end(), c2_ + c1_); +} + +void +CcsCeffDelayCalc::findCsmWaveform() +{ + for (size_t i = 0; i < region_count_; i++) { + double t1 = output_waveforms_->voltageTime(in_slew_, region_ceff_[i], + region_volts_[i]); + double t2 = output_waveforms_->voltageTime(in_slew_, region_ceff_[i], + region_volts_[i + 1]); + region_begin_times_[i] = t1; + region_end_times_[i] = t2; + double time_offset = (i == 0) + ? 0.0 + : t1 - (region_end_times_[i - 1] - region_time_offsets_[i - 1]); + region_time_offsets_[i] = time_offset; + + if (i == 0) + region_times_[i] = t1 - time_offset; + region_times_[i + 1] = t2 - time_offset; + } +} + +//////////////////////////////////////////////////////////////// + +ArcDcalcResult +CcsCeffDelayCalc::makeResult(const LibertyLibrary *drvr_library, + const RiseFall *rf, + ArcDelay &gate_delay, + Slew &drvr_slew, + const LoadPinIndexMap &load_pin_index_map) +{ + ArcDcalcResult dcalc_result(load_pin_index_map.size()); + debugPrint(debug_, "ccs_dcalc", 2, + "gate_delay %s drvr_slew %s", + delayAsString(gate_delay, this), + delayAsString(drvr_slew, this)); + dcalc_result.setGateDelay(gate_delay); + dcalc_result.setDrvrSlew(drvr_slew); + + for (auto load_pin_index : load_pin_index_map) { + const Pin *load_pin = load_pin_index.first; + size_t load_idx = load_pin_index.second; + ArcDelay wire_delay; + Slew load_slew; + loadDelaySlew(load_pin, drvr_library, rf, drvr_slew, wire_delay, load_slew); + dcalc_result.setWireDelay(load_idx, wire_delay); + dcalc_result.setLoadSlew(load_idx, load_slew); + } + return dcalc_result; +} + +void +CcsCeffDelayCalc::loadDelaySlew(const Pin *load_pin, + const LibertyLibrary *drvr_library, + const RiseFall *rf, + Slew &drvr_slew, + // Return values. + ArcDelay &wire_delay, + Slew &load_slew) +{ + ArcDelay wire_delay1 = 0.0; + Slew load_slew1 = drvr_slew; + bool elmore_exists = false; + float elmore = 0.0; + if (parasitic_ + && parasitics_->isPiElmore(parasitic_)) + parasitics_->findElmore(parasitic_, load_pin, elmore, elmore_exists); + + if (elmore_exists && + (elmore == 0.0 + // Elmore delay is small compared to driver slew. + || elmore < delayAsFloat(drvr_slew) * 1e-3)) { + wire_delay1 = elmore; + load_slew1 = drvr_slew; + } + else + loadDelaySlew(load_pin, drvr_slew, elmore, wire_delay1, load_slew1); + + thresholdAdjust(load_pin, drvr_library, rf, wire_delay, load_slew); + wire_delay = wire_delay1; + load_slew = load_slew1; +} + +void +CcsCeffDelayCalc::loadDelaySlew(const Pin *load_pin, + Slew &drvr_slew, + float elmore, + // Return values. + ArcDelay &delay, + Slew &slew) +{ + for (size_t i = 0; i <= region_count_; i++) { + region_ramp_times_[i] = region_times_[i]; + if (i < region_count_) + region_ramp_slopes_[i] = (region_volts_[i + 1] - region_volts_[i]) + / (region_times_[i + 1] - region_times_[i]); + } + + vl_fail_ = false; + double t_vl = findVlTime(vl_, elmore); + double t_vth = findVlTime(vth_, elmore); + double t_vh = findVlTime(vh_, elmore); + if (!vl_fail_) { + delay = t_vth - region_times_[region_vth_idx_]; + slew = t_vh - t_vl; + } + else { + delay = elmore; + slew = drvr_slew; + fail("load delay threshold crossing"); + } + debugPrint(debug_, "ccs_dcalc", 2, + "load %s delay %s slew %s", + network_->pathName(load_pin), + delayAsString(delay, this), + delayAsString(slew, this)); +} + +// Elmore (one pole) response to ramp with slope slew. +static void +rampElmoreV(double t, + double slew, + double elmore, + // Return values. + double &v, + double &dv_dt) +{ + double exp_t = 1.0 - exp2(-t / elmore); + v = slew * (t - elmore * exp_t); + // First derivative of loadVl dv/dt. + dv_dt = slew * exp_t; +} + +// Elmore (one pole) response to 2 segment ramps [0, vth] slew1, [vth, vdd] slew2. +void +CcsCeffDelayCalc::vl(double t, + double elmore, + // Return values. + double &vl, + double &dvl_dt) +{ + vl = 0.0; + dvl_dt = 0.0; + for (size_t i = 0; i < region_count_; i++) { + double t_begin = region_ramp_times_[i]; + double t_end = region_ramp_times_[i + 1]; + double ramp_slope = region_ramp_slopes_[i]; + if (t >= t_begin) { + double v, dv_dt; + rampElmoreV(t - t_begin, ramp_slope, elmore, v, dv_dt); + vl += v; + dvl_dt += dv_dt; + } + if (t > t_end) { + double v, dv_dt; + rampElmoreV(t - t_end, ramp_slope, elmore, v, dv_dt); + vl -= v; + dvl_dt -= dv_dt; + } + } +} + +// for debugging +double +CcsCeffDelayCalc::vl(double t, + double elmore) +{ + double vl1, dvl_dt; + vl(t, elmore, vl1, dvl_dt); + return vl1; +} + +double +CcsCeffDelayCalc::findVlTime(double v, + double elmore) +{ + double t_init = region_ramp_times_[0]; + double t_final = region_ramp_times_[region_count_]; + bool root_fail = false; + double time = findRoot([=] (double t, + double &y, + double &dy) { + vl(t, elmore, y, dy); + y -= v; + }, t_init, t_final + elmore * 3.0, .001, 20, root_fail); + vl_fail_ |= root_fail; + return time; +} + +//////////////////////////////////////////////////////////////// + +// Waveform accessors for swig/tcl. + +Table1 +CcsCeffDelayCalc::drvrWaveform(const Pin *in_pin, + const RiseFall *in_rf, + const Pin *drvr_pin, + const RiseFall *drvr_rf, + const Corner *corner, + const MinMax *min_max) +{ + bool dcalc_success = makeWaveformPreamble(in_pin, in_rf, drvr_pin, + drvr_rf, corner, min_max); + if (dcalc_success) + return drvrWaveform(in_slew_, drvr_rf); + else + return Table1(); +} + +Table1 +CcsCeffDelayCalc::drvrWaveform(const Slew &in_slew, + const RiseFall *drvr_rf) +{ + // Stitch together the ccs waveforms for each region. + FloatSeq *drvr_times = new FloatSeq; + FloatSeq *drvr_volts = new FloatSeq; + for (size_t i = 0; i < region_count_; i++) { + double t1 = region_begin_times_[i]; + double t2 = region_end_times_[i]; + size_t time_steps = 10; + double time_step = (t2 - t1) / time_steps; + double time_offset = region_time_offsets_[i]; + for (size_t s = 0; s <= time_steps; s++) { + double t = t1 + s * time_step; + drvr_times->push_back(t - time_offset); + double v = output_waveforms_->timeVoltage(delayAsFloat(in_slew), + region_ceff_[i], t); + if (drvr_rf == RiseFall::fall()) + v = vdd_ - v; + drvr_volts->push_back(v); + } + } + TableAxisPtr drvr_time_axis = make_shared(TableAxisVariable::time, + drvr_times); + Table1 drvr_table(drvr_volts, drvr_time_axis); + return drvr_table; +} + +// For debugging +Table1 +CcsCeffDelayCalc::loadWaveform(const Pin *in_pin, + const RiseFall *in_rf, + const Pin *drvr_pin, + const RiseFall *drvr_rf, + const Pin *load_pin, + const Corner *corner, + const MinMax *min_max) +{ + bool elmore_exists = false; + float elmore = 0.0; + if (parasitic_) { + parasitics_->findElmore(parasitic_, load_pin, elmore, elmore_exists); + bool dcalc_success = makeWaveformPreamble(in_pin, in_rf, drvr_pin, + drvr_rf, corner, min_max); + if (dcalc_success + && elmore_exists) { + FloatSeq *load_times = new FloatSeq; + FloatSeq *load_volts = new FloatSeq; + double t_vh = findVlTime(vh_, elmore); + double dt = t_vh / 20.0; + double v_final = vh_ + (vdd_ - vh_) * .8; + double v = 0.0; + for (double t = 0; v < v_final; t += dt) { + load_times->push_back(t); + + double ignore; + vl(t, elmore, v, ignore); + double v1 = (drvr_rf == RiseFall::rise()) ? v : vdd_ - v; + load_volts->push_back(v1); + } + TableAxisPtr load_time_axis = make_shared(TableAxisVariable::time, + load_times); + Table1 load_table(load_volts, load_time_axis); + return load_table; + } + } + return Table1(); +} + +Table1 +CcsCeffDelayCalc::drvrRampWaveform(const Pin *in_pin, + const RiseFall *in_rf, + const Pin *drvr_pin, + const RiseFall *drvr_rf, + const Pin *load_pin, + const Corner *corner, + const MinMax *min_max) +{ + bool elmore_exists = false; + float elmore = 0.0; + if (parasitic_) { + parasitics_->findElmore(parasitic_, load_pin, elmore, elmore_exists); + bool dcalc_success = makeWaveformPreamble(in_pin, in_rf, drvr_pin, + drvr_rf, corner, min_max); + if (dcalc_success + && elmore_exists) { + FloatSeq *load_times = new FloatSeq; + FloatSeq *load_volts = new FloatSeq; + for (size_t j = 0; j <= region_count_; j++) { + double t = region_ramp_times_[j]; + load_times->push_back(t); + double v = 0.0; + for (size_t i = 0; i < region_count_; i++) { + double t_begin = region_ramp_times_[i]; + double t_end = region_ramp_times_[i + 1]; + double ramp_slope = region_ramp_slopes_[i]; + if (t >= t_begin) + v += (t - t_begin) * ramp_slope; + if (t > t_end) + v -= (t - t_end) * ramp_slope; + } + double v1 = (drvr_rf == RiseFall::rise()) ? v : vdd_ - v; + load_volts->push_back(v1); + } + TableAxisPtr load_time_axis = make_shared(TableAxisVariable::time, + load_times); + Table1 load_table(load_volts, load_time_axis); + return load_table; + } + } + return Table1(); +} + +bool +CcsCeffDelayCalc::makeWaveformPreamble(const Pin *in_pin, + const RiseFall *in_rf, + const Pin *drvr_pin, + const RiseFall *drvr_rf, + const Corner *corner, + const MinMax *min_max) +{ + Vertex *in_vertex = graph_->pinLoadVertex(in_pin); + Vertex *drvr_vertex = graph_->pinDrvrVertex(drvr_pin); + Edge *edge = nullptr; + VertexInEdgeIterator edge_iter(drvr_vertex, graph_); + while (edge_iter.hasNext()) { + edge = edge_iter.next(); + Vertex *from_vertex = edge->from(graph_); + const Pin *from_pin = from_vertex->pin(); + if (from_pin == in_pin) + break; + } + if (edge) { + TimingArc *arc = nullptr; + for (TimingArc *arc1 : edge->timingArcSet()->arcs()) { + if (arc1->fromEdge()->asRiseFall() == in_rf + && arc1->toEdge()->asRiseFall() == drvr_rf) { + arc = arc1; + break; + } + } + if (arc) { + DcalcAnalysisPt *dcalc_ap = corner->findDcalcAnalysisPt(min_max); + const Slew &in_slew = graph_->slew(in_vertex, in_rf, dcalc_ap->index()); + parasitic_ = arc_delay_calc_->findParasitic(drvr_pin, drvr_rf, dcalc_ap); + if (parasitic_) { + parasitics_->piModel(parasitic_, c2_, rpi_, c1_); + LoadPinIndexMap load_pin_index_map = + graph_delay_calc_->makeLoadPinIndexMap(drvr_vertex); + gateDelay(drvr_pin, arc, in_slew, load_cap_, parasitic_, + load_pin_index_map, dcalc_ap); + return true; + } + } + } + return false; +} + +//////////////////////////////////////////////////////////////// + +string +CcsCeffDelayCalc::reportGateDelay(const Pin *drvr_pin, + const TimingArc *arc, + const Slew &in_slew, + float load_cap, + const Parasitic *parasitic, + const LoadPinIndexMap &load_pin_index_map, + const DcalcAnalysisPt *dcalc_ap, + int digits) +{ + Parasitic *pi_elmore = nullptr; + const RiseFall *rf = arc->toEdge()->asRiseFall(); + if (parasitic && !parasitics_->isPiElmore(parasitic)) { + const ParasiticAnalysisPt *ap = dcalc_ap->parasiticAnalysisPt(); + pi_elmore = parasitics_->reduceToPiElmore(parasitic, drvr_pin_, rf, + dcalc_ap->corner(), + dcalc_ap->constraintMinMax(), ap); + } + string report = table_dcalc_->reportGateDelay(drvr_pin, arc, in_slew, load_cap, + pi_elmore, load_pin_index_map, + dcalc_ap, digits); + parasitics_->deleteDrvrReducedParasitics(drvr_pin); + return report; +} + +void +CcsCeffDelayCalc::fail(const char *reason) +{ + // Report failures with a unique debug flag. + if (debug_->check("ccs_dcalc", 1) || debug_->check("dcalc_error", 1)) + report_->reportLine("delay_calc: CCS failed - %s", reason); +} + +} // namespace diff --git a/dcalc/CcsCeffDelayCalc.hh b/dcalc/CcsCeffDelayCalc.hh new file mode 100644 index 00000000..35094007 --- /dev/null +++ b/dcalc/CcsCeffDelayCalc.hh @@ -0,0 +1,157 @@ +// OpenSTA, Static Timing Analyzer +// Copyright (c) 2024, 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 "LumpedCapDelayCalc.hh" +#include "ArcDcalcWaveforms.hh" + +namespace sta { + +using std::vector; + +ArcDelayCalc * +makeCcsCeffDelayCalc(StaState *sta); + +class CcsCeffDelayCalc : public LumpedCapDelayCalc, public ArcDcalcWaveforms +{ +public: + CcsCeffDelayCalc(StaState *sta); + virtual ~CcsCeffDelayCalc(); + ArcDelayCalc *copy() override; + + ArcDcalcResult gateDelay(const Pin *drvr_pin, + const TimingArc *arc, + const Slew &in_slew, + float load_cap, + const Parasitic *parasitic, + const LoadPinIndexMap &load_pin_index_map, + const DcalcAnalysisPt *dcalc_ap) override; + string reportGateDelay(const Pin *drvr_pin, + const TimingArc *arc, + const Slew &in_slew, + float load_cap, + const Parasitic *parasitic, + const LoadPinIndexMap &load_pin_index_map, + const DcalcAnalysisPt *dcalc_ap, + int digits) override; + + Table1 drvrWaveform(const Pin *in_pin, + const RiseFall *in_rf, + const Pin *drvr_pin, + const RiseFall *drvr_rf, + const Corner *corner, + const MinMax *min_max) override; + Table1 loadWaveform(const Pin *in_pin, + const RiseFall *in_rf, + const Pin *drvr_pin, + const RiseFall *drvr_rf, + const Pin *load_pin, + const Corner *corner, + const MinMax *min_max) override; + Table1 drvrRampWaveform(const Pin *in_pin, + const RiseFall *in_rf, + const Pin *drvr_pin, + const RiseFall *drvr_rf, + const Pin *load_pin, + const Corner *corner, + const MinMax *min_max) override; + +protected: + typedef vector Region; + + void gateDelaySlew(const LibertyLibrary *drvr_library, + const RiseFall *rf, + // Return values. + ArcDelay &gate_delay, + Slew &drvr_slew); + void initRegions(const LibertyLibrary *drvr_library, + const RiseFall *rf); + void findCsmWaveform(); + ArcDcalcResult makeResult(const LibertyLibrary *drvr_library, + const RiseFall *rf, + ArcDelay &gate_delay, + Slew &drvr_slew, + const LoadPinIndexMap &load_pin_index_map); + void loadDelaySlew(const Pin *load_pin, + const LibertyLibrary *drvr_library, + const RiseFall *rf, + Slew &drvr_slew, + // Return values. + ArcDelay &wire_delay, + Slew &load_slew); + void loadDelaySlew(const Pin *load_pin, + Slew &drvr_slew, + float elmore, + // Return values. + ArcDelay &delay, + Slew &slew); + bool makeWaveformPreamble(const Pin *in_pin, + const RiseFall *in_rf, + const Pin *drvr_pin, + const RiseFall *drvr_rf, + const Corner *corner, + const MinMax *min_max); + double findVlTime(double v, + double elmore); + void vl(double t, + double elmore, + // Return values. + double &vl, + double &dvl_dt); + double vl(double t, + double elmore); + Table1 drvrWaveform(const Slew &in_slew, + const RiseFall *drvr_rf); + void fail(const char *reason); + + const Pin *drvr_pin_; + double in_slew_; + double load_cap_; + const Parasitic *parasitic_; + + OutputWaveforms *output_waveforms_; + double ref_time_; + float vdd_; + float vth_; + float vl_; + float vh_; + + float c2_; + float rpi_; + float c1_; + + size_t region_count_; + size_t region_vl_idx_; + size_t region_vth_idx_; + size_t region_vh_idx_; + + Region region_volts_; + Region region_ceff_; + Region region_times_; + Region region_begin_times_; + Region region_end_times_; + Region region_time_offsets_; + Region region_ramp_times_; + Region region_ramp_slopes_; + bool vl_fail_; + + const Unit *capacitance_unit_; + // Delay calculator to use when ccs waveforms are missing from liberty. + ArcDelayCalc *table_dcalc_; +}; + +} // namespace diff --git a/dcalc/DelayCalc.cc b/dcalc/DelayCalc.cc index ab032824..2fe5dcd5 100644 --- a/dcalc/DelayCalc.cc +++ b/dcalc/DelayCalc.cc @@ -22,6 +22,7 @@ #include "LumpedCapDelayCalc.hh" #include "DmpDelayCalc.hh" #include "ArnoldiDelayCalc.hh" +#include "CcsCeffDelayCalc.hh" namespace sta { @@ -37,6 +38,7 @@ registerDelayCalcs() registerDelayCalc("dmp_ceff_elmore", makeDmpCeffElmoreDelayCalc); registerDelayCalc("dmp_ceff_two_pole", makeDmpCeffTwoPoleDelayCalc); registerDelayCalc("arnoldi", makeArnoldiDelayCalc); + registerDelayCalc("ccs_ceff", makeCcsCeffDelayCalc); } void diff --git a/dcalc/DelayCalc.tcl b/dcalc/DelayCalc.tcl index 7af909e5..c93684db 100644 --- a/dcalc/DelayCalc.tcl +++ b/dcalc/DelayCalc.tcl @@ -16,6 +16,13 @@ namespace eval sta { +define_cmd_args "report_dcalc" \ + {[-from from_pin] [-to to_pin] [-corner corner] [-min] [-max] [-digits digits]} + +proc_redirect report_dcalc { + report_dcalc_cmd "report_dcalc" $args "-digits" +} + # Allow any combination of -from/-to pins. proc report_dcalc_cmd { cmd cmd_args digits_key } { global sta_report_default_digits diff --git a/dcalc/GraphDelayCalc.cc b/dcalc/GraphDelayCalc.cc index da3b40d0..87ccc66d 100644 --- a/dcalc/GraphDelayCalc.cc +++ b/dcalc/GraphDelayCalc.cc @@ -954,14 +954,14 @@ GraphDelayCalc::makeArcDcalcArgs(Vertex *drvr_vertex, // Shockingly one fpga vendor connects outputs with no timing arcs together. if (edge1) { Vertex *from_vertex = edge1->from(graph_); + const Pin *from_pin = from_vertex->pin(); const RiseFall *from_rf = arc1->fromEdge()->asRiseFall(); const RiseFall *drvr_rf = arc1->toEdge()->asRiseFall(); const Slew in_slew = edgeFromSlew(from_vertex, from_rf, edge1, dcalc_ap); const Pin *drvr_pin1 = drvr_vertex1->pin(); Parasitic *parasitic = arc_delay_calc->findParasitic(drvr_pin1, drvr_rf, dcalc_ap); - dcalc_args.push_back(ArcDcalcArg(drvr_pin1, edge1, arc1, in_slew, - parasitic)); + dcalc_args.emplace_back(from_pin, drvr_pin1, edge1, arc1, in_slew, parasitic); } } return dcalc_args; diff --git a/doc/OpenSTA.odt b/doc/OpenSTA.odt index 30afc942..2682fccf 100644 Binary files a/doc/OpenSTA.odt and b/doc/OpenSTA.odt differ diff --git a/doc/OpenSTA.pdf b/doc/OpenSTA.pdf index b04d5b6e..d0e5f76e 100644 Binary files a/doc/OpenSTA.pdf and b/doc/OpenSTA.pdf differ diff --git a/graph/Graph.cc b/graph/Graph.cc index 2007e610..c24d69ca 100644 --- a/graph/Graph.cc +++ b/graph/Graph.cc @@ -498,6 +498,37 @@ Graph::deleteOutEdge(Vertex *vertex, Graph::edge(next)->vertex_out_prev_ = prev; } +void +Graph::gateEdgeArc(const Pin *in_pin, + const RiseFall *in_rf, + const Pin *drvr_pin, + const RiseFall *drvr_rf, + // Return values. + Edge *&edge, + const TimingArc *&arc) const +{ + Vertex *in_vertex = pinLoadVertex(in_pin); + Vertex *drvr_vertex = pinDrvrVertex(drvr_pin); + // Iterate over load drivers to avoid driver fanout^2. + VertexInEdgeIterator edge_iter(drvr_vertex, this); + while (edge_iter.hasNext()) { + Edge *edge1 = edge_iter.next(); + if (edge1->from(this) == in_vertex) { + TimingArcSet *arc_set = edge1->timingArcSet(); + for (TimingArc *arc1 : arc_set->arcs()) { + if (arc1->fromEdge()->asRiseFall() == in_rf + && arc1->toEdge()->asRiseFall() == drvr_rf) { + edge = edge1; + arc = arc1; + return; + } + } + } + } + edge = nullptr; + arc = nullptr; +} + //////////////////////////////////////////////////////////////// Arrival * diff --git a/include/sta/ArcDelayCalc.hh b/include/sta/ArcDelayCalc.hh index 64f81069..eb56da6f 100644 --- a/include/sta/ArcDelayCalc.hh +++ b/include/sta/ArcDelayCalc.hh @@ -50,24 +50,41 @@ class ArcDcalcArg { public: ArcDcalcArg(); - ArcDcalcArg(const Pin *drvr_pin, - Edge *edge, - const TimingArc *arc, - const Slew in_slew, - const Parasitic *parasitic); + ArcDcalcArg(const ArcDcalcArg &arg); + ArcDcalcArg(const Pin *in_pin, + const Pin *drvr_pin, + Edge *edge, + const TimingArc *arc, + const Slew in_slew, + const Parasitic *parasitic); + ArcDcalcArg(const Pin *in_pin, + const Pin *drvr_pin, + Edge *edge, + const TimingArc *arc, + float in_delay); + const Pin *inPin() const { return in_pin_; } + const RiseFall *inEdge() const; const Pin *drvrPin() const { return drvr_pin_; } + LibertyCell *drvrCell() const; + const LibertyLibrary *drvrLibrary() const; + const RiseFall *drvrEdge() const; + const Net *drvrNet(const Network *network) const; Edge *edge() const { return edge_; } const TimingArc *arc() const { return arc_; } Slew inSlew() const { return in_slew_; } + void setInSlew(Slew in_slew); const Parasitic *parasitic() { return parasitic_; } void setParasitic(const Parasitic *parasitic); + float inputDelay() const { return input_delay_; } protected: + const Pin *in_pin_; const Pin *drvr_pin_; Edge *edge_; const TimingArc *arc_; Slew in_slew_; const Parasitic *parasitic_; + float input_delay_; }; // Arc delay calc result. diff --git a/include/sta/Bdd.hh b/include/sta/Bdd.hh new file mode 100644 index 00000000..bc95283d --- /dev/null +++ b/include/sta/Bdd.hh @@ -0,0 +1,57 @@ +// OpenSTA, Static Timing Analyzer +// Copyright (c) 2024, 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 "StaState.hh" +#include "LibertyClass.hh" + +#if CUDD +#include "cudd.h" +#else +struct DdNode; +struct DdManager; +#endif + +namespace sta { + +typedef std::map BddPortVarMap; +typedef std::map BddVarIdxPortMap; + +class Bdd : public StaState +{ +public: + Bdd(const StaState *sta); + ~Bdd(); + DdNode *funcBdd(const FuncExpr *expr); + DdNode *findNode(const LibertyPort *port); + const LibertyPort *nodePort(DdNode *node); + DdNode *ensureNode(const LibertyPort *port); + const LibertyPort *varIndexPort(int var_index); + BddPortVarMap &portVarMap() { return bdd_port_var_map_; } + + void clearVarMap(); + DdManager *cuddMgr() const { return cudd_mgr_; } + +private: + DdManager *cudd_mgr_; + BddPortVarMap bdd_port_var_map_; + BddVarIdxPortMap bdd_var_idx_port_map_; +}; + +} // namespace diff --git a/include/sta/CircuitSim.hh b/include/sta/CircuitSim.hh new file mode 100644 index 00000000..e0bf24d3 --- /dev/null +++ b/include/sta/CircuitSim.hh @@ -0,0 +1,23 @@ +// OpenSTA, Static Timing Analyzer +// Copyright (c) 2024, 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 + +namespace sta { + +enum class CircuitSim { hspice, ngspice, xyce }; + +} // namespace diff --git a/include/sta/FuncExpr.hh b/include/sta/FuncExpr.hh index 586c241a..0d4f725d 100644 --- a/include/sta/FuncExpr.hh +++ b/include/sta/FuncExpr.hh @@ -93,12 +93,12 @@ funcExprNot(FuncExpr *expr); class FuncExprPortIterator : public Iterator { public: - explicit FuncExprPortIterator(FuncExpr *expr); + explicit FuncExprPortIterator(const FuncExpr *expr); virtual bool hasNext() { return iter_.hasNext(); } virtual LibertyPort *next() { return iter_.next(); } private: - void findPorts(FuncExpr *expr); + void findPorts(const FuncExpr *expr); LibertyPortSet ports_; LibertyPortSet::ConstIterator iter_; diff --git a/include/sta/Graph.hh b/include/sta/Graph.hh index 00fec1f2..eb53b2e7 100644 --- a/include/sta/Graph.hh +++ b/include/sta/Graph.hh @@ -115,7 +115,6 @@ public: uint32_t count); PathVertexRep *prevPaths(Vertex *vertex) const; void clearPrevPaths(); - // Slews are reported slews in seconds. // Reported slew are the same as those in the liberty tables. // reported_slews = measured_slews / slew_derate_from_library // Measured slews are between slew_lower_threshold and slew_upper_threshold. @@ -141,6 +140,15 @@ public: void makeWireEdgesThruPin(const Pin *hpin); virtual void makeWireEdgesFromPin(const Pin *drvr_pin); virtual void deleteEdge(Edge *edge); + // Find the edge and timing arc on a gate between in_pin and drvr_pin. + void gateEdgeArc(const Pin *in_pin, + const RiseFall *in_rf, + const Pin *drvr_pin, + const RiseFall *drvr_rf, + // Return values. + Edge *&edge, + const TimingArc *&arc) const; + virtual ArcDelay arcDelay(const Edge *edge, const TimingArc *arc, DcalcAPIndex ap_index) const; diff --git a/include/sta/GraphDelayCalc.hh b/include/sta/GraphDelayCalc.hh index a93dd890..bf69f979 100644 --- a/include/sta/GraphDelayCalc.hh +++ b/include/sta/GraphDelayCalc.hh @@ -92,6 +92,14 @@ public: float &wire_cap, float &fanout, bool &has_set_load) const; + void parasiticLoad(const Pin *drvr_pin, + const RiseFall *rf, + const DcalcAnalysisPt *dcalc_ap, + const MultiDrvrNet *multi_drvr, + ArcDelayCalc *arc_delay_calc, + // Return values. + float &cap, + const Parasitic *¶sitic) const; LoadPinIndexMap makeLoadPinIndexMap(Vertex *drvr_vertex); void findDriverArcDelays(Vertex *drvr_vertex, Edge *edge, @@ -231,14 +239,6 @@ protected: const RiseFall *rf, const DcalcAnalysisPt *dcalc_ap, ArcDelayCalc *arc_delay_calc) const; - void parasiticLoad(const Pin *drvr_pin, - const RiseFall *rf, - const DcalcAnalysisPt *dcalc_ap, - const MultiDrvrNet *multi_drvr, - ArcDelayCalc *arc_delay_calc, - // Return values. - float &cap, - const Parasitic *¶sitic) const; void parasiticLoad(const Pin *drvr_pin, const RiseFall *rf, const DcalcAnalysisPt *dcalc_ap, diff --git a/include/sta/Liberty.hh b/include/sta/Liberty.hh index eef1af46..30d3a02e 100644 --- a/include/sta/Liberty.hh +++ b/include/sta/Liberty.hh @@ -319,7 +319,6 @@ public: DriverWaveform *findDriverWaveform(const char *name); DriverWaveform *driverWaveformDefault() { return driver_waveform_default_; } void addDriverWaveform(DriverWaveform *driver_waveform); - void ensureVoltageWaveforms(); protected: float degradeWireSlew(const TableModel *model, @@ -371,7 +370,6 @@ protected: DriverWaveformMap driver_waveform_map_; // Unnamed driver waveform. DriverWaveform *driver_waveform_default_; - bool have_voltage_waveforms_; static constexpr float input_threshold_default_ = .5; static constexpr float output_threshold_default_ = .5; @@ -533,6 +531,7 @@ public: // Check all liberty cells to make sure they exist // for all the defined corners. static void checkLibertyCorners(); + void ensureVoltageWaveforms(); protected: void addPort(ConcretePort *port); @@ -608,6 +607,7 @@ protected: bool leakage_power_exists_; LibertyPgPortMap pg_port_map_; bool has_internal_ports_; + bool have_voltage_waveforms_; private: friend class LibertyLibrary; @@ -795,7 +795,15 @@ public: DriverWaveform *driverWaveform(const RiseFall *rf) const; void setDriverWaveform(DriverWaveform *driver_waveform, const RiseFall *rf); - RiseFallMinMax clockTreePathDelays(); + void setClkTreeDelay(const TableModel *model, + const RiseFall *rf, + const MinMax *min_max); + float clkTreeDelay(float in_slew, + const RiseFall *rf, + const MinMax *min_max) const; + // Assumes input slew of 0.0. + RiseFallMinMax clkTreeDelays() const; + RiseFallMinMax clockTreePathDelays() const; // __attribute__ ((deprecated)) static bool equiv(const LibertyPort *port1, const LibertyPort *port2); @@ -837,6 +845,8 @@ protected: Vector corner_ports_; ReceiverModelPtr receiver_model_; DriverWaveform *driver_waveform_[RiseFall::index_count]; + // Redundant with clock_tree_path_delay timing arcs but faster to access. + const TableModel *clk_tree_delay_[RiseFall::index_count][MinMax::index_count]; unsigned int min_pulse_width_exists_:RiseFall::index_count; bool min_period_exists_:1; diff --git a/include/sta/NetworkClass.hh b/include/sta/NetworkClass.hh index 97fc0a39..bcda1ba5 100644 --- a/include/sta/NetworkClass.hh +++ b/include/sta/NetworkClass.hh @@ -50,6 +50,7 @@ typedef Iterator PortMemberIterator; typedef Vector PinSeq; typedef Vector InstanceSeq; typedef Vector NetSeq; +typedef std::vector ConstNetSeq; typedef Iterator InstanceChildIterator; typedef Iterator InstancePinIterator; typedef Iterator InstanceNetIterator; diff --git a/include/sta/Parasitics.hh b/include/sta/Parasitics.hh index a55143c9..23e589a7 100644 --- a/include/sta/Parasitics.hh +++ b/include/sta/Parasitics.hh @@ -163,11 +163,20 @@ public: // True if the parasitic network caps include pin capacitances. virtual bool includesPinCaps(const Parasitic *parasitic) const = 0; // Parasitic network component builders. + virtual ParasiticNode *findParasiticNode(Parasitic *parasitic, + const Net *net, + int id, + const Network *network) const = 0; // Make a subnode of the parasitic network net. virtual ParasiticNode *ensureParasiticNode(Parasitic *parasitic, const Net *net, int id, const Network *network) = 0; + // Find the parasitic node connected to pin. + virtual ParasiticNode *findParasiticNode(const Parasitic *parasitic, + const Pin *pin) const = 0; + virtual ParasiticNode *findNode(const Parasitic *parasitic, + const Pin *pin) const __attribute__ ((deprecated)); // Make a subnode of the parasitic network net connected to pin. virtual ParasiticNode *ensureParasiticNode(Parasitic *parasitic, const Pin *pin, @@ -175,14 +184,11 @@ public: // Increment the grounded capacitance on node. virtual void incrCap(ParasiticNode *node, float cap) = 0; - virtual const char *name(const ParasiticNode *node) = 0; + virtual const char *name(const ParasiticNode *node) const = 0; virtual const Pin *pin(const ParasiticNode *node) const = 0; virtual const Net *net(const ParasiticNode *node, const Network *network) const = 0; virtual bool isExternal(const ParasiticNode *node) const = 0; - // Find the parasitic node connected to pin. - virtual ParasiticNode *findNode(const Parasitic *parasitic, - const Pin *pin) const = 0; // Node capacitance to ground. virtual float nodeGndCap(const ParasiticNode *node) const = 0; diff --git a/include/sta/PowerClass.hh b/include/sta/PowerClass.hh index 63665609..8a8e28b3 100644 --- a/include/sta/PowerClass.hh +++ b/include/sta/PowerClass.hh @@ -18,6 +18,8 @@ namespace sta { +class Power; + enum class PwrActivityOrigin { global, diff --git a/include/sta/SdcClass.hh b/include/sta/SdcClass.hh index c8a9460a..7ef169a7 100644 --- a/include/sta/SdcClass.hh +++ b/include/sta/SdcClass.hh @@ -75,7 +75,9 @@ public: typedef Vector FloatSeq; typedef Vector IntSeq; typedef Vector ClockSeq; +typedef std::vector ConstClockSeq; typedef Set ClockSet; +typedef std::set ConstClockSet; typedef ClockSet ClockGroup; typedef Vector PinSetSeq; typedef MinMax SetupHold; diff --git a/include/sta/SearchClass.hh b/include/sta/SearchClass.hh index 66e82e7d..88096f0f 100644 --- a/include/sta/SearchClass.hh +++ b/include/sta/SearchClass.hh @@ -61,6 +61,7 @@ class MaxSkewCheck; class CharPtrLess; class SearchPred; class BfsFwdIterator; +class ClkDelays; // Tag compare using tag matching (tagMatch) critera. class TagMatchLess @@ -116,7 +117,6 @@ typedef Vector PathVertexSeq; typedef Vector SlackSeq; typedef Delay Crpr; typedef Vector PathRefSeq; -typedef MinMaxValues ClkDelays[RiseFall::index_count][RiseFall::index_count]; enum class ReportPathFormat { full, full_clock, diff --git a/include/sta/Sta.hh b/include/sta/Sta.hh index 34fd2375..be5433d8 100644 --- a/include/sta/Sta.hh +++ b/include/sta/Sta.hh @@ -57,8 +57,6 @@ class SearchPred; class Corner; class ClkSkews; class ReportField; -class Power; -class PowerResult; class EquivCells; typedef InstanceSeq::Iterator SlowDrvrIterator; @@ -913,15 +911,17 @@ public: void reportPath(Path *path); // Report clk skews for clks. - void reportClkSkew(ClockSet *clks, + void reportClkSkew(ConstClockSeq clks, const Corner *corner, const SetupHold *setup_hold, int digits); float findWorstClkSkew(const SetupHold *setup_hold); + + void reportClkLatency(ConstClockSeq clks, + const Corner *corner, + int digits); // Find min/max/rise/fall delays for clk. - void findClkDelays(const Clock *clk, - // Return values. - ClkDelays &delays); + ClkDelays findClkDelays(const Clock *clk); // Update arrival times for all pins. // If necessary updateTiming propagates arrivals around latch diff --git a/include/sta/TableModel.hh b/include/sta/TableModel.hh index ceb475bb..dafad465 100644 --- a/include/sta/TableModel.hh +++ b/include/sta/TableModel.hh @@ -318,6 +318,7 @@ public: TableAxisPtr axis1); virtual ~Table1(); Table1(Table1 &&table); + Table1(const Table1 &table); Table1 &operator= (Table1 &&table); int order() const override { return 1; } const TableAxis *axis1() const override { return axis1_.get(); } diff --git a/include/sta/WritePathSpice.hh b/include/sta/WritePathSpice.hh index ce43c42e..826e12ce 100644 --- a/include/sta/WritePathSpice.hh +++ b/include/sta/WritePathSpice.hh @@ -16,16 +16,11 @@ #pragma once -#include -#include - #include "StringSet.hh" +#include "CircuitSim.hh" namespace sta { -using std::string; -using std::set; - class Path; class StaState; @@ -41,11 +36,9 @@ writePathSpice(Path *path, const char *lib_subckt_filename, // Device model file included in spice file. const char *model_filename, - // Nets off of path to include in the spice run. - StdStringSet *off_path_pin_names, const char *power_name, const char *gnd_name, - bool measure_stmts, + CircuitSim ckt_sim, StaState *sta); } // namespace diff --git a/liberty/FuncExpr.cc b/liberty/FuncExpr.cc index 894ad09f..1de36ac3 100644 --- a/liberty/FuncExpr.cc +++ b/liberty/FuncExpr.cc @@ -345,14 +345,14 @@ funcExprNot(FuncExpr *expr) //////////////////////////////////////////////////////////////// -FuncExprPortIterator::FuncExprPortIterator(FuncExpr *expr) +FuncExprPortIterator::FuncExprPortIterator(const FuncExpr *expr) { findPorts(expr); iter_.init(ports_); } void -FuncExprPortIterator::findPorts(FuncExpr *expr) +FuncExprPortIterator::findPorts(const FuncExpr *expr) { if (expr) { if (expr->op() == FuncExpr::op_port) diff --git a/liberty/Liberty.cc b/liberty/Liberty.cc index af4dcdc7..7e52064e 100644 --- a/liberty/Liberty.cc +++ b/liberty/Liberty.cc @@ -84,8 +84,7 @@ LibertyLibrary::LibertyLibrary(const char *name, default_ocv_derate_(nullptr), buffers_(nullptr), inverters_(nullptr), - driver_waveform_default_(nullptr), - have_voltage_waveforms_(false) + driver_waveform_default_(nullptr) { // Scalar templates are builtin. for (int i = 0; i != table_template_type_count; i++) { @@ -895,33 +894,6 @@ LibertyLibrary::addDriverWaveform(DriverWaveform *driver_waveform) } } -void -LibertyLibrary::ensureVoltageWaveforms() -{ - if (!have_voltage_waveforms_) { - float vdd = 0.0f; - bool vdd_exists; - supplyVoltage("VDD", vdd, vdd_exists); - if (!vdd_exists || vdd == 0.0) - criticalError(1120, "library missing vdd"); - LibertyCellIterator cell_iter(this); - while (cell_iter.hasNext()) { - LibertyCell *cell = cell_iter.next(); - for (TimingArcSet *arc_set : cell->timingArcSets(nullptr, nullptr)) { - for (TimingArc *arc : arc_set->arcs()) { - GateTableModel*model = dynamic_cast(arc->model()); - if (model) { - OutputWaveforms *output_waveforms = model->outputWaveforms(); - if (output_waveforms) - output_waveforms->makeVoltageWaveforms(vdd); - } - } - } - } - have_voltage_waveforms_ = true; - } -} - //////////////////////////////////////////////////////////////// LibertyCellIterator::LibertyCellIterator(const LibertyLibrary *library) : @@ -969,7 +941,8 @@ LibertyCell::LibertyCell(LibertyLibrary *library, is_disabled_constraint_(false), leakage_power_(0.0), leakage_power_exists_(false), - has_internal_ports_(false) + has_internal_ports_(false), + have_voltage_waveforms_(false) { liberty_cell_ = this; } @@ -1101,13 +1074,13 @@ LibertyCell::setIsMemory(bool is_memory) } void -LibertyCell::LibertyCell::setIsPad(bool is_pad) +LibertyCell::setIsPad(bool is_pad) { is_pad_ = is_pad; } void -LibertyCell::LibertyCell::setIsClockCell(bool is_clock_cell) +LibertyCell::setIsClockCell(bool is_clock_cell) { is_clock_cell_ = is_clock_cell; } @@ -1919,6 +1892,29 @@ LibertyCell::latchCheckEnableEdge(TimingArcSet *check_set) return nullptr; } +void +LibertyCell::ensureVoltageWaveforms() +{ + if (!have_voltage_waveforms_) { + float vdd = 0.0; + bool vdd_exists; + liberty_library_->supplyVoltage("VDD", vdd, vdd_exists); + if (!vdd_exists || vdd == 0.0) + criticalError(1120, "library missing vdd"); + for (TimingArcSet *arc_set : timingArcSets()) { + for (TimingArc *arc : arc_set->arcs()) { + GateTableModel*model = dynamic_cast(arc->model()); + if (model) { + OutputWaveforms *output_waveforms = model->outputWaveforms(); + if (output_waveforms) + output_waveforms->makeVoltageWaveforms(vdd); + } + } + } + have_voltage_waveforms_ = true; + } +} + //////////////////////////////////////////////////////////////// LibertyCellPortIterator::LibertyCellPortIterator(const LibertyCell *cell) : @@ -2005,6 +2001,10 @@ LibertyPort::LibertyPort(LibertyCell *cell, liberty_port_ = this; min_pulse_width_[RiseFall::riseIndex()] = 0.0; min_pulse_width_[RiseFall::fallIndex()] = 0.0; + for (auto rf_index : RiseFall::rangeIndex()) { + for (auto mm_index : MinMax::rangeIndex()) + clk_tree_delay_[rf_index][mm_index] = nullptr; + } } LibertyPort::~LibertyPort() @@ -2598,31 +2598,47 @@ LibertyPort::setDriverWaveform(DriverWaveform *driver_waveform, } RiseFallMinMax -LibertyPort::clockTreePathDelays() +LibertyPort::clockTreePathDelays() const +{ + return clkTreeDelays(); +} + +RiseFallMinMax +LibertyPort::clkTreeDelays() const { RiseFallMinMax delays; - const TimingArcSetSeq &arc_sets = liberty_cell_->timingArcSets(nullptr, this); - for (TimingArcSet *arc_set : arc_sets) { - TimingRole *role = arc_set->role(); - if (role == TimingRole::clockTreePathMin() - || role == TimingRole::clockTreePathMax()) { - for (TimingArc *arc : arc_set->arcs()) { - TimingModel *model = arc->model(); - GateTimingModel *gate_model = dynamic_cast(model); - ArcDelay delay; - Slew slew; - gate_model->gateDelay(nullptr, 0.0, 0.0, false, delay, slew); - const RiseFall *rf = arc->toEdge()->asRiseFall(); - const MinMax *min_max = (role == TimingRole::clockTreePathMin()) - ? MinMax::min() - : MinMax::max(); - delays.setValue(rf, min_max, delayAsFloat(delay)); + for (const RiseFall *rf : RiseFall::range()) { + for (const MinMax *min_max : MinMax::range()) { + const TableModel *model = clk_tree_delay_[rf->index()][min_max->index()]; + if (model) { + float delay = model->findValue(0.0, 0.0, 0.0); + delays.setValue(rf, min_max, delay); } } } return delays; } +float +LibertyPort::clkTreeDelay(float in_slew, + const RiseFall *rf, + const MinMax *min_max) const +{ + const TableModel *model = clk_tree_delay_[rf->index()][min_max->index()]; + if (model) + return model->findValue(in_slew, 0.0, 0.0); + else + return 0.0; +} + +void +LibertyPort::setClkTreeDelay(const TableModel *model, + const RiseFall *rf, + const MinMax *min_max) +{ + clk_tree_delay_[rf->index()][min_max->index()] = model; +} + //////////////////////////////////////////////////////////////// LibertyPortSeq diff --git a/liberty/LibertyBuilder.cc b/liberty/LibertyBuilder.cc index 89a82662..dac8d59b 100644 --- a/liberty/LibertyBuilder.cc +++ b/liberty/LibertyBuilder.cc @@ -20,6 +20,8 @@ #include "TimingRole.hh" #include "FuncExpr.hh" #include "TimingArc.hh" +#include "TimingModel.hh" +#include "TableModel.hh" #include "InternalPower.hh" #include "LeakagePower.hh" #include "Sequential.hh" @@ -269,10 +271,10 @@ LibertyBuilder::makeTimingArcs(LibertyCell *cell, attrs); case TimingType::min_clock_tree_path: return makeClockTreePathArcs(cell, to_port, TimingRole::clockTreePathMin(), - attrs); + MinMax::min(), attrs); case TimingType::max_clock_tree_path: return makeClockTreePathArcs(cell, to_port, TimingRole::clockTreePathMax(), - attrs); + MinMax::max(), attrs); case TimingType::min_pulse_width: case TimingType::minimum_period: case TimingType::nochange_high_high: @@ -633,13 +635,17 @@ TimingArcSet * LibertyBuilder::makeClockTreePathArcs(LibertyCell *cell, LibertyPort *to_port, TimingRole *role, + const MinMax *min_max, TimingArcAttrsPtr attrs) { TimingArcSet *arc_set = makeTimingArcSet(cell, nullptr, to_port, role, attrs); for (auto to_rf : RiseFall::range()) { TimingModel *model = attrs->model(to_rf); - if (model) + if (model) { makeTimingArc(arc_set, nullptr, to_rf->asTransition(), model); + const GateTableModel *gate_model = dynamic_cast(model); + to_port->setClkTreeDelay(gate_model->delayModel(), to_rf, min_max); + } } return arc_set; } diff --git a/liberty/LibertyBuilder.hh b/liberty/LibertyBuilder.hh index 3b4896e4..820112c0 100644 --- a/liberty/LibertyBuilder.hh +++ b/liberty/LibertyBuilder.hh @@ -16,6 +16,7 @@ #pragma once +#include "MinMax.hh" #include "Vector.hh" #include "Transition.hh" #include "LibertyClass.hh" @@ -81,6 +82,7 @@ public: TimingArcSet *makeClockTreePathArcs(LibertyCell *cell, LibertyPort *to_port, TimingRole *role, + const MinMax *min_max, TimingArcAttrsPtr attrs); protected: diff --git a/liberty/LibertyReader.cc b/liberty/LibertyReader.cc index f26d7076..5d394ea4 100644 --- a/liberty/LibertyReader.cc +++ b/liberty/LibertyReader.cc @@ -295,7 +295,7 @@ LibertyReader::defineVisitors() &LibertyReader::visitClockGatingIntegratedCell); defineAttrVisitor("area", &LibertyReader::visitArea); defineAttrVisitor("dont_use", &LibertyReader::visitDontUse); - defineAttrVisitor("is_macro", &LibertyReader::visitIsMacro); + defineAttrVisitor("is_macro_cell", &LibertyReader::visitIsMacro); defineAttrVisitor("is_memory", &LibertyReader::visitIsMemory); defineAttrVisitor("is_pad", &LibertyReader::visitIsPad); defineAttrVisitor("is_clock_cell", &LibertyReader::visitIsClockCell); diff --git a/liberty/LibertyWriter.cc b/liberty/LibertyWriter.cc index 5a94f482..2a53babf 100644 --- a/liberty/LibertyWriter.cc +++ b/liberty/LibertyWriter.cc @@ -272,7 +272,7 @@ LibertyWriter::writeCell(const LibertyCell *cell) if (area > 0.0) fprintf(stream_, " area : %.3f \n", area); if (cell->isMacro()) - fprintf(stream_, " is_macro : true;\n"); + fprintf(stream_, " is_macro_cell : true;\n"); if (cell->interfaceTiming()) fprintf(stream_, " interface_timing : true;\n"); diff --git a/liberty/TableModel.cc b/liberty/TableModel.cc index 5604b7fb..39ce51ee 100644 --- a/liberty/TableModel.cc +++ b/liberty/TableModel.cc @@ -819,6 +819,13 @@ Table1::Table1(Table1 &&table) : table.axis1_ = nullptr; } +Table1::Table1(const Table1 &table) : + Table(), + values_(new FloatSeq(*table.values_)), + axis1_(table.axis1_) +{ +} + Table1::~Table1() { delete values_; diff --git a/parasitics/ConcreteParasitics.cc b/parasitics/ConcreteParasitics.cc index 97a5f7e9..3bcd71b4 100644 --- a/parasitics/ConcreteParasitics.cc +++ b/parasitics/ConcreteParasitics.cc @@ -487,8 +487,11 @@ ConcreteParasiticCapacitor::ConcreteParasiticCapacitor(size_t id, //////////////////////////////////////////////////////////////// ConcreteParasiticNetwork::ConcreteParasiticNetwork(const Net *net, - bool includes_pin_caps) : + bool includes_pin_caps, + const Network *network) : net_(net), + sub_nodes_(network), + pin_nodes_(network), max_node_id_(0), includes_pin_caps_(includes_pin_caps) { @@ -580,6 +583,29 @@ ConcreteParasiticNetwork::capacitance() const return cap; } +ConcreteParasiticNode * +ConcreteParasiticNetwork::findParasiticNode(const Net *net, + int id, + const Network *) const +{ + NetIdPair net_id(net, id); + auto id_node = sub_nodes_.find(net_id); + if (id_node == sub_nodes_.end()) + return nullptr; + else + return id_node->second; +} + +ConcreteParasiticNode * +ConcreteParasiticNetwork::findParasiticNode(const Pin *pin) const +{ + auto pin_node = pin_nodes_.find(pin); + if (pin_node == pin_nodes_.end()) + return nullptr; + else + return pin_node->second; +} + ConcreteParasiticNode * ConcreteParasiticNetwork::ensureParasiticNode(const Net *net, int id, @@ -623,22 +649,12 @@ ConcreteParasiticNetwork::ensureParasiticNode(const Pin *pin, return node; } -ConcreteParasiticNode * -ConcreteParasiticNetwork::findNode(const Pin *pin) const -{ - auto pin_node = pin_nodes_.find(pin); - if (pin_node == pin_nodes_.end()) - return nullptr; - else - return pin_node->second; -} - PinSet ConcreteParasiticNetwork::unannotatedLoads(const Pin *drvr_pin, const Parasitics *parasitics) const { PinSet loads = parasitics->loads(drvr_pin); - ParasiticNode *drvr_node = findNode(drvr_pin); + ParasiticNode *drvr_node = findParasiticNode(drvr_pin); if (drvr_node) { ParasiticNodeResistorMap resistor_map = parasitics->parasiticNodeResistorMap(this); @@ -713,6 +729,11 @@ ConcreteParasiticNetwork::disconnectPin(const Pin *pin, } } +NetIdPairLess::NetIdPairLess(const Network *network) : + net_less_(network) +{ +} + bool NetIdPairLess::operator()(const NetIdPair &net_id1, const NetIdPair &net_id2) const @@ -721,7 +742,7 @@ NetIdPairLess::operator()(const NetIdPair &net_id1, const Net *net2 = net_id2.first; int id1 = net_id1.second; int id2 = net_id2.second; - return net1 < net2 + return net_less_(net1, net2) || (net1 == net2 && id1 < id2); } @@ -1229,7 +1250,7 @@ ConcreteParasitics::makeParasiticNetwork(const Net *net, for (const Pin *drvr_pin : *network_->drivers(net)) deleteParasitics(drvr_pin, ap); } - parasitic = new ConcreteParasiticNetwork(net, includes_pin_caps); + parasitic = new ConcreteParasiticNetwork(net, includes_pin_caps, network_); parasitics[ap_index] = parasitic; return parasitic; } @@ -1286,6 +1307,17 @@ ConcreteParasitics::includesPinCaps(const Parasitic *parasitic) const return cparasitic->includesPinCaps(); } +ParasiticNode * +ConcreteParasitics::findParasiticNode(Parasitic *parasitic, + const Net *net, + int id, + const Network *network) const +{ + const ConcreteParasiticNetwork *cparasitic = + static_cast(parasitic); + return cparasitic->findParasiticNode(net, id, network); +} + ParasiticNode * ConcreteParasitics::ensureParasiticNode(Parasitic *parasitic, const Net *net, @@ -1297,6 +1329,15 @@ ConcreteParasitics::ensureParasiticNode(Parasitic *parasitic, return cparasitic->ensureParasiticNode(net, id, network); } +ParasiticNode * +ConcreteParasitics::findParasiticNode(const Parasitic *parasitic, + const Pin *pin) const +{ + const ConcreteParasiticNetwork *cparasitic = + static_cast(parasitic); + return cparasitic->findParasiticNode(pin); +} + ParasiticNode * ConcreteParasitics::ensureParasiticNode(Parasitic *parasitic, const Pin *pin, @@ -1373,7 +1414,7 @@ ConcreteParasitics::capacitors(const Parasitic *parasitic) const const char * -ConcreteParasitics::name(const ParasiticNode *node) +ConcreteParasitics::name(const ParasiticNode *node) const { const ConcreteParasiticNode *cnode = static_cast(node); @@ -1413,15 +1454,6 @@ ConcreteParasitics::isExternal(const ParasiticNode *node) const return cnode->isExternal(); } -ParasiticNode * -ConcreteParasitics::findNode(const Parasitic *parasitic, - const Pin *pin) const -{ - const ConcreteParasiticNetwork *cparasitic = - static_cast(parasitic); - return cparasitic->findNode(pin); -} - //////////////////////////////////////////////////////////////// size_t diff --git a/parasitics/ConcreteParasitics.hh b/parasitics/ConcreteParasitics.hh index 842b3046..04014fda 100644 --- a/parasitics/ConcreteParasitics.hh +++ b/parasitics/ConcreteParasitics.hh @@ -113,19 +113,23 @@ public: const ParasiticAnalysisPt *ap) override; void deleteParasiticNetworks(const Net *net) override; bool includesPinCaps(const Parasitic *parasitic) const override; + ParasiticNode *findParasiticNode(Parasitic *parasitic, + const Net *net, + int id, + const Network *network) const override; ParasiticNode *ensureParasiticNode(Parasitic *parasitic, const Net *net, int id, const Network *network) override; + ParasiticNode *findParasiticNode(const Parasitic *parasitic, + const Pin *pin) const override; ParasiticNode *ensureParasiticNode(Parasitic *parasitic, const Pin *pin, const Network *network) override; ParasiticNodeSeq nodes(const Parasitic *parasitic) const override; void incrCap(ParasiticNode *node, float cap) override; - const char *name(const ParasiticNode *node) override; - ParasiticNode *findNode(const Parasitic *parasitic, - const Pin *pin) const override; + const char *name(const ParasiticNode *node) const override; const Pin *pin(const ParasiticNode *node) const override; const Net *net(const ParasiticNode *node, const Network *network) const override; diff --git a/parasitics/ConcreteParasiticsPvt.hh b/parasitics/ConcreteParasiticsPvt.hh index c4bcc346..4979d63f 100644 --- a/parasitics/ConcreteParasiticsPvt.hh +++ b/parasitics/ConcreteParasiticsPvt.hh @@ -27,17 +27,23 @@ class ConcretePoleResidue; class ConcreteParasiticDevice; class ConcreteParasiticNode; -typedef std::map ConcreteElmoreLoadMap; -typedef std::map ConcretePoleResidueMap; typedef std::pair NetIdPair; -struct NetIdPairLess +class NetIdPairLess { +public: + NetIdPairLess(const Network *network); bool operator()(const NetIdPair &net_id1, const NetIdPair &net_id2) const; + +private: + const NetIdLess net_less_; }; + +typedef std::map ConcreteElmoreLoadMap; +typedef std::map ConcretePoleResidueMap; typedef std::map ConcreteParasiticSubNodeMap; -typedef std::map ConcreteParasiticPinNodeMap; +typedef std::map ConcreteParasiticPinNodeMap; typedef std::set ParasiticNodeSet; typedef std::set ParasiticResistorSet; typedef std::vector ParasiticResistorSeq; @@ -197,17 +203,21 @@ class ConcreteParasiticNetwork : public ParasiticNetwork, { public: ConcreteParasiticNetwork(const Net *net, - bool includes_pin_caps); + bool includes_pin_caps, + const Network *network); virtual ~ConcreteParasiticNetwork(); virtual bool isParasiticNetwork() const { return true; } const Net *net() { return net_; } bool includesPinCaps() const { return includes_pin_caps_; } + ConcreteParasiticNode *findParasiticNode(const Net *net, + int id, + const Network *network) const; ConcreteParasiticNode *ensureParasiticNode(const Net *net, int id, const Network *network); + ConcreteParasiticNode *findParasiticNode(const Pin *pin) const; ConcreteParasiticNode *ensureParasiticNode(const Pin *pin, const Network *network); - ConcreteParasiticNode *findNode(const Pin *pin) const; virtual float capacitance() const; ParasiticNodeSeq nodes() const; void disconnectPin(const Pin *pin, diff --git a/parasitics/Parasitics.cc b/parasitics/Parasitics.cc index 605ab87e..5d8a10c5 100644 --- a/parasitics/Parasitics.cc +++ b/parasitics/Parasitics.cc @@ -53,6 +53,13 @@ Parasitics::findParasiticNet(const Pin *pin) const return nullptr; } +ParasiticNode * +Parasitics::findNode(const Parasitic *parasitic, + const Pin *pin) const +{ + return findParasiticNode(parasitic, pin); +} + PinSet Parasitics::loads(const Pin *drvr_pin) const { diff --git a/parasitics/ReduceParasitics.cc b/parasitics/ReduceParasitics.cc index f6a692d1..4d8f3f75 100644 --- a/parasitics/ReduceParasitics.cc +++ b/parasitics/ReduceParasitics.cc @@ -306,7 +306,8 @@ reduceToPiElmore(const Parasitic *parasitic_network, StaState *sta) { Parasitics *parasitics = sta->parasitics(); - ParasiticNode *drvr_node = parasitics->findNode(parasitic_network, drvr_pin); + ParasiticNode *drvr_node = + parasitics->findParasiticNode(parasitic_network, drvr_pin); if (drvr_node) { debugPrint(sta->debug(), "parasitic_reduce", 1, "Reduce driver %s %s %s", sta->network()->pathName(drvr_pin), @@ -456,7 +457,8 @@ reduceToPiPoleResidue2(const Parasitic *parasitic_network, StaState *sta) { Parasitics *parasitics = sta->parasitics(); - ParasiticNode *drvr_node = parasitics->findNode(parasitic_network, drvr_pin); + ParasiticNode *drvr_node = + parasitics->findParasiticNode(parasitic_network, drvr_pin); if (drvr_node) { debugPrint(sta->debug(), "parasitic_reduce", 1, "Reduce driver %s", sta->network()->pathName(drvr_pin)); @@ -508,7 +510,8 @@ ReduceToPiPoleResidue2::findPolesResidues(const Parasitic *parasitic_network, while (pin_iter->hasNext()) { const Pin *pin = pin_iter->next(); if (network_->isLoad(pin)) { - ParasiticNode *load_node = parasitics_->findNode(parasitic_network, pin); + ParasiticNode *load_node = + parasitics_->findParasiticNode(parasitic_network, pin); if (load_node) { findPolesResidues(pi_pole_residue, drvr_pin, pin, load_node); } diff --git a/parasitics/SpefReader.cc b/parasitics/SpefReader.cc index d0efbd27..8767986a 100644 --- a/parasitics/SpefReader.cc +++ b/parasitics/SpefReader.cc @@ -468,38 +468,40 @@ SpefReader::findParasiticNode(char *name, *delim = '\0'; char *name2 = delim + 1; name = nameMapLookup(name); - Instance *inst = findInstanceRelative(name); - if (inst) { - // : - Pin *pin = network_->findPin(inst, name2); - if (pin) { - if (local_only - && !network_->isConnected(net_, pin)) - warn(1651, "%s not connected to net %s.", name, network_->pathName(net_)); - return parasitics_->ensureParasiticNode(parasitic_, pin, network_); + if (name) { + Instance *inst = findInstanceRelative(name); + if (inst) { + // : + Pin *pin = network_->findPin(inst, name2); + if (pin) { + if (local_only + && !network_->isConnected(net_, pin)) + warn(1651, "%s not connected to net %s.", name, network_->pathName(net_)); + return parasitics_->ensureParasiticNode(parasitic_, pin, network_); + } + else { + // Replace delimiter for error message. + *delim = delimiter_; + warn(1652, "pin %s not found.", name); + } } else { - // Replace delimiter for error message. + Net *net = findNet(name); + // Replace delimiter for error messages. *delim = delimiter_; - warn(1652, "pin %s not found.", name); - } - } - else { - Net *net = findNet(name); - // Replace delimiter for error messages. - *delim = delimiter_; - if (net) { - // : - const char *id_str = delim + 1; - if (isDigits(id_str)) { - int id = atoi(id_str); - if (local_only - && !network_->isConnected(net, net_)) - warn(1653, "%s not connected to net %s.", name, network_->pathName(net_)); - return parasitics_->ensureParasiticNode(parasitic_, net, id, network_); + if (net) { + // : + const char *id_str = delim + 1; + if (isDigits(id_str)) { + int id = atoi(id_str); + if (local_only + && !network_->isConnected(net, net_)) + warn(1653, "%s not connected to net %s.", name, network_->pathName(net_)); + return parasitics_->ensureParasiticNode(parasitic_, net, id, network_); + } + else + warn(1654, "node %s not a pin or net:number", name); } - else - warn(1654, "node %s not a pin or net:number", name); } } } diff --git a/power/Power.cc b/power/Power.cc index f46b3f2a..112af13d 100644 --- a/power/Power.cc +++ b/power/Power.cc @@ -47,13 +47,6 @@ #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; @@ -95,15 +88,10 @@ Power::Power(StaState *sta) : input_activity_{0.1, 0.5, PwrActivityOrigin::input}, seq_activity_map_(100, SeqPinHash(network_), SeqPinEqual()), activities_valid_(false), - cudd_mgr_(Cudd_Init(0, 0, CUDD_UNIQUE_SLOTS, CUDD_CACHE_SLOTS, 0)) + bdd_(sta) { } -Power::~Power() -{ - Cudd_Quit(cudd_mgr_); -} - void Power::setGlobalActivity(float activity, float duty) @@ -524,12 +512,12 @@ Power::evalActivity(FuncExpr *expr, if (func_port && func_port->direction()->isInternal()) return findSeqActivity(inst, func_port); else { - DdNode *bdd = funcBdd(expr); + DdNode *bdd = bdd_.funcBdd(expr); float duty = evalBddDuty(bdd, inst); float activity = evalBddActivity(bdd, inst); - Cudd_RecursiveDeref(cudd_mgr_, bdd); - clearVarMap(); + Cudd_RecursiveDeref(bdd_.cuddMgr(), bdd); + bdd_.clearVarMap(); return PwrActivity(activity, duty, PwrActivityOrigin::propagated); } } @@ -540,110 +528,19 @@ Power::evalDiffDuty(FuncExpr *expr, LibertyPort *from_port, const Instance *inst) { - DdNode *bdd = funcBdd(expr); - DdNode *var_node = bdd_port_var_map_[from_port]; + DdNode *bdd = bdd_.funcBdd(expr); + DdNode *var_node = bdd_.findNode(from_port); unsigned var_index = Cudd_NodeReadIndex(var_node); - DdNode *diff = Cudd_bddBooleanDiff(cudd_mgr_, bdd, var_index); + DdNode *diff = Cudd_bddBooleanDiff(bdd_.cuddMgr(), bdd, var_index); Cudd_Ref(diff); float duty = evalBddDuty(diff, inst); - Cudd_RecursiveDeref(cudd_mgr_, diff); - Cudd_RecursiveDeref(cudd_mgr_, bdd); - clearVarMap(); + Cudd_RecursiveDeref(bdd_.cuddMgr(), diff); + Cudd_RecursiveDeref(bdd_.cuddMgr(), bdd); + 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(1440, "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 @@ -651,9 +548,9 @@ Power::evalBddDuty(DdNode *bdd, const Instance *inst) { if (Cudd_IsConstant(bdd)) { - if (bdd == Cudd_ReadOne(cudd_mgr_)) + if (bdd == Cudd_ReadOne(bdd_.cuddMgr())) return 1.0; - else if (bdd == Cudd_ReadLogicZero(cudd_mgr_)) + else if (bdd == Cudd_ReadLogicZero(bdd_.cuddMgr())) return 0.0; else criticalError(1100, "unknown cudd constant"); @@ -662,10 +559,10 @@ Power::evalBddDuty(DdNode *bdd, 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]; + int var_index = Cudd_ReadPerm(bdd_.cuddMgr(), index); + const LibertyPort *port = bdd_.varIndexPort(var_index); if (port->direction()->isInternal()) - return findSeqActivity(inst, port).duty(); + return findSeqActivity(inst, const_cast(port)).duty(); else { const Pin *pin = findLinkPin(inst, port); if (pin) { @@ -689,17 +586,17 @@ Power::evalBddActivity(DdNode *bdd, const Instance *inst) { float activity = 0.0; - for (auto port_var : bdd_port_var_map_) { - LibertyPort *port = port_var.first; + for (auto port_var : bdd_.portVarMap()) { + const 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); + DdNode *diff = Cudd_bddBooleanDiff(bdd_.cuddMgr(), bdd, var_index); Cudd_Ref(diff); float diff_duty = evalBddDuty(diff, inst); - Cudd_RecursiveDeref(cudd_mgr_, diff); + Cudd_RecursiveDeref(bdd_.cuddMgr(), diff); float var_act = var_activity.activity() * diff_duty; activity += var_act; const Clock *clk = findClk(pin); diff --git a/power/Power.hh b/power/Power.hh index 81c13de1..d638b25d 100644 --- a/power/Power.hh +++ b/power/Power.hh @@ -24,6 +24,7 @@ #include "SdcClass.hh" #include "PowerClass.hh" #include "StaState.hh" +#include "Bdd.hh" struct DdNode; struct DdManager; @@ -38,8 +39,6 @@ class BfsFwdIterator; class Vertex; typedef std::pair SeqPin; -typedef Map BddPortVarMap; -typedef Map BddVarIdxPortMap; class SeqPinHash { @@ -68,7 +67,6 @@ class Power : public StaState { public: Power(StaState *sta); - ~Power(); void power(const Corner *corner, // Return values. PowerResult &total, @@ -196,10 +194,6 @@ protected: const Pin *&enable, 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, @@ -217,10 +211,7 @@ 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_; + Bdd bdd_; static constexpr int max_activity_passes_ = 100; diff --git a/search/Bdd.cc b/search/Bdd.cc new file mode 100644 index 00000000..88eda5a6 --- /dev/null +++ b/search/Bdd.cc @@ -0,0 +1,179 @@ +// OpenSTA, Static Timing Analyzer +// Copyright (c) 2024, 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 "Bdd.hh" + +#include "StaConfig.hh" +#include "Report.hh" +#include "FuncExpr.hh" + +#if CUDD +#include "cudd.h" +#else +#include +#define CUDD_UNIQUE_SLOTS 0 +#define CUDD_CACHE_SLOTS 0 +DdManager *Cudd_Init(int, int, int, int, int) { return nullptr; } +void Cudd_Quit(void *) {} +DdNode *Cudd_Not(void *) { return nullptr; } +DdNode *Cudd_bddOr(void *, void *, void *) { return nullptr; } +DdNode *Cudd_bddAnd(void *, void *, void *) { return nullptr; } +DdNode *Cudd_bddXor(void *, void *, void *) { return nullptr; } +DdNode *Cudd_ReadOne(void *) { return nullptr; } +DdNode *Cudd_ReadLogicZero(void *) { return nullptr; } +DdNode *Cudd_bddNewVar(void *) { return nullptr; } +int Cudd_NodeReadIndex(void *) { return 0;} +void Cudd_Ref(void *) {} +void Cudd_RecursiveDeref(void *, void *) {} +#endif + +namespace sta { + +Bdd::Bdd(const StaState *sta) : + StaState(sta), + cudd_mgr_(Cudd_Init(0, 0, CUDD_UNIQUE_SLOTS, CUDD_CACHE_SLOTS, 0)) +{ +} + +Bdd::~Bdd() +{ + Cudd_Quit(cudd_mgr_); +} + +DdNode * +Bdd::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(1440, "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 * +Bdd::findNode(const LibertyPort *port) +{ + auto port_var = bdd_port_var_map_.find(port); + if (port_var == bdd_port_var_map_.end()) + return nullptr; + else + return port_var->second; +} + +DdNode * +Bdd::ensureNode(const LibertyPort *port) +{ + auto port_var = bdd_port_var_map_.find(port); + DdNode *node = nullptr; + if (port_var == bdd_port_var_map_.end()) { + node = Cudd_bddNewVar(cudd_mgr_); + bdd_port_var_map_[port] = node; + unsigned var_index = Cudd_NodeReadIndex(node); + bdd_var_idx_port_map_[var_index] = port; + Cudd_Ref(node); + } + else + node = port_var->second; + return node; +} + +const LibertyPort * +Bdd::nodePort(DdNode *node) +{ + auto port_index = bdd_var_idx_port_map_.find(Cudd_NodeReadIndex(node)); + if (port_index == bdd_var_idx_port_map_.end()) + return nullptr; + else + return port_index->second; +} + +const LibertyPort * +Bdd::varIndexPort(int var_index) +{ + auto index_port = bdd_var_idx_port_map_.find(var_index); + if (index_port == bdd_var_idx_port_map_.end()) + return nullptr; + else + return index_port->second; +} + +void +Bdd::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(); +} + +} // namespace diff --git a/search/ClkDelays.hh b/search/ClkDelays.hh new file mode 100644 index 00000000..3aadfa88 --- /dev/null +++ b/search/ClkDelays.hh @@ -0,0 +1,70 @@ +// OpenSTA, Static Timing Analyzer +// Copyright (c) 2024, 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 "MinMax.hh" +#include "StaState.hh" +#include "Transition.hh" +#include "PathVertex.hh" + +namespace sta { + +class ClkDelays +{ +public: + ClkDelays(); + void delay(const RiseFall *src_rf, + const RiseFall *end_rf, + const MinMax *min_max, + // Return values. + float &insertion, + float &delay, + float &lib_clk_delay, + float &latency, + PathVertex &path, + bool &exists); + void latency(const RiseFall *src_rf, + const RiseFall *end_rf, + const MinMax *min_max, + // Return values. + float &delay, + bool &exists); + static float latency(PathVertex *clk_path, + StaState *sta); + void setLatency(const RiseFall *src_rf, + const RiseFall *end_rf, + const MinMax *min_max, + PathVertex *path, + StaState *sta); + +private: + static float insertionDelay(PathVertex *clk_path, + StaState *sta); + static float delay(PathVertex *clk_path, + StaState *sta); + static float clkTreeDelay(PathVertex *clk_path, + StaState *sta); + + float insertion_[RiseFall::index_count][RiseFall::index_count][MinMax::index_count]; + float delay_[RiseFall::index_count][RiseFall::index_count][MinMax::index_count]; + float lib_clk_delay_[RiseFall::index_count][RiseFall::index_count][MinMax::index_count]; + float latency_[RiseFall::index_count][RiseFall::index_count][MinMax::index_count]; + PathVertex path_[RiseFall::index_count][RiseFall::index_count][MinMax::index_count]; + bool exists_[RiseFall::index_count][RiseFall::index_count][MinMax::index_count]; +}; + +} // namespace diff --git a/search/ClkLatency.cc b/search/ClkLatency.cc new file mode 100644 index 00000000..9e2e91a4 --- /dev/null +++ b/search/ClkLatency.cc @@ -0,0 +1,299 @@ +// OpenSTA, Static Timing Analyzer +// Copyright (c) 2024, 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 "ClkLatency.hh" + +#include + +#include "Report.hh" +#include "Debug.hh" +#include "Units.hh" +#include "Liberty.hh" +#include "Network.hh" +#include "Clock.hh" +#include "Graph.hh" +#include "PathVertex.hh" +#include "StaState.hh" +#include "Search.hh" +#include "PathAnalysisPt.hh" +#include "ClkInfo.hh" + +namespace sta { + +ClkLatency::ClkLatency(StaState *sta) : + StaState(sta) +{ +} + +ClkDelays +ClkLatency::findClkDelays(const Clock *clk, + const Corner *corner) +{ + ConstClockSeq clks; + clks.push_back(clk); + ClkDelayMap clk_delay_map = findClkDelays(clks, corner); + return clk_delay_map[clk]; +} + +void +ClkLatency::reportClkLatency(ConstClockSeq clks, + const Corner *corner, + int digits) +{ + ClkDelayMap clk_delay_map = findClkDelays(clks, corner); + + // Sort the clocks to report in a stable order. + ConstClockSeq sorted_clks; + for (const Clock *clk : clks) + sorted_clks.push_back(clk); + std::sort(sorted_clks.begin(), sorted_clks.end(), ClkNameLess()); + + for (const Clock *clk : sorted_clks) { + ClkDelays clk_delays = clk_delay_map[clk]; + reportClkLatency(clk, clk_delays, digits); + report_->reportBlankLine(); + } +} + +void +ClkLatency::reportClkLatency(const Clock *clk, + ClkDelays &clk_delays, + int digits) +{ + Unit *time_unit = units_->timeUnit(); + report_->reportLine("Clock %s", clk->name()); + for (const RiseFall *src_rf : RiseFall::range()) { + for (const RiseFall *end_rf : RiseFall::range()) { + PathVertex path_min; + float insertion_min; + float delay_min; + float lib_clk_delay_min; + float latency_min; + bool exists_min; + clk_delays.delay(src_rf, end_rf, MinMax::min(), insertion_min, + delay_min, lib_clk_delay_min, latency_min, + path_min, exists_min); + PathVertex path_max; + float insertion_max; + float delay_max; + float lib_clk_delay_max; + float latency_max; + bool exists_max; + clk_delays.delay(src_rf, end_rf, MinMax::max(), insertion_max, + delay_max, lib_clk_delay_max, latency_max, + path_max, exists_max); + if (exists_min & exists_max) { + report_->reportLine("%s -> %s", + src_rf->name(), + end_rf->name()); + report_->reportLine(" min max"); + + report_->reportLine("%7s %7s source latency", + time_unit->asString(insertion_min, digits), + time_unit->asString(insertion_max, digits)); + report_->reportLine("%7s %7s network latency %s", + time_unit->asString(delay_min, digits), + "", + sdc_network_->pathName(path_min.pin(this))); + report_->reportLine("%7s %7s network latency %s", + "", + time_unit->asString(delay_max, digits), + sdc_network_->pathName(path_max.pin(this))); + if (lib_clk_delay_min != 0.0 + || lib_clk_delay_max != 0.0) + report_->reportLine("%7s %7s liberty clock tree delay", + time_unit->asString(lib_clk_delay_min, digits), + time_unit->asString(lib_clk_delay_max, digits)); + report_->reportLine("---------------"); + report_->reportLine("%7s %7s latency", + time_unit->asString(latency_min, digits), + time_unit->asString(latency_max, digits)); + float skew = latency_max - latency_min; + report_->reportLine(" %7s skew", + time_unit->asString(skew, digits)); + report_->reportBlankLine(); + } + } + } +} + +ClkDelayMap +ClkLatency::findClkDelays(ConstClockSeq clks, + const Corner *corner) +{ + ClkDelayMap clk_delay_map; + // Make entries for the relevant clocks to filter path clocks. + for (const Clock *clk : clks) + clk_delay_map[clk]; + for (Vertex *clk_vertex : *graph_->regClkVertices()) { + VertexPathIterator path_iter(clk_vertex, this); + while (path_iter.hasNext()) { + PathVertex *path = path_iter.next(); + const ClockEdge *path_clk_edge = path->clkEdge(this); + const PathAnalysisPt *path_ap = path->pathAnalysisPt(this); + if (path_clk_edge + && (corner == nullptr + || path_ap->corner() == corner)) { + const Clock *path_clk = path_clk_edge->clock(); + auto delays_itr = clk_delay_map.find(path_clk); + if (delays_itr != clk_delay_map.end()) { + ClkDelays &clk_delays = delays_itr->second; + const RiseFall *clk_rf = path_clk_edge->transition(); + const MinMax *min_max = path->minMax(this); + const RiseFall *end_rf = path->transition(this); + float latency = ClkDelays::latency(path, this); + float clk_latency; + bool exists; + clk_delays.latency(clk_rf, end_rf, min_max, clk_latency, exists); + if (!exists || min_max->compare(latency, clk_latency)) + clk_delays.setLatency(clk_rf, end_rf, min_max, path, this); + } + } + } + } + return clk_delay_map; +} + +//////////////////////////////////////////////////////////////// + +ClkDelays::ClkDelays() +{ + for (auto src_rf_index : RiseFall::rangeIndex()) { + for (auto end_rf_index : RiseFall::rangeIndex()) { + for (auto mm_index : MinMax::rangeIndex()) { + insertion_[src_rf_index][end_rf_index][mm_index] = 0.0; + delay_[src_rf_index][end_rf_index][mm_index] = 0.0; + lib_clk_delay_[src_rf_index][end_rf_index][mm_index] = 0.0; + latency_[src_rf_index][end_rf_index][mm_index] = 0.0; + exists_[src_rf_index][end_rf_index][mm_index] = false; + } + } + } +} + +void +ClkDelays::delay(const RiseFall *src_rf, + const RiseFall *end_rf, + const MinMax *min_max, + // Return values. + float &insertion, + float &delay, + float &lib_clk_delay, + float &latency, + PathVertex &path, + bool &exists) +{ + int src_rf_index = src_rf->index(); + int end_rf_index = end_rf->index(); + int mm_index = min_max->index(); + path = path_[src_rf_index][end_rf_index][mm_index]; + insertion = insertion_[src_rf_index][end_rf_index][mm_index]; + delay = delay_[src_rf_index][end_rf_index][mm_index]; + lib_clk_delay = lib_clk_delay_[src_rf_index][end_rf_index][mm_index]; + latency = latency_[src_rf_index][end_rf_index][mm_index]; + exists = exists_[src_rf_index][end_rf_index][mm_index]; +} + +void +ClkDelays::latency(const RiseFall *src_rf, + const RiseFall *end_rf, + const MinMax *min_max, + // Return values. + float &latency, + bool &exists) +{ + int src_rf_index = src_rf->index(); + int end_rf_index = end_rf->index(); + int mm_index = min_max->index(); + latency = latency_[src_rf_index][end_rf_index][mm_index]; + exists = exists_[src_rf_index][end_rf_index][mm_index]; +} + +void +ClkDelays::setLatency(const RiseFall *src_rf, + const RiseFall *end_rf, + const MinMax *min_max, + PathVertex *path, + StaState *sta) +{ + int src_rf_index = src_rf->index(); + int end_rf_index = end_rf->index(); + int mm_index = min_max->index(); + + float insertion = insertionDelay(path, sta); + insertion_[src_rf_index][end_rf_index][mm_index] = insertion; + + float delay1 = delay(path, sta); + delay_[src_rf_index][end_rf_index][mm_index] = delay1; + + float lib_clk_delay = clkTreeDelay(path, sta); + lib_clk_delay_[src_rf_index][end_rf_index][mm_index] = lib_clk_delay; + + float latency = insertion + delay1 + lib_clk_delay; + latency_[src_rf_index][end_rf_index][mm_index] = latency; + + path_[src_rf_index][end_rf_index][mm_index] = *path; + exists_[src_rf_index][end_rf_index][mm_index] = true; +} + +float +ClkDelays::latency(PathVertex *clk_path, + StaState *sta) +{ + + float insertion = insertionDelay(clk_path, sta); + float delay1 = delay(clk_path, sta); + float lib_clk_delay = clkTreeDelay(clk_path, sta); + return insertion + delay1 + lib_clk_delay; +} + +float +ClkDelays::delay(PathVertex *clk_path, + StaState *sta) +{ + Arrival arrival = clk_path->arrival(sta); + const ClockEdge *path_clk_edge = clk_path->clkEdge(sta); + return delayAsFloat(arrival) - path_clk_edge->time(); +} + +float +ClkDelays::insertionDelay(PathVertex *clk_path, + StaState *sta) +{ + const ClockEdge *clk_edge = clk_path->clkEdge(sta); + const Clock *clk = clk_edge->clock(); + const RiseFall *clk_rf = clk_edge->transition(); + ClkInfo *clk_info = clk_path->clkInfo(sta); + const Pin *src_pin = clk_info->clkSrc(); + const PathAnalysisPt *path_ap = clk_path->pathAnalysisPt(sta); + const MinMax *min_max = clk_path->minMax(sta); + return sta->search()->clockInsertion(clk, src_pin, clk_rf, min_max, min_max, path_ap); +} + +float +ClkDelays::clkTreeDelay(PathVertex *clk_path, + StaState *sta) +{ + const Vertex *vertex = clk_path->vertex(sta); + const Pin *pin = vertex->pin(); + const LibertyPort *port = sta->network()->libertyPort(pin); + const MinMax *min_max = clk_path->minMax(sta); + const RiseFall *rf = clk_path->transition(sta); + Slew slew = clk_path->slew(sta); + return port->clkTreeDelay(slew, rf, min_max); +} + +} // namespace diff --git a/search/ClkLatency.hh b/search/ClkLatency.hh new file mode 100644 index 00000000..4bbe5fab --- /dev/null +++ b/search/ClkLatency.hh @@ -0,0 +1,52 @@ +// OpenSTA, Static Timing Analyzer +// Copyright (c) 2024, 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 "SdcClass.hh" +#include "StaState.hh" +#include "Transition.hh" +#include "SearchClass.hh" +#include "PathVertex.hh" +#include "ClkDelays.hh" + +namespace sta { + +typedef std::map ClkDelayMap; + +// Find and report clock skews between source/target registers. +class ClkLatency : public StaState +{ +public: + ClkLatency(StaState *sta); + // Report clk latency for clks. + void reportClkLatency(ConstClockSeq clks, + const Corner *corner, + int digits); + ClkDelays findClkDelays(const Clock *clk, + const Corner *corner); + +protected: + ClkDelayMap findClkDelays(ConstClockSeq clks, + const Corner *corner); + void reportClkLatency(const Clock *clk, + ClkDelays &clk_delays, + int digits); +}; + +} // namespace diff --git a/search/ClkSkew.cc b/search/ClkSkew.cc index 971e1ea1..b3fd5160 100644 --- a/search/ClkSkew.cc +++ b/search/ClkSkew.cc @@ -17,12 +17,13 @@ #include "ClkSkew.hh" #include // abs +#include #include "Report.hh" #include "Debug.hh" -#include "Fuzzy.hh" #include "Units.hh" #include "TimingArc.hh" +#include "Liberty.hh" #include "Network.hh" #include "Graph.hh" #include "Sdc.hh" @@ -52,10 +53,15 @@ public: PathVertex *tgtPath() { return &tgt_path_; } float srcLatency(StaState *sta); float tgtLatency(StaState *sta); + float srcClkTreeDelay(StaState *sta); + float tgtClkTreeDelay(StaState *sta); Crpr crpr(StaState *sta); float skew() const { return skew_; } private: + float clkTreeDelay(PathVertex &clk_path, + StaState *sta); + PathVertex src_path_; PathVertex tgt_path_; float skew_; @@ -94,14 +100,41 @@ float ClkSkew::srcLatency(StaState *sta) { Arrival src_arrival = src_path_.arrival(sta); - return delayAsFloat(src_arrival) - src_path_.clkEdge(sta)->time(); + return delayAsFloat(src_arrival) - src_path_.clkEdge(sta)->time() + + clkTreeDelay(src_path_, sta); +} + +float +ClkSkew::srcClkTreeDelay(StaState *sta) +{ + return clkTreeDelay(src_path_, sta); } float ClkSkew::tgtLatency(StaState *sta) { Arrival tgt_arrival = tgt_path_.arrival(sta); - return delayAsFloat(tgt_arrival) - tgt_path_.clkEdge(sta)->time(); + return delayAsFloat(tgt_arrival) - tgt_path_.clkEdge(sta)->time() + + clkTreeDelay(tgt_path_, sta); +} + +float +ClkSkew::tgtClkTreeDelay(StaState *sta) +{ + return clkTreeDelay(tgt_path_, sta); +} + +float +ClkSkew::clkTreeDelay(PathVertex &clk_path, + StaState *sta) +{ + const Vertex *vertex = clk_path.vertex(sta); + const Pin *pin = vertex->pin(); + const LibertyPort *port = sta->network()->libertyPort(pin); + const MinMax *min_max = clk_path.minMax(sta); + const RiseFall *rf = clk_path.transition(sta); + Slew slew = clk_path.slew(sta); + return port->clkTreeDelay(slew, rf, min_max); } Crpr @@ -119,106 +152,129 @@ ClkSkews::ClkSkews(StaState *sta) : } void -ClkSkews::reportClkSkew(ClockSet *clks, +ClkSkews::reportClkSkew(ConstClockSeq clks, const Corner *corner, const SetupHold *setup_hold, int digits) { - ClkSkewMap skews; - findClkSkew(clks, corner, setup_hold, skews); + ClkSkewMap skews = findClkSkew(clks, corner, setup_hold); // Sort the clocks to report in a stable order. - ClockSeq sorted_clks; - for (Clock *clk : *clks) + ConstClockSeq sorted_clks; + for (const Clock *clk : clks) sorted_clks.push_back(clk); - sort(sorted_clks, ClkNameLess()); + std::sort(sorted_clks.begin(), sorted_clks.end(), ClkNameLess()); - Unit *time_unit = units_->timeUnit(); - ClockSeq::Iterator clk_iter2(sorted_clks); - while (clk_iter2.hasNext()) { - Clock *clk = clk_iter2.next(); + for (const Clock *clk : sorted_clks) { report_->reportLine("Clock %s", clk->name()); - ClkSkew *clk_skew = skews.findKey(clk); - if (clk_skew) { - report_->reportLine("Latency CRPR Skew"); - PathVertex *src_path = clk_skew->srcPath(); - PathVertex *tgt_path = clk_skew->tgtPath(); - report_->reportLine("%s %s", - sdc_network_->pathName(src_path->pin(this)), - src_path->transition(this)->asString()); - report_->reportLine("%7s", - time_unit->asString(clk_skew->srcLatency(this), digits)); - report_->reportLine("%s %s", - sdc_network_->pathName(tgt_path->pin(this)), - tgt_path->transition(this)->asString()); - report_->reportLine("%7s %7s %7s", - time_unit->asString(clk_skew->tgtLatency(this), digits), - time_unit->asString(delayAsFloat(-clk_skew->crpr(this)), - digits), - time_unit->asString(clk_skew->skew(), digits)); - } + auto skew_itr = skews.find(clk); + if (skew_itr != skews.end()) + reportClkSkew(skew_itr->second, digits); else report_->reportLine("No launch/capture paths found."); report_->reportBlankLine(); } +} - skews.deleteContents(); +void +ClkSkews::reportClkSkew(ClkSkew &clk_skew, + int digits) +{ + Unit *time_unit = units_->timeUnit(); + PathVertex *src_path = clk_skew.srcPath(); + PathVertex *tgt_path = clk_skew.tgtPath(); + float src_latency = clk_skew.srcLatency(this); + float tgt_latency = clk_skew.tgtLatency(this); + float src_clk_tree_delay = clk_skew.srcClkTreeDelay(this); + float tgt_clk_tree_delay = clk_skew.tgtClkTreeDelay(this); + + if (src_clk_tree_delay != 0.0) + src_latency -= src_clk_tree_delay; + report_->reportLine("%7s source latency %s %s", + time_unit->asString(src_latency, digits), + sdc_network_->pathName(src_path->pin(this)), + src_path->transition(this)->asString()); + if (src_clk_tree_delay != 0.0) + report_->reportLine("%7s source clock tree delay", + time_unit->asString(src_clk_tree_delay, digits)); + + if (tgt_clk_tree_delay != 0.0) + tgt_latency -= tgt_clk_tree_delay; + report_->reportLine("%7s target latency %s %s", + time_unit->asString(-tgt_latency, digits), + sdc_network_->pathName(tgt_path->pin(this)), + tgt_path->transition(this)->asString()); + if (tgt_clk_tree_delay != 0.0) + report_->reportLine("%7s target clock tree delay", + time_unit->asString(-tgt_clk_tree_delay, digits)); + + report_->reportLine("%7s CRPR", + time_unit->asString(delayAsFloat(-clk_skew.crpr(this)), + digits)); + report_->reportLine("--------------"); + report_->reportLine("%7s %s skew", + time_unit->asString(clk_skew.skew(), digits), + src_path->minMax(this) == MinMax::max() ? "setup" : "hold"); } float ClkSkews::findWorstClkSkew(const Corner *corner, const SetupHold *setup_hold) { - ClockSet clks; - for (Clock *clk : *sdc_->clocks()) - clks.insert(clk); - ClkSkewMap skews; - findClkSkew(&clks, corner, setup_hold, skews); + ConstClockSeq clks; + for (const Clock *clk : *sdc_->clocks()) + clks.push_back(clk); + ClkSkewMap skews = findClkSkew(clks, corner, setup_hold); float worst_skew = 0.0; for (auto clk_skew_itr : skews) { - ClkSkew *clk_skew = clk_skew_itr.second; - float skew = clk_skew->skew(); + ClkSkew &clk_skew = clk_skew_itr.second; + float skew = clk_skew.skew(); if (abs(skew) > abs(worst_skew)) worst_skew = skew; } - skews.deleteContents(); return worst_skew; } -void -ClkSkews::findClkSkew(ClockSet *clks, +ClkSkewMap +ClkSkews::findClkSkew(ConstClockSeq &clks, const Corner *corner, - const SetupHold *setup_hold, - ClkSkewMap &skews) + const SetupHold *setup_hold) { + ClkSkewMap skews; + + ConstClockSet clk_set; + for (const Clock *clk : clks) + clk_set.insert(clk); + for (Vertex *src_vertex : *graph_->regClkVertices()) { - if (hasClkPaths(src_vertex, clks)) { + if (hasClkPaths(src_vertex, clk_set)) { VertexOutEdgeIterator edge_iter(src_vertex, graph_); while (edge_iter.hasNext()) { Edge *edge = edge_iter.next(); if (edge->role()->genericRole() == TimingRole::regClkToQ()) { Vertex *q_vertex = edge->to(graph_); - RiseFall *rf = edge->timingArcSet()->isRisingFallingEdge(); - RiseFallBoth *src_rf = rf + const RiseFall *rf = edge->timingArcSet()->isRisingFallingEdge(); + const RiseFallBoth *src_rf = rf ? rf->asRiseFallBoth() : RiseFallBoth::riseFall(); - findClkSkewFrom(src_vertex, q_vertex, src_rf, clks, + findClkSkewFrom(src_vertex, q_vertex, src_rf, clk_set, corner, setup_hold, skews); } } } } + return skews; } bool ClkSkews::hasClkPaths(Vertex *vertex, - ClockSet *clks) + ConstClockSet &clks) { VertexPathIterator path_iter(vertex, this); while (path_iter.hasNext()) { PathVertex *path = path_iter.next(); const Clock *path_clk = path->clock(this); - if (clks->hasKey(const_cast(path_clk))) + if (clks.find(path_clk) != clks.end()) return true; } return false; @@ -227,16 +283,14 @@ ClkSkews::hasClkPaths(Vertex *vertex, void ClkSkews::findClkSkewFrom(Vertex *src_vertex, Vertex *q_vertex, - RiseFallBoth *src_rf, - ClockSet *clks, + const RiseFallBoth *src_rf, + ConstClockSet &clk_set, const Corner *corner, const SetupHold *setup_hold, ClkSkewMap &skews) { VertexSet endpoints = findFanout(q_vertex); - VertexSet::Iterator end_iter(endpoints); - while (end_iter.hasNext()) { - Vertex *end = end_iter.next(); + for (Vertex *end : endpoints) { VertexInEdgeIterator edge_iter(end, graph_); while (edge_iter.hasNext()) { Edge *edge = edge_iter.next(); @@ -247,12 +301,12 @@ ClkSkews::findClkSkewFrom(Vertex *src_vertex, || ((setup_hold == SetupHold::min() && role->genericRole() == TimingRole::hold())))) { Vertex *tgt_vertex = edge->from(graph_); - RiseFall *tgt_rf1 = edge->timingArcSet()->isRisingFallingEdge(); - RiseFallBoth *tgt_rf = tgt_rf1 + const RiseFall *tgt_rf1 = edge->timingArcSet()->isRisingFallingEdge(); + const RiseFallBoth *tgt_rf = tgt_rf1 ? tgt_rf1->asRiseFallBoth() : RiseFallBoth::riseFall(); findClkSkew(src_vertex, src_rf, tgt_vertex, tgt_rf, - clks, corner, setup_hold, skews); + clk_set, corner, setup_hold, skews); } } } @@ -260,13 +314,13 @@ ClkSkews::findClkSkewFrom(Vertex *src_vertex, void ClkSkews::findClkSkew(Vertex *src_vertex, - RiseFallBoth *src_rf, + const RiseFallBoth *src_rf, Vertex *tgt_vertex, - RiseFallBoth *tgt_rf, - ClockSet *clks, + const RiseFallBoth *tgt_rf, + ConstClockSet &clk_set, const Corner *corner, const SetupHold *setup_hold, - ClkSkewMap &skews) + ClkSkewMap &skews) { Unit *time_unit = units_->timeUnit(); const SetupHold *tgt_min_max = setup_hold->opposite(); @@ -276,7 +330,7 @@ ClkSkews::findClkSkew(Vertex *src_vertex, const Clock *src_clk = src_path->clock(this); if (src_rf->matches(src_path->transition(this)) && src_path->minMax(this) == setup_hold - && clks->hasKey(const_cast(src_clk))) { + && clk_set.find(src_clk) != clk_set.end()) { Corner *src_corner = src_path->pathAnalysisPt(this)->corner(); if (corner == nullptr || src_corner == corner) { @@ -290,7 +344,7 @@ ClkSkews::findClkSkew(Vertex *src_vertex, && tgt_path->minMax(this) == tgt_min_max && tgt_path->pathAnalysisPt(this)->corner() == src_corner) { ClkSkew probe(src_path, tgt_path, this); - ClkSkew *clk_skew = skews.findKey(const_cast(src_clk)); + ClkSkew &clk_skew = skews[src_clk]; debugPrint(debug_, "clk_skew", 2, "%s %s %s -> %s %s %s crpr = %s skew = %s", network_->pathName(src_path->pin(this)), @@ -301,12 +355,9 @@ ClkSkews::findClkSkew(Vertex *src_vertex, time_unit->asString(probe.tgtLatency(this)), delayAsString(probe.crpr(this), this), time_unit->asString(probe.skew())); - if (clk_skew == nullptr) { - clk_skew = new ClkSkew(probe); - skews[src_clk] = clk_skew; - } - else if (abs(probe.skew()) > abs(clk_skew->skew())) - *clk_skew = probe; + if (clk_skew.srcPath()->isNull() + || abs(probe.skew()) > abs(clk_skew.skew())) + clk_skew = probe; } } } @@ -358,31 +409,4 @@ ClkSkews::findFanout(Vertex *from) return endpoints; } -//////////////////////////////////////////////////////////////// - -void -ClkSkews::findClkDelays(const Clock *clk, - // Return values. - ClkDelays &delays) -{ - for (Vertex *clk_vertex : *graph_->regClkVertices()) { - VertexPathIterator path_iter(clk_vertex, this); - while (path_iter.hasNext()) { - PathVertex *path = path_iter.next(); - const ClockEdge *path_clk_edge = path->clkEdge(this); - if (path_clk_edge) { - const RiseFall *clk_rf = path_clk_edge->transition(); - const Clock *path_clk = path_clk_edge->clock(); - if (path_clk == clk) { - Arrival arrival = path->arrival(this); - Delay clk_delay = delayAsFloat(arrival) - path_clk_edge->time(); - const MinMax *min_max = path->minMax(this); - const RiseFall *rf = path->transition(this); - delays[clk_rf->index()][rf->index()].setValue(min_max, clk_delay); - } - } - } - } -} - } // namespace diff --git a/search/ClkSkew.hh b/search/ClkSkew.hh index 97497436..ca2491b3 100644 --- a/search/ClkSkew.hh +++ b/search/ClkSkew.hh @@ -16,17 +16,19 @@ #pragma once -#include "Map.hh" +#include + #include "SdcClass.hh" #include "StaState.hh" #include "Transition.hh" #include "SearchClass.hh" +#include "PathVertex.hh" namespace sta { class ClkSkew; -typedef Map ClkSkewMap; +typedef std::map ClkSkewMap; // Find and report clock skews between source/target registers. class ClkSkews : public StaState @@ -34,40 +36,38 @@ class ClkSkews : public StaState public: ClkSkews(StaState *sta); // Report clk skews for clks. - void reportClkSkew(ClockSet *clks, + void reportClkSkew(ConstClockSeq clks, const Corner *corner, const SetupHold *setup_hold, int digits); // Find worst clock skew between src/target registers. float findWorstClkSkew(const Corner *corner, const SetupHold *setup_hold); - void findClkDelays(const Clock *clk, - // Return values. - ClkDelays &delays); protected: - void findClkSkew(ClockSet *clks, - const Corner *corner, - const SetupHold *setup_hold, - ClkSkewMap &skews); + ClkSkewMap findClkSkew(ConstClockSeq &clks, + const Corner *corner, + const SetupHold *setup_hold); bool hasClkPaths(Vertex *vertex, - ClockSet *clks); + ConstClockSet &clks); void findClkSkewFrom(Vertex *src_vertex, Vertex *q_vertex, - RiseFallBoth *src_rf, - ClockSet *clks, + const RiseFallBoth *src_rf, + ConstClockSet &clk_set, const Corner *corner, const SetupHold *setup_hold, ClkSkewMap &skews); void findClkSkew(Vertex *src_vertex, - RiseFallBoth *src_rf, + const RiseFallBoth *src_rf, Vertex *tgt_vertex, - RiseFallBoth *tgt_rf, - ClockSet *clks, + const RiseFallBoth *tgt_rf, + ConstClockSet &clk_set, const Corner *corner, const SetupHold *setup_hold, ClkSkewMap &skews); VertexSet findFanout(Vertex *from); + void reportClkSkew(ClkSkew &clk_skew, + int digits); }; } // namespace diff --git a/search/MakeTimingModel.cc b/search/MakeTimingModel.cc index 2f6eee93..ff9a861f 100644 --- a/search/MakeTimingModel.cc +++ b/search/MakeTimingModel.cc @@ -40,7 +40,7 @@ #include "Sta.hh" #include "VisitPathEnds.hh" #include "ArcDelayCalc.hh" -#include "ClkSkew.hh" +#include "ClkLatency.hh" namespace sta { @@ -156,7 +156,22 @@ void MakeTimingModel::makeCell() { cell_ = lib_builder_->makeCell(library_, cell_name_, filename_); - cell_->setInterfaceTiming(true); + cell_->setIsMacro(true); + cell_->setArea(findArea()); +} + +float +MakeTimingModel::findArea() +{ + float area = 0.0; + LeafInstanceIterator *leaf_iter = network_->leafInstanceIterator(); + while (leaf_iter->hasNext()) { + const Instance *inst = leaf_iter->next(); + const LibertyCell *cell = network_->libertyCell(inst); + area += cell->area(); + } + delete leaf_iter; + return area; } void @@ -527,17 +542,17 @@ MakeTimingModel::findClkInsertionDelays() size_t clk_count = clks->size(); if (clk_count == 1) { for (const Clock *clk : *clks) { - ClkDelays delays; - sta_->findClkDelays(clk, delays); + ClkDelays delays = sta_->findClkDelays(clk); 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(); - for (const int end_rf_index : RiseFall::rangeIndex()) { - Delay delay1; + for (const RiseFall *end_rf : RiseFall::range()) { + PathVertex clk_path; + float insertion, delay1, lib_clk_delay, latency; bool exists; - delays[clk_rf_index][end_rf_index].value(min_max, delay1, exists); + delays.delay(clk_rf, end_rf, min_max, insertion, delay1, + lib_clk_delay, latency, clk_path, exists); if (exists) delay = min_max->minMax(delay, delayAsFloat(delay1)); } @@ -551,7 +566,8 @@ MakeTimingModel::findClkInsertionDelays() TimingRole *role = (min_max == MinMax::min()) ? TimingRole::clockTreePathMin() : TimingRole::clockTreePathMax(); - lib_builder_->makeClockTreePathArcs(cell_, lib_port, role, attrs); + lib_builder_->makeClockTreePathArcs(cell_, lib_port, role, + min_max, attrs); } } } diff --git a/search/MakeTimingModelPvt.hh b/search/MakeTimingModelPvt.hh index 55689296..da5f15b8 100644 --- a/search/MakeTimingModelPvt.hh +++ b/search/MakeTimingModelPvt.hh @@ -57,6 +57,7 @@ public: private: void makeLibrary(); void makeCell(); + float findArea(); void makePorts(); void checkClock(Clock *clk); void findTimingFromInputs(); diff --git a/search/Sim.cc b/search/Sim.cc index 30db0f0c..2e37b334 100644 --- a/search/Sim.cc +++ b/search/Sim.cc @@ -60,14 +60,13 @@ Sim::Sim(StaState *sta) : invalid_load_pins_(network_), instances_with_const_pins_(network_), instances_to_annotate_(network_), - cudd_mgr_(Cudd_Init(0, 0, CUDD_UNIQUE_SLOTS, CUDD_CACHE_SLOTS, 0)) + bdd_(sta) { } Sim::~Sim() { delete observer_; - Cudd_Quit(cudd_mgr_); } #if CUDD @@ -82,17 +81,18 @@ Sim::functionSense(const FuncExpr *expr, expr->asString()); bool increasing, decreasing; { - UniqueLock lock(cudd_lock_); - DdNode *bdd = funcBdd(expr, inst); + UniqueLock lock(bdd_lock_); + DdNode *bdd = funcBddSim(expr, inst); + DdManager *cudd_mgr = bdd_.cuddMgr(); LibertyPort *input_port = network_->libertyPort(input_pin); - DdNode *input_node = ensureNode(input_port); + DdNode *input_node = bdd_.ensureNode(input_port); unsigned int input_index = Cudd_NodeReadIndex(input_node); - 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(); + 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); + bdd_.clearVarMap(); } TimingSense sense; if (increasing && decreasing) @@ -107,127 +107,57 @@ Sim::functionSense(const FuncExpr *expr, return sense; } -void -Sim::clearSymtab() const -{ - for (auto name_node : symtab_) { - DdNode *sym_node = name_node.second; - Cudd_RecursiveDeref(cudd_mgr_, sym_node); - } - symtab_.clear(); -} - LogicValue Sim::evalExpr(const FuncExpr *expr, - const Instance *inst) const + const Instance *inst) { - UniqueLock lock(cudd_lock_); - DdNode *bdd = funcBdd(expr, inst); + UniqueLock lock(bdd_lock_); + DdNode *bdd = funcBddSim(expr, inst); LogicValue value = LogicValue::unknown; - if (bdd == Cudd_ReadLogicZero(cudd_mgr_)) + DdManager *cudd_mgr = bdd_.cuddMgr(); + if (bdd == Cudd_ReadLogicZero(cudd_mgr)) value = LogicValue::zero; - else if (bdd == Cudd_ReadOne(cudd_mgr_)) + else if (bdd == Cudd_ReadOne(cudd_mgr)) value = LogicValue::one; + if (bdd) { - Cudd_RecursiveDeref(cudd_mgr_, bdd); - clearSymtab(); + Cudd_RecursiveDeref(bdd_.cuddMgr(), bdd); + bdd_.clearVarMap(); } return value; } -// Returns nullptr if the expression simply references an internal port. +// BDD with instance pin values substituted. DdNode * -Sim::funcBdd(const FuncExpr *expr, - const Instance *inst) const +Sim::funcBddSim(const FuncExpr *expr, + const Instance *inst) { - DdNode *left = nullptr; - DdNode *right = nullptr; - DdNode *result = nullptr; - switch (expr->op()) { - case FuncExpr::op_port: { - LibertyPort *port = expr->port(); - Pin *pin = network_->findPin(inst, port); - // Internal ports don't have instance pins. - if (pin) { + DdNode *bdd = bdd_.funcBdd(expr); + DdManager *cudd_mgr = bdd_.cuddMgr(); + InstancePinIterator *pin_iter = network_->pinIterator(inst); + while (pin_iter->hasNext()) { + const Pin *pin = pin_iter->next(); + const LibertyPort *port = network_->libertyPort(pin); + DdNode *port_node = bdd_.findNode(port); + if (port_node) { LogicValue value = logicValue(pin); + int var_index = Cudd_NodeReadIndex(port_node); + //printf("%s %d %c\n", port->name(), var_index, logicValueString(value)); switch (value) { case LogicValue::zero: - result = Cudd_ReadLogicZero(cudd_mgr_); - break; + bdd = Cudd_bddCompose(cudd_mgr, bdd, Cudd_ReadLogicZero(cudd_mgr), var_index); + Cudd_Ref(bdd); + break; case LogicValue::one: - result = Cudd_ReadOne(cudd_mgr_); - break; + bdd = Cudd_bddCompose(cudd_mgr, bdd, Cudd_ReadOne(cudd_mgr), var_index); + Cudd_Ref(bdd); + break; default: - result = ensureNode(port); - break; + break; } } - break; } - case FuncExpr::op_not: - left = funcBdd(expr->left(), inst); - if (left) - result = Cudd_Not(left); - break; - case FuncExpr::op_or: - left = funcBdd(expr->left(), inst); - right = funcBdd(expr->right(), inst); - 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(), inst); - right = funcBdd(expr->right(), inst); - 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(), inst); - right = funcBdd(expr->right(), inst); - 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(1520, "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 * -Sim::ensureNode(LibertyPort *port) const -{ - const char *port_name = port->name(); - DdNode *node = symtab_.findKey(port_name); - if (node == nullptr) { - node = Cudd_bddNewVar(cudd_mgr_); - symtab_[port_name] = node; - Cudd_Ref(node); - } - return node; + return bdd; } #else @@ -468,7 +398,7 @@ Sim::functionSense(const FuncExpr *expr, LogicValue Sim::evalExpr(const FuncExpr *expr, - const Instance *inst) const + const Instance *inst) { switch (expr->op()) { case FuncExpr::op_port: { @@ -1199,7 +1129,7 @@ isCondDisabled(Edge *edge, const Pin *from_pin, const Pin *to_pin, const Network *network, - const Sim *sim) + Sim *sim) { bool is_disabled; FuncExpr *disable_cond; @@ -1214,7 +1144,7 @@ isCondDisabled(Edge *edge, const Pin *from_pin, const Pin *to_pin, const Network *network, - const Sim *sim, + Sim *sim, bool &is_disabled, FuncExpr *&disable_cond) { @@ -1248,7 +1178,7 @@ bool isModeDisabled(Edge *edge, const Instance *inst, const Network *network, - const Sim *sim) + Sim *sim) { bool is_disabled; FuncExpr *disable_cond; @@ -1261,7 +1191,7 @@ void isModeDisabled(Edge *edge, const Instance *inst, const Network *network, - const Sim *sim, + Sim *sim, bool &is_disabled, FuncExpr *&disable_cond) { diff --git a/search/Sim.hh b/search/Sim.hh index 25a808f1..eeca92a4 100644 --- a/search/Sim.hh +++ b/search/Sim.hh @@ -25,9 +25,7 @@ #include "GraphClass.hh" #include "SdcClass.hh" #include "StaState.hh" - -struct DdNode; -struct DdManager; +#include "Bdd.hh" namespace sta { @@ -35,7 +33,6 @@ class SimObserver; typedef Map PinValueMap; typedef std::queue EvalQueue; -typedef Map BddSymbolTable; // Propagate constants from constraints and netlist tie high/low // connections thru gates. @@ -50,7 +47,7 @@ public: void ensureConstantsPropagated(); void constantsInvalid(); LogicValue evalExpr(const FuncExpr *expr, - const Instance *inst) const; + const Instance *inst); LogicValue logicValue(const Pin *pin) const; bool logicZeroOne(const Pin *pin) const; bool logicZeroOne(const Vertex *vertex) const; @@ -110,6 +107,8 @@ protected: const Pin *load_pin); void setSimValue(Vertex *vertex, LogicValue value); + DdNode *funcBddSim(const FuncExpr *expr, + const Instance *inst); SimObserver *observer_; bool valid_; @@ -128,15 +127,8 @@ protected: // Instances with constant pin values for annotateVertexEdges. InstanceSet instances_with_const_pins_; InstanceSet instances_to_annotate_; - - DdNode *funcBdd(const FuncExpr *expr, - const Instance *inst) const; - DdNode *ensureNode(LibertyPort *port) const; - void clearSymtab() const; - - DdManager *cudd_mgr_; - mutable BddSymbolTable symtab_; - mutable std::mutex cudd_lock_; + Bdd bdd_; + mutable std::mutex bdd_lock_; }; // Abstract base class for Sim value change observer. @@ -160,7 +152,7 @@ isCondDisabled(Edge *edge, const Pin *from_pin, const Pin *to_pin, const Network *network, - const Sim *sim); + Sim *sim); // isCondDisabled but also return the cond expression that causes // the disable. This can differ from the edge cond expression @@ -172,7 +164,7 @@ isCondDisabled(Edge *edge, const Pin *from_pin, const Pin *to_pin, const Network *network, - const Sim *sim, + Sim *sim, bool &is_disabled, FuncExpr *&disable_cond); @@ -182,12 +174,12 @@ bool isModeDisabled(Edge *edge, const Instance *inst, const Network *network, - const Sim *sim); + Sim *sim); void isModeDisabled(Edge *edge, const Instance *inst, const Network *network, - const Sim *sim, + Sim *sim, bool &is_disabled, FuncExpr *&disable_cond); diff --git a/search/Sta.cc b/search/Sta.cc index e1246db9..f6012128 100644 --- a/search/Sta.cc +++ b/search/Sta.cc @@ -63,6 +63,7 @@ #include "CheckMinPeriods.hh" #include "CheckMaxSkews.hh" #include "ClkSkew.hh" +#include "ClkLatency.hh" #include "FindRegister.hh" #include "ReportPath.hh" #include "VisitPathGroupVertices.hh" @@ -2600,7 +2601,7 @@ Sta::updateTiming(bool full) //////////////////////////////////////////////////////////////// void -Sta::reportClkSkew(ClockSet *clks, +Sta::reportClkSkew(ConstClockSeq clks, const Corner *corner, const SetupHold *setup_hold, int digits) @@ -2616,15 +2617,6 @@ Sta::findWorstClkSkew(const SetupHold *setup_hold) return clk_skews_->findWorstClkSkew(cmd_corner_, setup_hold); } -void -Sta::findClkDelays(const Clock *clk, - // Return values. - ClkDelays &delays) -{ - clkSkewPreamble(); - clk_skews_->findClkDelays(clk, delays); -} - void Sta::clkSkewPreamble() { @@ -2635,6 +2627,26 @@ Sta::clkSkewPreamble() //////////////////////////////////////////////////////////////// +void +Sta::reportClkLatency(ConstClockSeq clks, + const Corner *corner, + int digits) +{ + ensureClkArrivals(); + ClkLatency clk_latency(this); + clk_latency.reportClkLatency(clks, corner, digits); +} + +ClkDelays +Sta::findClkDelays(const Clock *clk) +{ + ensureClkArrivals(); + ClkLatency clk_latency(this); + return clk_latency.findClkDelays(clk, nullptr); +} + +//////////////////////////////////////////////////////////////// + void Sta::delaysInvalid() { diff --git a/search/WritePathSpice.cc b/search/WritePathSpice.cc index 608c32d9..93b0970e 100644 --- a/search/WritePathSpice.cc +++ b/search/WritePathSpice.cc @@ -17,7 +17,6 @@ #include "WritePathSpice.hh" #include -#include #include #include "Debug.hh" @@ -27,9 +26,9 @@ #include "FuncExpr.hh" #include "Units.hh" #include "Sequential.hh" -#include "TableModel.hh" #include "Liberty.hh" #include "TimingArc.hh" +#include "TableModel.hh" #include "PortDirection.hh" #include "Network.hh" #include "Graph.hh" @@ -42,6 +41,7 @@ #include "PathExpanded.hh" #include "StaState.hh" #include "Sim.hh" +#include "WriteSpice.hh" namespace sta { @@ -49,135 +49,47 @@ using std::ofstream; using std::ifstream; using std::max; -typedef Map CellSpicePortNames; typedef int Stage; -typedef Map ParasiticNodeMap; -typedef Map LibertyPortLogicValues; - -void -streamPrint(ofstream &stream, - const char *fmt, - ...) __attribute__((format (printf, 2, 3))); //////////////////////////////////////////////////////////////// -class WritePathSpice : public StaState +class WritePathSpice : public WriteSpice { public: WritePathSpice(Path *path, const char *spice_filename, - const char *subckt_filename, + const char *subckt_filename, const char *lib_subckt_filename, const char *model_filename, - StdStringSet *off_path_pin_names, const char *power_name, const char *gnd_name, - bool measure_stmts, + CircuitSim ckt_sim, const StaState *sta); - ~WritePathSpice(); void writeSpice(); private: void writeHeader(); + void writePrintStmt(); void writeStageInstances(); void writeInputSource(); - void writeRampVoltSource(const Pin *pin, - const RiseFall *rf, - float slew, - float time, - int &volt_index); void writeStageSubckts(); void writeInputStage(Stage stage); void writeMeasureStmts(); void writeMeasureStmt(const Pin *pin); void writeGateStage(Stage stage); - void writeSubcktInst(const Pin *input_pin); - void writeSubcktInstVoltSrcs(Stage stage, - const Pin *input_pin, - int &volt_index, - LibertyPortLogicValues &port_values, - const Clock *clk, - DcalcAPIndex dcalc_ap_index); void writeStageParasitics(Stage stage); - void writeStageParasiticNetwork(Pin *drvr_pin, - Parasitic *parasitic); - void writeStagePiElmore(Pin *drvr_pin, - Parasitic *parasitic); - void writeNullParasitics(Pin *drvr_pin); void writeSubckts(); - StdStringSet findPathCellnames(); + StdStringSet findPathCellNames(); void findPathCellSubckts(StdStringSet &path_cell_names); - void recordSpicePortNames(const char *cell_name, - StringVector &tokens); float maxTime(); float pathMaxTime(); - const char *nodeName(ParasiticNode *node); - void initNodeMap(const char *net_name); - const char *spiceTrans(const RiseFall *rf); void writeMeasureDelayStmt(Stage stage, Path *from_path, Path *to_path); void writeMeasureSlewStmt(Stage stage, Path *path); - void gatePortValues(Stage stage, - // Return values. - LibertyPortLogicValues &port_values, - const Clock *&clk, - DcalcAPIndex &dcalc_ap_index); - void regPortValues(Stage stage, - // Return values. - LibertyPortLogicValues &port_values, - const Clock *&clk, - DcalcAPIndex &dcalc_ap_index); - void gatePortValues(const Instance *inst, - FuncExpr *expr, - LibertyPort *input_port, - // Return values. - LibertyPortLogicValues &port_values); - void seqPortValues(Sequential *seq, - const RiseFall *rf, - // Return values. - LibertyPortLogicValues &port_values); void writeInputWaveform(); void writeClkWaveform(); - void writeWaveformEdge(const RiseFall *rf, - float time, - float slew); - void writeWaveformVoltSource(const Pin *pin, - DriverWaveform *drvr_waveform, - const RiseFall *rf, - float slew, - int &volt_index); - void writeClkedStepSource(const Pin *pin, - const RiseFall *rf, - const Clock *clk, - DcalcAPIndex dcalc_ap_index, - int &volt_index); - float clkWaveformTImeOffset(const Clock *clk); - float findSlew(Path *path); - float findSlew(Path *path, - const RiseFall *rf, - TimingArc *next_arc); - float findSlew(Vertex *vertex, - const RiseFall *rf, - TimingArc *next_arc, - DcalcAPIndex dcalc_ap_index); - LibertyPort *onePort(FuncExpr *expr); - void writeVoltageSource(const char *inst_name, - const char *port_name, - float voltage, - int &volt_index); - void writeVoltageSource(LibertyCell *cell, - const char *inst_name, - const char *subckt_port_name, - const char *pg_port_name, - float voltage, - int &volt_index); - float slewAxisMinValue(TimingArc *arc); - float pgPortVoltage(LibertyPgPort *pg_port); - void writePrintStmt(); - float railToRailSlew(float slew, - const RiseFall *rf); // Stage "accessors". // @@ -215,129 +127,62 @@ private: const char *stageLoadPinName(Stage stage); LibertyCell *stageLibertyCell(Stage stage); Instance *stageInstance(Stage stage); - StdStringSet stageOffPathPinNames(Stage stage); + float findSlew(Path *path); + float findSlew(Path *path, + const RiseFall *rf, + TimingArc *next_arc); Path *path_; - const char *spice_filename_; - const char *subckt_filename_; - const char *lib_subckt_filename_; - const char *model_filename_; - StdStringSet *off_path_pin_names_; - const char *power_name_; - const char *gnd_name_; - bool measure_stmts_; - - ofstream spice_stream_; PathExpanded path_expanded_; - CellSpicePortNames cell_spice_port_names_; - ParasiticNodeMap node_map_; - int next_node_index_; - const char *net_name_; - float power_voltage_; - float gnd_voltage_; - LibertyLibrary *default_library_; - // Resistance to use to simulate a short circuit between spice nodes. - float short_ckt_resistance_; // Input clock waveform cycles. int clk_cycle_count_; + + using WriteSpice::writeHeader; + using WriteSpice::writePrintStmt; + using WriteSpice::writeSubckts; + using WriteSpice::writeVoltageSource; + using WriteSpice::writeMeasureDelayStmt; + using WriteSpice::writeMeasureSlewStmt; + using WriteSpice::findSlew; }; //////////////////////////////////////////////////////////////// -class SubcktEndsMissing : public Exception -{ -public: - SubcktEndsMissing(const char *cell_name, - const char *subckt_filename); - const char *what() const noexcept; - -protected: - string what_; -}; - -SubcktEndsMissing::SubcktEndsMissing(const char *cell_name, - const char *subckt_filename) : - Exception() -{ - what_ = "spice subckt for cell "; - what_ += cell_name; - what_ += " missing .ends in "; - what_ += subckt_filename; -} - -const char * -SubcktEndsMissing::what() const noexcept -{ - return what_.c_str(); -} - -//////////////////////////////////////////////////////////////// - void writePathSpice(Path *path, const char *spice_filename, const char *subckt_filename, const char *lib_subckt_filename, const char *model_filename, - StdStringSet *off_path_pin_names, const char *power_name, const char *gnd_name, - bool measure_stmts, + CircuitSim ckt_sim, StaState *sta) { if (sta->network()->defaultLibertyLibrary() == nullptr) sta->report()->error(1600, "No liberty libraries found,"); WritePathSpice writer(path, spice_filename, subckt_filename, - lib_subckt_filename, model_filename, - off_path_pin_names, power_name, gnd_name, - measure_stmts, sta); + lib_subckt_filename, model_filename, + power_name, gnd_name, ckt_sim, sta); writer.writeSpice(); } WritePathSpice::WritePathSpice(Path *path, - const char *spice_filename, + const char *spice_filename, const char *subckt_filename, const char *lib_subckt_filename, const char *model_filename, - StdStringSet *off_path_pin_names, const char *power_name, const char *gnd_name, - bool measure_stmts, + CircuitSim ckt_sim, const StaState *sta) : - StaState(sta), + WriteSpice(spice_filename, subckt_filename, lib_subckt_filename, + model_filename, power_name, gnd_name, ckt_sim, sta), path_(path), - spice_filename_(spice_filename), - subckt_filename_(subckt_filename), - lib_subckt_filename_(lib_subckt_filename), - model_filename_(model_filename), - off_path_pin_names_(off_path_pin_names), - power_name_(power_name), - gnd_name_(gnd_name), - measure_stmts_(measure_stmts), path_expanded_(sta), - net_name_(nullptr), - default_library_(network_->defaultLibertyLibrary()), - short_ckt_resistance_(.0001), clk_cycle_count_(3) { - bool exists = false; - default_library_->supplyVoltage(power_name_, power_voltage_, exists); - if (!exists) { - DcalcAnalysisPt *dcalc_ap = path_->dcalcAnalysisPt(this); - const OperatingConditions *op_cond = dcalc_ap->operatingConditions(); - if (op_cond == nullptr) - op_cond = network_->defaultLibertyLibrary()->defaultOperatingConditions(); - power_voltage_ = op_cond->voltage(); - } - default_library_->supplyVoltage(gnd_name_, gnd_voltage_, exists); - if (!exists) - gnd_voltage_ = 0.0; -} - -WritePathSpice::~WritePathSpice() -{ - stringDelete(net_name_); - cell_spice_port_names_.deleteContents(); + initPowerGnd( path_->dcalcAnalysisPt(this)); } void @@ -350,10 +195,10 @@ WritePathSpice::writeSpice() writeSubckts(); writeHeader(); writePrintStmt(); - writeStageInstances(); - if (measure_stmts_) + if (ckt_sim_ == CircuitSim::hspice) writeMeasureStmts(); writeInputSource(); + writeStageInstances(); writeStageSubckts(); streamPrint(spice_stream_, ".end\n"); spice_stream_.close(); @@ -362,55 +207,29 @@ WritePathSpice::writeSpice() throw FileNotWritable(spice_filename_); } -// Use c++17 fs::path(filename).stem() -static string -filenameStem(const char *filename) -{ - string filename1 = filename; - const size_t last_slash_idx = filename1.find_last_of("\\/"); - if (last_slash_idx != std::string::npos) - return filename1.substr(last_slash_idx + 1); - else - return filename1; -} - void WritePathSpice::writeHeader() { - const MinMax *min_max = path_->minMax(this); - Pvt *pvt = sdc_->operatingConditions(min_max); - if (pvt == nullptr) - pvt = default_library_->defaultOperatingConditions(); Path *start_path = path_expanded_.startPath(); - streamPrint(spice_stream_, "* Path from %s %s to %s %s\n", - network_->pathName(start_path->pin(this)), - start_path->transition(this)->asString(), - network_->pathName(path_->pin(this)), - path_->transition(this)->asString()); - streamPrint(spice_stream_, ".include \"%s\"\n", model_filename_); - string subckt_filename_stem = filenameStem(subckt_filename_); - streamPrint(spice_stream_, ".include \"%s\"\n", subckt_filename_stem.c_str()); - + string title = stdstrPrint("Path from %s %s to %s %s", + network_->pathName(start_path->pin(this)), + start_path->transition(this)->asString(), + network_->pathName(path_->pin(this)), + path_->transition(this)->asString()); float max_time = maxTime(); float time_step = 1e-13; - streamPrint(spice_stream_, ".tran %.3g %.3g\n\n", - time_step, max_time); - // Suppress printing model parameters. - streamPrint(spice_stream_, ".options nomod\n"); + writeHeader(title, max_time, time_step); } void WritePathSpice::writePrintStmt() { - streamPrint(spice_stream_, ".print tran"); + StdStringSeq node_names; for (Stage stage = stageFirst(); stage <= stageLast(); stage++) { - streamPrint(spice_stream_, " v(%s)", stageDrvrPinName(stage)); - streamPrint(spice_stream_, " v(%s)", stageLoadPinName(stage)); - StdStringSet off_path_names = stageOffPathPinNames(stage); - for (const string &off_path_name : off_path_names) - streamPrint(spice_stream_, " v(%s)", off_path_name.c_str()); + node_names.push_back(stageDrvrPinName(stage)); + node_names.push_back(stageLoadPinName(stage)); } - streamPrint(spice_stream_, "\n\n"); + writePrintStmt(node_names); } float @@ -457,15 +276,6 @@ WritePathSpice::pathMaxTime() return max_time; } -float -WritePathSpice::railToRailSlew(float slew, - const RiseFall *rf) -{ - float lower = default_library_->slewLowerThreshold(rf); - float upper = default_library_->slewUpperThreshold(rf); - return slew / (upper - lower); -} - void WritePathSpice::writeStageInstances() { @@ -483,48 +293,17 @@ WritePathSpice::writeStageInstances() stageLoadPinName(stage), stage_cname); else { - streamPrint(spice_stream_, "x%s %s %s %s", + streamPrint(spice_stream_, "x%s %s %s %s %s\n", stage_cname, stageGateInputPinName(stage), stageDrvrPinName(stage), - stageLoadPinName(stage)); - StdStringSet off_path_names = stageOffPathPinNames(stage); - for (const string &off_path_name : off_path_names) - streamPrint(spice_stream_, " %s", off_path_name.c_str()); - streamPrint(spice_stream_, " %s\n", stage_cname); + stageLoadPinName(stage), + stage_cname); } } streamPrint(spice_stream_, "\n"); } -float -WritePathSpice::pgPortVoltage(LibertyPgPort *pg_port) -{ - LibertyLibrary *liberty = pg_port->cell()->libertyLibrary(); - float voltage = 0.0; - bool exists; - const char *voltage_name = pg_port->voltageName(); - if (voltage_name) { - liberty->supplyVoltage(voltage_name, voltage, exists); - if (!exists) { - if (stringEqual(voltage_name, power_name_)) - voltage = power_voltage_; - else if (stringEqual(voltage_name, gnd_name_)) - voltage = gnd_voltage_; - else - report_->error(1601 , "pg_pin %s/%s voltage %s not found,", - pg_port->cell()->name(), - pg_port->name(), - voltage_name); - } - } - else - report_->error(1602, "Liberty pg_port %s/%s missing voltage_name attribute,", - pg_port->cell()->name(), - pg_port->name()); - return voltage; -} - void WritePathSpice::writeInputSource() { @@ -554,7 +333,6 @@ WritePathSpice::writeInputWaveform() float dt = railToRailSlew(slew0, rf); float time0 = dt * threshold; - int volt_index = 1; const Pin *drvr_pin = stageDrvrPin(input_stage); const Pin *load_pin = stageLoadPin(input_stage); const LibertyPort *load_port = network_->libertyPort(load_pin); @@ -562,71 +340,9 @@ WritePathSpice::writeInputWaveform() if (load_port) drvr_waveform = load_port->driverWaveform(rf); if (drvr_waveform) - writeWaveformVoltSource(drvr_pin, drvr_waveform, - rf, slew0, volt_index); + writeWaveformVoltSource(drvr_pin, drvr_waveform, rf, 0.0, slew0); else - writeRampVoltSource(drvr_pin, rf, slew0, time0, volt_index); -} - -void -WritePathSpice::writeWaveformVoltSource(const Pin *pin, - DriverWaveform *drvr_waveform, - const RiseFall *rf, - float slew, - int &volt_index) -{ - float volt0, volt1, volt_factor; - if (rf == RiseFall::rise()) { - volt0 = gnd_voltage_; - volt1 = power_voltage_; - volt_factor = power_voltage_; - } - else { - volt0 = power_voltage_; - volt1 = gnd_voltage_; - volt_factor = -power_voltage_; - } - streamPrint(spice_stream_, "v%d %s 0 pwl(\n", - volt_index, - network_->pathName(pin)); - streamPrint(spice_stream_, "+%.3e %.3e\n", 0.0, volt0); - Table1 waveform = drvr_waveform->waveform(slew); - const TableAxis *time_axis = waveform.axis1(); - for (size_t time_index = 0; time_index < time_axis->size(); time_index++) { - float time = time_axis->axisValue(time_index); - float wave_volt = waveform.value(time_index); - float volt = volt0 + wave_volt * volt_factor; - streamPrint(spice_stream_, "+%.3e %.3e\n", time, volt); - } - streamPrint(spice_stream_, "+%.3e %.3e\n", maxTime(), volt1); - streamPrint(spice_stream_, "+)\n"); - volt_index++; -} - -void -WritePathSpice::writeRampVoltSource(const Pin *pin, - const RiseFall *rf, - float slew, - float time, - int &volt_index) -{ - float volt0, volt1; - if (rf == RiseFall::rise()) { - volt0 = gnd_voltage_; - volt1 = power_voltage_; - } - else { - volt0 = power_voltage_; - volt1 = gnd_voltage_; - } - streamPrint(spice_stream_, "v%d %s 0 pwl(\n", - volt_index, - network_->pathName(pin)); - streamPrint(spice_stream_, "+%.3e %.3e\n", 0.0, volt0); - writeWaveformEdge(rf, time, slew); - streamPrint(spice_stream_, "+%.3e %.3e\n", maxTime(), volt1); - streamPrint(spice_stream_, "+)\n"); - volt_index++; + writeRampVoltSource(drvr_pin, rf, time0, slew0); } void @@ -636,9 +352,10 @@ WritePathSpice::writeClkWaveform() PathRef *input_path = stageDrvrPath(input_stage); TimingArc *next_arc = stageGateArc(input_stage + 1); const ClockEdge *clk_edge = input_path->clkEdge(this); + const Clock *clk = clk_edge->clock(); float period = clk->period(); - float time_offset = clkWaveformTImeOffset(clk); + float time_offset = clkWaveformTimeOffset(clk); RiseFall *rf0, *rf1; float volt0; if (clk_edge->time() < period) { @@ -662,16 +379,10 @@ WritePathSpice::writeClkWaveform() writeWaveformEdge(rf0, time0, slew0); writeWaveformEdge(rf1, time1, slew1); } - streamPrint(spice_stream_, "+%.3e %.3e\n", maxTime(), volt0); + streamPrint(spice_stream_, "+%.3e %.3e\n", max_time_, volt0); streamPrint(spice_stream_, "+)\n"); } -float -WritePathSpice::clkWaveformTImeOffset(const Clock *clk) -{ - return clk->period() / 10; -} - float WritePathSpice::findSlew(Path *path) { @@ -683,80 +394,14 @@ WritePathSpice::findSlew(Path *path) float WritePathSpice::findSlew(Path *path, - const RiseFall *rf, - TimingArc *next_arc) + const RiseFall *rf, + TimingArc *next_arc) { Vertex *vertex = path->vertex(this); DcalcAPIndex dcalc_ap_index = path->dcalcAnalysisPt(this)->index(); return findSlew(vertex, rf, next_arc, dcalc_ap_index); } -float -WritePathSpice::findSlew(Vertex *vertex, - const RiseFall *rf, - TimingArc *next_arc, - DcalcAPIndex dcalc_ap_index) -{ - float slew = delayAsFloat(graph_->slew(vertex, rf, dcalc_ap_index)); - if (slew == 0.0 && next_arc) - slew = slewAxisMinValue(next_arc); - if (slew == 0.0) - slew = units_->timeUnit()->scale(); - return slew; -} - -// Look up the smallest slew axis value in the timing arc delay table. -float -WritePathSpice::slewAxisMinValue(TimingArc *arc) -{ - GateTableModel *gate_model = dynamic_cast(arc->model()); - if (gate_model) { - const TableModel *model = gate_model->delayModel(); - const TableAxis *axis1 = model->axis1(); - TableAxisVariable var1 = axis1->variable(); - if (var1 == TableAxisVariable::input_transition_time - || var1 == TableAxisVariable::input_net_transition) - return axis1->axisValue(0); - - const TableAxis *axis2 = model->axis2(); - TableAxisVariable var2 = axis2->variable(); - if (var2 == TableAxisVariable::input_transition_time - || var2 == TableAxisVariable::input_net_transition) - return axis2->axisValue(0); - - const TableAxis *axis3 = model->axis3(); - TableAxisVariable var3 = axis3->variable(); - if (var3 == TableAxisVariable::input_transition_time - || var3 == TableAxisVariable::input_net_transition) - return axis3->axisValue(0); - } - return 0.0; -} - -// Write PWL rise/fall edge that crosses threshold at time. -void -WritePathSpice::writeWaveformEdge(const RiseFall *rf, - float time, - float slew) -{ - float volt0, volt1; - if (rf == RiseFall::rise()) { - volt0 = gnd_voltage_; - volt1 = power_voltage_; - } - else { - volt0 = power_voltage_; - volt1 = gnd_voltage_; - } - float threshold = default_library_->inputThreshold(rf); - float dt = railToRailSlew(slew, rf); - float time0 = time - dt * threshold; - float time1 = time0 + dt; - if (time0 > 0.0) - streamPrint(spice_stream_, "+%.3e %.3e\n", time0, volt0); - streamPrint(spice_stream_, "+%.3e %.3e\n", time1, volt1); -} - //////////////////////////////////////////////////////////////// void @@ -789,73 +434,19 @@ WritePathSpice::writeMeasureDelayStmt(Stage stage, Path *from_path, Path *to_path) { - const char *from_pin_name = network_->pathName(from_path->pin(this)); - const RiseFall *from_rf = from_path->transition(this); - float from_threshold = power_voltage_ * default_library_->inputThreshold(from_rf); - - const char *to_pin_name = network_->pathName(to_path->pin(this)); - const RiseFall *to_rf = to_path->transition(this); - float to_threshold = power_voltage_ * default_library_->inputThreshold(to_rf); - string stage_name = stageName(stage); - streamPrint(spice_stream_, - ".measure tran %s_%s_delay_%s\n", - stage_name.c_str(), - from_pin_name, - to_pin_name); - streamPrint(spice_stream_, - "+trig v(%s) val=%.3f %s=last\n", - from_pin_name, - from_threshold, - spiceTrans(from_rf)); - streamPrint(spice_stream_, - "+targ v(%s) val=%.3f %s=last\n", - to_pin_name, - to_threshold, - spiceTrans(to_rf)); + writeMeasureDelayStmt(from_path->pin(this), from_path->transition(this), + to_path->pin(this), to_path->transition(this), + stageName(stage)); } void WritePathSpice::writeMeasureSlewStmt(Stage stage, Path *path) { - const char *pin_name = network_->pathName(path->pin(this)); + const Pin *pin = path->pin(this); const RiseFall *rf = path->transition(this); - const char *spice_rf = spiceTrans(rf); - float lower = power_voltage_ * default_library_->slewLowerThreshold(rf); - float upper = power_voltage_ * default_library_->slewUpperThreshold(rf); - float threshold1, threshold2; - if (rf == RiseFall::rise()) { - threshold1 = lower; - threshold2 = upper; - } - else { - threshold1 = upper; - threshold2 = lower; - } - string stage_name = stageName(stage); - streamPrint(spice_stream_, - ".measure tran %s_%s_slew\n", - stage_name.c_str(), - pin_name); - streamPrint(spice_stream_, - "+trig v(%s) val=%.3f %s=last\n", - pin_name, - threshold1, - spice_rf); - streamPrint(spice_stream_, - "+targ v(%s) val=%.3f %s=last\n", - pin_name, - threshold2, - spice_rf); -} - -const char * -WritePathSpice::spiceTrans(const RiseFall *rf) -{ - if (rf == RiseFall::rise()) - return "RISE"; - else - return "FALL"; + string prefix = stageName(stage); + writeMeasureSlewStmt(pin, rf, prefix); } void @@ -866,6 +457,9 @@ WritePathSpice::writeStageSubckts() streamPrint(spice_stream_, "***************\n\n"); for (Stage stage = stageFirst(); stage <= stageLast(); stage++) { + cap_index_ = 1; + res_index_ = 1; + volt_index_ = 1; if (stage == stageFirst()) writeInputStage(stage); else @@ -881,9 +475,9 @@ WritePathSpice::writeInputStage(Stage stage) // External driver not handled. const char *drvr_pin_name = stageDrvrPinName(stage); const char *load_pin_name = stageLoadPinName(stage); - string stage_name = stageName(stage); + string prefix = stageName(stage); streamPrint(spice_stream_, ".subckt %s %s %s\n", - stage_name.c_str(), + prefix.c_str(), drvr_pin_name, load_pin_name); writeStageParasitics(stage); @@ -900,625 +494,53 @@ WritePathSpice::writeGateStage(Stage stage) const char *drvr_pin_name = stageDrvrPinName(stage); const Pin *load_pin = stageLoadPin(stage); const char *load_pin_name = stageLoadPinName(stage); - streamPrint(spice_stream_, ".subckt stage%d %s %s %s", - stage, - input_pin_name, - drvr_pin_name, - load_pin_name); - StdStringSet off_path_names = stageOffPathPinNames(stage); - for (const string &off_path_name : off_path_names) - streamPrint(spice_stream_, " %s", off_path_name.c_str()); - streamPrint(spice_stream_, "\n"); + string subckt_name = "stage" + std::to_string(stage); - // Driver subckt call. Instance *inst = stageInstance(stage); LibertyPort *input_port = stageGateInputPort(stage); LibertyPort *drvr_port = stageDrvrPort(stage); + + streamPrint(spice_stream_, ".subckt %s %s %s %s\n", + subckt_name.c_str(), + input_pin_name, + drvr_pin_name, + load_pin_name); + + // Driver subckt call. streamPrint(spice_stream_, "* Gate %s %s -> %s\n", network_->pathName(inst), input_port->name(), drvr_port->name()); writeSubcktInst(input_pin); + + PathRef *drvr_path = stageDrvrPath(stage); + DcalcAPIndex dcalc_ap_index = drvr_path->dcalcAnalysisPt(this)->index(); + const RiseFall *drvr_rf = drvr_path->transition(this); + Edge *gate_edge = stageGateEdge(stage); + LibertyPortLogicValues port_values; - DcalcAPIndex dcalc_ap_index; - const Clock *clk; - int volt_index = 1; - gatePortValues(stage, port_values, clk, dcalc_ap_index); - writeSubcktInstVoltSrcs(stage, input_pin, volt_index, - port_values, clk, dcalc_ap_index); + bool is_clked; + gatePortValues(input_pin, drvr_pin, drvr_rf, gate_edge, + port_values, is_clked); + + const Clock *clk = (is_clked) ? stageDrvrPath(stage)->clock(this) : nullptr; + writeSubcktInstVoltSrcs(input_pin, port_values, clk, dcalc_ap_index); streamPrint(spice_stream_, "\n"); - port_values.clear(); - auto pin_iter = network_->connectedPinIterator(drvr_pin); - while (pin_iter->hasNext()) { - const Pin *pin = pin_iter->next(); - if (pin != drvr_pin - && pin != load_pin - && network_->direction(pin)->isAnyInput() - && !network_->isHierarchical(pin) - && !network_->isTopLevelPort(pin)) { - streamPrint(spice_stream_, "* Side load %s\n", network_->pathName(pin)); - writeSubcktInst(pin); - writeSubcktInstVoltSrcs(stage, pin, volt_index, port_values, nullptr, 0); - streamPrint(spice_stream_, "\n"); - } - } - delete pin_iter; - + writeSubcktInstLoads(drvr_pin, load_pin); writeStageParasitics(stage); streamPrint(spice_stream_, ".ends\n\n"); } -void -WritePathSpice::writeSubcktInst(const Pin *input_pin) -{ - const Instance *inst = network_->instance(input_pin); - const char *inst_name = network_->pathName(inst); - LibertyCell *cell = network_->libertyCell(inst); - const char *cell_name = cell->name(); - StringVector *spice_port_names = cell_spice_port_names_[cell_name]; - streamPrint(spice_stream_, "x%s", inst_name); - for (string subckt_port_name : *spice_port_names) { - const char *subckt_port_cname = subckt_port_name.c_str(); - Pin *pin = network_->findPin(inst, subckt_port_cname); - LibertyPgPort *pg_port = cell->findPgPort(subckt_port_cname); - const char *pin_name; - if (pin) { - pin_name = network_->pathName(pin); - streamPrint(spice_stream_, " %s", pin_name); - } - else if (pg_port) - streamPrint(spice_stream_, " %s/%s", inst_name, subckt_port_cname); - else if (stringEq(subckt_port_cname, power_name_) - || stringEq(subckt_port_cname, gnd_name_)) - streamPrint(spice_stream_, " %s/%s", inst_name, subckt_port_cname); - } - streamPrint(spice_stream_, " %s\n", cell_name); -} - -// Power/ground and input voltage sources. -void -WritePathSpice::writeSubcktInstVoltSrcs(Stage stage, - const Pin *input_pin, - int &volt_index, - LibertyPortLogicValues &port_values, - const Clock *clk, - DcalcAPIndex dcalc_ap_index) - -{ - const Instance *inst = network_->instance(input_pin); - LibertyCell *cell = network_->libertyCell(inst); - const char *cell_name = cell->name(); - StringVector *spice_port_names = cell_spice_port_names_[cell_name]; - - const Pin *drvr_pin = stageDrvrPin(stage); - const LibertyPort *input_port = network_->libertyPort(input_pin); - const LibertyPort *drvr_port = network_->libertyPort(drvr_pin); - const char *input_port_name = input_port->name(); - const char *drvr_port_name = drvr_port->name(); - const char *inst_name = network_->pathName(inst); - - debugPrint(debug_, "write_spice", 2, "subckt %s", cell->name()); - for (string subckt_port_sname : *spice_port_names) { - const char *subckt_port_name = subckt_port_sname.c_str(); - LibertyPgPort *pg_port = cell->findPgPort(subckt_port_name); - debugPrint(debug_, "write_spice", 2, " port %s%s", - subckt_port_name, - pg_port ? " pwr/gnd" : ""); - if (pg_port) - writeVoltageSource(inst_name, subckt_port_name, - pgPortVoltage(pg_port), volt_index); - else if (stringEq(subckt_port_name, power_name_)) - writeVoltageSource(inst_name, subckt_port_name, - power_voltage_, volt_index); - else if (stringEq(subckt_port_name, gnd_name_)) - writeVoltageSource(inst_name, subckt_port_name, - gnd_voltage_, volt_index); - else if (!(stringEq(subckt_port_name, input_port_name) - || stringEq(subckt_port_name, drvr_port_name))) { - // Input voltage to sensitize path from gate input to output. - LibertyPort *port = cell->findLibertyPort(subckt_port_name); - if (port - && port->direction()->isAnyInput()) { - const Pin *pin = network_->findPin(inst, port); - // Look for tie high/low or propagated constant values. - LogicValue port_value = sim_->logicValue(pin); - if (port_value == LogicValue::unknown) { - bool has_value; - LogicValue value; - port_values.findKey(port, value, has_value); - if (has_value) - port_value = value; - } - switch (port_value) { - case LogicValue::zero: - case LogicValue::unknown: - writeVoltageSource(cell, inst_name, subckt_port_name, - port->relatedGroundPin(), - gnd_voltage_, - volt_index); - break; - case LogicValue::one: - writeVoltageSource(cell, inst_name, subckt_port_name, - port->relatedPowerPin(), - power_voltage_, - volt_index); - break; - case LogicValue::rise: - writeClkedStepSource(pin, RiseFall::rise(), clk, - dcalc_ap_index, volt_index); - break; - case LogicValue::fall: - writeClkedStepSource(pin, RiseFall::fall(), clk, - dcalc_ap_index, volt_index); - break; - } - } - } - } -} - -// PWL voltage source that rises half way into the first clock cycle. -void -WritePathSpice::writeClkedStepSource(const Pin *pin, - const RiseFall *rf, - const Clock *clk, - DcalcAPIndex dcalc_ap_index, - int &volt_index) -{ - Vertex *vertex = graph_->pinLoadVertex(pin); - float slew = findSlew(vertex, rf, nullptr, dcalc_ap_index); - float time = clkWaveformTImeOffset(clk) + clk->period() / 2.0; - writeRampVoltSource(pin, rf, slew, time, volt_index); -} - -void -WritePathSpice::writeVoltageSource(const char *inst_name, - const char *port_name, - float voltage, - int &volt_index) -{ - streamPrint(spice_stream_, "v%d %s/%s 0 %.3f\n", - volt_index, - inst_name, port_name, - voltage); - volt_index++; -} - -void -WritePathSpice::writeVoltageSource(LibertyCell *cell, - const char *inst_name, - const char *subckt_port_name, - const char *pg_port_name, - float voltage, - int &volt_index) -{ - if (pg_port_name) { - LibertyPgPort *pg_port = cell->findPgPort(pg_port_name); - if (pg_port) - voltage = pgPortVoltage(pg_port); - else - report_->error(1603, "%s pg_port %s not found,", - cell->name(), - pg_port_name); - - } - writeVoltageSource(inst_name, subckt_port_name, voltage, volt_index); -} - -void -WritePathSpice::gatePortValues(Stage stage, - // Return values. - LibertyPortLogicValues &port_values, - const Clock *&clk, - DcalcAPIndex &dcalc_ap_index) -{ - clk = nullptr; - dcalc_ap_index = 0; - - Edge *gate_edge = stageGateEdge(stage); - LibertyPort *drvr_port = stageDrvrPort(stage); - if (gate_edge - && gate_edge->role()->genericRole() == TimingRole::regClkToQ()) - regPortValues(stage, port_values, clk, dcalc_ap_index); - else if (drvr_port->function()) { - Pin *input_pin = stageGateInputPin(stage); - LibertyPort *input_port = network_->libertyPort(input_pin); - Instance *inst = network_->instance(input_pin); - gatePortValues(inst, drvr_port->function(), input_port, port_values); - } -} - -void -WritePathSpice::regPortValues(Stage stage, - // Return values. - LibertyPortLogicValues &port_values, - const Clock *&clk, - DcalcAPIndex &dcalc_ap_index) -{ - LibertyPort *drvr_port = stageDrvrPort(stage); - FuncExpr *drvr_expr = drvr_port->function(); - if (drvr_expr) { - LibertyPort *q_port = drvr_expr->port(); - if (q_port) { - // Drvr (register/latch output) function should be a reference - // to an internal port like IQ or IQN. - LibertyCell *cell = stageLibertyCell(stage); - Sequential *seq = cell->outputPortSequential(q_port); - if (seq) { - PathRef *drvr_path = stageDrvrPath(stage); - const RiseFall *drvr_rf = drvr_path->transition(this); - seqPortValues(seq, drvr_rf, port_values); - clk = drvr_path->clock(this); - dcalc_ap_index = drvr_path->dcalcAnalysisPt(this)->index(); - } - else - report_->error(1604, "no register/latch found for path from %s to %s,", - stageGateInputPort(stage)->name(), - stageDrvrPort(stage)->name()); - } - } -} - -// Find the logic values for expression inputs to enable paths input_port. -void -WritePathSpice::gatePortValues(const Instance *inst, - FuncExpr *expr, - LibertyPort *input_port, - // Return values. - LibertyPortLogicValues &port_values) -{ - FuncExpr *left = expr->left(); - FuncExpr *right = expr->right(); - switch (expr->op()) { - case FuncExpr::op_port: - break; - case FuncExpr::op_not: - gatePortValues(inst, left, input_port, port_values); - break; - case FuncExpr::op_or: - if (left->hasPort(input_port) - && right->op() == FuncExpr::op_port) - port_values[right->port()] = LogicValue::zero; - else if (left->hasPort(input_port) - && right->op() == FuncExpr::op_not - && right->left()->op() == FuncExpr::op_port) - // input_port + !right_port - port_values[right->left()->port()] = LogicValue::one; - else if (right->hasPort(input_port) - && left->op() == FuncExpr::op_port) - port_values[left->port()] = LogicValue::zero; - else if (right->hasPort(input_port) - && left->op() == FuncExpr::op_not - && left->left()->op() == FuncExpr::op_port) - // input_port + !left_port - port_values[left->left()->port()] = LogicValue::one; - else { - gatePortValues(inst, left, input_port, port_values); - gatePortValues(inst, right, input_port, port_values); - } - break; - case FuncExpr::op_and: - if (left->hasPort(input_port) - && right->op() == FuncExpr::op_port) - port_values[right->port()] = LogicValue::one; - else if (left->hasPort(input_port) - && right->op() == FuncExpr::op_not - && right->left()->op() == FuncExpr::op_port) - // input_port * !right_port - port_values[right->left()->port()] = LogicValue::zero; - else if (right->hasPort(input_port) - && left->op() == FuncExpr::op_port) - port_values[left->port()] = LogicValue::one; - else if (right->hasPort(input_port) - && left->op() == FuncExpr::op_not - && left->left()->op() == FuncExpr::op_port) - // input_port * !left_port - port_values[left->left()->port()] = LogicValue::zero; - else { - gatePortValues(inst, left, input_port, port_values); - gatePortValues(inst, right, input_port, port_values); - } - break; - case FuncExpr::op_xor: - // Need to know timing arc sense to get this right. - if (left->port() == input_port - && right->op() == FuncExpr::op_port) - port_values[right->port()] = LogicValue::zero; - else if (right->port() == input_port - && left->op() == FuncExpr::op_port) - port_values[left->port()] = LogicValue::zero; - else { - gatePortValues(inst, left, input_port, port_values); - gatePortValues(inst, right, input_port, port_values); - } - break; - case FuncExpr::op_one: - case FuncExpr::op_zero: - break; - } -} - -void -WritePathSpice::seqPortValues(Sequential *seq, - const RiseFall *rf, - // Return values. - LibertyPortLogicValues &port_values) -{ - FuncExpr *data = seq->data(); - LibertyPort *port = onePort(data); - if (port) { - TimingSense sense = data->portTimingSense(port); - switch (sense) { - case TimingSense::positive_unate: - if (rf == RiseFall::rise()) - port_values[port] = LogicValue::rise; - else - port_values[port] = LogicValue::fall; - break; - case TimingSense::negative_unate: - if (rf == RiseFall::rise()) - port_values[port] = LogicValue::fall; - else - port_values[port] = LogicValue::rise; - break; - case TimingSense::non_unate: - case TimingSense::none: - case TimingSense::unknown: - default: - break; - } - } -} - -// Pick a port, any port... -LibertyPort * -WritePathSpice::onePort(FuncExpr *expr) -{ - FuncExpr *left = expr->left(); - FuncExpr *right = expr->right(); - LibertyPort *port; - switch (expr->op()) { - case FuncExpr::op_port: - return expr->port(); - case FuncExpr::op_not: - return onePort(left); - case FuncExpr::op_or: - case FuncExpr::op_and: - case FuncExpr::op_xor: - port = onePort(left); - if (port == nullptr) - port = onePort(right); - return port; - case FuncExpr::op_one: - case FuncExpr::op_zero: - default: - return nullptr; - } -} - void WritePathSpice::writeStageParasitics(Stage stage) { PathRef *drvr_path = stageDrvrPath(stage); - Pin *drvr_pin = stageDrvrPin(stage); - - Net *net = network_->net(drvr_pin); - const char *net_name = net ? network_->pathName(net) : network_->pathName(drvr_pin); - initNodeMap(net_name); - streamPrint(spice_stream_, "* Net %s\n", net_name); - DcalcAnalysisPt *dcalc_ap = drvr_path->dcalcAnalysisPt(this); ParasiticAnalysisPt *parasitic_ap = dcalc_ap->parasiticAnalysisPt(); - Parasitic *parasitic = parasitics_->findParasiticNetwork(drvr_pin, parasitic_ap); - if (parasitic) - writeStageParasiticNetwork(drvr_pin, parasitic); - else { - const RiseFall *drvr_rf = drvr_path->transition(this); - parasitic = parasitics_->findPiElmore(drvr_pin, drvr_rf, parasitic_ap); - if (parasitic) - writeStagePiElmore(drvr_pin, parasitic); - else { - streamPrint(spice_stream_, "* No parasitics found for this net.\n"); - writeNullParasitics(drvr_pin); - } - } -} - -void -WritePathSpice::writeStageParasiticNetwork(Pin *drvr_pin, - Parasitic *parasitic) -{ - Set reachable_pins; - int res_index = 1; - int cap_index = 1; - - // Sort resistors for consistent regression results. - ParasiticResistorSeq resistors = parasitics_->resistors(parasitic); - sort(resistors.begin(), resistors.end(), - [=] (const ParasiticResistor *r1, - const ParasiticResistor *r2) { - return parasitics_->id(r1) < parasitics_->id(r2); - }); - for (ParasiticResistor *resistor : resistors) { - float resistance = parasitics_->value(resistor); - ParasiticNode *node1 = parasitics_->node1(resistor); - ParasiticNode *node2 = parasitics_->node2(resistor); - streamPrint(spice_stream_, "R%d %s %s %.3e\n", - res_index, - nodeName(node1), - nodeName(node2), - resistance); - res_index++; - - const Pin *pin1 = parasitics_->pin(node1); - reachable_pins.insert(pin1); - const Pin *pin2 = parasitics_->pin(node2); - reachable_pins.insert(pin2); - } - - ParasiticCapacitorSeq capacitors = parasitics_->capacitors(parasitic); - sort(capacitors.begin(), capacitors.end(), - [=] (const ParasiticCapacitor *c1, - const ParasiticCapacitor *c2) { - return parasitics_->id(c1) < parasitics_->id(c2); - }); - for (ParasiticCapacitor *capacitor : capacitors) { - // Ground coupling caps for now. - ParasiticNode *node1 = parasitics_->node1(capacitor); - float cap = parasitics_->value(capacitor); - streamPrint(spice_stream_, "C%d %s 0 %.3e\n", - cap_index, - nodeName(node1), - cap); - cap_index++; - } - - // Add resistors from drvr to load for missing parasitic connections. - auto pin_iter = network_->connectedPinIterator(drvr_pin); - while (pin_iter->hasNext()) { - const Pin *pin = pin_iter->next(); - if (pin != drvr_pin - && network_->isLoad(pin) - && !network_->isHierarchical(pin) - && !reachable_pins.hasKey(pin)) { - streamPrint(spice_stream_, "R%d %s %s %.3e\n", - res_index, - network_->pathName(drvr_pin), - network_->pathName(pin), - short_ckt_resistance_); - res_index++; - } - } - delete pin_iter; - - // Sort node capacitors for consistent regression results. - ParasiticNodeSeq nodes = parasitics_->nodes(parasitic); - sort(nodes.begin(), nodes.end(), - [=] (const ParasiticNode *node1, - const ParasiticNode *node2) { - const char *name1 = parasitics_->name(node1); - const char *name2 = parasitics_->name(node2); - return stringLess(name1, name2); - }); - - for (ParasiticNode *node : nodes) { - float cap = parasitics_->nodeGndCap(node); - // Spice has a cow over zero value caps. - if (cap > 0.0) { - streamPrint(spice_stream_, "C%d %s 0 %.3e\n", - cap_index, - nodeName(node), - cap); - cap_index++; - } - } -} - -void -WritePathSpice::writeStagePiElmore(Pin *drvr_pin, - Parasitic *parasitic) -{ - float c2, rpi, c1; - parasitics_->piModel(parasitic, c2, rpi, c1); - const char *c1_node = "n1"; - streamPrint(spice_stream_, "RPI %s %s %.3e\n", - network_->pathName(drvr_pin), - c1_node, - rpi); - if (c2 > 0.0) - streamPrint(spice_stream_, "C2 %s 0 %.3e\n", - network_->pathName(drvr_pin), - c2); - if (c1 > 0.0) - streamPrint(spice_stream_, "C1 %s 0 %.3e\n", - c1_node, - c1); - - int load_index = 3; - auto pin_iter = network_->connectedPinIterator(drvr_pin); - while (pin_iter->hasNext()) { - const Pin *load_pin = pin_iter->next(); - if (load_pin != drvr_pin - && network_->isLoad(load_pin) - && !network_->isHierarchical(load_pin)) { - float elmore; - bool exists; - parasitics_->findElmore(parasitic, load_pin, elmore, exists); - if (exists) { - streamPrint(spice_stream_, "E%d el%d 0 %s 0 1.0\n", - load_index, - load_index, - network_->pathName(drvr_pin)); - streamPrint(spice_stream_, "R%d el%d %s 1.0\n", - load_index, - load_index, - network_->pathName(load_pin)); - streamPrint(spice_stream_, "C%d %s 0 %.3e\n", - load_index, - network_->pathName(load_pin), - elmore); - } - else - // Add resistor from drvr to load for missing elmore. - streamPrint(spice_stream_, "R%d %s %s %.3e\n", - load_index, - network_->pathName(drvr_pin), - network_->pathName(load_pin), - short_ckt_resistance_); - load_index++; - } - } - delete pin_iter; -} - -void -WritePathSpice::writeNullParasitics(Pin *drvr_pin) -{ - int res_index = 1; - // Add resistors from drvr to load for missing parasitic connections. - auto pin_iter = network_->connectedPinIterator(drvr_pin); - while (pin_iter->hasNext()) { - const Pin *load_pin = pin_iter->next(); - if (load_pin != drvr_pin - && network_->isLoad(load_pin) - && !network_->isHierarchical(load_pin)) { - streamPrint(spice_stream_, "R%d %s %s %.3e\n", - res_index, - network_->pathName(drvr_pin), - network_->pathName(load_pin), - short_ckt_resistance_); - res_index++; - } - } - delete pin_iter; -} - -void -WritePathSpice::initNodeMap(const char *net_name) -{ - stringDelete(net_name_); - node_map_.clear(); - next_node_index_ = 1; - net_name_ = stringCopy(net_name); -} - -const char * -WritePathSpice::nodeName(ParasiticNode *node) -{ - const Pin *pin = parasitics_->pin(node); - if (pin) - return parasitics_->name(node); - else { - int node_index; - bool node_index_exists; - node_map_.findKey(node, node_index, node_index_exists); - if (!node_index_exists) { - node_index = next_node_index_++; - node_map_[node] = node_index; - } - return stringPrintTmp("%s/%d", net_name_, node_index); - } + NetSet coupling_nets; + writeDrvrParasitics(stageDrvrPin(stage), drvr_path->transition(this), + coupling_nets, parasitic_ap); } //////////////////////////////////////////////////////////////// @@ -1528,64 +550,12 @@ WritePathSpice::nodeName(ParasiticNode *node) void WritePathSpice::writeSubckts() { - StdStringSet path_cell_names = findPathCellnames(); - findPathCellSubckts(path_cell_names); - - ifstream lib_subckts_stream(lib_subckt_filename_); - if (lib_subckts_stream.is_open()) { - ofstream subckts_stream(subckt_filename_); - if (subckts_stream.is_open()) { - string line; - while (getline(lib_subckts_stream, line)) { - // .subckt [args..] - StringVector tokens; - split(line, " \t", tokens); - if (tokens.size() >= 2 - && stringEqual(tokens[0].c_str(), ".subckt")) { - const char *cell_name = tokens[1].c_str(); - if (path_cell_names.find(cell_name) != path_cell_names.end()) { - subckts_stream << line << "\n"; - bool found_ends = false; - while (getline(lib_subckts_stream, line)) { - subckts_stream << line << "\n"; - if (stringBeginEqual(line.c_str(), ".ends")) { - subckts_stream << "\n"; - found_ends = true; - break; - } - } - if (!found_ends) - throw SubcktEndsMissing(cell_name, lib_subckt_filename_); - path_cell_names.erase(cell_name); - } - recordSpicePortNames(cell_name, tokens); - } - } - subckts_stream.close(); - lib_subckts_stream.close(); - - if (!path_cell_names.empty()) { - string missing_cells; - for (const string &cell_name : path_cell_names) { - missing_cells += "\n"; - missing_cells += cell_name; - } - report_->error(1605, "The subkct file %s is missing definitions for %s", - lib_subckt_filename_, - missing_cells.c_str()); - } - } - else { - lib_subckts_stream.close(); - throw FileNotWritable(subckt_filename_); - } - } - else - throw FileNotReadable(lib_subckt_filename_); + StdStringSet cell_names = findPathCellNames(); + writeSubckts(cell_names); } StdStringSet -WritePathSpice::findPathCellnames() +WritePathSpice::findPathCellNames() { StdStringSet path_cell_names; for (Stage stage = stageFirst(); stage <= stageLast(); stage++) { @@ -1613,69 +583,6 @@ WritePathSpice::findPathCellnames() return path_cell_names; } -// Subckts can call subckts (asap7). -void -WritePathSpice::findPathCellSubckts(StdStringSet &path_cell_names) -{ - ifstream lib_subckts_stream(lib_subckt_filename_); - if (lib_subckts_stream.is_open()) { - string line; - while (getline(lib_subckts_stream, line)) { - // .subckt [args..] - StringVector tokens; - split(line, " \t", tokens); - if (tokens.size() >= 2 - && stringEqual(tokens[0].c_str(), ".subckt")) { - const char *cell_name = tokens[1].c_str(); - if (path_cell_names.find(cell_name) != path_cell_names.end()) { - // Scan the subckt definition for subckt calls. - string stmt; - while (getline(lib_subckts_stream, line)) { - if (line[0] == '+') - stmt += line.substr(1); - else { - // Process previous statement. - if (tolower(stmt[0]) == 'x') { - split(stmt, " \t", tokens); - string &subckt_cell = tokens[tokens.size() - 1]; - path_cell_names.insert(subckt_cell); - } - stmt = line; - } - if (stringBeginEqual(line.c_str(), ".ends")) - break; - } - } - } - } - } - else - throw FileNotReadable(lib_subckt_filename_); -} - -void -WritePathSpice::recordSpicePortNames(const char *cell_name, - StringVector &tokens) -{ - LibertyCell *cell = network_->findLibertyCell(cell_name); - if (cell) { - StringVector *spice_port_names = new StringVector; - for (size_t i = 2; i < tokens.size(); i++) { - const char *port_name = tokens[i].c_str(); - LibertyPort *port = cell->findLibertyPort(port_name); - LibertyPgPort *pg_port = cell->findPgPort(port_name); - if (port == nullptr - && pg_port == nullptr - && !stringEqual(port_name, power_name_) - && !stringEqual(port_name, gnd_name_)) - report_->error(1606, "subckt %s port %s has no corresponding liberty port, pg_port and is not power or ground.", - cell_name, port_name); - spice_port_names->push_back(port_name); - } - cell_spice_port_names_[cell_name] = spice_port_names; - } -} - //////////////////////////////////////////////////////////////// Stage @@ -1826,26 +733,6 @@ WritePathSpice::stageLoadPinName(Stage stage) return network_->pathName(pin); } -StdStringSet -WritePathSpice::stageOffPathPinNames(Stage stage) -{ - StdStringSet pin_names; - if (off_path_pin_names_) { - const PathRef *path = stageDrvrPath(stage); - Vertex *drvr = path->vertex(this); - VertexOutEdgeIterator edge_iter(drvr, graph_); - while (edge_iter.hasNext()) { - Edge *edge = edge_iter.next(); - Vertex *load = edge->to(graph_); - const Pin *load_pin = load->pin(); - string load_pin_name = network_->pathName(load_pin); - if (off_path_pin_names_->find(load_pin_name) != off_path_pin_names_->end()) - pin_names.insert(load_pin_name); - } - } - return pin_names; -} - Instance * WritePathSpice::stageInstance(Stage stage) { @@ -1860,23 +747,4 @@ WritePathSpice::stageLibertyCell(Stage stage) return network_->libertyPort(pin)->libertyCell(); } -//////////////////////////////////////////////////////////////// - -// fprintf for c++ streams. -// Yes, I hate formatted output to ostream THAT much. -void -streamPrint(ofstream &stream, - const char *fmt, - ...) -{ - va_list args; - va_start(args, fmt); - char *result = nullptr; - if (vasprintf(&result, fmt, args) == -1) - criticalError(267, "out of memory"); - stream << result; - free(result); - va_end(args); -} - } // namespace diff --git a/search/WriteSpice.cc b/search/WriteSpice.cc new file mode 100644 index 00000000..1f6539da --- /dev/null +++ b/search/WriteSpice.cc @@ -0,0 +1,1267 @@ +// OpenSTA, Static Timing Analyzer +// Copyright (c) 2024, 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 "WriteSpice.hh" + +#include "Debug.hh" +#include "Units.hh" +#include "TableModel.hh" +#include "TimingRole.hh" +#include "FuncExpr.hh" +#include "Sequential.hh" +#include "PortDirection.hh" +#include "TimingArc.hh" +#include "Liberty.hh" +#include "Network.hh" +#include "Graph.hh" +#include "Sim.hh" +#include "Clock.hh" +#include "PathVertex.hh" +#include "DcalcAnalysisPt.hh" +#include "Bdd.hh" + +namespace sta { + +using std::ifstream; + +Net * +pinNet(const Pin *pin, + const Network *network); + +class SubcktEndsMissing : public Exception +{ +public: + SubcktEndsMissing(const char *cell_name, + const char *subckt_filename); + const char *what() const noexcept; + +protected: + string what_; +}; + +SubcktEndsMissing::SubcktEndsMissing(const char *cell_name, + const char *subckt_filename) : + Exception() +{ + what_ = "spice subckt for cell "; + what_ += cell_name; + what_ += " missing .ends in "; + what_ += subckt_filename; +} + +const char * +SubcktEndsMissing::what() const noexcept +{ + return what_.c_str(); +} + +//////////////////////////////////////////////////////////////// + +WriteSpice::WriteSpice(const char *spice_filename, + const char *subckt_filename, + const char *lib_subckt_filename, + const char *model_filename, + const char *power_name, + const char *gnd_name, + CircuitSim ckt_sim, + const StaState *sta) : + StaState(sta), + spice_filename_(spice_filename), + subckt_filename_(subckt_filename), + lib_subckt_filename_(lib_subckt_filename), + model_filename_(model_filename), + power_name_(power_name), + gnd_name_(gnd_name), + ckt_sim_(ckt_sim), + default_library_(network_->defaultLibertyLibrary()), + short_ckt_resistance_(.0001), + cap_index_(1), + res_index_(1), + volt_index_(1), + next_node_index_(1), + bdd_(sta) +{ +} + +void +WriteSpice::initPowerGnd(const DcalcAnalysisPt *dcalc_ap) +{ + bool exists = false; + default_library_->supplyVoltage(power_name_, power_voltage_, exists); + if (!exists) { + const OperatingConditions *op_cond = dcalc_ap->operatingConditions(); + if (op_cond == nullptr) + op_cond = network_->defaultLibertyLibrary()->defaultOperatingConditions(); + power_voltage_ = op_cond->voltage(); + } + default_library_->supplyVoltage(gnd_name_, gnd_voltage_, exists); + if (!exists) + gnd_voltage_ = 0.0; +} + +// Use c++17 fs::path(filename).stem() +static string +filenameStem(const char *filename) +{ + string filename1 = filename; + const size_t last_slash_idx = filename1.find_last_of("\\/"); + if (last_slash_idx != std::string::npos) + return filename1.substr(last_slash_idx + 1); + else + return filename1; +} + +void +WriteSpice::writeHeader(string &title, + float max_time, + float time_step) +{ + streamPrint(spice_stream_, "* %s\n", title.c_str()); + streamPrint(spice_stream_, ".include \"%s\"\n", model_filename_); + string subckt_filename_stem = filenameStem(subckt_filename_); + streamPrint(spice_stream_, ".include \"%s\"\n", subckt_filename_stem.c_str()); + + streamPrint(spice_stream_, ".tran %.3g %.3g\n", time_step, max_time); + // Suppress printing model parameters. + if (ckt_sim_ == CircuitSim::hspice) + streamPrint(spice_stream_, ".options nomod\n"); + streamPrint(spice_stream_, "\n"); + max_time_ = max_time; +} + +void +WriteSpice::writePrintStmt(StdStringSeq &node_names) +{ + streamPrint(spice_stream_, ".print tran"); + if (ckt_sim_ == CircuitSim::xyce) { + string csv_filename = replaceFileExt(spice_filename_, "csv"); + streamPrint(spice_stream_, " format=csv file=%s", csv_filename.c_str()); + writeGnuplotFile(node_names); + } + for (string &name : node_names) + streamPrint(spice_stream_, " v(%s)", name.c_str()); + streamPrint(spice_stream_, "\n\n"); +} + +string +WriteSpice::replaceFileExt(string filename, + const char *ext) +{ + size_t dot = filename.rfind('.'); + string ext_filename = filename.substr(0, dot + 1); + ext_filename += ext; + return ext_filename; +} + +// Write gnuplot command file for use with xyce csv file. +void +WriteSpice::writeGnuplotFile(StdStringSeq &node_nanes) +{ + string gnuplot_filename = replaceFileExt(spice_filename_, "gnuplot"); + string csv_filename = replaceFileExt(spice_filename_, "csv"); + ofstream gnuplot_stream; + gnuplot_stream.open(gnuplot_filename); + if (gnuplot_stream.is_open()) { + streamPrint(gnuplot_stream, "set datafile separator ','\n"); + streamPrint(gnuplot_stream, "set key autotitle columnhead\n"); + streamPrint(gnuplot_stream, "plot\\\n"); + streamPrint(gnuplot_stream, "\"%s\" using 1:2 with lines", + csv_filename.c_str()); + for (size_t i = 3; i <= node_nanes.size() + 1; i++) { + streamPrint(gnuplot_stream, ",\\\n"); + streamPrint(gnuplot_stream, "'' using 1:%lu with lines", i); + } + streamPrint(gnuplot_stream, "\n"); + streamPrint(gnuplot_stream, "pause mouse close\n"); + gnuplot_stream.close(); + } +} + +void +WriteSpice::writeSubckts(StdStringSet &cell_names) +{ + findCellSubckts(cell_names); + ifstream lib_subckts_stream(lib_subckt_filename_); + if (lib_subckts_stream.is_open()) { + ofstream subckts_stream(subckt_filename_); + if (subckts_stream.is_open()) { + string line; + while (getline(lib_subckts_stream, line)) { + // .subckt [args..] + StringVector tokens; + split(line, " \t", tokens); + if (tokens.size() >= 2 + && stringEqual(tokens[0].c_str(), ".subckt")) { + const char *cell_name = tokens[1].c_str(); + if (cell_names.find(cell_name) != cell_names.end()) { + subckts_stream << line << "\n"; + bool found_ends = false; + while (getline(lib_subckts_stream, line)) { + subckts_stream << line << "\n"; + if (stringBeginEqual(line.c_str(), ".ends")) { + subckts_stream << "\n"; + found_ends = true; + break; + } + } + if (!found_ends) + throw SubcktEndsMissing(cell_name, lib_subckt_filename_); + cell_names.erase(cell_name); + } + recordSpicePortNames(cell_name, tokens); + } + } + subckts_stream.close(); + lib_subckts_stream.close(); + + if (!cell_names.empty()) { + string missing_cells; + for (const string &cell_name : cell_names) { + missing_cells += "\n"; + missing_cells += cell_name; + } + report_->error(1605, "The subkct file %s is missing definitions for %s", + lib_subckt_filename_, + missing_cells.c_str()); + } + } + else { + lib_subckts_stream.close(); + throw FileNotWritable(subckt_filename_); + } + } + else + throw FileNotReadable(lib_subckt_filename_); +} + +void +WriteSpice::recordSpicePortNames(const char *cell_name, + StringVector &tokens) +{ + LibertyCell *cell = network_->findLibertyCell(cell_name); + if (cell) { + StringVector &spice_port_names = cell_spice_port_names_[cell_name]; + for (size_t i = 2; i < tokens.size(); i++) { + const char *port_name = tokens[i].c_str(); + LibertyPort *port = cell->findLibertyPort(port_name); + LibertyPgPort *pg_port = cell->findPgPort(port_name); + if (port == nullptr + && pg_port == nullptr + && !stringEqual(port_name, power_name_) + && !stringEqual(port_name, gnd_name_)) + report_->error(1606, "subckt %s port %s has no corresponding liberty port, pg_port and is not power or ground.", + cell_name, port_name); + spice_port_names.push_back(port_name); + } + } +} + +// Subckts can call subckts (asap7). +void +WriteSpice::findCellSubckts(StdStringSet &cell_names) +{ + ifstream lib_subckts_stream(lib_subckt_filename_); + if (lib_subckts_stream.is_open()) { + string line; + while (getline(lib_subckts_stream, line)) { + // .subckt [args..] + StringVector tokens; + split(line, " \t", tokens); + if (tokens.size() >= 2 + && stringEqual(tokens[0].c_str(), ".subckt")) { + const char *cell_name = tokens[1].c_str(); + if (cell_names.find(cell_name) != cell_names.end()) { + // Scan the subckt definition for subckt calls. + string stmt; + while (getline(lib_subckts_stream, line)) { + if (line[0] == '+') + stmt += line.substr(1); + else { + // Process previous statement. + if (tolower(stmt[0]) == 'x') { + split(stmt, " \t", tokens); + string &subckt_cell = tokens[tokens.size() - 1]; + cell_names.insert(subckt_cell); + } + stmt = line; + } + if (stringBeginEqual(line.c_str(), ".ends")) + break; + } + } + } + } + } + else + throw FileNotReadable(lib_subckt_filename_); +} + +//////////////////////////////////////////////////////////////// + +void +WriteSpice::writeSubcktInst(const Pin *input_pin) +{ + const Instance *inst = network_->instance(input_pin); + const char *inst_name = network_->pathName(inst); + LibertyCell *cell = network_->libertyCell(inst); + const char *cell_name = cell->name(); + StringVector &spice_port_names = cell_spice_port_names_[cell_name]; + streamPrint(spice_stream_, "x%s", inst_name); + for (string subckt_port_name : spice_port_names) { + const char *subckt_port_cname = subckt_port_name.c_str(); + Pin *pin = network_->findPin(inst, subckt_port_cname); + LibertyPgPort *pg_port = cell->findPgPort(subckt_port_cname); + const char *pin_name; + if (pin) { + pin_name = network_->pathName(pin); + streamPrint(spice_stream_, " %s", pin_name); + } + else if (pg_port) + streamPrint(spice_stream_, " %s/%s", inst_name, subckt_port_cname); + else if (stringEq(subckt_port_cname, power_name_) + || stringEq(subckt_port_cname, gnd_name_)) + streamPrint(spice_stream_, " %s/%s", inst_name, subckt_port_cname); + } + streamPrint(spice_stream_, " %s\n", cell_name); +} + +// Power/ground and input voltage sources. +void +WriteSpice::writeSubcktInstVoltSrcs(const Pin *input_pin, + LibertyPortLogicValues &port_values, + const Clock *clk, + DcalcAPIndex dcalc_ap_index) +{ + const Instance *inst = network_->instance(input_pin); + LibertyCell *cell = network_->libertyCell(inst); + const char *cell_name = cell->name(); + StringVector &spice_port_names = cell_spice_port_names_[cell_name]; + + const LibertyPort *input_port = network_->libertyPort(input_pin); + const char *inst_name = network_->pathName(inst); + + debugPrint(debug_, "write_spice", 2, "subckt %s", cell->name()); + for (string subckt_port_sname : spice_port_names) { + const char *subckt_port_name = subckt_port_sname.c_str(); + LibertyPort *port = cell->findLibertyPort(subckt_port_name); + LibertyPgPort *pg_port = cell->findPgPort(subckt_port_name); + debugPrint(debug_, "write_spice", 2, " port %s%s", + subckt_port_name, + pg_port ? " pwr/gnd" : ""); + if (pg_port) + writeVoltageSource(inst_name, subckt_port_name, + pgPortVoltage(pg_port)); + else if (stringEq(subckt_port_name, power_name_)) + writeVoltageSource(inst_name, subckt_port_name, power_voltage_); + else if (stringEq(subckt_port_name, gnd_name_)) + writeVoltageSource(inst_name, subckt_port_name, gnd_voltage_); + else if (port + && port != input_port + && port->direction()->isAnyInput()) { + // Input voltage to sensitize path from gate input to output. + const Pin *pin = network_->findPin(inst, port); + // Look for tie high/low or propagated constant values. + LogicValue port_value = sim_->logicValue(pin); + if (port_value == LogicValue::unknown) { + bool has_value; + LogicValue value; + port_values.findKey(port, value, has_value); + if (has_value) + port_value = value; + } + switch (port_value) { + case LogicValue::zero: + case LogicValue::unknown: + writeVoltageSource(cell, inst_name, subckt_port_name, + port->relatedGroundPin(), + gnd_voltage_); + break; + case LogicValue::one: + writeVoltageSource(cell, inst_name, subckt_port_name, + port->relatedPowerPin(), + power_voltage_); + break; + case LogicValue::rise: + writeClkedStepSource(pin, RiseFall::rise(), clk, dcalc_ap_index); + + break; + case LogicValue::fall: + writeClkedStepSource(pin, RiseFall::fall(), clk, dcalc_ap_index); + break; + } + } + } +} + +void +WriteSpice::writeVoltageSource(const char *inst_name, + const char *port_name, + float voltage) +{ + string node_name = inst_name; + node_name += '/'; + node_name += port_name; + writeVoltageSource(node_name.c_str(), voltage); +} + +void +WriteSpice::writeVoltageSource(LibertyCell *cell, + const char *inst_name, + const char *subckt_port_name, + const char *pg_port_name, + float voltage) +{ + if (pg_port_name) { + LibertyPgPort *pg_port = cell->findPgPort(pg_port_name); + if (pg_port) + voltage = pgPortVoltage(pg_port); + else + report_->error(1603, "%s pg_port %s not found,", + cell->name(), + pg_port_name); + + } + writeVoltageSource(inst_name, subckt_port_name, voltage); +} + +float +WriteSpice::pgPortVoltage(LibertyPgPort *pg_port) +{ + LibertyLibrary *liberty = pg_port->cell()->libertyLibrary(); + float voltage = 0.0; + bool exists; + const char *voltage_name = pg_port->voltageName(); + if (voltage_name) { + liberty->supplyVoltage(voltage_name, voltage, exists); + if (!exists) { + if (stringEqual(voltage_name, power_name_)) + voltage = power_voltage_; + else if (stringEqual(voltage_name, gnd_name_)) + voltage = gnd_voltage_; + else + report_->error(1601 , "pg_pin %s/%s voltage %s not found,", + pg_port->cell()->name(), + pg_port->name(), + voltage_name); + } + } + else + report_->error(1602, "Liberty pg_port %s/%s missing voltage_name attribute,", + pg_port->cell()->name(), + pg_port->name()); + return voltage; +} + +// PWL voltage source that rises half way into the first clock cycle. +void +WriteSpice::writeClkedStepSource(const Pin *pin, + const RiseFall *rf, + const Clock *clk, + DcalcAPIndex dcalc_ap_index) +{ + Vertex *vertex = graph_->pinLoadVertex(pin); + float slew = findSlew(vertex, rf, nullptr, dcalc_ap_index); + float time = clkWaveformTimeOffset(clk) + clk->period() / 2.0; + writeRampVoltSource(pin, rf, time, slew); +} + +float +WriteSpice::clkWaveformTimeOffset(const Clock *clk) +{ + return clk->period() / 10; +} + +//////////////////////////////////////////////////////////////// + +float +WriteSpice::findSlew(Vertex *vertex, + const RiseFall *rf, + TimingArc *next_arc, + DcalcAPIndex dcalc_ap_index) +{ + float slew = delayAsFloat(graph_->slew(vertex, rf, dcalc_ap_index)); + if (slew == 0.0 && next_arc) + slew = slewAxisMinValue(next_arc); + if (slew == 0.0) + slew = units_->timeUnit()->scale(); + return slew; +} + +// Look up the smallest slew axis value in the timing arc delay table. +float +WriteSpice::slewAxisMinValue(TimingArc *arc) +{ + GateTableModel *gate_model = dynamic_cast(arc->model()); + if (gate_model) { + const TableModel *model = gate_model->delayModel(); + const TableAxis *axis1 = model->axis1(); + TableAxisVariable var1 = axis1->variable(); + if (var1 == TableAxisVariable::input_transition_time + || var1 == TableAxisVariable::input_net_transition) + return axis1->axisValue(0); + + const TableAxis *axis2 = model->axis2(); + TableAxisVariable var2 = axis2->variable(); + if (var2 == TableAxisVariable::input_transition_time + || var2 == TableAxisVariable::input_net_transition) + return axis2->axisValue(0); + + const TableAxis *axis3 = model->axis3(); + TableAxisVariable var3 = axis3->variable(); + if (var3 == TableAxisVariable::input_transition_time + || var3 == TableAxisVariable::input_net_transition) + return axis3->axisValue(0); + } + return 0.0; +} + +//////////////////////////////////////////////////////////////// + +void +WriteSpice::writeDrvrParasitics(const Pin *drvr_pin, + const RiseFall *drvr_rf, + const NetSet &aggressor_nets, + const ParasiticAnalysisPt *parasitic_ap) +{ + Net *net = network_->net(drvr_pin); + const char *net_name = net ? network_->pathName(net) : network_->pathName(drvr_pin); + streamPrint(spice_stream_, "* Net %s\n", net_name); + + Parasitic *parasitic = parasitics_->findParasiticNetwork(drvr_pin, parasitic_ap); + node_map_.clear(); + next_node_index_ = 1; + if (parasitic) + writeParasiticNetwork(drvr_pin, parasitic, aggressor_nets); + else { + parasitic = parasitics_->findPiElmore(drvr_pin, drvr_rf, parasitic_ap); + if (parasitic) + writePiElmore(drvr_pin, parasitic); + else { + streamPrint(spice_stream_, "* No parasitics found for this net.\n"); + writeNullParasitic(drvr_pin); + } + } +} + +void +WriteSpice::writeParasiticNetwork(const Pin *drvr_pin, + const Parasitic *parasitic, + const NetSet &coupling_nets) +{ + Set reachable_pins; + // Sort resistors for consistent regression results. + ParasiticResistorSeq resistors = parasitics_->resistors(parasitic); + sort(resistors.begin(), resistors.end(), + [=] (const ParasiticResistor *r1, + const ParasiticResistor *r2) { + return parasitics_->id(r1) < parasitics_->id(r2); + }); + for (ParasiticResistor *resistor : resistors) { + float resistance = parasitics_->value(resistor); + ParasiticNode *node1 = parasitics_->node1(resistor); + ParasiticNode *node2 = parasitics_->node2(resistor); + streamPrint(spice_stream_, "R%d %s %s %.3e\n", + res_index_++, + nodeName(node1), + nodeName(node2), + resistance); + + // Necessary but not sufficient. Need a DFS. + const Pin *pin1 = parasitics_->pin(node1); + if (pin1) + reachable_pins.insert(pin1); + const Pin *pin2 = parasitics_->pin(node2); + if (pin2) + reachable_pins.insert(pin2); + } + + // Add resistors from drvr to load for missing parasitic connections. + auto pin_iter = network_->connectedPinIterator(drvr_pin); + while (pin_iter->hasNext()) { + const Pin *pin = pin_iter->next(); + if (pin != drvr_pin + && network_->isLoad(pin) + && !network_->isHierarchical(pin) + && !reachable_pins.hasKey(pin)) { + streamPrint(spice_stream_, "R%d %s %s %.3e\n", + res_index_++, + network_->pathName(drvr_pin), + network_->pathName(pin), + short_ckt_resistance_); + } + } + delete pin_iter; + + // Sort coupling capacitors consistent regression results. + ParasiticCapacitorSeq capacitors = parasitics_->capacitors(parasitic); + sort(capacitors.begin(), capacitors.end(), + [=] (const ParasiticCapacitor *c1, + const ParasiticCapacitor *c2) { + return parasitics_->id(c1) < parasitics_->id(c2); + }); + const Net *net = pinNet(drvr_pin, network_); + for (ParasiticCapacitor *capacitor : capacitors) { + ParasiticNode *node1 = parasitics_->node1(capacitor); + ParasiticNode *node2 = parasitics_->node2(capacitor); + float cap = parasitics_->value(capacitor); + const Net *net1 = node1 ? parasitics_->net(node1, network_) : nullptr; + const Net *net2 = node2 ? parasitics_->net(node2, network_) : nullptr; + const ParasiticNode *net_node = nullptr; + const char *coupling_name; + if (net1 == net) { + net_node = node1; + coupling_name = net2 && coupling_nets.hasKey(net2) ? nodeName(node2) : "0"; + } + else if (net2 == net) { + net_node = node2; + coupling_name = net1 && coupling_nets.hasKey(net1) ? nodeName(node1) : "0"; + } + if (net_node) + streamPrint(spice_stream_, "C%d %s %s %.3e\n", + cap_index_++, + nodeName(net_node), + coupling_name, + cap); + } + + // Sort nodes for consistent regression results. + ParasiticNodeSeq nodes = parasitics_->nodes(parasitic); + sort(nodes.begin(), nodes.end(), + [=] (const ParasiticNode *node1, + const ParasiticNode *node2) { + const char *name1 = parasitics_->name(node1); + const char *name2 = parasitics_->name(node2); + return stringLess(name1, name2); + }); + + for (ParasiticNode *node : nodes) { + float cap = parasitics_->nodeGndCap(node); + // Spice has a cow over zero value caps. + if (cap > 0.0) { + streamPrint(spice_stream_, "C%d %s 0 %.3e\n", + cap_index_++, + nodeName(node), + cap); + } + } +} + +Net * +pinNet(const Pin *pin, + const Network *network) +{ + Net *net = network->net(pin); + // Pins on the top level instance may not have nets. + // Use the net connected to the pin's terminal. + if (net == nullptr && network->isTopLevelPort(pin)) { + Term *term = network->term(pin); + if (term) + return network->net(term); + } + return net; +} + +const char * +WriteSpice::nodeName(const ParasiticNode *node) +{ + const Pin *pin = parasitics_->pin(node); + if (pin) + return parasitics_->name(node); + else { + int node_index; + auto index_itr = node_map_.find(node); + if (index_itr == node_map_.end()) { + node_index = next_node_index_++; + node_map_[node] = node_index; + } + else + node_index = index_itr->second; + const Net *net = parasitics_->net(node, network_); + const char *net_name = network_->pathName(net); + return stringPrintTmp("%s/%d", net_name, node_index); + } +} + +void +WriteSpice::writePiElmore(const Pin *drvr_pin, + const Parasitic *parasitic) +{ + float c2, rpi, c1; + parasitics_->piModel(parasitic, c2, rpi, c1); + const char *c1_node = "n1"; + streamPrint(spice_stream_, "RPI %s %s %.3e\n", + network_->pathName(drvr_pin), + c1_node, + rpi); + if (c2 > 0.0) + streamPrint(spice_stream_, "C2 %s 0 %.3e\n", + network_->pathName(drvr_pin), + c2); + if (c1 > 0.0) + streamPrint(spice_stream_, "C1 %s 0 %.3e\n", + c1_node, + c1); + + int load_index = 3; + auto pin_iter = network_->connectedPinIterator(drvr_pin); + while (pin_iter->hasNext()) { + const Pin *load_pin = pin_iter->next(); + if (load_pin != drvr_pin + && network_->isLoad(load_pin) + && !network_->isHierarchical(load_pin)) { + float elmore; + bool exists; + parasitics_->findElmore(parasitic, load_pin, elmore, exists); + if (exists) { + streamPrint(spice_stream_, "E%d el%d 0 %s 0 1.0\n", + load_index, + load_index, + network_->pathName(drvr_pin)); + streamPrint(spice_stream_, "R%d el%d %s 1.0\n", + load_index, + load_index, + network_->pathName(load_pin)); + streamPrint(spice_stream_, "C%d %s 0 %.3e\n", + load_index, + network_->pathName(load_pin), + elmore); + } + else + // Add resistor from drvr to load for missing elmore. + streamPrint(spice_stream_, "R%d %s %s %.3e\n", + load_index, + network_->pathName(drvr_pin), + network_->pathName(load_pin), + short_ckt_resistance_); + load_index++; + } + } + delete pin_iter; +} + +void +WriteSpice::writeNullParasitic(const Pin *drvr_pin) +{ + // Add resistors from drvr to load for missing parasitic connections. + auto pin_iter = network_->connectedPinIterator(drvr_pin); + while (pin_iter->hasNext()) { + const Pin *load_pin = pin_iter->next(); + if (load_pin != drvr_pin + && network_->isLoad(load_pin) + && !network_->isHierarchical(load_pin)) { + streamPrint(spice_stream_, "R%d %s %s %.3e\n", + res_index_++, + network_->pathName(drvr_pin), + network_->pathName(load_pin), + short_ckt_resistance_); + } + } + delete pin_iter; +} + +//////////////////////////////////////////////////////////////// + +void +WriteSpice::writeVoltageSource(const char *node_name, + float voltage) +{ + streamPrint(spice_stream_, "v%d %s 0 %.3f\n", + volt_index_++, + node_name, + voltage); +} + +void +WriteSpice::writeWaveformVoltSource(const Pin *pin, + DriverWaveform *drvr_waveform, + const RiseFall *rf, + float delay, + float slew) +{ + float volt0, volt1, volt_factor; + if (rf == RiseFall::rise()) { + volt0 = gnd_voltage_; + volt1 = power_voltage_; + volt_factor = power_voltage_; + } + else { + volt0 = power_voltage_; + volt1 = gnd_voltage_; + volt_factor = -power_voltage_; + } + streamPrint(spice_stream_, "v%d %s 0 pwl(\n", + volt_index_++, + network_->pathName(pin)); + streamPrint(spice_stream_, "+%.3e %.3e\n", 0.0, volt0); + Table1 waveform = drvr_waveform->waveform(slew); + const TableAxis *time_axis = waveform.axis1(); + for (size_t time_index = 0; time_index < time_axis->size(); time_index++) { + float time = delay + time_axis->axisValue(time_index); + float wave_volt = waveform.value(time_index); + float volt = volt0 + wave_volt * volt_factor; + streamPrint(spice_stream_, "+%.3e %.3e\n", time, volt); + } + streamPrint(spice_stream_, "+%.3e %.3e\n", max_time_, volt1); + streamPrint(spice_stream_, "+)\n"); +} + +void +WriteSpice::writeRampVoltSource(const Pin *pin, + const RiseFall *rf, + float time, + float slew) +{ + float volt0, volt1; + if (rf == RiseFall::rise()) { + volt0 = gnd_voltage_; + volt1 = power_voltage_; + } + else { + volt0 = power_voltage_; + volt1 = gnd_voltage_; + } + streamPrint(spice_stream_, "v%d %s 0 pwl(\n", + volt_index_++, + network_->pathName(pin)); + streamPrint(spice_stream_, "+%.3e %.3e\n", 0.0, volt0); + writeWaveformEdge(rf, time, slew); + streamPrint(spice_stream_, "+%.3e %.3e\n", max_time_, volt1); + streamPrint(spice_stream_, "+)\n"); +} + +// Write PWL rise/fall edge that crosses threshold at time. +void +WriteSpice::writeWaveformEdge(const RiseFall *rf, + float time, + float slew) +{ + float volt0, volt1; + if (rf == RiseFall::rise()) { + volt0 = gnd_voltage_; + volt1 = power_voltage_; + } + else { + volt0 = power_voltage_; + volt1 = gnd_voltage_; + } + float threshold = default_library_->inputThreshold(rf); + float dt = railToRailSlew(slew, rf); + float time0 = time - dt * threshold; + float time1 = time0 + dt; + if (time0 > 0.0) + streamPrint(spice_stream_, "+%.3e %.3e\n", time0, volt0); + streamPrint(spice_stream_, "+%.3e %.3e\n", time1, volt1); +} + +float +WriteSpice::railToRailSlew(float slew, + const RiseFall *rf) +{ + float lower = default_library_->slewLowerThreshold(rf); + float upper = default_library_->slewUpperThreshold(rf); + return slew / (upper - lower); +} + +//////////////////////////////////////////////////////////////// + +// Find the logic values for expression inputs to enable paths from input_port. +void +WriteSpice::gatePortValues(const Pin *input_pin, + const Pin *drvr_pin, + const RiseFall *drvr_rf, + const Edge *gate_edge, + // Return values. + LibertyPortLogicValues &port_values, + bool &is_clked) +{ + is_clked = false; + const Instance *inst = network_->instance(input_pin); + const LibertyPort *input_port = network_->libertyPort(input_pin); + const LibertyPort *drvr_port = network_->libertyPort(drvr_pin); + const FuncExpr *drvr_func = drvr_port->function(); + if (drvr_func) { + if (gate_edge + && gate_edge->role()->genericRole() == TimingRole::regClkToQ()) + regPortValues(input_pin, drvr_rf, drvr_port, drvr_func, + port_values, is_clked); + else + gatePortValues(inst, drvr_func, input_port, port_values); + } +} + +#if CUDD + +void +WriteSpice::gatePortValues(const Instance *, + const FuncExpr *expr, + const LibertyPort *input_port, + // Return values. + LibertyPortLogicValues &port_values) +{ + DdNode *bdd = bdd_.funcBdd(expr); + DdNode *input_node = bdd_.findNode(input_port); + unsigned input_node_index = Cudd_NodeReadIndex(input_node); + DdManager *cudd_mgr = bdd_.cuddMgr(); + DdNode *diff = Cudd_bddBooleanDiff(cudd_mgr, bdd, input_node_index); + int *cube; + CUDD_VALUE_TYPE value; + DdGen *cube_gen = Cudd_FirstCube(cudd_mgr, diff, &cube, &value); + + FuncExprPortIterator port_iter(expr); + while (port_iter.hasNext()) { + const LibertyPort *port = port_iter.next(); + if (port != input_port) { + DdNode *port_node = bdd_.findNode(port); + int var_index = Cudd_NodeReadIndex(port_node); + LogicValue value; + switch (cube[var_index]) { + case 0: + value = LogicValue::zero; + break; + case 1: + value = LogicValue::one; + break; + case 2: + default: + value = LogicValue::unknown; + break; + } + port_values[port] = value; + } + } + Cudd_GenFree(cube_gen); + Cudd_Ref(diff); + bdd_.clearVarMap(); +} + +#else + +void +WriteSpice::gatePortValues(const Instance *inst, + const FuncExpr *expr, + const LibertyPort *input_port, + // Return values. + LibertyPortLogicValues &port_values) +{ + FuncExpr *left = expr->left(); + FuncExpr *right = expr->right(); + switch (expr->op()) { + case FuncExpr::op_port: + break; + case FuncExpr::op_not: + gatePortValues(inst, left, input_port, port_values); + break; + case FuncExpr::op_or: + if (left->hasPort(input_port) + && right->op() == FuncExpr::op_port) { + gatePortValues(inst, left, input_port, port_values); + port_values[right->port()] = LogicValue::zero; + } + else if (left->hasPort(input_port) + && right->op() == FuncExpr::op_not + && right->left()->op() == FuncExpr::op_port) { + // input_port + !right_port + gatePortValues(inst, left, input_port, port_values); + port_values[right->left()->port()] = LogicValue::one; + } + else if (right->hasPort(input_port) + && left->op() == FuncExpr::op_port) { + gatePortValues(inst, right, input_port, port_values); + port_values[left->port()] = LogicValue::zero; + } + else if (right->hasPort(input_port) + && left->op() == FuncExpr::op_not + && left->left()->op() == FuncExpr::op_port) { + // input_port + !left_port + gatePortValues(inst, right, input_port, port_values); + port_values[left->left()->port()] = LogicValue::one; + } + else { + gatePortValues(inst, left, input_port, port_values); + gatePortValues(inst, right, input_port, port_values); + } + break; + case FuncExpr::op_and: + if (left->hasPort(input_port) + && right->op() == FuncExpr::op_port) { + gatePortValues(inst, left, input_port, port_values); + port_values[right->port()] = LogicValue::one; + } + else if (left->hasPort(input_port) + && right->op() == FuncExpr::op_not + && right->left()->op() == FuncExpr::op_port) { + // input_port * !right_port + gatePortValues(inst, left, input_port, port_values); + port_values[right->left()->port()] = LogicValue::zero; + } + else if (right->hasPort(input_port) + && left->op() == FuncExpr::op_port) { + gatePortValues(inst, right, input_port, port_values); + port_values[left->port()] = LogicValue::one; + } + else if (right->hasPort(input_port) + && left->op() == FuncExpr::op_not + && left->left()->op() == FuncExpr::op_port) { + // input_port * !left_port + gatePortValues(inst, right, input_port, port_values); + port_values[left->left()->port()] = LogicValue::zero; + } + else { + gatePortValues(inst, left, input_port, port_values); + gatePortValues(inst, right, input_port, port_values); + } + break; + case FuncExpr::op_xor: + // Need to know timing arc sense to get this right. + if (left->port() == input_port + && right->op() == FuncExpr::op_port) + port_values[right->port()] = LogicValue::zero; + else if (right->port() == input_port + && left->op() == FuncExpr::op_port) + port_values[left->port()] = LogicValue::zero; + else { + gatePortValues(inst, left, input_port, port_values); + gatePortValues(inst, right, input_port, port_values); + } + break; + case FuncExpr::op_one: + case FuncExpr::op_zero: + break; + } +} + +#endif + +void +WriteSpice::regPortValues(const Pin *input_pin, + const RiseFall *drvr_rf, + const LibertyPort *drvr_port, + const FuncExpr *drvr_func, + // Return values. + LibertyPortLogicValues &port_values, + bool &is_clked) +{ + is_clked = false; + LibertyPort *q_port = drvr_func->port(); + if (q_port) { + // Drvr (register/latch output) function should be a reference + // to an internal port like IQ or IQN. + LibertyCell *cell = drvr_port->libertyCell(); + Sequential *seq = cell->outputPortSequential(q_port); + if (seq) { + seqPortValues(seq, drvr_rf, port_values); + is_clked = true; + } + else { + const LibertyPort *input_port = network_->libertyPort(input_pin); + report_->error(1604, "no register/latch found for path from %s to %s,", + input_port->name(), + drvr_port->name()); + } + } +} + +void +WriteSpice::seqPortValues(Sequential *seq, + const RiseFall *rf, + // Return values. + LibertyPortLogicValues &port_values) +{ + FuncExpr *data = seq->data(); + LibertyPort *port = onePort(data); + if (port) { + TimingSense sense = data->portTimingSense(port); + switch (sense) { + case TimingSense::positive_unate: + if (rf == RiseFall::rise()) + port_values[port] = LogicValue::rise; + else + port_values[port] = LogicValue::fall; + break; + case TimingSense::negative_unate: + if (rf == RiseFall::rise()) + port_values[port] = LogicValue::fall; + else + port_values[port] = LogicValue::rise; + break; + case TimingSense::non_unate: + case TimingSense::none: + case TimingSense::unknown: + default: + break; + } + } +} + +// Pick a port, any port... +LibertyPort * +WriteSpice::onePort(FuncExpr *expr) +{ + FuncExpr *left = expr->left(); + FuncExpr *right = expr->right(); + LibertyPort *port; + switch (expr->op()) { + case FuncExpr::op_port: + return expr->port(); + case FuncExpr::op_not: + return onePort(left); + case FuncExpr::op_or: + case FuncExpr::op_and: + case FuncExpr::op_xor: + port = onePort(left); + if (port == nullptr) + port = onePort(right); + return port; + case FuncExpr::op_one: + case FuncExpr::op_zero: + default: + return nullptr; + } +} + +//////////////////////////////////////////////////////////////// + +PinSeq +WriteSpice::drvrLoads(const Pin *drvr_pin) +{ + PinSeq loads; + Vertex *drvr_vertex = graph_->pinDrvrVertex(drvr_pin); + 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_); + const Pin *load_pin = load_vertex->pin(); + loads.push_back(load_pin); + } + } + return loads; +} + +void +WriteSpice::writeSubcktInstLoads(const Pin *drvr_pin, + const Pin *exclude) +{ + streamPrint(spice_stream_, "* Load pins\n"); + PinSeq drvr_loads = drvrLoads(drvr_pin); + // Do not sensitize side load gates. + LibertyPortLogicValues port_values; + for (const Pin *load_pin : drvr_loads) { + if (load_pin != exclude + && network_->direction(load_pin)->isAnyInput() + && !network_->isHierarchical(load_pin) + && !network_->isTopLevelPort(load_pin)) { + writeSubcktInst(load_pin); + writeSubcktInstVoltSrcs(load_pin, port_values, nullptr, 0); + streamPrint(spice_stream_, "\n"); + } + } +} + +//////////////////////////////////////////////////////////////// + +void +WriteSpice::writeMeasureDelayStmt(const Pin *from_pin, + const RiseFall *from_rf, + const Pin *to_pin, + const RiseFall *to_rf, + string prefix) +{ + const char *from_pin_name = network_->pathName(from_pin); + float from_threshold = power_voltage_ * default_library_->inputThreshold(from_rf); + const char *to_pin_name = network_->pathName(to_pin); + float to_threshold = power_voltage_ * default_library_->inputThreshold(to_rf); + streamPrint(spice_stream_, + ".measure tran %s_%s_delay_%s\n", + prefix.c_str(), + from_pin_name, + to_pin_name); + streamPrint(spice_stream_, + "+trig v(%s) val=%.3f %s=last\n", + from_pin_name, + from_threshold, + spiceTrans(from_rf)); + streamPrint(spice_stream_, + "+targ v(%s) val=%.3f %s=last\n", + to_pin_name, + to_threshold, + spiceTrans(to_rf)); +} + +void +WriteSpice::writeMeasureSlewStmt(const Pin *pin, + const RiseFall *rf, + string prefix) +{ + const char *pin_name = network_->pathName(pin); + const char *spice_rf = spiceTrans(rf); + float lower = power_voltage_ * default_library_->slewLowerThreshold(rf); + float upper = power_voltage_ * default_library_->slewUpperThreshold(rf); + float threshold1, threshold2; + if (rf == RiseFall::rise()) { + threshold1 = lower; + threshold2 = upper; + } + else { + threshold1 = upper; + threshold2 = lower; + } + streamPrint(spice_stream_, + ".measure tran %s_%s_slew\n", + prefix.c_str(), + pin_name); + streamPrint(spice_stream_, + "+trig v(%s) val=%.3f %s=last\n", + pin_name, + threshold1, + spice_rf); + streamPrint(spice_stream_, + "+targ v(%s) val=%.3f %s=last\n", + pin_name, + threshold2, + spice_rf); +} + +//////////////////////////////////////////////////////////////// + + +const char * +WriteSpice::spiceTrans(const RiseFall *rf) +{ + if (rf == RiseFall::rise()) + return "RISE"; + else + return "FALL"; +} + +// fprintf for c++ streams. +// Yes, I hate formatted output to ostream THAT much. +void +streamPrint(ofstream &stream, + const char *fmt, + ...) +{ + va_list args; + va_start(args, fmt); + char *result = nullptr; + if (vasprintf(&result, fmt, args) == -1) + criticalError(267, "out of memory"); + stream << result; + free(result); + va_end(args); +} + +} diff --git a/search/WriteSpice.hh b/search/WriteSpice.hh new file mode 100644 index 00000000..9d45d55f --- /dev/null +++ b/search/WriteSpice.hh @@ -0,0 +1,191 @@ +// OpenSTA, Static Timing Analyzer +// Copyright (c) 2024, 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 +#include +#include + +#include "StaState.hh" +#include "StringSet.hh" +#include "Liberty.hh" +#include "GraphClass.hh" +#include "Parasitics.hh" +#include "Bdd.hh" +#include "CircuitSim.hh" + +namespace sta { + +using std::string; +using std::ofstream; + +typedef std::map ParasiticNodeMap; +typedef Map CellSpicePortNames; +typedef Map LibertyPortLogicValues; +typedef std::vector StdStringSeq; + +// Utilities for writing a spice deck. +class WriteSpice : public StaState +{ +public: + WriteSpice(const char *spice_filename, + const char *subckt_filename, + const char *lib_subckt_filename, + const char *model_filename, + const char *power_name, + const char *gnd_name, + CircuitSim ckt_sim, + const StaState *sta); + +protected: + void initPowerGnd(const DcalcAnalysisPt *dcalc_ap); + void writeHeader(string &title, + float max_time, + float time_step); + void writePrintStmt(StdStringSeq &node_names); + void writeGnuplotFile(StdStringSeq &node_nanes); + void writeSubckts(StdStringSet &cell_names); + void findCellSubckts(StdStringSet &cell_names); + void recordSpicePortNames(const char *cell_name, + StringVector &tokens); + void writeSubcktInst(const Pin *input_pin); + void writeSubcktInstVoltSrcs(const Pin *input_pin, + LibertyPortLogicValues &port_values, + const Clock *clk, + DcalcAPIndex dcalc_ap_index); + float pgPortVoltage(LibertyPgPort *pg_port); + void writeVoltageSource(const char *inst_name, + const char *port_name, + float voltage); + void writeVoltageSource(LibertyCell *cell, + const char *inst_name, + const char *subckt_port_name, + const char *pg_port_name, + float voltage); + void writeClkedStepSource(const Pin *pin, + const RiseFall *rf, + const Clock *clk, + DcalcAPIndex dcalc_ap_index); + void writeDrvrParasitics(const Pin *drvr_pin, + const RiseFall *drvr_rf, + // Nets with parasitics to include coupling caps to. + const NetSet &coupling_nets, + const ParasiticAnalysisPt *parasitic_ap); + void writeParasiticNetwork(const Pin *drvr_pin, + const Parasitic *parasitic, + const NetSet &aggressor_nets); + void writePiElmore(const Pin *drvr_pin, + const Parasitic *parasitic); + void writeNullParasitic(const Pin *drvr_pin); + const char *nodeName(const ParasiticNode *node); + + void writeVoltageSource(const char *node_name, + float voltage); + void writeRampVoltSource(const Pin *pin, + const RiseFall *rf, + float time, + float slew); + void writeWaveformVoltSource(const Pin *pin, + DriverWaveform *drvr_waveform, + const RiseFall *rf, + float delay, + float slew); + void writeWaveformEdge(const RiseFall *rf, + float time, + float slew); + float railToRailSlew(float slew, + const RiseFall *rf); + void seqPortValues(Sequential *seq, + const RiseFall *rf, + // Return values. + LibertyPortLogicValues &port_values); + LibertyPort *onePort(FuncExpr *expr); + void writeMeasureDelayStmt(const Pin *from_pin, + const RiseFall *from_rf, + const Pin *to_pin, + const RiseFall *to_rf, + string prefix); + void writeMeasureSlewStmt(const Pin *pin, + const RiseFall *rf, + string prefix); + const char *spiceTrans(const RiseFall *rf); + float findSlew(Vertex *vertex, + const RiseFall *rf, + TimingArc *next_arc, + DcalcAPIndex dcalc_ap_index); + float slewAxisMinValue(TimingArc *arc); + float clkWaveformTimeOffset(const Clock *clk); + + void gatePortValues(const Pin *input_pin, + const Pin *drvr_pin, + const RiseFall *drvr_rf, + const Edge *gate_edge, + // Return values. + LibertyPortLogicValues &port_values, + bool &is_clked); + void regPortValues(const Pin *input_pin, + const RiseFall *drvr_rf, + const LibertyPort *drvr_port, + const FuncExpr *drvr_func, + // Return values. + LibertyPortLogicValues &port_values, + bool &is_clked); + void gatePortValues(const Instance *inst, + const FuncExpr *expr, + const LibertyPort *input_port, + // Return values. + LibertyPortLogicValues &port_values); + void writeSubcktInstLoads(const Pin *drvr_pin, + const Pin *exclude); + PinSeq drvrLoads(const Pin *drvr_pin); + void writeSubcktInstVoltSrcs(); + string replaceFileExt(string filename, + const char *ext); + + const char *spice_filename_; + const char *subckt_filename_; + const char *lib_subckt_filename_; + const char *model_filename_; + const char *power_name_; + const char *gnd_name_; + CircuitSim ckt_sim_; + + ofstream spice_stream_; + LibertyLibrary *default_library_; + float power_voltage_; + float gnd_voltage_; + float max_time_; + // Resistance to use to simulate a short circuit between spice nodes. + float short_ckt_resistance_; + // Input clock waveform cycles. + // Sequential device numbers. + int cap_index_; + int res_index_; + int volt_index_; + ParasiticNodeMap node_map_; + int next_node_index_; + CellSpicePortNames cell_spice_port_names_; + Bdd bdd_; +}; + +void +streamPrint(ofstream &stream, + const char *fmt, + ...) __attribute__((format (printf, 2, 3))); + +} // namespace diff --git a/tcl/CmdArgs.tcl b/tcl/CmdArgs.tcl index a53562d5..8fed92ee 100644 --- a/tcl/CmdArgs.tcl +++ b/tcl/CmdArgs.tcl @@ -477,6 +477,16 @@ proc parse_rise_fall_flags { flags_var } { } } +proc parse_rise_fall_arg { arg } { + if { $arg eq "r" || $arg eq "^" || $arg eq "rise" } { + return "rise" + } elseif { $arg eq "f" || $arg eq "v" || $arg eq "fall" } { + return "fall" + } else { + error "unknown rise/fall transition name." + } +} + proc parse_min_max_flags { flags_var } { upvar 1 $flags_var flags if { [info exists flags(-min)] && [info exists flags(-max)] } { diff --git a/tcl/Sdc.tcl b/tcl/Sdc.tcl index 25b4d142..4ce727b6 100644 --- a/tcl/Sdc.tcl +++ b/tcl/Sdc.tcl @@ -2943,6 +2943,10 @@ proc set_input_transition { args } { ################################################################ +# set_load -wire_load port external wire load +# set_load -pin_load port external pin load +# set_load port same as -pin_load +# set_load net overrides parasitics define_cmd_args "set_load" \ {[-corner corner] [-rise] [-fall] [-max] [-min] [-subtract_pin_load]\ [-pin_load] [-wire_load] capacitance objects} @@ -2958,7 +2962,7 @@ proc set_load { args } { set subtract_pin_load [info exists flags(-subtract_pin_load)] set corner [parse_corner_or_all keys] set min_max [parse_min_max_all_check_flags flags] - set tr [parse_rise_fall_flags flags] + set rf [parse_rise_fall_flags flags] set cap [lindex $args 0] check_positive_float "capacitance" $cap @@ -2969,11 +2973,11 @@ proc set_load { args } { # -pin_load is the default. if { $pin_load || (!$pin_load && !$wire_load) } { foreach port $ports { - set_port_ext_pin_cap $port $tr $corner $min_max $cap + set_port_ext_pin_cap $port $rf $corner $min_max $cap } } elseif { $wire_load } { foreach port $ports { - set_port_ext_wire_cap $port $subtract_pin_load $tr $corner $min_max $cap + set_port_ext_wire_cap $port $subtract_pin_load $rf $corner $min_max $cap } } } @@ -2984,7 +2988,7 @@ proc set_load { args } { if { $wire_load } { sta_warn 465 "-wire_load not allowed for net objects." } - if { $tr != "rise_fall" } { + if { $rf != "rise_fall" } { sta_warn 466 "-rise/-fall not allowed for net objects." } foreach net $nets { diff --git a/tcl/Search.tcl b/tcl/Search.tcl index da485fc1..4c5750ed 100644 --- a/tcl/Search.tcl +++ b/tcl/Search.tcl @@ -312,7 +312,7 @@ proc delays_are_inf { delays } { define_cmd_args "report_clock_skew" {[-setup|-hold]\ [-clock clocks]\ - [-corner corner]]\ + [-corner corner]\ [-digits digits]} proc_redirect report_clock_skew { @@ -351,6 +351,36 @@ proc_redirect report_clock_skew { ################################################################ +define_cmd_args "report_clock_latency" {[-clock clocks]\ + [-corner corner]\ + [-digits digits]} + +proc_redirect report_clock_latency { + global sta_report_default_digits + + parse_key_args "report_clock_" args \ + keys {-clock -corner -digits} flags {} + check_argc_eq0 "report_clock_latency" $args + + if [info exists keys(-clock)] { + set clks [get_clocks_warn "-clocks" $keys(-clock)] + } else { + set clks [all_clocks] + } + set corner [parse_corner_or_all keys] + if [info exists keys(-digits)] { + set digits $keys(-digits) + check_positive_integer "-digits" $digits + } else { + set digits $sta_report_default_digits + } + if { $clks != {} } { + report_clk_latency $clks $corner $digits + } +} + +################################################################ + define_cmd_args "report_checks" \ {[-from from_list|-rise_from from_list|-fall_from from_list]\ [-through through_list|-rise_through through_list|-fall_through through_list]\ @@ -648,15 +678,6 @@ proc report_capacitance_limits { net corner min_max violators verbose nosplit } ################################################################ -define_cmd_args "report_dcalc" \ - {[-from from_pin] [-to to_pin] [-corner corner] [-min] [-max] [-digits digits]} - -proc_redirect report_dcalc { - report_dcalc_cmd "report_dcalc" $args "-digits" -} - -################################################################ - define_cmd_args "report_disabled_edges" {} ################################################################ @@ -777,7 +798,7 @@ proc_redirect report_path { check_argc_eq2 "report_path" $args set pin_arg [lindex $args 0] - set tr [parse_rise_fall_arg [lindex $args 1]] + set rf [parse_rise_fall_arg [lindex $args 1]] set pin [get_port_pin_error "pin" $pin_arg] if { [$pin is_hierarchical] } { @@ -787,7 +808,7 @@ proc_redirect report_path { if { $vertex != "NULL" } { if { $report_all } { set first 1 - set path_iter [$vertex path_iterator $tr $min_max] + set path_iter [$vertex path_iterator $rf $min_max] while {[$path_iter has_next]} { set path [$path_iter next] if { $first } { @@ -804,7 +825,7 @@ proc_redirect report_path { } $path_iter finish } else { - set worst_path [vertex_worst_arrival_path_rf $vertex $tr $min_max] + set worst_path [vertex_worst_arrival_path_rf $vertex $rf $min_max] if { $worst_path != "NULL" } { if { $report_tags } { report_line "Tag: [$worst_path tag]" @@ -818,16 +839,6 @@ proc_redirect report_path { } } -proc parse_rise_fall_arg { arg } { - if { $arg eq "r" || $arg eq "^" || $arg eq "rise" } { - return "rise" - } elseif { $arg eq "f" || $arg eq "v" || $arg eq "fall" } { - return "fall" - } else { - error "unknown rise/fall transition name." - } -} - proc parse_report_path_options { cmd args_var default_format unknown_key_is_error } { variable path_options diff --git a/tcl/StaTcl.i b/tcl/StaTcl.i index d6d3b692..39d4ec55 100644 --- a/tcl/StaTcl.i +++ b/tcl/StaTcl.i @@ -3035,14 +3035,22 @@ report_path_cmd(PathRef *path) } void -report_clk_skew(ClockSet *clks, +report_clk_skew(ConstClockSeq clks, const Corner *corner, const SetupHold *setup_hold, int digits) { cmdLinkedNetwork(); Sta::sta()->reportClkSkew(clks, corner, setup_hold, digits); - delete clks; +} + +void +report_clk_latency(ConstClockSeq clks, + const Corner *corner, + int digits) +{ + cmdLinkedNetwork(); + Sta::sta()->reportClkLatency(clks, corner, digits); } float @@ -3534,16 +3542,14 @@ write_path_spice_cmd(PathRef *path, const char *subckt_filename, const char *lib_subckt_filename, const char *model_filename, - StdStringSet *off_path_pins, const char *power_name, const char *gnd_name, - bool measure_stmts) + CircuitSim ckt_sim) { Sta *sta = Sta::sta(); writePathSpice(path, spice_filename, subckt_filename, - lib_subckt_filename, model_filename, off_path_pins, - power_name, gnd_name, measure_stmts, sta); - delete off_path_pins; + lib_subckt_filename, model_filename, + power_name, gnd_name, ckt_sim, sta); } void diff --git a/tcl/StaTclTypes.i b/tcl/StaTclTypes.i index a2af7ecb..473452dc 100644 --- a/tcl/StaTclTypes.i +++ b/tcl/StaTclTypes.i @@ -1,4 +1,4 @@ - +// Swig TCL input/output type parsers. %{ #include "Machine.hh" @@ -22,6 +22,8 @@ #include "search/Tag.hh" #include "PathEnd.hh" #include "SearchClass.hh" +#include "CircuitSim.hh" +#include "ArcDelayCalc.hh" #include "Sta.hh" namespace sta { @@ -38,9 +40,9 @@ cmdGraph(); template Vector * -tclListSeq(Tcl_Obj *const source, - swig_type_info *swig_type, - Tcl_Interp *interp) +tclListSeqPtr(Tcl_Obj *const source, + swig_type_info *swig_type, + Tcl_Interp *interp) { int argc; Tcl_Obj **argv; @@ -60,11 +62,33 @@ tclListSeq(Tcl_Obj *const source, return nullptr; } +template +std::vector +tclListSeq(Tcl_Obj *const source, + swig_type_info *swig_type, + Tcl_Interp *interp) +{ + int argc; + Tcl_Obj **argv; + + std::vector seq; + if (Tcl_ListObjGetElements(interp, source, &argc, &argv) == TCL_OK + && argc > 0) { + for (int i = 0; i < argc; i++) { + void *obj; + // Ignore returned TCL_ERROR because can't get swig_type_info. + SWIG_ConvertPtr(argv[i], &obj, swig_type, false); + seq.push_back(reinterpret_cast(obj)); + } + } + return seq; +} + template SET_TYPE * -tclListSet(Tcl_Obj *const source, - swig_type_info *swig_type, - Tcl_Interp *interp) +tclListSetPtr(Tcl_Obj *const source, + swig_type_info *swig_type, + Tcl_Interp *interp) { int argc; Tcl_Obj **argv; @@ -83,6 +107,29 @@ tclListSet(Tcl_Obj *const source, return nullptr; } +template +SET_TYPE +tclListSet(Tcl_Obj *const source, + swig_type_info *swig_type, + Tcl_Interp *interp) +{ + int argc; + Tcl_Obj **argv; + if (Tcl_ListObjGetElements(interp, source, &argc, &argv) == TCL_OK + && argc > 0) { + SET_TYPE set; + for (int i = 0; i < argc; i++) { + void *obj; + // Ignore returned TCL_ERROR because can't get swig_type_info. + SWIG_ConvertPtr(argv[i], &obj, swig_type, false); + set.insert(reinterpret_cast(obj)); + } + return set; + } + else + return SET_TYPE(); +} + template SET_TYPE * tclListNetworkSet(Tcl_Obj *const source, @@ -361,7 +408,7 @@ using namespace sta; } %typemap(in) CellSeq* { - $1 = tclListSeq($input, SWIGTYPE_p_Cell, interp); + $1 = tclListSeqPtr($input, SWIGTYPE_p_Cell, interp); } %typemap(out) CellSeq { @@ -396,7 +443,7 @@ using namespace sta; } %typemap(in) PortSeq* { - $1 = tclListSeq($input, SWIGTYPE_p_Port, interp); + $1 = tclListSeqPtr($input, SWIGTYPE_p_Port, interp); } %typemap(out) PortSeq { @@ -567,7 +614,7 @@ using namespace sta; } %typemap(in) InstanceSeq* { - $1 = tclListSeq($input, SWIGTYPE_p_Instance, interp); + $1 = tclListSeqPtr($input, SWIGTYPE_p_Instance, interp); } %typemap(out) InstanceSeq { @@ -617,6 +664,10 @@ using namespace sta; seqPtrTclList($1, SWIGTYPE_p_Net, interp); } +%typemap(in) ConstNetSeq { + $1 = tclListSeq($input, SWIGTYPE_p_Net, interp); +} + %typemap(out) NetSeq { seqTclList($1, SWIGTYPE_p_Net, interp); } @@ -646,6 +697,10 @@ using namespace sta; Tcl_SetObjResult(interp, obj); } +%typemap(in) ConstClockSeq { + $1 = tclListSeq($input, SWIGTYPE_p_Clock, interp); +} + %typemap(out) ClockSeq* { seqPtrTclList($1, SWIGTYPE_p_Clock, interp); } @@ -660,7 +715,7 @@ using namespace sta; } %typemap(in) PinSeq* { - $1 = tclListSeq($input, SWIGTYPE_p_Pin, interp); + $1 = tclListSeqPtr($input, SWIGTYPE_p_Pin, interp); } %typemap(in) PinSet* { @@ -687,8 +742,12 @@ using namespace sta; Tcl_SetObjResult(interp, list); } +%typemap(in) ConstClockSet { + $1 = tclListSet($input, SWIGTYPE_p_Clock, interp); +} + %typemap(in) ClockSet* { - $1 = tclListSet($input, SWIGTYPE_p_Clock, interp); + $1 = tclListSetPtr($input, SWIGTYPE_p_Clock, interp); } %typemap(out) ClockSet* { @@ -1011,7 +1070,7 @@ using namespace sta; } %typemap(in) ExceptionThruSeq* { - $1 = tclListSeq($input, SWIGTYPE_p_ExceptionThru, interp); + $1 = tclListSeqPtr($input, SWIGTYPE_p_ExceptionThru, interp); } %typemap(out) Vertex* { @@ -1037,7 +1096,7 @@ using namespace sta; } %typemap(in) EdgeSeq* { - $1 = tclListSeq($input, SWIGTYPE_p_Edge, interp); + $1 = tclListSeqPtr($input, SWIGTYPE_p_Edge, interp); } %typemap(out) EdgeSeq { @@ -1348,3 +1407,22 @@ using namespace sta; break; } } + +%typemap(in) CircuitSim { + int length; + char *arg = Tcl_GetStringFromObj($input, &length); + if (stringEq(arg, "hspice")) + $1 = CircuitSim::hspice; + else if (stringEq(arg, "ngspice")) + $1 = CircuitSim::ngspice; + else if (stringEq(arg, "xyce")) + $1 = CircuitSim::xyce; + else { + tclArgError(interp, "unknown circuit simulator %s.", arg); + return TCL_ERROR; + } +} + +%typemap(in) ArcDcalcArgPtrSeq { + $1 = tclListSeq($input, SWIGTYPE_p_ArcDcalcArg, interp); +} diff --git a/tcl/WritePathSpice.tcl b/tcl/WritePathSpice.tcl index 8604471d..19541b72 100644 --- a/tcl/WritePathSpice.tcl +++ b/tcl/WritePathSpice.tcl @@ -22,13 +22,13 @@ define_cmd_args "write_path_spice" { -path_args path_args\ -model_file model_file\ -power power\ -ground ground\ - [-measure_stmts]} + [-simulator hspice|ngspice|xyce]} proc write_path_spice { args } { parse_key_args "write_path_spice" args \ keys {-spice_directory -lib_subckt_file -model_file \ - -power -ground -path_args} \ - flags {-measure_stmts} + -power -ground -path_args -simulator} \ + flags {} if { [info exists keys(-spice_directory)] } { set spice_dir [file nativename $keys(-spice_directory)] @@ -75,7 +75,7 @@ proc write_path_spice { args } { sta_error 609 "No -ground specified." } - set measure_stmts [info exists keys(-measure_stmts)] + set ckt_sim [parse_ckt_sim_key keys] if { ![info exists keys(-path_args)] } { sta_error 610 "No -path_args specified." @@ -92,11 +92,27 @@ proc write_path_spice { args } { set spice_file [file join $spice_dir "$path_name.sp"] set subckt_file [file join $spice_dir "$path_name.subckt"] write_path_spice_cmd $path $spice_file $subckt_file \ - $lib_subckt_file $model_file {} $power $ground $measure_stmts + $lib_subckt_file $model_file $power $ground $ckt_sim incr path_index } } } +set ::ckt_sims {hspice ngspice xyce} + +proc parse_ckt_sim_key { keys_var } { + upvar 1 $keys_var keys + global ckt_sims + + set ckt_sim "ngspice" + if { [info exists keys(-simulator)] } { + set ckt_sim [file nativename $keys(-simulator)] + if { [lsearch $ckt_sims $ckt_sim] == -1 } { + sta_error 1710 "Unknown circuit simulator" + } + } + return $ckt_sim +} + # sta namespace end. }