From f6253af8a9de38e934f4dde13c72dca9edb26934 Mon Sep 17 00:00:00 2001 From: James Cherry Date: Tue, 27 Feb 2024 10:00:48 -0700 Subject: [PATCH 01/11] ccs ceff delay calc commit 87130be63ddbf1a7fb65986b02839eb4c0b13168 Author: James Cherry Date: Tue Feb 27 09:49:02 2024 -0700 ccs ceff delay calc Signed-off-by: James Cherry commit de0dd38dabda2f7ef51b49c196c2787a0d3c5784 Author: James Cherry Date: Tue Feb 27 07:40:11 2024 -0700 dcalc public funcs Signed-off-by: James Cherry commit dd7fcb12f929b9b0a391653cad42e617f9cbdd3b Author: James Cherry Date: Mon Feb 26 09:08:37 2024 -0700 mv CircuitSim.hh to include Signed-off-by: James Cherry commit 9663e46d28ece544ee1453f229990c9db9e0efec Author: James Cherry Date: Sun Feb 25 17:58:57 2024 -0700 ArcDcalcArg Signed-off-by: James Cherry commit 76b0588034faaefd2302c865c441975f76386d3f Author: James Cherry Date: Sun Feb 25 15:36:46 2024 -0700 ensureVoltageWaveforms Signed-off-by: James Cherry commit f88e67b861c56752e5b36efe2b552ba0077a7180 Author: James Cherry Date: Sun Feb 25 15:00:02 2024 -0700 const Signed-off-by: James Cherry commit 8f32cc571dcadee0185b08f951a1f79d46e7984d Author: James Cherry Date: Sun Feb 25 14:57:51 2024 -0700 Graph::gateEdgeArc Signed-off-by: James Cherry commit ac3cb35cb6732d7ecbf0532d7351a3ff2a917fc9 Author: James Cherry Date: Sun Feb 25 14:31:30 2024 -0700 ConcreteParasiticSubNodeMap, ConcreteParasiticPinNodeMap use id cmp Signed-off-by: James Cherry commit cbfe4eac463036c26a64701239d7651d91a09778 Author: James Cherry Date: Sun Feb 25 14:08:41 2024 -0700 WriteSpice Signed-off-by: James Cherry commit 8b5d30f1a8b1ccb8c9cbd9d7ba93418907c41b2a Author: James Cherry Date: Sat Feb 24 09:45:46 2024 -0700 emplace_push Signed-off-by: James Cherry commit 5335a2eaaf737ed7c7a8cff30654a68c4ac4c8e4 Author: James Cherry Date: Fri Feb 23 16:19:30 2024 -0700 Parasitics::findParasiticNode Signed-off-by: James Cherry commit ce92f3caf28afb0e0384799f08166cfb0aecfea0 Author: James Cherry Date: Fri Feb 23 15:53:28 2024 -0700 Parasitics::findParasiticNode Signed-off-by: James Cherry commit 0c591430c725a3ebd50d2892673dca76e023dc32 Author: James Cherry Date: Fri Feb 23 09:03:18 2024 -0700 Parsitics::name(node) const Signed-off-by: James Cherry commit 499c297e64d1487388f549843ff9ea05e8555cfc Author: James Cherry Date: Fri Feb 23 09:03:07 2024 -0700 write_spice umr Signed-off-by: James Cherry commit 6984c398dbce9e6266fab8377a844bc518481d9d Author: James Cherry Date: Thu Feb 22 18:42:34 2024 -0700 gcc warning Signed-off-by: James Cherry commit edec16519806013623194d8201e804dec81a51dd Author: James Cherry Date: Thu Feb 22 17:54:11 2024 -0700 no cuddification Signed-off-by: James Cherry commit 4a0e1070c179b2f8615b604c362359ce4b3a0e2e Author: James Cherry Date: Thu Feb 22 17:29:46 2024 -0700 sim const Signed-off-by: James Cherry commit 2e941fafa631f6b9bc0f82784b9146de2449e9c5 Author: James Cherry Date: Thu Feb 22 17:29:39 2024 -0700 sdc comment Signed-off-by: James Cherry commit 1c12f56aee7115fcb06807b5b6c626d1a419ccdc Author: James Cherry Date: Wed Feb 21 13:13:29 2024 -0700 Sim use Bdd class Signed-off-by: James Cherry commit b70c41d5caec56c3001b834141b6dab89bb933ed Author: James Cherry Date: Tue Feb 20 12:18:27 2024 -0700 write_spice coupling caps Signed-off-by: James Cherry commit 614d2cd41a1a9cf850dbe480954a5f58ee0dc21e Author: James Cherry Date: Mon Feb 19 14:37:30 2024 -0700 write_spice time offset Signed-off-by: James Cherry commit f0ba1fca0dfca384e6fb0be302bba9ced71ee41c Author: James Cherry Date: Mon Feb 19 10:59:18 2024 -0700 class Bdd for cudd Signed-off-by: James Cherry commit 24c94756334fce5e70e97ce0ee31375ae4e59b84 Author: James Cherry Date: Sun Feb 18 08:58:30 2024 -0700 WriteSpice Signed-off-by: James Cherry commit 47a4505d88bdfe4a85056895f8b7d842e07dce8d Author: James Cherry Date: Fri Feb 16 21:34:23 2024 -0700 default sim ngspice Signed-off-by: James Cherry commit 06e279555a076e218f0a9c308e8937a6fc8fdea4 Author: James Cherry Date: Fri Feb 16 21:34:01 2024 -0700 WriteSpice refactor Signed-off-by: James Cherry commit 06e3f0734edbbbd69ad063e97d1d8cca92a83aea Author: James Cherry Date: Thu Feb 15 15:18:35 2024 -0700 mv report_dcalc to DelayCalc.tcl Signed-off-by: James Cherry commit 922056471a6d380699bbd0623f95637401d23eff Author: James Cherry Date: Thu Feb 15 14:27:31 2024 -0700 WriteSpice::cell_spice_port_names_ Signed-off-by: James Cherry commit 732922ead68097e3f7da268ecc5ae2ca2daa4492 Author: James Cherry Date: Thu Feb 15 13:35:13 2024 -0700 WritePathSpice.hh Signed-off-by: James Cherry commit 8cd6e2ffc6ad66e831630273b5eacd192259191e Author: James Cherry Date: Thu Feb 15 10:11:39 2024 -0700 small Signed-off-by: James Cherry commit f7f6bfb49f43ddc3e45c294f89c8814d60df5220 Author: James Cherry Date: Thu Feb 15 09:48:09 2024 -0700 refactor WritePathSpice Signed-off-by: James Cherry commit f74db730c3e8c67a24d531266510e4376db463d3 Author: James Cherry Date: Wed Feb 14 09:22:01 2024 -0700 Sta.hh Signed-off-by: James Cherry commit 051532deef203cae97e32e8af7a2348bfd8912cc Author: James Cherry Date: Wed Feb 14 08:14:44 2024 -0700 PowerClass.hh Signed-off-by: James Cherry commit bfb8357d1093e5d3da14e708acd21fc21ba3b0dd Author: James Cherry Date: Wed Feb 14 08:08:56 2024 -0700 doc Signed-off-by: James Cherry commit 8fe28ec91b234d9d8210019aa46a2e8107aa497a Author: James Cherry Date: Wed Feb 14 07:32:34 2024 -0700 ClkSkew use seq instead of set Signed-off-by: James Cherry commit c4e3a3a0315ab4f6160a707e838423bb734f5363 Author: James Cherry Date: Tue Feb 13 19:26:45 2024 -0700 report_clock_latency Signed-off-by: James Cherry commit 51fb6657d9706c7443e1c269cfe63cf080b05d50 Author: James Cherry Date: Tue Feb 13 11:10:11 2024 -0700 report_clock_latency Signed-off-by: James Cherry commit e639ee129d13e1c11b34bca0762b8136b18563f3 Author: James Cherry Date: Mon Feb 12 11:19:06 2024 -0700 ClkSkew use map Signed-off-by: James Cherry commit e91d3ea8142a73b7b607dfdf53b3fce8e2f16984 Author: James Cherry Date: Mon Feb 12 10:18:27 2024 -0700 report_clock_skew report format Signed-off-by: James Cherry commit c650b7ec63b83382ba9cec7d187ffee8a031c2ce Author: James Cherry Date: Mon Feb 12 09:22:29 2024 -0700 report_clock_skew include macro clock_tree_path_delay Signed-off-by: James Cherry commit cf14b230a9944b95ba43ef7c09e553d9014990eb Author: James Cherry Date: Sun Feb 11 11:03:29 2024 -0700 clk skew range iter Signed-off-by: James Cherry commit e7e0342e063ac876d00d03fd1ff0eab1715cfde4 Author: James Cherry Date: Sun Feb 11 08:11:29 2024 -0700 write_spice sensitize and3 Signed-off-by: James Cherry commit 743ceb676c763ac5bcbf05e630a4da1b507c537d Author: James Cherry Date: Sat Feb 10 18:07:04 2024 -0700 write spice Signed-off-by: James Cherry Signed-off-by: James Cherry --- CMakeLists.txt | 150 +-- dcalc/ArcDelayCalc.cc | 74 +- dcalc/ArnoldiReduce.cc | 3 +- dcalc/CcsCeffDelayCalc.cc | 678 ++++++++++++++ dcalc/CcsCeffDelayCalc.hh | 157 ++++ dcalc/DelayCalc.cc | 2 + dcalc/DelayCalc.tcl | 7 + dcalc/GraphDelayCalc.cc | 4 +- doc/OpenSTA.odt | Bin 105281 -> 105659 bytes doc/OpenSTA.pdf | Bin 251507 -> 251739 bytes graph/Graph.cc | 31 + include/sta/ArcDelayCalc.hh | 29 +- include/sta/Bdd.hh | 57 ++ include/sta/CircuitSim.hh | 23 + include/sta/FuncExpr.hh | 4 +- include/sta/Graph.hh | 10 +- include/sta/GraphDelayCalc.hh | 16 +- include/sta/Liberty.hh | 16 +- include/sta/NetworkClass.hh | 1 + include/sta/Parasitics.hh | 14 +- include/sta/PowerClass.hh | 2 + include/sta/SdcClass.hh | 2 + include/sta/SearchClass.hh | 2 +- include/sta/Sta.hh | 12 +- include/sta/TableModel.hh | 1 + include/sta/WritePathSpice.hh | 11 +- liberty/FuncExpr.cc | 4 +- liberty/Liberty.cc | 114 ++- liberty/LibertyBuilder.cc | 12 +- liberty/LibertyBuilder.hh | 2 + liberty/TableModel.cc | 7 + parasitics/ConcreteParasitics.cc | 80 +- parasitics/ConcreteParasitics.hh | 10 +- parasitics/ConcreteParasiticsPvt.hh | 22 +- parasitics/Parasitics.cc | 7 + parasitics/ReduceParasitics.cc | 9 +- parasitics/SpefReader.cc | 58 +- power/Power.cc | 141 +-- power/Power.hh | 13 +- search/Bdd.cc | 179 ++++ search/ClkDelays.hh | 70 ++ search/ClkLatency.cc | 299 ++++++ search/ClkLatency.hh | 52 ++ search/ClkSkew.cc | 220 +++-- search/ClkSkew.hh | 32 +- search/MakeTimingModel.cc | 17 +- search/Sim.cc | 162 +--- search/Sim.hh | 28 +- search/Sta.cc | 32 +- search/WritePathSpice.cc | 1308 ++------------------------- search/WriteSpice.cc | 1267 ++++++++++++++++++++++++++ search/WriteSpice.hh | 191 ++++ tcl/CmdArgs.tcl | 10 + tcl/Sdc.tcl | 12 +- tcl/Search.tcl | 57 +- tcl/StaTcl.i | 20 +- tcl/StaTclTypes.i | 106 ++- tcl/WritePathSpice.tcl | 26 +- 58 files changed, 3967 insertions(+), 1906 deletions(-) create mode 100644 dcalc/CcsCeffDelayCalc.cc create mode 100644 dcalc/CcsCeffDelayCalc.hh create mode 100644 include/sta/Bdd.hh create mode 100644 include/sta/CircuitSim.hh create mode 100644 search/Bdd.cc create mode 100644 search/ClkDelays.hh create mode 100644 search/ClkLatency.cc create mode 100644 search/ClkLatency.hh create mode 100644 search/WriteSpice.cc create mode 100644 search/WriteSpice.hh 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 30afc942bfb3925e7e5bdbad91fe93567e9f1fce..2682fccf734bdb5effd1926c615791fa27c44906 100644 GIT binary patch delta 75389 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%0Agt52d(r!!8WQZ_Nqi0$HEJ9YD zefXSz?KXZ9Ur^NU_W@Ou`3qH>&fmuoFSMI%Vc_IdHCjy0{bJg0AzCo~O*q_}k36ZV zZ$Ti)rF!n4gy5bGb!*VC@rO|yzu>*q2<+FM+zb;vAn;ikd7Yz&2`z%QDAZ_$3K%43qJ|WT%E7Zs-vR~y?CB@Tbw92A`qv8Kh_0E5t#b5XL&$*F;icL1MorU>)~vbp8T8lcv@B@o%#Cb} zcxpZDMvmO{>xwRPW`1yd>lCd@v|3#v0T}+wHg|v@F|q_l{bhS4hd|aIwp% zRuPGk+sfELGXIWQiQ3{_&T&v6Q9Ulg29(9Mh+*ARi66Ljna2W*}B< z;072Moq4z#r~8PjW4^#Q<@a$U4GB|vI9XL|$?mMR&O&KG4cIN!4@>Iu@brNVO)wd% z7a~ocZ<*X(Eey*MO`M8&7U|7Eoohpiw03}hHq-T~{`{KEs2WN*k?NtdI{TYiTab(B zzwLtiFE^gMd% zXi?T1-6w^C3B+?hWA(I2arlOM3hGr=pzFk(nUpLiqJqSMIi3CB2QPAo76qztbs@ z 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 z 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 P$YBv;Dk{d-CSv~wi~^Xz 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 2bcc11a8..62ce4817 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. @@ -163,7 +180,7 @@ public: const DcalcAnalysisPt *dcalc_ap, // Return values. ArcDelay &gate_delay, - Slew &drvr_slew) __attribute__ ((deprecated)); + Slew &drvr_slew) __attribute__ ((deprecated)); // Find gate delays and slews for parallel gates. virtual ArcDcalcResultSeq gateDelays(ArcDcalcArgSeq &args, 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..15a37283 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 3cec13a0..e6c34f4f 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; - 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; + 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/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 cd4cc8a6..358e2250 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..24e8c6e3 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 { @@ -527,17 +527,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 +551,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/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. } From ad4050a3b2902c42cf47cf218c9f6f5a2698a174 Mon Sep 17 00:00:00 2001 From: James Cherry Date: Fri, 1 Mar 2024 08:21:46 -0700 Subject: [PATCH 02/11] ConcreteParasiticNetwork::deleteNodes Signed-off-by: James Cherry --- parasitics/ConcreteParasitics.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/parasitics/ConcreteParasitics.cc b/parasitics/ConcreteParasitics.cc index 358e2250..3bcd71b4 100644 --- a/parasitics/ConcreteParasitics.cc +++ b/parasitics/ConcreteParasitics.cc @@ -511,7 +511,7 @@ ConcreteParasiticNetwork::deleteNodes() delete node; } for (auto pin_node : pin_nodes_) { - ParasiticNode *node = pin_node.second; + ConcreteParasiticNode *node = pin_node.second; delete node; } } From 1d16e230528030ea39af36a3c981686c83fc177d Mon Sep 17 00:00:00 2001 From: James Cherry Date: Fri, 1 Mar 2024 08:57:41 -0700 Subject: [PATCH 03/11] write_timing_model include is_macro_cell Signed-off-by: James Cherry --- liberty/LibertyReader.cc | 2 +- liberty/LibertyWriter.cc | 2 +- search/MakeTimingModel.cc | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) 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/search/MakeTimingModel.cc b/search/MakeTimingModel.cc index 24e8c6e3..9d82c8fb 100644 --- a/search/MakeTimingModel.cc +++ b/search/MakeTimingModel.cc @@ -156,7 +156,7 @@ void MakeTimingModel::makeCell() { cell_ = lib_builder_->makeCell(library_, cell_name_, filename_); - cell_->setInterfaceTiming(true); + cell_->setIsMacro(true); } void From b39dd24339f4108602555f19cb744cd6793a32eb Mon Sep 17 00:00:00 2001 From: James Cherry Date: Fri, 1 Mar 2024 09:23:23 -0700 Subject: [PATCH 04/11] write_timing_model area Signed-off-by: James Cherry --- search/MakeTimingModel.cc | 15 +++++++++++++++ search/MakeTimingModelPvt.hh | 1 + 2 files changed, 16 insertions(+) diff --git a/search/MakeTimingModel.cc b/search/MakeTimingModel.cc index 9d82c8fb..ff9a861f 100644 --- a/search/MakeTimingModel.cc +++ b/search/MakeTimingModel.cc @@ -157,6 +157,21 @@ MakeTimingModel::makeCell() { cell_ = lib_builder_->makeCell(library_, cell_name_, filename_); 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 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(); From bb2ea3dc640171347ab2574dcd304d18415750e7 Mon Sep 17 00:00:00 2001 From: James Cherry Date: Sun, 3 Mar 2024 09:46:21 -0700 Subject: [PATCH 05/11] readme Signed-off-by: James Cherry --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 3c6bd4b0..6d5b0dad 100644 --- a/README.md +++ b/README.md @@ -61,8 +61,8 @@ compiled locally. Derivative works are supported as long as they adhere to the GPL license requirements. However, OpenSTA is not supported by a public community of developers as many other open source projects are. The copyright and develpment are exclusive to -Parallax Software. OpenSTA does not solicit or accept external code -contributions. +Parallax Software. Contributors must signing the Contributor License +Agreement (doc/CLA.txt) when submitting pull requests. Removing copyright and license notices from OpenSTA sources (or any other open source project for that matter) is illegal. This should be From 84d6735fa2916e0dc848da383aace13c6774535e Mon Sep 17 00:00:00 2001 From: James Cherry Date: Tue, 5 Mar 2024 21:10:52 -0700 Subject: [PATCH 06/11] CodingGuidelines Signed-off-by: James Cherry --- doc/CodingGuidelines.txt | 55 +++++++++++++--------------------------- 1 file changed, 17 insertions(+), 38 deletions(-) diff --git a/doc/CodingGuidelines.txt b/doc/CodingGuidelines.txt index 80ea0eee..1c6f8dce 100644 --- a/doc/CodingGuidelines.txt +++ b/doc/CodingGuidelines.txt @@ -2,25 +2,21 @@ Naming conventions directory - lowercase (directory) filename - corresponding class name without prefix (Filename) -class - capitalized (ClassName) -member function - lowercase/capitalized (memberFunction) -member variable - lowercase/underscore/trailing underscore (member_variable_) +class - upper camel case (ClassName) +member function - upper camel case (memberFunction) +member variable - snake case with trailing underscore (member_variable_) Trailing underscore prevents conflict with accessor member function name. -function - lowercase/capitalized (functionName) +function - lower camel case (functionName) comments - use capitalized sentences that end with periods C++ code files should use a .cc file extension C++ header files should use a .hh file extension -Use ifdef/define's to protect headers from being read more than once. -Name the define variable the same as the header in uppercase. -For example, for Clock.hh +Use pragmas to protect headers from being read more than once instead of +ifdef/define. - #ifndef STA_CLOCK_H - #define STA_CLOCK_H - ... - #endif + #pragma once In general it is better to for class variables to use pointers to objects of other classes rather than embedding the instance directly. @@ -35,22 +31,23 @@ where Directory is the capitalized name of the sub-directory. Place comments describing public functions and classes in header files rather than code files because a consumer is more likely to have access to the header and that is the first place they will look. -Comments for private functions can be in the source file. The return type of a function should be on the line before the -function name. Spaces should be added after commas in the argument -list. Split the function arguments to fit on one line. For example: +function name. Arguments should be on separate lines to make it easier +to remove or add them without having to reformat the lines as they +change length. return_type -function(type1 arg1, type2, arg2) +function(type1 arg1, + type2 arg2) { } Functions should be less than one screen long. Break long functions -up into smaller ones. Lines should be less than 80 characters long. +up into smaller ones. Lines should be less than 90 characters long. -Try to avoid assignments inside `if'-conditions. For example, don't -write this: +Avoid assignments inside `if'-conditions. For example, don't write +this: if ((foo = (char *) malloc (sizeof *foo)) == 0) fatal ("virtual memory exhausted"); @@ -102,8 +99,8 @@ Don't declare class variables as const. It means any downstream code that accesses the member cannot modify it, which is overly restrictive. -Never use [] to lookup a map value because it creates a key/null value -pair if the lookup fails. Use sta::Map::findKey instead. +Avoid using [] to lookup a map value because it creates a key/null value +pair if the lookup fails. Use map::find or sta::Map::findKey instead. Avoid nested classes/enums because SWIG has trouble with them. @@ -127,24 +124,6 @@ cmd unknown keyword option cmd unknown sdf pin not found -................................................................ - -if configure.ac changes - autoconf - -if Makefile.am changes - automake - -Adding a new source file - Add header and source to source_dir/Makefile.am - cd source_dir; make clean - -Adding a new source directory - Add to configure.ac STA_SUBDIRS, AC_CONFIG_FILES - bootstrap - configure - make - ................................................................ Swig notes From b4e58d5b74f74b688cc811a6c8e056c5de1ec642 Mon Sep 17 00:00:00 2001 From: James Cherry Date: Wed, 6 Mar 2024 09:53:55 -0700 Subject: [PATCH 07/11] graph delay calc N^2 from bidirect pad ring pins Signed-off-by: James Cherry --- graph/Graph.cc | 56 +++++++++++++++++++++++++++++++++++++++++--- include/sta/Graph.hh | 4 ++++ 2 files changed, 57 insertions(+), 3 deletions(-) diff --git a/graph/Graph.cc b/graph/Graph.cc index c24d69ca..3ce7aec6 100644 --- a/graph/Graph.cc +++ b/graph/Graph.cc @@ -276,10 +276,19 @@ Graph::makeWireEdgesFromPin(const Pin *drvr_pin, { // Find all drivers and loads on the net to avoid N*M run time // for large fanin/fanout nets. - PinSeq loads, drvrs; + PinSeq drvrs, loads; FindNetDrvrLoads visitor(drvr_pin, visited_drvrs, loads, drvrs, network_); network_->visitConnectedPins(drvr_pin, visitor); + if (isIsolatedNet(drvrs, loads)) { + for (auto drvr_pin : drvrs) { + visited_drvrs.insert(drvr_pin); + debugPrint(debug_, "graph", 1, "ignoring isolated driver %s", + network_->pathName(drvr_pin)); + } + return; + } + for (auto drvr_pin : drvrs) { for (auto load_pin : loads) { if (drvr_pin != load_pin) @@ -288,6 +297,35 @@ Graph::makeWireEdgesFromPin(const Pin *drvr_pin, } } +// Check for nets with bidirect drivers that have no fanin or +// fanout. One example of these nets are bidirect pad ring pins +// are connected together but have no function but are marked +// as signal nets. +// These nets tickle N^2 behaviors that have no function. +bool +Graph::isIsolatedNet(PinSeq &drvrs, + PinSeq &loads) const +{ + if (drvrs.size() < 10) + return false; + // Check that all drivers have no fanin. + for (auto drvr_pin : drvrs) { + Vertex *drvr_vertex = pinDrvrVertex(drvr_pin); + if (network_->isTopLevelPort(drvr_pin) + || drvr_vertex->hasFanin()) + return false; + } + // Check for fanout on the load pins. + for (auto load_pin : loads) { + Vertex *load_vertex = pinLoadVertex(load_pin); + if (load_vertex->hasFanout() + || load_vertex->hasChecks()) { + return false; + } + } + return true; +} + void Graph::makeWireEdgesToPin(const Pin *to_pin) { @@ -536,7 +574,7 @@ Graph::makeArrivals(Vertex *vertex, uint32_t count) { if (vertex->arrivals() != arrival_null) - debugPrint(debug_, "leaks", 617, "arrival leak"); + debugPrint(debug_, "graph", 1, "arrival leak"); Arrival *arrivals; ArrivalId id; { @@ -569,7 +607,7 @@ Graph::makeRequireds(Vertex *vertex, uint32_t count) { if (vertex->requireds() != arrival_null) - debugPrint(debug_, "leaks", 617, "required leak"); + debugPrint(debug_, "graph", 1, "required leak"); Required *requireds; ArrivalId id; { @@ -1281,6 +1319,18 @@ Vertex::setIsDisabledConstraint(bool disabled) is_disabled_constraint_ = disabled; } +bool +Vertex::hasFanin() const +{ + return in_edges_ != edge_id_null; +} + +bool +Vertex::hasFanout() const +{ + return out_edges_ != edge_id_null; +} + void Vertex::setHasChecks(bool has_checks) { diff --git a/include/sta/Graph.hh b/include/sta/Graph.hh index eb53b2e7..593e0f2f 100644 --- a/include/sta/Graph.hh +++ b/include/sta/Graph.hh @@ -221,6 +221,8 @@ protected: void makePinVertices(const Instance *inst); void makeWireEdgesFromPin(const Pin *drvr_pin, PinSet &visited_drvrs); + bool isIsolatedNet(PinSeq &drvrs, + PinSeq &loads) const; void makeWireEdges(); virtual void makeInstDrvrWireEdges(const Instance *inst, PinSet &visited_drvrs); @@ -289,6 +291,8 @@ public: Level level() const { return level_; } void setLevel(Level level); bool isRoot() const{ return level_ == 0; } + bool hasFanin() const; + bool hasFanout() const; LevelColor color() const { return static_cast(color_); } void setColor(LevelColor color); ArrivalId arrivals() { return arrivals_; } From 1740c894b404fa7d32bbbdbb9192659813c0fb67 Mon Sep 17 00:00:00 2001 From: James Cherry Date: Wed, 6 Mar 2024 19:03:30 -0700 Subject: [PATCH 08/11] OR 4732 Liberty support for positive/negative clock_tree_path Signed-off-by: James Cherry --- include/sta/Liberty.hh | 12 ++++++-- liberty/Liberty.cc | 44 +++++++++++++++++++-------- liberty/LibertyBuilder.cc | 22 ++++++++++++-- search/ClkDelays.hh | 4 +-- search/ClkLatency.cc | 4 +-- search/MakeTimingModel.cc | 58 +++++++++++++++++++++--------------- search/MakeTimingModelPvt.hh | 4 +++ 7 files changed, 103 insertions(+), 45 deletions(-) diff --git a/include/sta/Liberty.hh b/include/sta/Liberty.hh index 15a37283..27f0fd1e 100644 --- a/include/sta/Liberty.hh +++ b/include/sta/Liberty.hh @@ -796,10 +796,16 @@ public: void setDriverWaveform(DriverWaveform *driver_waveform, const RiseFall *rf); void setClkTreeDelay(const TableModel *model, - const RiseFall *rf, + const RiseFall *from_rf, + const RiseFall *to_rf, const MinMax *min_max); + // Should be deprecated. float clkTreeDelay(float in_slew, - const RiseFall *rf, + const RiseFall *from_rf, + const MinMax *min_max) const; + float clkTreeDelay(float in_slew, + const RiseFall *from_rf, + const RiseFall *to_rf, const MinMax *min_max) const; // Assumes input slew of 0.0. RiseFallMinMax clkTreeDelays() const; @@ -846,7 +852,7 @@ protected: 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]; + const TableModel *clk_tree_delay_[RiseFall::index_count][RiseFall::index_count][MinMax::index_count]; unsigned int min_pulse_width_exists_:RiseFall::index_count; bool min_period_exists_:1; diff --git a/liberty/Liberty.cc b/liberty/Liberty.cc index e6c34f4f..05fae172 100644 --- a/liberty/Liberty.cc +++ b/liberty/Liberty.cc @@ -2001,9 +2001,11 @@ 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; + for (auto from_rf_index : RiseFall::rangeIndex()) { + for (auto to_rf_index : RiseFall::rangeIndex()) { + for (auto mm_index : MinMax::rangeIndex()) + clk_tree_delay_[from_rf_index][to_rf_index][mm_index] = nullptr; + } } } @@ -2607,12 +2609,15 @@ RiseFallMinMax LibertyPort::clkTreeDelays() const { RiseFallMinMax delays; - 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); + for (const RiseFall *from_rf : RiseFall::range()) { + for (const RiseFall *to_rf : RiseFall::range()) { + for (const MinMax *min_max : MinMax::range()) { + const TableModel *model = + clk_tree_delay_[from_rf->index()][to_rf->index()][min_max->index()]; + if (model) { + float delay = model->findValue(0.0, 0.0, 0.0); + delays.setValue(from_rf, min_max, delay); + } } } } @@ -2624,7 +2629,21 @@ LibertyPort::clkTreeDelay(float in_slew, const RiseFall *rf, const MinMax *min_max) const { - const TableModel *model = clk_tree_delay_[rf->index()][min_max->index()]; + const TableModel *model = clk_tree_delay_[rf->index()][rf->index()][min_max->index()]; + if (model) + return model->findValue(in_slew, 0.0, 0.0); + else + return 0.0; +} + +float +LibertyPort::clkTreeDelay(float in_slew, + const RiseFall *from_rf, + const RiseFall *to_rf, + const MinMax *min_max) const +{ + const TableModel *model = + clk_tree_delay_[from_rf->index()][to_rf->index()][min_max->index()]; if (model) return model->findValue(in_slew, 0.0, 0.0); else @@ -2633,10 +2652,11 @@ LibertyPort::clkTreeDelay(float in_slew, void LibertyPort::setClkTreeDelay(const TableModel *model, - const RiseFall *rf, + const RiseFall *from_rf, + const RiseFall *to_rf, const MinMax *min_max) { - clk_tree_delay_[rf->index()][min_max->index()] = model; + clk_tree_delay_[from_rf->index()][to_rf->index()][min_max->index()] = model; } //////////////////////////////////////////////////////////////// diff --git a/liberty/LibertyBuilder.cc b/liberty/LibertyBuilder.cc index dac8d59b..39a1f1b8 100644 --- a/liberty/LibertyBuilder.cc +++ b/liberty/LibertyBuilder.cc @@ -642,9 +642,27 @@ LibertyBuilder::makeClockTreePathArcs(LibertyCell *cell, for (auto to_rf : RiseFall::range()) { TimingModel *model = attrs->model(to_rf); 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); + RiseFall *opp_rf = to_rf->opposite(); + switch (attrs->timingSense()) { + case TimingSense::positive_unate: + makeTimingArc(arc_set, to_rf, to_rf, model); + to_port->setClkTreeDelay(gate_model->delayModel(), to_rf, to_rf, min_max); + break; + case TimingSense::negative_unate: + makeTimingArc(arc_set, opp_rf, to_rf, model); + to_port->setClkTreeDelay(gate_model->delayModel(), opp_rf, to_rf, min_max); + break; + case TimingSense::non_unate: + case TimingSense::unknown: + makeTimingArc(arc_set, to_rf, to_rf, model); + makeTimingArc(arc_set, opp_rf, to_rf, model); + to_port->setClkTreeDelay(gate_model->delayModel(), to_rf, to_rf, min_max); + to_port->setClkTreeDelay(gate_model->delayModel(), opp_rf, to_rf, min_max); + break; + case TimingSense::none: + break; + } } } return arc_set; diff --git a/search/ClkDelays.hh b/search/ClkDelays.hh index 3aadfa88..003b705c 100644 --- a/search/ClkDelays.hh +++ b/search/ClkDelays.hh @@ -36,13 +36,13 @@ public: float &lib_clk_delay, float &latency, PathVertex &path, - bool &exists); + bool &exists) const; void latency(const RiseFall *src_rf, const RiseFall *end_rf, const MinMax *min_max, // Return values. float &delay, - bool &exists); + bool &exists) const; static float latency(PathVertex *clk_path, StaState *sta); void setLatency(const RiseFall *src_rf, diff --git a/search/ClkLatency.cc b/search/ClkLatency.cc index 9e2e91a4..8c9c90ce 100644 --- a/search/ClkLatency.cc +++ b/search/ClkLatency.cc @@ -194,7 +194,7 @@ ClkDelays::delay(const RiseFall *src_rf, float &lib_clk_delay, float &latency, PathVertex &path, - bool &exists) + bool &exists) const { int src_rf_index = src_rf->index(); int end_rf_index = end_rf->index(); @@ -213,7 +213,7 @@ ClkDelays::latency(const RiseFall *src_rf, const MinMax *min_max, // Return values. float &latency, - bool &exists) + bool &exists) const { int src_rf_index = src_rf->index(); int end_rf_index = end_rf->index(); diff --git a/search/MakeTimingModel.cc b/search/MakeTimingModel.cc index ff9a861f..c35dfa23 100644 --- a/search/MakeTimingModel.cc +++ b/search/MakeTimingModel.cc @@ -544,30 +544,8 @@ MakeTimingModel::findClkInsertionDelays() for (const Clock *clk : *clks) { ClkDelays delays = sta_->findClkDelays(clk); for (const MinMax *min_max : MinMax::range()) { - TimingArcAttrsPtr attrs = nullptr; - for (const RiseFall *clk_rf : RiseFall::range()) { - float delay = min_max->initValue(); - for (const RiseFall *end_rf : RiseFall::range()) { - PathVertex clk_path; - float insertion, delay1, lib_clk_delay, latency; - bool 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)); - } - TimingModel *model = makeGateModelScalar(delay, clk_rf); - if (attrs == nullptr) - attrs = std::make_shared(); - attrs->setModel(clk_rf, model); - } - if (attrs) - attrs->setTimingSense(TimingSense::positive_unate); - TimingRole *role = (min_max == MinMax::min()) - ? TimingRole::clockTreePathMin() - : TimingRole::clockTreePathMax(); - lib_builder_->makeClockTreePathArcs(cell_, lib_port, role, - min_max, attrs); + makeClkTreePaths(lib_port, min_max, TimingSense::positive_unate, delays); + makeClkTreePaths(lib_port, min_max, TimingSense::negative_unate, delays); } } } @@ -577,6 +555,38 @@ MakeTimingModel::findClkInsertionDelays() delete port_iter; } +void +MakeTimingModel::makeClkTreePaths(LibertyPort *lib_port, + const MinMax *min_max, + TimingSense sense, + const ClkDelays &delays) +{ + TimingArcAttrsPtr attrs = nullptr; + for (const RiseFall *clk_rf : RiseFall::range()) { + const RiseFall *end_rf = (sense == TimingSense::positive_unate) + ? clk_rf + : clk_rf->opposite(); + PathVertex clk_path; + float insertion, delay, lib_clk_delay, latency; + bool exists; + delays.delay(clk_rf, end_rf, min_max, insertion, delay, + lib_clk_delay, latency, clk_path, exists); + if (exists) { + TimingModel *model = makeGateModelScalar(delay, end_rf); + if (attrs == nullptr) + attrs = std::make_shared(); + attrs->setModel(end_rf, model); + } + } + if (attrs) { + attrs->setTimingSense(sense); + TimingRole *role = (min_max == MinMax::min()) + ? TimingRole::clockTreePathMin() + : TimingRole::clockTreePathMax(); + lib_builder_->makeClockTreePathArcs(cell_, lib_port, role, min_max, attrs); + } +} + //////////////////////////////////////////////////////////////// LibertyPort * diff --git a/search/MakeTimingModelPvt.hh b/search/MakeTimingModelPvt.hh index da5f15b8..e7587f6f 100644 --- a/search/MakeTimingModelPvt.hh +++ b/search/MakeTimingModelPvt.hh @@ -64,6 +64,10 @@ private: void findTimingFromInput(Port *input_port); void findClkedOutputPaths(); void findClkInsertionDelays(); + void makeClkTreePaths(LibertyPort *lib_port, + const MinMax *min_max, + TimingSense sense, + const ClkDelays &delays); void findOutputDelays(const RiseFall *input_rf, OutputPinDelays &output_pin_delays); void makeSetupHoldTimingArcs(const Pin *input_pin, From 5fb37b6d19afc21f0483d8b0d73e85dc7cabccd5 Mon Sep 17 00:00:00 2001 From: Matt Liberty Date: Wed, 6 Mar 2024 20:40:25 -0800 Subject: [PATCH 09/11] remove deprecated attribute Signed-off-by: Matt Liberty --- include/sta/ArcDelayCalc.hh | 2 +- include/sta/Liberty.hh | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/include/sta/ArcDelayCalc.hh b/include/sta/ArcDelayCalc.hh index 62ce4817..b1dabe55 100644 --- a/include/sta/ArcDelayCalc.hh +++ b/include/sta/ArcDelayCalc.hh @@ -180,7 +180,7 @@ public: const DcalcAnalysisPt *dcalc_ap, // Return values. ArcDelay &gate_delay, - Slew &drvr_slew) __attribute__ ((deprecated)); + Slew &drvr_slew); // __attribute__ ((deprecated)); // Find gate delays and slews for parallel gates. virtual ArcDcalcResultSeq gateDelays(ArcDcalcArgSeq &args, diff --git a/include/sta/Liberty.hh b/include/sta/Liberty.hh index 27f0fd1e..1d6e16c6 100644 --- a/include/sta/Liberty.hh +++ b/include/sta/Liberty.hh @@ -809,7 +809,7 @@ public: const MinMax *min_max) const; // Assumes input slew of 0.0. RiseFallMinMax clkTreeDelays() const; - RiseFallMinMax clockTreePathDelays() const __attribute__ ((deprecated)); + RiseFallMinMax clockTreePathDelays() const; // __attribute__ ((deprecated)); static bool equiv(const LibertyPort *port1, const LibertyPort *port2); From 71c4289ab8c3697d5a83abb81f40bdb620593a7b Mon Sep 17 00:00:00 2001 From: Matt Liberty Date: Wed, 6 Mar 2024 20:40:46 -0800 Subject: [PATCH 10/11] silence compiler false error Signed-off-by: Matt Liberty --- liberty/Liberty.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/liberty/Liberty.cc b/liberty/Liberty.cc index 05fae172..e486915c 100644 --- a/liberty/Liberty.cc +++ b/liberty/Liberty.cc @@ -1896,7 +1896,7 @@ void LibertyCell::ensureVoltageWaveforms() { if (!have_voltage_waveforms_) { - float vdd; + float vdd = 0; bool vdd_exists; liberty_library_->supplyVoltage("VDD", vdd, vdd_exists); if (!vdd_exists || vdd == 0.0) From 68deda8054ea9d2aa10255a5dce47e795e1760fa Mon Sep 17 00:00:00 2001 From: Matt Liberty Date: Wed, 6 Mar 2024 20:41:11 -0800 Subject: [PATCH 11/11] aoivd crash on cell without Liberty Signed-off-by: Matt Liberty --- search/MakeTimingModel.cc | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/search/MakeTimingModel.cc b/search/MakeTimingModel.cc index c35dfa23..bacdaeea 100644 --- a/search/MakeTimingModel.cc +++ b/search/MakeTimingModel.cc @@ -168,7 +168,8 @@ MakeTimingModel::findArea() while (leaf_iter->hasNext()) { const Instance *inst = leaf_iter->next(); const LibertyCell *cell = network_->libertyCell(inst); - area += cell->area(); + if (cell) + area += cell->area(); } delete leaf_iter; return area;