diff --git a/CMakeLists.txt b/CMakeLists.txt index 312c1edc..5206d5c3 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -176,7 +176,7 @@ set(STA_SOURCE search/VisitPathEnds.cc search/VisitPathGroupVertices.cc search/WorstSlack.cc - search/WriteSpice.cc + search/WritePathSpice.cc util/Condition.cc util/Debug.cc @@ -346,7 +346,7 @@ set(STA_HEADERS search/VisitPathEnds.hh search/VisitPathGroupVertices.hh search/WorstSlack.hh - search/WriteSpice.hh + search/WritePathSpice.hh util/Condition.hh util/Debug.hh @@ -514,91 +514,88 @@ get_filename_component(TCL_HEADER_DIR "${TCL_HEADER}" PATH) ################################################################ -# TCL files included as part of the executable are shoved into TclInitVar.cc. -# These files are encoded and shipped as part of the executable -# so that they do not have to be installed on the client host. -add_custom_command(OUTPUT ${STA_HOME}/app/TclInitVar.cc - COMMAND etc/TclEncode.tcl app/TclInitVar.cc tcl_inits ${STA_TCL_FILES} - WORKING_DIRECTORY ${STA_HOME} - DEPENDS ${STA_TCL_FILES} etc/TclEncode.tcl - ) - find_package(FLEX) find_package(BISON) -# Verilog scan/parse. -bison_target(VerilogParser verilog/VerilogParse.yy ${STA_HOME}/verilog/VerilogParse.cc - DEFINES_FILE ${STA_HOME}/verilog/VerilogParse.hh - COMPILE_FLAGS --name-prefix=VerilogParse_ - ) +# LibertyExpr scan/parse. +bison_target(LibertyExprParser liberty/LibertyExprParse.yy ${STA_HOME}/liberty/LibertyExprParse.cc + DEFINES_FILE ${STA_HOME}/liberty/LibertyExprParse.hh + COMPILE_FLAGS --name-prefix=LibertyExprParse_ + ) -# flex seems to ignore -o -add_custom_command(OUTPUT ${STA_HOME}/verilog/VerilogLex.cc ${STA_HOME}/verilog/VerilogLex.hh - COMMAND ${FLEX_EXECUTABLE} --prefix=VerilogLex_ --header-file=${STA_HOME}/verilog/VerilogLex.hh ${STA_HOME}/verilog/VerilogLex.ll - COMMAND mv lex.yy.c ${STA_HOME}/verilog/VerilogLex.cc +flex_target(LibertyExprLex liberty/LibertyExprLex.ll ${STA_HOME}/liberty/LibertyExprLex.cc + DEFINES_FILE ${STA_HOME}/liberty/LibertyExprLex.hh + COMPILE_FLAGS --prefix=LibertyExprLex_ ) +add_flex_bison_dependency(LibertyExprLex LibertyExprParser) + # Liberty scan/parse. bison_target(LibertyParser liberty/LibertyParse.yy ${STA_HOME}/liberty/LibertyParse.cc DEFINES_FILE ${STA_HOME}/liberty/LibertyParse.hh COMPILE_FLAGS --name-prefix=LibertyParse_ ) -# flex seems to ignore -o -add_custom_command(OUTPUT ${STA_HOME}/liberty/LibertyLex.cc ${STA_HOME}/liberty/LibertyLex.hh - COMMAND ${FLEX_EXECUTABLE} --prefix=LibertyLex_ --header-file=${STA_HOME}/liberty/LibertyLex.hh ${STA_HOME}/liberty/LibertyLex.ll - COMMAND mv lex.yy.c ${STA_HOME}/liberty/LibertyLex.cc - ) +flex_target(LibertyLex liberty/LibertyLex.ll ${STA_HOME}/liberty/LibertyLex.cc + DEFINES_FILE ${STA_HOME}/liberty/LibertyLex.hh + COMPILE_FLAGS --prefix=LibertyLex_ + ) -# LibertyExpr scan/parse. -bison_target(LibertyExprParser liberty/LibertyExprParse.yy ${STA_HOME}/liberty/LibertyExprParse.cc - DEFINES_FILE ${STA_HOME}/liberty/LibertyExprParse.hh - COMPILE_FLAGS --name-prefix=LibertyExprParse_ - ) - -# flex seems to ignore -o -add_custom_command(OUTPUT ${STA_HOME}/liberty/LibertyExprLex.cc ${STA_HOME}/liberty/LibertyExprLex.hh - COMMAND ${FLEX_EXECUTABLE} --prefix=LibertyExprLex_ --header-file=${STA_HOME}/liberty/LibertyExprLex.hh ${STA_HOME}/liberty/LibertyExprLex.ll - COMMAND mv lex.yy.c ${STA_HOME}/liberty/LibertyExprLex.cc - ) - -# Sdf scan/parse. -bison_target(SdfParser sdf/SdfParse.yy ${STA_HOME}/sdf/SdfParse.cc - DEFINES_FILE ${STA_HOME}/sdf/SdfParse.hh - COMPILE_FLAGS --name-prefix=SdfParse_ - ) - -# flex seems to ignore -o -add_custom_command(OUTPUT ${STA_HOME}/sdf/SdfLex.cc ${STA_HOME}/sdf/SdfLex.hh - COMMAND ${FLEX_EXECUTABLE} --prefix=SdfLex_ --header-file=${STA_HOME}/sdf/SdfLex.hh ${STA_HOME}/sdf/SdfLex.ll - COMMAND mv lex.yy.c ${STA_HOME}/sdf/SdfLex.cc - ) +add_flex_bison_dependency(LibertyLex LibertyParser) # Spef scan/parse. bison_target(SpefParser parasitics/SpefParse.yy ${STA_HOME}/parasitics/SpefParse.cc - DEFINES_FILE ${STA_HOME}/parasitics/SpefParse.hh - COMPILE_FLAGS --name-prefix=SpefParse_ - ) + DEFINES_FILE ${STA_HOME}/parasitics/SpefParse.hh + COMPILE_FLAGS --name-prefix=SpefParse_ + ) -# flex seems to ignore -o -add_custom_command(OUTPUT ${STA_HOME}/parasitics/SpefLex.cc ${STA_HOME}/parasitics/SpefLex.hh - COMMAND ${FLEX_EXECUTABLE} --prefix=SpefLex_ --header-file=${STA_HOME}/parasitics/SpefLex.hh ${STA_HOME}/parasitics/SpefLex.ll - COMMAND mv lex.yy.c ${STA_HOME}/parasitics/SpefLex.cc - ) +flex_target(SpefLex parasitics/SpefLex.ll ${STA_HOME}/parasitics/SpefLex.cc + DEFINES_FILE ${STA_HOME}/parasitics/SpefLex.hh + COMPILE_FLAGS --prefix=SpefLex_ + ) +add_flex_bison_dependency(SpefLex SpefParser) # Spf scan/parse. bison_target(SpfParser parasitics/SpfParse.yy ${STA_HOME}/parasitics/SpfParse.cc - DEFINES_FILE ${STA_HOME}/parasitics/SpfParse.hh - COMPILE_FLAGS --name-prefix=SpfParse_ + DEFINES_FILE ${STA_HOME}/parasitics/SpfParse.hh + COMPILE_FLAGS --name-prefix=SpfParse_ + ) + +flex_target(SpfLex parasitics/SpfLex.ll ${STA_HOME}/parasitics/SpfLex.cc + DEFINES_FILE ${STA_HOME}/parasitics/SpfLex.hh + COMPILE_FLAGS --prefix=SpfLex_ + ) + +add_flex_bison_dependency(SpfLex SpfParser) + +# Verilog scan/parse. +bison_target(VerilogParser verilog/VerilogParse.yy ${STA_HOME}/verilog/VerilogParse.cc + DEFINES_FILE ${STA_HOME}/verilog/VerilogParse.hh + COMPILE_FLAGS --name-prefix=VerilogParse_ ) -# flex seems to ignore -o -add_custom_command(OUTPUT ${STA_HOME}/parasitics/SpfLex.cc ${STA_HOME}/parasitics/SpfLex.hh - COMMAND ${FLEX_EXECUTABLE} --prefix=SpfLex_ --header-file=${STA_HOME}/parasitics/SpfLex.hh ${STA_HOME}/parasitics/SpfLex.ll - COMMAND mv lex.yy.c ${STA_HOME}/parasitics/SpfLex.cc +flex_target(VerilogLex verilog/VerilogLex.ll ${STA_HOME}/verilog/VerilogLex.cc + DEFINES_FILE ${STA_HOME}/verilog/VerilogLex.hh + COMPILE_FLAGS --prefix=VerilogLex_ + ) + +add_flex_bison_dependency(VerilogLex VerilogParser) + +# Sdf scan/parse. +bison_target(SdfParser sdf/SdfParse.yy ${STA_HOME}/sdf/SdfParse.cc + DEFINES_FILE ${STA_HOME}/sdf/SdfParse.hh + COMPILE_FLAGS --name-prefix=SdfParse_ + ) + +flex_target(SdfLex sdf/SdfLex.ll ${STA_HOME}/sdf/SdfLex.cc + DEFINES_FILE ${STA_HOME}/sdf/SdfLex.hh + COMPILE_FLAGS --prefix=SdfLex_ ) +add_flex_bison_dependency(SdfLex SdfParser) + + ################################################################ include(FindSWIG) @@ -612,6 +609,17 @@ add_custom_command(OUTPUT ${STA_HOME}/app/StaApp_wrap.cc ################################################################ +# TCL files included as part of the executable are shoved into TclInitVar.cc. +# These files are encoded and shipped as part of the executable +# so that they do not have to be installed on the client host. +add_custom_command(OUTPUT ${STA_HOME}/app/TclInitVar.cc + COMMAND etc/TclEncode.tcl app/TclInitVar.cc tcl_inits ${STA_TCL_FILES} + WORKING_DIRECTORY ${STA_HOME} + DEPENDS ${STA_TCL_FILES} etc/TclEncode.tcl + ) + +################################################################ + set(STA_INCLUDE_DIRS app dcalc diff --git a/INSTALL b/INSTALL index 9b778bd6..8e77671a 100644 --- a/INSTALL +++ b/INSTALL @@ -19,6 +19,10 @@ Building from a dist tarfile does not require the GNU Autotools, Bison or Flex to be installed and is the recommended method unless you are a developer. +Builds are also supported with cmake described later in this file. +Autotools based builds are currently supported for compatibility but +are deprecated. + Building from a tarfile ----------------------- @@ -65,6 +69,10 @@ handling. It is available here: https://www.davidkebo.com/source/cudd_versions/cudd-3.0.0.tar.gz https://sourceforge.net/projects/cudd-mirror/ +Note that the file hierarchy of the CUDD installation changed with +version 3.0. This build only supports the 3.0 layout but only small +changes to configure.ac are required to support older versions. + The Zlib library is an optional. If the configure script finds libz, OpenSTA can read Verilog, SDF, SPF, and SPEF files compressed with gzip. @@ -108,6 +116,41 @@ it. ./configure [options...] make +Building with Cmake +------------------- + +Building with cmake requires bison and flex. + + git clone https://xp-dev.com/git/opensta + cd opensta + mkdir build + cd build + cmake ../ + make + +The resulting executable is app/sta for compatibility with configure. +The library without main is app/libSTA.a. + +Optional cmake variables passed as -D= arguments to cmake. + + MAKE_BUILD_TYPE DEBUG|RELEASE + TCL_LIB - path to tcl library + TCL_HEADER - path to tcl.h + TCL_INIT - path to init.tcl + CMAKE_INSTALL_PREFIX + +If TCL_LIB is specified the cmake script will attempt to locate +the header and init files from its path. + +Default install directory is /usr/local. +To install in a different directory with cmake use: + + cmake .. -DCMAKE_INSTALL_PREFIX= + +or use the DESTDIR variable with make. + + make DESTDIR= install + Building on Windoz ------------------ The Win32 API does not natively support the pthreads API. The @@ -148,36 +191,3 @@ form: All commands in one .tcl file (usually run.tcl) for small cases No calls to "exit" No shell scripts to envoke the sta. - ----------------------------------------------------------------- -Building with Cmake - - git clone https://xp-dev.com/git/opensta - cd opensta - mkdir build - cd build - cmake ../ - make - -The resulting executable is app/sta for compatibility with configure. -The library without main is app/libSTA.a. - -Optional cmake variables passed as -D= arguments to cmake. - - MAKE_BUILD_TYPE DEBUG|RELEASE - TCL_LIB - path to tcl library - TCL_HEADER - path to tcl.h - TCL_INIT - path to init.tcl - CMAKE_INSTALL_PREFIX - -If TCL_LIB is specified the cmake script will attempt to locate -the header and init files from its path. - -Default install directory is /usr/local. -To install in a different directory with cmake use: - - cmake .. -DCMAKE_INSTALL_PREFIX= - -or use the DESTDIR variable with make. - - make DESTDIR= install diff --git a/app/StaMain.cc b/app/StaMain.cc index 6f025d5f..17c738c7 100644 --- a/app/StaMain.cc +++ b/app/StaMain.cc @@ -121,6 +121,9 @@ staTclAppInit(Tcl_Interp *interp) int argc = sta_argc; char **argv = sta_argv; + // source init.tcl + Tcl_Init(interp); + // Define swig commands. sta_swig_init(interp); diff --git a/doc/BugLog b/doc/BugLog index 37d62f82..4868b3e1 100644 --- a/doc/BugLog +++ b/doc/BugLog @@ -5,4 +5,5 @@ Release 2.0 Patches 2018/11/08 corners > 2 causes internal error 2018/11/09 Verilog ignore attributes (* blah *) 2018/12/24 all_fanout from input port -2018/12/25 liberty pg_types \ No newline at end of file +2018/12/25 liberty pg_types +2019/01/03 liberty 2D bus names diff --git a/doc/OpenSTA.odt b/doc/OpenSTA.odt index 411c8025..43d96c59 100644 Binary files a/doc/OpenSTA.odt and b/doc/OpenSTA.odt differ diff --git a/doc/OpenSTA.pdf b/doc/OpenSTA.pdf index 98f5b22a..a17ffb94 100644 Binary files a/doc/OpenSTA.pdf and b/doc/OpenSTA.pdf differ diff --git a/liberty/Liberty.cc b/liberty/Liberty.cc index bdde953d..74575ebb 100644 --- a/liberty/Liberty.cc +++ b/liberty/Liberty.cc @@ -778,9 +778,18 @@ LibertyLibrary::addSupplyVoltage(const char *supply_name, } float -LibertyLibrary::supplyVoltage(const char *supply_name) +LibertyLibrary::supplyVoltage(const char *supply_name) const { - return supply_voltage_map_[supply_name]; + float voltage; + bool exists; + supply_voltage_map_.findKey(supply_name, voltage, exists); + return voltage; +} + +bool +LibertyLibrary::supplyExists(const char *supply_name) const +{ + return supply_voltage_map_.hasKey(supply_name); } //////////////////////////////////////////////////////////////// diff --git a/liberty/Liberty.hh b/liberty/Liberty.hh index 0d276c84..baaf07ae 100644 --- a/liberty/Liberty.hh +++ b/liberty/Liberty.hh @@ -268,7 +268,8 @@ public: void addOcvDerate(OcvDerate *derate); void addSupplyVoltage(const char *suppy_name, float voltage); - float supplyVoltage(const char *suppy_name); + bool supplyExists(const char *suppy_name) const; + float supplyVoltage(const char *suppy_name) const; // Make scaled cell. Call LibertyCell::addScaledCell after it is complete. LibertyCell *makeScaledCell(const char *name, diff --git a/liberty/LibertyExprLex.ll b/liberty/LibertyExprLex.ll index f53c4077..8b243313 100644 --- a/liberty/LibertyExprLex.ll +++ b/liberty/LibertyExprLex.ll @@ -43,8 +43,6 @@ libertyExprFlushBuffer() %} /* %option debug */ -%option prefix="LibertyExprLex_" -%option outfile="lex.yy.c" %option noyywrap %option nounput %option never-interactive diff --git a/liberty/LibertyLex.ll b/liberty/LibertyLex.ll index 7c69b249..f55fc70c 100644 --- a/liberty/LibertyLex.ll +++ b/liberty/LibertyLex.ll @@ -44,8 +44,6 @@ libertyParseFlushBuffer() %} /* %option debug */ -%option prefix="LibertyLex_" -%option outfile="lex.yy.c" %option noyywrap %option nounput %option never-interactive @@ -63,6 +61,7 @@ BUS_SUB {BUS_LEFT}{DIGIT}+{BUS_RIGHT} BUS_RANGE {BUS_LEFT}{DIGIT}+:{DIGIT}+{BUS_RIGHT} PIN_NAME ({ALPHA}|_)({ALPHA}|{DIGIT}|_)* BUS_NAME {PIN_NAME}({BUS_SUB}|{BUS_RANGE}) +BUS_NAME2 {PIN_NAME}{BUS_SUB}({BUS_SUB}|{BUS_RANGE}) MIXED_NAME {BUS_NAME}_{PIN_NAME} HNAME ({PIN_NAME}|{BUS_NAME}|{MIXED_NAME})([\/.]({PIN_NAME}|{BUS_NAME}|{MIXED_NAME}))+ /* ocv_table_template(2D_ocv_template) */ @@ -97,6 +96,7 @@ EOL \r?\n {PIN_NAME}{TOKEN_END} | {BUS_NAME}{TOKEN_END} | +{BUS_NAME2}{TOKEN_END} | {MIXED_NAME}{TOKEN_END} | {HNAME}{TOKEN_END} | {BUS_STYLE}{TOKEN_END} | diff --git a/liberty/LibertyParse.yy b/liberty/LibertyParse.yy index da92b141..109035cd 100644 --- a/liberty/LibertyParse.yy +++ b/liberty/LibertyParse.yy @@ -39,7 +39,6 @@ int LibertyParse_error(const char *msg); sta::LibertyStmt *stmt; } - %token FLOAT %token STRING KEYWORD diff --git a/liberty/Makefile.am b/liberty/Makefile.am index 9c7882d2..5c0ff658 100644 --- a/liberty/Makefile.am +++ b/liberty/Makefile.am @@ -64,6 +64,12 @@ libliberty_la_SOURCES = \ LibertyExprLex.ll: LibertyExprParse.hh +LibertyExprLex.cc: LibertyExprLex.ll + $(LEX) $(LFLAGS) -o LibertyExprLex.cc --prefix=LibertyExprLex_ --header-file=LibertyExprLex.hh LibertyExprLex.ll + +LibertyLex.cc: LibertyLex.ll + $(LEX) $(LFLAGS) -o LibertyLex.cc --prefix=LibertyLex_ --header-file=LibertyLex.hh LibertyLex.ll + LibertyLex.ll: LibertyParse.hh # Rules to support automake pre 1.12 that name header .h instead of .hh diff --git a/parasitics/Makefile.am b/parasitics/Makefile.am index 92460c40..c6903c8b 100644 --- a/parasitics/Makefile.am +++ b/parasitics/Makefile.am @@ -51,8 +51,14 @@ libparasitics_la_SOURCES = \ SpfLex.ll: SpfParse.hh +SpfLex.cc: SpfLex.ll + $(LEX) $(LFLAGS) -o SpfLex.cc --prefix=SpfLex_ --header-file=SpfLex.hh SpfLex.ll + SpefLex.ll: SpefParse.hh +SpefLex.cc: SpefLex.ll + $(LEX) $(LFLAGS) -o SpefLex.cc --prefix=SpefLex_ --header-file=SpefLex.hh SpefLex.ll + # Rules to support automake pre 1.12 that name header .h instead of .hh SpfParse.hh: SpfParse.cc if test -f SpfParse.h; then \ diff --git a/parasitics/NullParasitics.cc b/parasitics/NullParasitics.cc index 53bb918d..f3c77061 100644 --- a/parasitics/NullParasitics.cc +++ b/parasitics/NullParasitics.cc @@ -424,6 +424,14 @@ NullParasitics::value(const ParasiticDevice *, return 0.0; } +void +NullParasitics::resistorNodes(const ParasiticDevice *, + // Return values. + ParasiticNode *&, + ParasiticNode *&) const +{ +} + ParasiticNode * NullParasitics::otherNode(const ParasiticDevice *, ParasiticNode *) const diff --git a/parasitics/NullParasitics.hh b/parasitics/NullParasitics.hh index 2a42e708..b1767f61 100644 --- a/parasitics/NullParasitics.hh +++ b/parasitics/NullParasitics.hh @@ -164,6 +164,10 @@ public: virtual const char *name(const ParasiticDevice *device) const; virtual float value(const ParasiticDevice *device, const ParasiticAnalysisPt *ap) const; + virtual void resistorNodes(const ParasiticDevice *device, + // Return values. + ParasiticNode *&node1, + ParasiticNode *&node2) const; virtual ParasiticNode *otherNode(const ParasiticDevice *device, ParasiticNode *node) const; // Reduce parasitic network to reduce_to model. diff --git a/parasitics/SpefLex.ll b/parasitics/SpefLex.ll index 601890a3..bca213fb 100644 --- a/parasitics/SpefLex.ll +++ b/parasitics/SpefLex.ll @@ -43,8 +43,6 @@ spefResetScanner() %} /* %option debug */ -%option prefix="SpefLex_" -%option outfile="lex.yy.c" %option noyywrap %option nounput %option never-interactive diff --git a/parasitics/SpfLex.ll b/parasitics/SpfLex.ll index 845e96d3..ec78ac3c 100644 --- a/parasitics/SpfLex.ll +++ b/parasitics/SpfLex.ll @@ -47,8 +47,6 @@ spfResetScanner() %} /* %option debug */ -%option prefix="SpfLex_" -%option outfile="lex.yy.c" %option noyywrap %option nounput %option never-interactive diff --git a/sdf/Makefile.am b/sdf/Makefile.am index ed3d8a7a..af3a3948 100644 --- a/sdf/Makefile.am +++ b/sdf/Makefile.am @@ -31,6 +31,9 @@ libsdf_la_SOURCES = \ SdfLex.ll: SdfParse.hh +SdfLex.cc: SdfLex.ll + $(LEX) $(LFLAGS) -o SdfLex.cc --prefix=SdfLex_ --header-file=SdfLex.hh SdfLex.ll + # Rules to support automake pre 1.12 that name header .h instead of .hh SdfParse.hh: SdfParse.cc if test -f SdfParse.h; then \ diff --git a/sdf/SdfLex.ll b/sdf/SdfLex.ll index a5ab4faa..a2ab2817 100644 --- a/sdf/SdfLex.ll +++ b/sdf/SdfLex.ll @@ -33,8 +33,6 @@ sdfFlushBuffer() %} /* %option debug */ -%option prefix="SdfLex_" -%option outfile="lex.yy.c" %option noyywrap %option nounput %option never-interactive diff --git a/search/Makefile.am b/search/Makefile.am index 1d35c7ab..8dc33222 100644 --- a/search/Makefile.am +++ b/search/Makefile.am @@ -57,7 +57,7 @@ include_HEADERS = \ VisitPathEnds.hh \ VisitPathGroupVertices.hh \ WorstSlack.hh \ - WriteSpice.hh + WritePathSpice.hh libsearch_la_SOURCES = \ Bfs.cc \ @@ -99,7 +99,7 @@ libsearch_la_SOURCES = \ VisitPathEnds.cc \ VisitPathGroupVertices.cc \ WorstSlack.cc \ - WriteSpice.cc + WritePathSpice.cc libs: $(lib_LTLIBRARIES) diff --git a/search/WritePathSpice.cc b/search/WritePathSpice.cc new file mode 100644 index 00000000..66684400 --- /dev/null +++ b/search/WritePathSpice.cc @@ -0,0 +1,1075 @@ +// OpenSTA, Static Timing Analyzer +// Copyright (c) 2019, 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 +#include +#include +#include +#include "Machine.hh" +#include "Debug.hh" +#include "Error.hh" +#include "Report.hh" +#include "StringUtil.hh" +#include "FuncExpr.hh" +#include "Units.hh" +#include "Liberty.hh" +#include "TimingArc.hh" +#include "Network.hh" +#include "Graph.hh" +#include "Sdc.hh" +#include "DcalcAnalysisPt.hh" +#include "Parasitics.hh" +#include "PathAnalysisPt.hh" +#include "Path.hh" +#include "PathRef.hh" +#include "PathExpanded.hh" +#include "StaState.hh" +#include "WritePathSpice.hh" + +namespace sta { + +using std::string; +using std::ofstream; +using std::ifstream; + +typedef Vector StringVector; +typedef Map CellSpicePortNames; +typedef int Stage; +typedef Map ParasiticNodeMap; + +void +split(const string &text, + const string &delims, + // Return values. + StringVector &tokens); +void +streamPrint(ofstream &stream, + const char *fmt, + ...) __attribute__((format (printf, 2, 3))); +void +stringPrint(string &str, + const char *fmt, + ...) __attribute__((format (printf, 2, 3))); + +//////////////////////////////////////////////////////////////// + +class WritePathSpice : public StaState +{ +public: + WritePathSpice(Path *path, + 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, + const StaState *sta); + ~WritePathSpice(); + void writeSpice();; + +private: + void writeHeader(); + void writeStageInstances(); + void writeInputSource(); + void writeStageSubckts(); + void writeInputStage(Stage stage); + void writeMeasureStmts(); + void writeMeasureStmt(const Pin *pin); + void writeGateStage(Stage stage); + void writeStageVoltageSources(LibertyCell *cell, + StringVector *spice_port_names, + const char *inst_name, + LibertyPort *from_port, + LibertyPort *drvr_port); + void writeStageParasitics(Stage stage); + void writeSubckts(); + void findPathCellnames(// Return values. + StringSet &path_cell_names); + void recordSpicePortNames(const char *cell_name, + StringVector &tokens); + float pgPortVoltage(const char *pg_port_name, + LibertyCell *cell); + float pgPortVoltage(LibertyPgPort *pg_port); + float maxTime(); + const char *nodeName(ParasiticNode *node); + void initNodeMap(const char *net_name); + const char *spiceTrans(const TransRiseFall *tr); + void writeMeasureDelayStmt(Stage stage, + Path *input_path); + void writeMeasureSlewStmt(Stage stage, + Path *path); + + // Stage "accessors". + // Internally a stage index from stageFirst() to stageLast() + // is turned into an index into path_expanded_. + // A path from an input port has no GateInputPath. + Stage stageFirst(); + Stage stageLast(); + string stageName(Stage stage); + int stageGateInputPathIndex(Stage stage); + int stageDrvrPathIndex(Stage stage); + int stageLoadPathIndex(Stage stage); + PathRef *stageGateInputPath(Stage stage); + PathRef *stageDrvrPath(Stage stage); + PathRef *stageLoadPath(Stage stage); + TimingArc *stageGateArc(Stage stage); + TimingArc *stageWireArc(Stage stage); + Edge *stageGateEdge(Stage stage); + Edge *stageWireEdge(Stage stage); + Pin *stageInputPin(Stage stage); + Pin *stageDrvrPin(Stage stage); + Pin *stageLoadPin(Stage stage); + const char *stageGateInputPinName(Stage stage); + const char *stageDrvrPinName(Stage stage); + const char *stageLoadPinName(Stage stage); + + Path *path_; + 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_; + + 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_; + + // Resistance to use to simulate a short circuit between spice nodes. + static const float short_ckt_resistance_; +}; + +//////////////////////////////////////////////////////////////// + +class SubcktEndsMissing : public StaException +{ +public: + SubcktEndsMissing(const char *cell_name, + const char *subckt_filename);; + const char *what() const throw(); + +protected: + string what_; +}; + +SubcktEndsMissing::SubcktEndsMissing(const char *cell_name, + const char *subckt_filename) +{ + what_ = "Error: spice subckt for cell "; + what_ += cell_name; + what_ += " missing .ends in "; + what_ += subckt_filename; +} + +const char * +SubcktEndsMissing::what() const throw() +{ + 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, + const char *power_name, + const char *gnd_name, + StaState *sta) +{ + WritePathSpice writer(path, spice_filename, subckt_filename, + lib_subckt_filename, model_filename, + power_name, gnd_name, sta); + writer.writeSpice(); +} + +const float WritePathSpice::short_ckt_resistance_ = .0001; + +WritePathSpice::WritePathSpice(Path *path, + 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, + const StaState *sta) : + StaState(sta), + path_(path), + 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), + path_expanded_(sta), + net_name_(NULL) +{ + auto lib = network_->defaultLibertyLibrary(); + power_voltage_ = lib->supplyVoltage(power_name_); + gnd_voltage_ = lib->supplyVoltage(gnd_name_); +} + +WritePathSpice::~WritePathSpice() +{ + cell_spice_port_names_.deleteContents(); +} + +void +WritePathSpice::writeSpice() +{ + spice_stream_.open(spice_filename_); + if (spice_stream_.is_open()) { + path_expanded_.expand(path_, true); + // Find subckt port names as a side-effect of writeSubckts. + writeSubckts(); + writeHeader(); + writeStageInstances(); + writeMeasureStmts(); + writeInputSource(); + writeStageSubckts(); + streamPrint(spice_stream_, ".end\n"); + spice_stream_.close(); + } + else + throw FileNotWritable(spice_filename_); +} + +void +WritePathSpice::writeHeader() +{ + const MinMax *min_max = path_->minMax(this); + const Pvt *pvt = sdc_->operatingConditions(min_max); + if (pvt == NULL) + pvt = network_->defaultLibertyLibrary()->defaultOperatingConditions(); + float temp = pvt->temperature(); + streamPrint(spice_stream_, ".temp %.1f\n", temp); + streamPrint(spice_stream_, ".include \"%s\"\n", model_filename_); + streamPrint(spice_stream_, ".include \"%s\"\n", subckt_filename_); + + float max_time = maxTime(); + float time_step = max_time / 1e+3; + streamPrint(spice_stream_, ".tran %.3g %.3g\n\n", + time_step, max_time); +} + +float +WritePathSpice::maxTime() +{ + Stage input_stage = stageFirst(); + Path *input_path = stageDrvrPath(input_stage); + auto input_slew = input_path->slew(this); + auto end_slew = path_->slew(this); + auto max_time = (input_slew + path_->arrival(this) + end_slew * 2) * 1.5; + return max_time; +} + +void +WritePathSpice::writeStageInstances() +{ + streamPrint(spice_stream_, "*****************\n"); + streamPrint(spice_stream_, "* Stage instances\n"); + streamPrint(spice_stream_, "*****************\n\n"); + + for (Stage stage = stageFirst(); stage <= stageLast(); stage++) { + const char *stage_name = stageName(stage).c_str(); + if (stage == stageFirst()) + streamPrint(spice_stream_, "x%s %s %s %s\n", + stage_name, + stageDrvrPinName(stage), + stageLoadPinName(stage), + stage_name); + else + streamPrint(spice_stream_, "x%s %s %s %s %s\n", + stage_name, + stageGateInputPinName(stage), + stageDrvrPinName(stage), + stageLoadPinName(stage), + stage_name); + } + streamPrint(spice_stream_, "\n"); +} + +float +WritePathSpice::pgPortVoltage(const char *pg_port_name, + LibertyCell *cell) +{ + auto pg_port = cell->findPgPort(pg_port_name); + return pgPortVoltage(pg_port); +} + +float +WritePathSpice::pgPortVoltage(LibertyPgPort *pg_port) +{ + auto cell = pg_port->cell(); + auto voltage_name = pg_port->voltageName(); + auto lib = cell->libertyLibrary(); + float voltage = lib->supplyVoltage(voltage_name); + return voltage; +} + +void +WritePathSpice::writeInputSource() +{ + streamPrint(spice_stream_, "**************\n"); + streamPrint(spice_stream_, "* Input source\n"); + streamPrint(spice_stream_, "**************\n\n"); + + Stage input_stage = stageFirst(); + streamPrint(spice_stream_, "v1 %s 0 pwl(\n", + stageDrvrPinName(input_stage)); + auto wire_arc = stageWireArc(input_stage); + float volt0, volt1; + if (wire_arc->fromTrans()->asRiseFall() == TransRiseFall::rise()) { + volt0 = gnd_voltage_; + volt1 = power_voltage_; + } + else { + volt0 = power_voltage_; + volt1 = gnd_voltage_; + } + Path *input_path = stageDrvrPath(input_stage); + auto input_slew = input_path->slew(this); + if (input_slew == 0.0) + input_slew = maxTime() / 1e+3; + // Arbitrary offset. + float time0 = input_slew; + float time1 = time0 + input_slew; + streamPrint(spice_stream_, "+%.3e %.3e\n", 0.0, volt0); + streamPrint(spice_stream_, "+%.3e %.3e\n", time0, volt0); + streamPrint(spice_stream_, "+%.3e %.3e\n", time1, volt1); + streamPrint(spice_stream_, "+%.3e %.3e\n", maxTime(), volt1); + streamPrint(spice_stream_, "+)\n\n"); +} + +void +WritePathSpice::writeMeasureStmts() +{ + streamPrint(spice_stream_, "********************\n"); + streamPrint(spice_stream_, "* Measure statements\n"); + streamPrint(spice_stream_, "********************\n\n"); + + for (Stage stage = stageFirst(); stage <= stageLast(); stage++) { + auto input_path = (stage == stageFirst()) + ? stageDrvrPath(stage) + : stageGateInputPath(stage); + writeMeasureDelayStmt(stage, input_path); + writeMeasureSlewStmt(stage, input_path); + if (stage == stageLast()) + writeMeasureSlewStmt(stage, stageLoadPath(stage)); + } + streamPrint(spice_stream_, "\n"); +} + +void +WritePathSpice::writeMeasureDelayStmt(Stage stage, + Path *input_path) +{ + auto lib = network_->defaultLibertyLibrary(); + auto input_pin_name = network_->pathName(input_path->pin(this)); + auto input_tr = input_path->transition(this); + auto input_threshold = power_voltage_ * lib->inputThreshold(input_tr); + + auto load_path = stageLoadPath(stage); + auto load_pin_name = stageLoadPinName(stage); + auto load_tr = load_path->transition(this); + auto load_threshold = power_voltage_ * lib->inputThreshold(load_tr); + streamPrint(spice_stream_, + ".measure tran %s_%s_delay_%s trig v(%s) val=%.3f %s=last targ v(%s) val=%.3f %s=last\n", + stageName(stage).c_str(), + input_pin_name, + load_pin_name, + input_pin_name, + input_threshold, + spiceTrans(input_tr), + load_pin_name, + load_threshold, + spiceTrans(load_tr)); +} + +void +WritePathSpice::writeMeasureSlewStmt(Stage stage, + Path *path) +{ + auto lib = network_->defaultLibertyLibrary(); + auto pin_name = network_->pathName(path->pin(this)); + auto tr = path->transition(this); + auto spice_tr = spiceTrans(tr); + auto lower = power_voltage_ * lib->slewLowerThreshold(tr); + auto upper = power_voltage_ * lib->slewUpperThreshold(tr); + float threshold1, threshold2; + if (tr == TransRiseFall::rise()) { + threshold1 = lower; + threshold2 = upper; + } + else { + threshold1 = upper; + threshold2 = lower; + } + streamPrint(spice_stream_, + ".measure tran %s_%s_slew trig v(%s) val=%.3f %s=last targ v(%s) val=%.3f %s=last\n", + stageName(stage).c_str(), + pin_name, + pin_name, + threshold1, + spice_tr, + pin_name, + threshold2, + spice_tr); +} + +const char * +WritePathSpice::spiceTrans(const TransRiseFall *tr) +{ + if (tr == TransRiseFall::rise()) + return "RISE"; + else + return "FALL"; +} + +void +WritePathSpice::writeStageSubckts() +{ + streamPrint(spice_stream_, "***************\n"); + streamPrint(spice_stream_, "* Stage subckts\n"); + streamPrint(spice_stream_, "***************\n\n"); + + for (Stage stage = stageFirst(); stage <= stageLast(); stage++) { + if (stage == stageFirst()) + writeInputStage(stage); + else + writeGateStage(stage); + } +} + +// Input port to first gate input. +void +WritePathSpice::writeInputStage(Stage stage) +{ + // Input arc. + // External driver not handled. + auto drvr_pin_name = stageDrvrPinName(stage); + auto load_pin_name = stageLoadPinName(stage); + streamPrint(spice_stream_, ".subckt %s %s %s\n", + stageName(stage).c_str(), + drvr_pin_name, + load_pin_name); + writeStageParasitics(stage); + streamPrint(spice_stream_, ".ends\n\n"); +} + +// Gate and load parasitics. +void +WritePathSpice::writeGateStage(Stage stage) +{ + auto input_pin = stageInputPin(stage); + auto input_pin_name = stageGateInputPinName(stage); + auto drvr_pin = stageDrvrPin(stage); + auto drvr_pin_name = stageDrvrPinName(stage); + auto load_pin_name = stageLoadPinName(stage); + streamPrint(spice_stream_, ".subckt stage%d %s %s %s\n", + stage, + input_pin_name, + drvr_pin_name, + load_pin_name); + Instance *inst = network_->instance(input_pin); + const char *inst_name = network_->pathName(inst); + LibertyCell *cell = network_->libertyCell(inst); + const char *cell_name = cell->name(); + auto spice_port_names = cell_spice_port_names_[cell_name]; + + // Instance subckt call. + streamPrint(spice_stream_, "x%s", inst_name); + StringVector::Iterator port_iter(spice_port_names); + while (port_iter.hasNext()) { + const char *subckt_port_name = port_iter.next().c_str(); + auto pin = network_->findPin(inst, subckt_port_name); + auto pg_port = cell->findPgPort(subckt_port_name); + 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_name); + } + streamPrint(spice_stream_, " %s\n", cell_name); + + writeStageVoltageSources(cell, spice_port_names, + inst_name, + network_->libertyPort(input_pin), + network_->libertyPort(drvr_pin)); + writeStageParasitics(stage); + streamPrint(spice_stream_, ".ends\n\n"); +} + +typedef Map LibertyPortLogicValues; + +// Find the logic values for expression inputs to enable paths from_port. +void +sensitizationValues(FuncExpr *expr, + LibertyPort *from_port, + // Return values. + LibertyPortLogicValues &port_values) +{ + switch (expr->op()) { + case FuncExpr::op_port: { + break; + } + case FuncExpr::op_not: { + sensitizationValues(expr->left(), from_port, port_values); + break; + } + case FuncExpr::op_or: { + FuncExpr *left = expr->left(); + FuncExpr *right = expr->right(); + if (left->port() == from_port + && right->op() == FuncExpr::op_port) + port_values[right->port()] = logic_zero; + else if (right->port() == from_port + && left->op() == FuncExpr::op_port) + port_values[left->port()] = logic_zero; + break; + } + case FuncExpr::op_and: { + FuncExpr *left = expr->left(); + FuncExpr *right = expr->right(); + if (left->port() == from_port + && right->op() == FuncExpr::op_port) + port_values[right->port()] = logic_one; + else if (right->port() == from_port + && left->op() == FuncExpr::op_port) + port_values[left->port()] = logic_one; + break; + } + case FuncExpr::op_xor: { + // Need to know timing arc sense to get this right. + FuncExpr *left = expr->left(); + FuncExpr *right = expr->right(); + if (left->port() == from_port + && right->op() == FuncExpr::op_port) + port_values[right->port()] = logic_zero; + else if (right->port() == from_port + && left->op() == FuncExpr::op_port) + port_values[left->port()] = logic_zero; + break; + } + case FuncExpr::op_one: + case FuncExpr::op_zero: + break; + } +} + +// Power/ground and input voltage sources. +void +WritePathSpice::writeStageVoltageSources(LibertyCell *cell, + StringVector *spice_port_names, + const char *inst_name, + LibertyPort *from_port, + LibertyPort *drvr_port) +{ + auto from_port_name = from_port->name(); + auto drvr_port_name = drvr_port->name(); + LibertyLibrary *lib = cell->libertyLibrary(); + LibertyPortLogicValues port_values; + sensitizationValues(drvr_port->function(), from_port, port_values); + int volt_source = 1; + debugPrint1(debug_, "write_spice", 2, "subckt %s\n", cell->name()); + StringVector::Iterator port_iter(spice_port_names); + while (port_iter.hasNext()) { + auto subckt_port_name = port_iter.next().c_str(); + auto pg_port = cell->findPgPort(subckt_port_name); + debugPrint2(debug_, "write_spice", 2, " port %s%s\n", + subckt_port_name, + pg_port ? " pwr/gnd" : ""); + if (pg_port) { + auto voltage = pgPortVoltage(pg_port); + streamPrint(spice_stream_, "v%d %s/%s 0 %.3f\n", + volt_source, + inst_name, subckt_port_name, + voltage); + volt_source++; + } else if (!(stringEq(subckt_port_name, from_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) { + const char *pg_port_name = NULL; + bool port_has_value; + LogicValue port_value; + port_values.findKey(port, port_value, port_has_value); + if (port_has_value) { + switch (port_value) { + case logic_zero: + pg_port_name = port->relatedGroundPin(); + break; + case logic_one: + pg_port_name = port->relatedPowerPin(); + break; + default: + break; + } + if (pg_port_name) { + auto pg_port = cell->findPgPort(pg_port_name); + if (pg_port) { + auto voltage_name = pg_port->voltageName(); + if (voltage_name) { + float voltage = lib->supplyVoltage(voltage_name); + streamPrint(spice_stream_, "v%d %s/%s 0 %.3f\n", + volt_source, + inst_name, subckt_port_name, + voltage); + volt_source++; + } + else + report_->error("port %s %s voltage %s not found,\n", + subckt_port_name, + pg_port_name, + voltage_name); + } + else + report_->error("port %s %s not found,\n", + subckt_port_name, + pg_port_name); + } + } + } + } + } +} + +class ParasiticNodeNameLess +{ +public: + ParasiticNodeNameLess(Parasitics *parasitics); + bool operator()(const ParasiticNode *node1, + const ParasiticNode *node2) const; + +private: + Parasitics *parasitics_; +}; + +ParasiticNodeNameLess::ParasiticNodeNameLess(Parasitics *parasitics) : + parasitics_(parasitics) +{ +} + +bool +ParasiticNodeNameLess::operator()(const ParasiticNode *node1, + const ParasiticNode *node2) const +{ + return stringLess(parasitics_->name(node1), + parasitics_->name(node2)); +} + +typedef Set ParasiticDeviceSet; +// Use node names rather than pointers for less for stable results. +typedef Set ParasiticNodeSet; + +void +findParasiticDevicesNodes(ParasiticNode *node, + Parasitics *parasitics, + // Return values. + ParasiticNodeSet &nodes, + ParasiticDeviceSet &devices) +{ + nodes.insert(node); + auto device_iter = parasitics->deviceIterator(node); + while (device_iter->hasNext()) { + auto device = device_iter->next(); + if (!devices.hasKey(device)) { + devices.insert(device); + auto other_node = parasitics->otherNode(device, node); + findParasiticDevicesNodes(other_node, parasitics, nodes, devices); + } + } + delete device_iter; +} + +void +WritePathSpice::writeStageParasitics(Stage stage) +{ + auto drvr_path = stageDrvrPath(stage); + auto drvr_pin = stageDrvrPin(stage); + auto load_pin = stageLoadPin(stage); + auto dcalc_ap = drvr_path->dcalcAnalysisPt(this); + auto parasitic_ap = dcalc_ap->parasiticAnalysisPt(); + auto parasitic = parasitics_->findParasiticNetwork(drvr_pin, parasitic_ap); + int resistor_index = 1; + int cap_index = 1; + if (parasitic) { + Net *net = network_->net(drvr_pin); + auto net_name = + net ? network_->pathName(net) : network_->pathName(drvr_pin); + initNodeMap(net_name); + streamPrint(spice_stream_, "* Net %s\n", net_name); + auto node = parasitics_->findNode(parasitic, drvr_pin); + ParasiticNodeNameLess name_less(parasitics_); + ParasiticNodeSet nodes(name_less); + ParasiticDeviceSet devices; + findParasiticDevicesNodes(node, parasitics_, nodes, devices); + ParasiticDeviceSet::Iterator device_iter(devices); + while (device_iter.hasNext()) { + auto device = device_iter.next(); + auto resistance = parasitics_->value(device, parasitic_ap); + if (parasitics_->isResistor(device)) { + ParasiticNode *node1, *node2; + parasitics_->resistorNodes(device, node1, node2); + streamPrint(spice_stream_, "R%d %s %s %.3e\n", + resistor_index, + nodeName(node1), + nodeName(node2), + resistance); + resistor_index++; + } + else if (parasitics_->isCouplingCap(device)) { + } + } + ParasiticNodeSet::Iterator node_iter(nodes); + while (node_iter.hasNext()) { + auto node = node_iter.next(); + auto cap = parasitics_->nodeGndCap(node, parasitic_ap); + streamPrint(spice_stream_, "C%d %s 0 %.3e\n", + cap_index, + nodeName(node), + cap); + cap_index++; + } + } + else + streamPrint(spice_stream_, "R1 %s %s %.3e\n", + network_->pathName(drvr_pin), + network_->pathName(load_pin), + short_ckt_resistance_); +} + +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) +{ + auto pin = parasitics_->connectionPin(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(strlen(net_name_) + 10, "%s/%d", + net_name_, node_index); + } +} + +//////////////////////////////////////////////////////////////// + +// Copy the subckt definition from lib_subckt_filename for +// each cell in path to path_subckt_filename. +void +WritePathSpice::writeSubckts() +{ + StringSet path_cell_names; + findPathCellnames(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.hasKey(cell_name)) { + subckts_stream << line << "\n"; + bool found_ends = false; + while (getline(lib_subckts_stream, line)) { + subckts_stream << line << "\n"; + if (stringEqual(line.c_str(), ".ends")) { + subckts_stream << "\n"; + found_ends = true; + break; + } + } + if (!found_ends) + throw SubcktEndsMissing(cell_name, lib_subckt_filename_); + path_cell_names.eraseKey(cell_name); + } + recordSpicePortNames(cell_name, tokens); + } + } + subckts_stream.close(); + lib_subckts_stream.close(); + + if (!path_cell_names.empty()) { + StringSet::Iterator cell_iter(path_cell_names); + report_->error("The following subkcts are missing from %s\n", + lib_subckt_filename_); + while (cell_iter.hasNext()) { + const char *cell_name = cell_iter.next(); + report_->printError(" %s\n", cell_name); + } + } + } + else { + lib_subckts_stream.close(); + throw FileNotWritable(subckt_filename_); + } + } + else + throw FileNotReadable(lib_subckt_filename_); +} + +void +WritePathSpice::findPathCellnames(// Return values. + StringSet &path_cell_names) +{ + for (Stage stage = stageFirst(); stage <= stageLast(); stage++) { + auto arc = stageGateArc(stage); + if (arc) { + LibertyCell *cell = arc->set()->libertyCell(); + if (cell) { + debugPrint1(debug_, "write_spice", 2, "cell %s\n", cell->name()); + path_cell_names.insert(cell->name()); + } + } + } +} + +void +WritePathSpice::recordSpicePortNames(const char *cell_name, + StringVector &tokens) +{ + auto cell = network_->findLibertyCell(cell_name); + auto spice_port_names = new StringVector; + for (int i = 2; i < tokens.size(); i++) { + const char *port_name = tokens[i].c_str(); + auto port = cell->findLibertyPort(port_name); + auto pg_port = cell->findPgPort(port_name); + if (port == NULL && pg_port == NULL) + report_->error("subckt %s port %s has no corresponding liberty port or pg_port.\n", + cell_name, port_name); + spice_port_names->push_back(port_name); + } + cell_spice_port_names_[cell_name] = spice_port_names; +} + +//////////////////////////////////////////////////////////////// + +Stage +WritePathSpice::stageFirst() +{ + return 1; +} + +Stage +WritePathSpice::stageLast() +{ + return (path_expanded_.size() + 1) / 2; +} + +string +WritePathSpice::stageName(Stage stage) +{ + string name; + stringPrint(name, "stage%d", stage); + return name; +} + +int +WritePathSpice::stageGateInputPathIndex(Stage stage) +{ + return stage * 2 - 3; +} + +int +WritePathSpice::stageDrvrPathIndex(Stage stage) +{ + return stage * 2 - 2; +} + +int +WritePathSpice::stageLoadPathIndex(Stage stage) +{ + return stage * 2 - 1; +} + +PathRef * +WritePathSpice::stageGateInputPath(Stage stage) +{ + int path_index = stageGateInputPathIndex(stage); + return path_expanded_.path(path_index); +} + +PathRef * +WritePathSpice::stageDrvrPath(Stage stage) +{ + int path_index = stageDrvrPathIndex(stage); + return path_expanded_.path(path_index); +} + +PathRef * +WritePathSpice::stageLoadPath(Stage stage) +{ + int path_index = stageLoadPathIndex(stage); + return path_expanded_.path(path_index); +} + +TimingArc * +WritePathSpice::stageGateArc(Stage stage) +{ + int path_index = stageDrvrPathIndex(stage); + if (path_index >= 0) + return path_expanded_.prevArc(path_index); + else + return NULL; +} + +TimingArc * +WritePathSpice::stageWireArc(Stage stage) +{ + int path_index = stageLoadPathIndex(stage); + return path_expanded_.prevArc(path_index); +} + +Edge * +WritePathSpice::stageGateEdge(Stage stage) +{ + PathRef *path = stageGateInputPath(stage); + TimingArc *arc = stageGateArc(stage); + return path->prevEdge(arc, this); +} + +Edge * +WritePathSpice::stageWireEdge(Stage stage) +{ + PathRef *path = stageLoadPath(stage); + TimingArc *arc = stageWireArc(stage); + return path->prevEdge(arc, this); +} + +Pin * +WritePathSpice::stageInputPin(Stage stage) +{ + PathRef *path = stageGateInputPath(stage); + return path->pin(this); +} + +Pin * +WritePathSpice::stageDrvrPin(Stage stage) +{ + PathRef *path = stageDrvrPath(stage); + return path->pin(this); +} + +Pin * +WritePathSpice::stageLoadPin(Stage stage) +{ + PathRef *path = stageLoadPath(stage); + return path->pin(this); +} + +const char * +WritePathSpice::stageGateInputPinName(Stage stage) +{ + const Pin *pin = stageInputPin(stage); + return network_->pathName(pin); +} + +const char * +WritePathSpice::stageDrvrPinName(Stage stage) +{ + Pin *pin = stageDrvrPin(stage); + return network_->pathName(pin); +} + +const char * +WritePathSpice::stageLoadPinName(Stage stage) +{ + const Pin *pin = stageLoadPin(stage); + return network_->pathName(pin); +} + +//////////////////////////////////////////////////////////////// + +void +split(const string &text, + const string &delims, + // Return values. + StringVector &tokens) +{ + auto start = text.find_first_not_of(delims); + auto end = text.find_first_of(delims, start); + while (end != string::npos) { + tokens.push_back(text.substr(start, end - start)); + start = text.find_first_not_of(delims, end); + end = text.find_first_of(delims, start); + } + if (start != string::npos) + tokens.push_back(text.substr(start)); +} + +// 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; + vasprintf(&result, fmt, args); + stream << result; + free(result); + va_end(args); +} + +// print for c++ strings. +void +stringPrint(string &str, + const char *fmt, + ...) +{ + va_list args; + va_start(args, fmt); + char *result; + vasprintf(&result, fmt, args); + str = result; + free(result); + va_end(args); +} + +} // namespace diff --git a/search/WritePathSpice.hh b/search/WritePathSpice.hh new file mode 100644 index 00000000..e49cdcbd --- /dev/null +++ b/search/WritePathSpice.hh @@ -0,0 +1,39 @@ +// OpenSTA, Static Timing Analyzer +// Copyright (c) 2019, Parallax Software, Inc. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +#ifndef STA_WRITE_PATH_SPICE_H +#define STA_WRITE_PATH_SPICE_H + +namespace sta { + +// Write a spice deck for path. +// Throws FileNotReadable, FileNotWritable, SubcktEndsMissing +void +writePathSpice(Path *path, + // Spice file written for path. + const char *spice_filename, + // Subckts used by path included in spice file. + const char *subckt_filename, + // File of all cell spice subckt definitions. + const char *lib_subckt_filename, + // Device model file included in spice file. + const char *model_filename, + const char *power_name, + const char *gnd_name, + StaState *sta); + +} // namespace +#endif diff --git a/tcl/Cmds.tcl b/tcl/Cmds.tcl index b4d738f1..bfa885e3 100644 --- a/tcl/Cmds.tcl +++ b/tcl/Cmds.tcl @@ -1860,5 +1860,90 @@ proc object_name_cmp { obj1 obj2 } { return [string compare [$obj1 object_name] [$obj2 object_name]] } +################################################################ + +define_cmd_args "write_path_spice" { -path_args path_args\ + -spice_file spice_file\ + -subckt_file subckt_file\ + -lib_subckt_file lib_subckts_file\ + -model_file model_file\ + -power power\ + -ground ground} + +proc write_path_spice { args } { + parse_key_args "write_spice" args \ + keys {-spice_file -subckt_file -lib_subckt_file \ + -model_file -power -ground -path_args} \ + flags {} + + if { [info exists keys(-spice_file)] } { + set spice_file $keys(-spice_file) + if { [file exists $spice_file] && ![file writable $spice_file] } { + sta_error "-spice_file $spice_file is not writable.\n" + } + } else { + sta_error "No -spice_file specified.\n" + } + + if { [info exists keys(-subckt_file)] } { + set subckt_file $keys(-subckt_file) + if { [file exists $subckt_file] && ![file writable $subckt_file] } { + sta_error "-subckt_file $subckt_file is not writable.\n" + } + } else { + sta_error "No -subckt_file specified.\n" + } + + if { [info exists keys(-lib_subckt_file)] } { + set lib_subckt_file $keys(-lib_subckt_file) + if { ![file readable $lib_subckt_file] } { + sta_error "-lib_subckt_file $lib_subckt_file is not readable.\n" + } + } else { + sta_error "No -lib_subckt_file specified.\n" + } + + if { [info exists keys(-lib_subckt_file)] } { + set model_file $keys(-model_file) + if { ![file readable $model_file] } { + sta_error "-model_file $model_file is not readable.\n" + } + } else { + sta_error "No -model_file specified.\n" + } + + if { [info exists keys(-power)] } { + set power $keys(-power) + if { ![liberty_supply_exists $power] } { + sta_error "liberty $power not found.\n" + } + } else { + sta_error "No -power specified.\n" + } + + if { [info exists keys(-ground)] } { + set ground $keys(-ground) + if { ![liberty_supply_exists $ground] } { + sta_error "liberty $ground not found.\n" + } + } else { + sta_error "No -ground specified.\n" + } + + if { ![info exists keys(-path_args)] } { + sta_error "No -path_args specified.\n" + } + set path_args $keys(-path_args) + set path_ends [eval [concat get_timing_paths $path_args]] + if { $path_ends == {} } { + sta_error "No paths found for -path_args $path_args.\n" + } + set path_end [lindex $path_ends 0] + set path [$path_end path] + + write_path_spice_cmd $path $spice_file $subckt_file \ + $lib_subckt_file $model_file $power $ground +} + # sta namespace end. } diff --git a/tcl/StaTcl.i b/tcl/StaTcl.i index 2d01b484..f9a9b43f 100644 --- a/tcl/StaTcl.i +++ b/tcl/StaTcl.i @@ -78,7 +78,7 @@ #include "ReportPath.hh" #include "Power.hh" #include "Property.hh" -#include "WriteSpice.hh" +#include "WritePathSpice.hh" #include "Sta.hh" namespace sta { @@ -4587,16 +4587,26 @@ write_sdc_cmd(const char *filename, } void -write_spice(PathRef *path, - const char *spice_filename, - const char *subckts_filename, - const char *lib_subckts_filename, - const char *models_filename) +write_path_spice_cmd(PathRef *path, + 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) { - cmdLinkedNetwork(); Sta *sta = Sta::sta(); - writeSpice(path, spice_filename, subckts_filename, - lib_subckts_filename, models_filename, sta); + writePathSpice(path, spice_filename, subckt_filename, + lib_subckt_filename, model_filename, + power_name, gnd_name, sta); +} + +bool +liberty_supply_exists(const char *supply_name) +{ + auto network = Sta::sta()->network(); + auto lib = network->defaultLibertyLibrary(); + return lib->supplyExists(supply_name); } void diff --git a/tcl/Util.tcl b/tcl/Util.tcl index 2dedc0d5..699a5e36 100644 --- a/tcl/Util.tcl +++ b/tcl/Util.tcl @@ -394,6 +394,7 @@ proc check_percent { cmd_arg arg } { # This rename provices a mechanism to refer to the original TCL # command. rename source builtin_source +rename unknown builtin_unknown # Numeric expressions eval to themselves so braces aren't required # around bus names like foo[2] or foo[*]. @@ -433,178 +434,4 @@ proc sta_unknown { args } { } } -# Copied from init.tcl -proc builtin_unknown args { - variable ::tcl::UnknownPending - global auto_noexec auto_noload env tcl_interactive - - - if {[info exists ::errorInfo]} { - set savedErrorInfo $::errorInfo - } - if {[info exists ::errorCode]} { - set savedErrorCode $::errorCode - } - - set name [lindex $args 0] - if {![info exists auto_noload]} { - # - # Make sure we're not trying to load the same proc twice. - # - if {[info exists UnknownPending($name)]} { - return -code error "self-referential recursion\ - in \"unknown\" for command \"$name\"" - } - set UnknownPending($name) pending - set ret [catch { - auto_load $name [uplevel 1 {::namespace current}] - } msg opts] - unset UnknownPending($name) - if {$ret != 0} { - dict append opts -errorinfo "\n (autoloading \"$name\")" - return -options $opts $msg - } - if {![array size UnknownPending]} { - unset UnknownPending - } - if {$msg} { - if {[info exists savedErrorCode]} { - set ::errorCode $savedErrorCode - } else { - unset -nocomplain ::errorCode - } - if {[info exists savedErrorInfo]} { - set ::errorInfo $savedErrorInfo - } else { - unset -nocomplain ::errorInfo - } - set code [catch {uplevel 1 $args} msg opts] - if {$code == 1} { - # - # Compute stack trace contribution from the [uplevel]. - # Note the dependence on how Tcl_AddErrorInfo, etc. - # construct the stack trace. - # - set errorInfo [dict get $opts -errorinfo] - set errorCode [dict get $opts -errorcode] - set cinfo $args - if {[string bytelength $cinfo] > 150} { - set cinfo [string range $cinfo 0 150] - while {[string bytelength $cinfo] > 150} { - set cinfo [string range $cinfo 0 end-1] - } - append cinfo ... - } - append cinfo "\"\n (\"uplevel\" body line 1)" - append cinfo "\n invoked from within" - append cinfo "\n\"uplevel 1 \$args\"" - # - # Try each possible form of the stack trace - # and trim the extra contribution from the matching case - # - set expect "$msg\n while executing\n\"$cinfo" - if {$errorInfo eq $expect} { - # - # The stack has only the eval from the expanded command - # Do not generate any stack trace here. - # - dict unset opts -errorinfo - dict incr opts -level - return -options $opts $msg - } - # - # Stack trace is nested, trim off just the contribution - # from the extra "eval" of $args due to the "catch" above. - # - set expect "\n invoked from within\n\"$cinfo" - set exlen [string length $expect] - set eilen [string length $errorInfo] - set i [expr {$eilen - $exlen - 1}] - set einfo [string range $errorInfo 0 $i] - # - # For now verify that $errorInfo consists of what we are about - # to return plus what we expected to trim off. - # - if {$errorInfo ne "$einfo$expect"} { - error "Tcl bug: unexpected stack trace in \"unknown\"" {} \ - [list CORE UNKNOWN BADTRACE $einfo $expect $errorInfo] - } - return -code error -errorcode $errorCode \ - -errorinfo $einfo $msg - } else { - dict incr opts -level - return -options $opts $msg - } - } - } - - if {([info level] == 1) && ([info script] eq "") \ - && [info exists tcl_interactive] && $tcl_interactive} { - if {![info exists auto_noexec]} { - set new [auto_execok $name] - if {$new ne ""} { - set redir "" - if {[namespace which -command console] eq ""} { - set redir ">&@stdout <@stdin" - } - uplevel 1 [list ::catch \ - [concat exec $redir $new [lrange $args 1 end]] \ - ::tcl::UnknownResult ::tcl::UnknownOptions] - dict incr ::tcl::UnknownOptions -level - return -options $::tcl::UnknownOptions $::tcl::UnknownResult - } - } - if {$name eq "!!"} { - set newcmd [history event] - } elseif {[regexp {^!(.+)$} $name -> event]} { - set newcmd [history event $event] - } elseif {[regexp {^\^([^^]*)\^([^^]*)\^?$} $name -> old new]} { - set newcmd [history event -1] - catch {regsub -all -- $old $newcmd $new newcmd} - } - if {[info exists newcmd]} { - tclLog $newcmd - history change $newcmd 0 - uplevel 1 [list ::catch $newcmd \ - ::tcl::UnknownResult ::tcl::UnknownOptions] - dict incr ::tcl::UnknownOptions -level - return -options $::tcl::UnknownOptions $::tcl::UnknownResult - } - - set ret [catch {set candidates [info commands $name*]} msg] - if {$name eq "::"} { - set name "" - } - if {$ret != 0} { - dict append opts -errorinfo \ - "\n (expanding command prefix \"$name\" in unknown)" - return -options $opts $msg - } - # Filter out bogus matches when $name contained - # a glob-special char [Bug 946952] - if {$name eq ""} { - # Handle empty $name separately due to strangeness - # in [string first] (See RFE 1243354) - set cmds $candidates - } else { - set cmds [list] - foreach x $candidates { - if {[string first $name $x] == 0} { - lappend cmds $x - } - } - } - if {[llength $cmds] == 1} { - uplevel 1 [list ::catch [lreplace $args 0 0 [lindex $cmds 0]] \ - ::tcl::UnknownResult ::tcl::UnknownOptions] - dict incr ::tcl::UnknownOptions -level - return -options $::tcl::UnknownOptions $::tcl::UnknownResult - } - if {[llength $cmds]} { - return -code error "ambiguous command name \"$name\": [lsort $cmds]" - } - } - return -code error "invalid command name \"$name\"" -} - namespace unknown sta_unknown diff --git a/verilog/Makefile.am b/verilog/Makefile.am index 8948848a..0c028045 100644 --- a/verilog/Makefile.am +++ b/verilog/Makefile.am @@ -27,6 +27,9 @@ libverilog_la_SOURCES = \ VerilogLex.ll: VerilogParse.hh +VerilogLex.cc: VerilogLex.ll + $(LEX) $(LFLAGS) -o VerilogLex.cc --prefix=VerilogLex_ --header-file=VerilogLex.hh VerilogLex.ll + # Rules to support automake pre 1.12 that name header .h instead of .hh VerilogParse.hh: VerilogParse.cc if test -f VerilogParse.h; then \ diff --git a/verilog/VerilogLex.ll b/verilog/VerilogLex.ll index 12c49733..4ab6881c 100644 --- a/verilog/VerilogLex.ll +++ b/verilog/VerilogLex.ll @@ -36,8 +36,6 @@ verilogFlushBuffer() %} /* %option debug */ -%option prefix="VerilogLex_" -%option outfile="lex.yy.c" %option noyywrap %option nounput %option never-interactive