write_spice alpha
This commit is contained in:
parent
a6e21377e6
commit
9435640d5a
|
|
@ -18,12 +18,16 @@ Makefile.in
|
|||
.libs
|
||||
.deps
|
||||
|
||||
# /
|
||||
/build
|
||||
/configure
|
||||
/m4
|
||||
/compile
|
||||
/config.*
|
||||
/config.h
|
||||
/config.guess
|
||||
/config.h.in
|
||||
/config.log
|
||||
/config.sub
|
||||
/config.status
|
||||
/libtool
|
||||
/ltmain.sh
|
||||
/install-sh
|
||||
|
|
@ -50,9 +54,11 @@ Makefile.in
|
|||
|
||||
# /liberty/
|
||||
/liberty/LibertyExprLex.cc
|
||||
/liberty/LibertyExprLex.hh
|
||||
/liberty/LibertyExprParse.cc
|
||||
/liberty/LibertyExprParse.h
|
||||
/liberty/LibertyLex.cc
|
||||
/liberty/LibertyLex.hh
|
||||
/liberty/LibertyParse.cc
|
||||
/liberty/LibertyParse.h
|
||||
/liberty/LibertyExprParse.hh
|
||||
|
|
@ -60,18 +66,18 @@ Makefile.in
|
|||
|
||||
# /parasitics/
|
||||
/parasitics/SpefLex.cc
|
||||
/parasitics/SpefLex.hh
|
||||
/parasitics/SpefParse.cc
|
||||
/parasitics/SpefParse.h
|
||||
/parasitics/SpfLex.cc
|
||||
/parasitics/SpfParse.cc
|
||||
/parasitics/SpfParse.h
|
||||
/parasitics/SpefParse.hh
|
||||
/parasitics/SpfLex.cc
|
||||
/parasitics/SpfLex.hh
|
||||
/parasitics/SpfParse.cc
|
||||
/parasitics/SpfParse.hh
|
||||
|
||||
# /sdf/
|
||||
/sdf/SdfLex.cc
|
||||
/sdf/SdfLex.hh
|
||||
/sdf/SdfParse.cc
|
||||
/sdf/SdfParse.h
|
||||
/sdf/SdfParse.hh
|
||||
|
||||
# /tcl/
|
||||
|
|
@ -85,6 +91,7 @@ Makefile.in
|
|||
/test_native
|
||||
|
||||
# /verilog/
|
||||
/verilog/VerilogLex.hh
|
||||
/verilog/VerilogLex.cc
|
||||
/verilog/VerilogParse.cc
|
||||
/verilog/VerilogParse.h
|
||||
|
|
|
|||
33
INSTALL
33
INSTALL
|
|
@ -148,3 +148,36 @@ 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<var>=<value> 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=<prefix_path>
|
||||
|
||||
or use the DESTDIR variable with make.
|
||||
|
||||
make DESTDIR=<prefix_path> install
|
||||
|
|
|
|||
|
|
@ -42,7 +42,7 @@ StaApp_wrap.cc: $(SWIG_DEPEND) StaApp.i ../verilog/Verilog.i
|
|||
# so that they do not have to be installed on the client host.
|
||||
|
||||
TclInitVar.cc: ../etc/TclEncode.tcl $(TCL_INIT_FILES)
|
||||
../etc/TclEncode.tcl TclInitVar.cc "tcl_inits" $(TCL_INIT_DIR) \
|
||||
../etc/TclEncode.tcl TclInitVar.cc "tcl_inits" \
|
||||
$(TCL_INIT_FILES) ../verilog/Verilog.tcl
|
||||
|
||||
EXTRA_DIST = \
|
||||
|
|
|
|||
40
configure.ac
40
configure.ac
|
|
@ -50,7 +50,6 @@ case "${HOST_OS}" in
|
|||
INCLUDE_DIRS="/usr/include"
|
||||
LIB_DIRS="/usr/lib /usr/local/lib /usr/lib/i386-linux-gnu"
|
||||
LIB_EXTS="a so .so.1"
|
||||
TCL_INIT_DIRS="/usr/lib/tcl /usr/lib /usr/share/tcltk"
|
||||
;;
|
||||
|
||||
x86_64)
|
||||
|
|
@ -58,7 +57,6 @@ case "${HOST_OS}" in
|
|||
INCLUDE_DIRS="/usr/include"
|
||||
LIB_DIRS="/usr/lib64 /usr/lib /usr/lib/x86_64-linux-gnu"
|
||||
LIB_EXTS="a so"
|
||||
TCL_INIT_DIRS="/usr/lib64/tcl /usr/lib /usr/share/tcltk /usr/share"
|
||||
;;
|
||||
|
||||
*)
|
||||
|
|
@ -66,7 +64,6 @@ case "${HOST_OS}" in
|
|||
INCLUDE_DIRS="/usr/include"
|
||||
LIB_DIRS="/usr/lib"
|
||||
LIB_EXTS="a so"
|
||||
TCL_INIT_DIRS="/usr/lib/tcl /usr/lib"
|
||||
;;
|
||||
esac
|
||||
;;
|
||||
|
|
@ -76,7 +73,6 @@ case "${HOST_OS}" in
|
|||
INCLUDE_DIRS="/usr/include"
|
||||
LIB_DIRS="/usr/lib"
|
||||
LIB_EXTS="a so"
|
||||
TCL_INIT_DIRS="/usr/share"
|
||||
;;
|
||||
|
||||
Darwin)
|
||||
|
|
@ -88,14 +84,12 @@ case "${HOST_OS}" in
|
|||
CC=clang++
|
||||
INCLUDE_DIRS="/usr/local/include /usr/include"
|
||||
LIB_DIRS="/usr/local/lib /usr/lib"
|
||||
TCL_INIT_DIRS="/usr/local/lib/tcl8.6"
|
||||
else
|
||||
# gcc
|
||||
CXX=g++
|
||||
CC=g++
|
||||
INCLUDE_DIRS="/usr/local/opt/tcl-tk/include /usr/local/opt/zlib/include /usr/include"
|
||||
LIB_DIRS="/usr/local/opt/tcl-tk/lib /usr/local/opt/zlib/lib /usr/local/lib"
|
||||
TCL_INIT_DIRS="/usr/local/opt/tcl-tk/lib/tcl8.6"
|
||||
fi
|
||||
LIB_EXTS="dylib"
|
||||
;;
|
||||
|
|
@ -105,7 +99,6 @@ case "${HOST_OS}" in
|
|||
INCLUDE_DIRS="/usr/include /usr/local/include"
|
||||
LIB_DIRS="/usr/lib /usr/local/lib"
|
||||
LIB_EXTS="a so"
|
||||
TCL_INIT_DIRS="/usr/lib/tcl /usr/share /usr/share/tcl"
|
||||
;;
|
||||
|
||||
esac
|
||||
|
|
@ -167,11 +160,6 @@ AC_ARG_WITH(lib,
|
|||
[LIB_DIRS="${withval}"],
|
||||
[])
|
||||
|
||||
AC_ARG_WITH(tcl,
|
||||
[AS_HELP_STRING([--with-tcl=dirs],[directories to search for Tcl init files])],
|
||||
[TCL_INIT_DIRS="${withval}"],
|
||||
[])
|
||||
|
||||
AC_ARG_WITH(cudd,
|
||||
[AS_HELP_STRING([--with-cudd=path],[use CUDD BDD package, defaults to $CUDD])],
|
||||
[CUDD_ARG="${withval}"],
|
||||
|
|
@ -274,39 +262,13 @@ for tcl_minor in $tcl_minors; do
|
|||
AC_MSG_RESULT(not found)
|
||||
fi
|
||||
|
||||
AC_MSG_CHECKING(for Tcl $tcl_major.$tcl_minor init files)
|
||||
TCL_INIT_DIR=""
|
||||
for dir in $TCL_INIT_DIRS ; do
|
||||
init_dir=$dir
|
||||
init_path="$dir/init.tcl"
|
||||
if test -r $init_path; then
|
||||
AC_MSG_RESULT($init_path)
|
||||
TCL_INIT_DIR="$init_dir"
|
||||
break
|
||||
fi
|
||||
|
||||
init_dir="$dir/tcl$tcl_major.$tcl_minor"
|
||||
init_path="$init_dir/init.tcl"
|
||||
if test -r $init_path; then
|
||||
AC_MSG_RESULT($init_path)
|
||||
TCL_INIT_DIR="$init_dir"
|
||||
break
|
||||
fi
|
||||
done
|
||||
if test -z "$TCL_INIT_DIR"; then
|
||||
AC_MSG_RESULT(not found)
|
||||
fi
|
||||
|
||||
if test $found_tcl_header = true &&
|
||||
test -n "$TCL_LIB" &&
|
||||
test -n "$TCL_INIT_DIR"; then
|
||||
test -n "$TCL_LIB"; then
|
||||
break
|
||||
fi
|
||||
|
||||
done
|
||||
|
||||
AC_SUBST(TCL_INIT_DIR, $TCL_INIT_DIR)
|
||||
|
||||
#--------------------------------------------------------------------
|
||||
# Locate the Zlib package
|
||||
#--------------------------------------------------------------------
|
||||
|
|
|
|||
BIN
doc/OpenSTA.odt
BIN
doc/OpenSTA.odt
Binary file not shown.
|
|
@ -27,8 +27,7 @@ exec tclsh $0 ${1+"$@"}
|
|||
|
||||
set encoded_filename [lindex $argv 0]
|
||||
set init_var [lindex $argv 1]
|
||||
set tcl_init_dir [lindex $argv 2]
|
||||
set init_filenames [lrange $argv 3 end]
|
||||
set init_filenames [lrange $argv 2 end]
|
||||
|
||||
set mail_log 0
|
||||
if [info exists env(STA_MAIL_LOG)] {
|
||||
|
|
@ -78,12 +77,6 @@ proc encode_file { filename } {
|
|||
close $in_stream
|
||||
}
|
||||
|
||||
# init.tcl requires tcl_library to be bound for autoloading other files.
|
||||
# Allow the environment variable TCL_INIT_DIR to override the TCL init
|
||||
# file directory found by configure.
|
||||
encode_line "if \[info exists env(TCL_INIT_DIR)] { set tcl_library \$env(TCL_INIT_DIR) } else { set tcl_library \"$tcl_init_dir\" }"
|
||||
encode_line {source [file join $tcl_library init.tcl]}
|
||||
|
||||
foreach filename $init_filenames {
|
||||
encode_file $filename
|
||||
}
|
||||
|
|
|
|||
|
|
@ -14,8 +14,8 @@
|
|||
// You should have received a copy of the GNU General Public License
|
||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
#ifndef STA_DELAY_FLOAT_CLASS_H
|
||||
#define STA_DELAY_FLOAT_CLASS_H
|
||||
#ifndef STA_DELAY_NORMAL2_H
|
||||
#define STA_DELAY_NORMAL2_H
|
||||
|
||||
#include "MinMax.hh"
|
||||
|
||||
|
|
|
|||
|
|
@ -2714,10 +2714,12 @@ OcvDerate::setDerateTable(const TransRiseFall *tr,
|
|||
|
||||
////////////////////////////////////////////////////////////////
|
||||
|
||||
LibertyPgPort::LibertyPgPort(const char *name) :
|
||||
LibertyPgPort::LibertyPgPort(const char *name,
|
||||
LibertyCell *cell) :
|
||||
name_(stringCopy(name)),
|
||||
pg_type_(unknown),
|
||||
voltage_name_(NULL)
|
||||
voltage_name_(NULL),
|
||||
cell_(cell)
|
||||
{
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1021,9 +1021,11 @@ public:
|
|||
internal_power, internal_ground,
|
||||
nwell, pwell,
|
||||
deepnwell, deeppwell};
|
||||
LibertyPgPort(const char *name);
|
||||
LibertyPgPort(const char *name,
|
||||
LibertyCell *cell);
|
||||
~LibertyPgPort();
|
||||
const char *name() { return name_; }
|
||||
LibertyCell *cell() const { return cell_; }
|
||||
PgType pgType() const { return pg_type_; }
|
||||
void setPgType(PgType type);
|
||||
const char *voltageName() const { return voltage_name_; }
|
||||
|
|
@ -1033,6 +1035,7 @@ private:
|
|||
const char *name_;
|
||||
PgType pg_type_;
|
||||
const char *voltage_name_;
|
||||
LibertyCell *cell_;
|
||||
};
|
||||
|
||||
} // namespace
|
||||
|
|
|
|||
|
|
@ -289,7 +289,6 @@ LibertyReader::defineVisitors()
|
|||
&LibertyReader::endScaledCell);
|
||||
defineAttrVisitor("clock_gating_integrated_cell",
|
||||
&LibertyReader::visitClockGatingIntegratedCell);
|
||||
|
||||
defineAttrVisitor("area", &LibertyReader::visitArea);
|
||||
defineAttrVisitor("dont_use", &LibertyReader::visitDontUse);
|
||||
defineAttrVisitor("is_macro", &LibertyReader::visitIsMacro);
|
||||
|
|
@ -4730,7 +4729,7 @@ LibertyReader::beginPgPin(LibertyGroup *group)
|
|||
{
|
||||
if (cell_) {
|
||||
const char *name = group->firstName();
|
||||
pg_port_ = new LibertyPgPort(name);
|
||||
pg_port_ = new LibertyPgPort(name, cell_);
|
||||
cell_->addPgPort(pg_port_);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1885,6 +1885,18 @@ ConcreteParasitics::value(const ParasiticDevice *device,
|
|||
return cdevice->value();
|
||||
}
|
||||
|
||||
void
|
||||
ConcreteParasitics::resistorNodes(const ParasiticDevice *device,
|
||||
// Return values.
|
||||
ParasiticNode *&node1,
|
||||
ParasiticNode *&node2) const
|
||||
{
|
||||
const ConcreteParasiticResistor *cdevice =
|
||||
static_cast<const ConcreteParasiticResistor*>(device);
|
||||
node1 = cdevice->node1();
|
||||
node2 = cdevice->node2();
|
||||
}
|
||||
|
||||
ParasiticNode *
|
||||
ConcreteParasitics::otherNode(const ParasiticDevice *device,
|
||||
ParasiticNode *node) const
|
||||
|
|
|
|||
|
|
@ -175,6 +175,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;
|
||||
|
||||
|
|
|
|||
|
|
@ -322,8 +322,8 @@ public:
|
|||
virtual ParasiticNode *otherNode(ParasiticNode *node) const;
|
||||
virtual void replaceNode(ConcreteParasiticNode *from_node,
|
||||
ConcreteParasiticNode *to_node);
|
||||
ConcreteParasiticNode *node1() { return node_; }
|
||||
ConcreteParasiticNode *node2() { return other_node_; }
|
||||
ConcreteParasiticNode *node1() const { return node_; }
|
||||
ConcreteParasiticNode *node2() const { return other_node_; }
|
||||
|
||||
private:
|
||||
ConcreteParasiticNode *other_node_;
|
||||
|
|
|
|||
|
|
@ -248,6 +248,10 @@ public:
|
|||
// Device "value" (resistance, capacitance).
|
||||
virtual float value(const ParasiticDevice *device,
|
||||
const ParasiticAnalysisPt *ap) const = 0;
|
||||
virtual void resistorNodes(const ParasiticDevice *device,
|
||||
// Return values.
|
||||
ParasiticNode *&node1,
|
||||
ParasiticNode *&node2) const = 0;
|
||||
virtual ParasiticNode *otherNode(const ParasiticDevice *device,
|
||||
ParasiticNode *node) const = 0;
|
||||
|
||||
|
|
|
|||
|
|
@ -56,7 +56,8 @@ include_HEADERS = \
|
|||
VertexVisitor.hh \
|
||||
VisitPathEnds.hh \
|
||||
VisitPathGroupVertices.hh \
|
||||
WorstSlack.hh
|
||||
WorstSlack.hh \
|
||||
WriteSpice.hh
|
||||
|
||||
libsearch_la_SOURCES = \
|
||||
Bfs.cc \
|
||||
|
|
@ -97,7 +98,8 @@ libsearch_la_SOURCES = \
|
|||
VertexVisitor.cc \
|
||||
VisitPathEnds.cc \
|
||||
VisitPathGroupVertices.cc \
|
||||
WorstSlack.cc
|
||||
WorstSlack.cc \
|
||||
WriteSpice.cc
|
||||
|
||||
libs: $(lib_LTLIBRARIES)
|
||||
|
||||
|
|
|
|||
|
|
@ -27,6 +27,11 @@
|
|||
|
||||
namespace sta {
|
||||
|
||||
PathExpanded::PathExpanded(const StaState *sta) :
|
||||
sta_(sta)
|
||||
{
|
||||
}
|
||||
|
||||
PathExpanded::PathExpanded(const Path *path,
|
||||
// Expand generated clk source paths.
|
||||
bool expand_genclks,
|
||||
|
|
|
|||
|
|
@ -28,6 +28,7 @@ namespace sta {
|
|||
class PathExpanded
|
||||
{
|
||||
public:
|
||||
PathExpanded(const StaState *sta);
|
||||
// Expand path for lookup by index.
|
||||
PathExpanded(const Path *path,
|
||||
const StaState *sta);
|
||||
|
|
@ -35,6 +36,8 @@ public:
|
|||
// Expand generated clk source paths.
|
||||
bool expand_genclks,
|
||||
const StaState *sta);
|
||||
void expand(const Path *path,
|
||||
bool expand_genclks);
|
||||
size_t size() const { return paths_.size(); }
|
||||
// path(0) is the startpoint.
|
||||
// path(size()-1) is the endpoint.
|
||||
|
|
@ -55,8 +58,6 @@ public:
|
|||
Edge *&d_q_edge);
|
||||
|
||||
protected:
|
||||
void expand(const Path *path,
|
||||
bool expand_genclks);
|
||||
void expandGenclk(PathRef *clk_path);
|
||||
// Convert external index that starts at the path root
|
||||
// and increases to an index for paths_ (reversed).
|
||||
|
|
|
|||
|
|
@ -0,0 +1,946 @@
|
|||
// OpenSTA, Static Timing Analyzer
|
||||
// Copyright (c) 2018, Parallax Software, Inc.
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
#include <string>
|
||||
#include <iostream>
|
||||
#include <fstream>
|
||||
#include <regex>
|
||||
#include "Machine.hh"
|
||||
#include "Debug.hh"
|
||||
#include "Error.hh"
|
||||
#include "Report.hh"
|
||||
#include "StringUtil.hh"
|
||||
#include "FuncExpr.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 "WriteSpice.hh"
|
||||
|
||||
namespace sta {
|
||||
|
||||
using std::string;
|
||||
using std::ofstream;
|
||||
using std::ifstream;
|
||||
|
||||
typedef Vector<string> StringVector;
|
||||
typedef Map<string, StringVector*> CellSpicePortNames;
|
||||
typedef int Stage;
|
||||
typedef Map<ParasiticNode*, int> 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 WriteSpice : public StaState
|
||||
{
|
||||
public:
|
||||
WriteSpice(Path *path,
|
||||
const char *spice_filename,
|
||||
const char *subckts_filename,
|
||||
const char *lib_subckts_filename,
|
||||
const char *models_filename,
|
||||
const StaState *sta);
|
||||
~WriteSpice();
|
||||
void writeSpice();;
|
||||
|
||||
private:
|
||||
void writeHeader();
|
||||
void writeStageInstances();
|
||||
void writeInputSource();
|
||||
void writeStageSubckts();
|
||||
void writeInputStage(Stage stage);
|
||||
void writeMeasureStmts();
|
||||
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);
|
||||
|
||||
// Stage "accessors".
|
||||
// Internally a stage index from stageFirst() to stageLast()
|
||||
// is turned into an index into path_expanded_.
|
||||
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 *stageInputPinName(Stage stage);
|
||||
const char *stageDrvrPinName(Stage stage);
|
||||
const char *stageLoadPinName(Stage stage);
|
||||
|
||||
Path *path_;
|
||||
const char *spice_filename_;
|
||||
const char *subckts_filename_;
|
||||
const char *lib_subckts_filename_;
|
||||
const char *models_filename_;
|
||||
|
||||
ofstream spice_stream_;
|
||||
PathExpanded path_expanded_;
|
||||
CellSpicePortNames cell_spice_port_names_;
|
||||
ParasiticNodeMap node_map_;
|
||||
int next_node_index_;
|
||||
const char *net_name_;
|
||||
|
||||
// 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
|
||||
writeSpice (Path *path,
|
||||
const char *spice_filename,
|
||||
const char *subckts_filename,
|
||||
const char *lib_subckts_filename,
|
||||
const char *models_filename,
|
||||
StaState *sta)
|
||||
{
|
||||
WriteSpice writer(path, spice_filename, subckts_filename,
|
||||
lib_subckts_filename, models_filename, sta);
|
||||
writer.writeSpice();
|
||||
}
|
||||
|
||||
const float WriteSpice::short_ckt_resistance_ = .0001;
|
||||
|
||||
WriteSpice::WriteSpice(Path *path,
|
||||
const char *spice_filename,
|
||||
const char *subckts_filename,
|
||||
const char *lib_subckts_filename,
|
||||
const char *models_filename,
|
||||
const StaState *sta) :
|
||||
StaState(sta),
|
||||
path_(path),
|
||||
spice_filename_(spice_filename),
|
||||
subckts_filename_(subckts_filename),
|
||||
lib_subckts_filename_(lib_subckts_filename),
|
||||
models_filename_(models_filename),
|
||||
path_expanded_(sta),
|
||||
net_name_(NULL)
|
||||
{
|
||||
}
|
||||
|
||||
WriteSpice::~WriteSpice()
|
||||
{
|
||||
cell_spice_port_names_.deleteContents();
|
||||
}
|
||||
|
||||
void
|
||||
WriteSpice::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();
|
||||
writeInputSource();
|
||||
writeStageSubckts();
|
||||
streamPrint(spice_stream_, ".end\n");
|
||||
spice_stream_.close();
|
||||
}
|
||||
else
|
||||
throw FileNotWritable(spice_filename_);
|
||||
}
|
||||
|
||||
void
|
||||
WriteSpice::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", models_filename_);
|
||||
streamPrint(spice_stream_, ".include \"%s\"\n", subckts_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
|
||||
WriteSpice::maxTime()
|
||||
{
|
||||
float end_slew = path_->slew(this);
|
||||
float max_time = (path_->arrival(this) + end_slew * 2) * 1.5;
|
||||
return max_time;
|
||||
}
|
||||
|
||||
void
|
||||
WriteSpice::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,
|
||||
stageInputPinName(stage),
|
||||
stageDrvrPinName(stage),
|
||||
stageLoadPinName(stage),
|
||||
stage_name);
|
||||
}
|
||||
streamPrint(spice_stream_, "\n");
|
||||
}
|
||||
|
||||
float
|
||||
WriteSpice::pgPortVoltage(const char *pg_port_name,
|
||||
LibertyCell *cell)
|
||||
{
|
||||
auto pg_port = cell->findPgPort(pg_port_name);
|
||||
return pgPortVoltage(pg_port);
|
||||
}
|
||||
|
||||
float
|
||||
WriteSpice::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
|
||||
WriteSpice::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);
|
||||
auto load_pin = stageLoadPin(input_stage);
|
||||
auto cell = network_->libertyCell(network_->instance(load_pin));
|
||||
auto load_port = network_->libertyPort(load_pin);
|
||||
const char *pg_gnd_port_name = load_port->relatedGroundPin();
|
||||
const char *pg_pwr_port_name = load_port->relatedPowerPin();
|
||||
auto gnd_volt = pgPortVoltage(pg_gnd_port_name, cell);
|
||||
auto pwr_volt = pgPortVoltage(pg_pwr_port_name, cell);
|
||||
float volt0, volt1;
|
||||
if (wire_arc->fromTrans()->asRiseFall() == TransRiseFall::rise()) {
|
||||
volt0 = gnd_volt;
|
||||
volt1 = pwr_volt;
|
||||
}
|
||||
else {
|
||||
volt0 = pwr_volt;
|
||||
volt1 = gnd_volt;
|
||||
}
|
||||
float time0 = .1e-9;
|
||||
float time1 = .2e-9;
|
||||
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
|
||||
WriteSpice::writeMeasureStmts()
|
||||
{
|
||||
streamPrint(spice_stream_, "********************\n");
|
||||
streamPrint(spice_stream_, "* Measure statements\n");
|
||||
streamPrint(spice_stream_, "********************\n\n");
|
||||
}
|
||||
|
||||
void
|
||||
WriteSpice::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
|
||||
WriteSpice::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
|
||||
WriteSpice::writeGateStage(Stage stage)
|
||||
{
|
||||
auto input_pin = stageInputPin(stage);
|
||||
auto input_pin_name = stageInputPinName(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<LibertyPort*, LogicValue> 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
|
||||
WriteSpice::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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
typedef Set<ParasiticDevice*> ParasiticDeviceSet;
|
||||
typedef Set<ParasiticNode*> 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
|
||||
WriteSpice::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);
|
||||
ParasiticNodeSet nodes;
|
||||
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
|
||||
WriteSpice::initNodeMap(const char *net_name)
|
||||
{
|
||||
stringDelete(net_name_);
|
||||
node_map_.clear();
|
||||
next_node_index_ = 1;
|
||||
net_name_ = stringCopy(net_name);
|
||||
}
|
||||
|
||||
const char *
|
||||
WriteSpice::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_subckts_filename for
|
||||
// each cell in path to path_subckts_filename.
|
||||
void
|
||||
WriteSpice::writeSubckts()
|
||||
{
|
||||
StringSet path_cell_names;
|
||||
findPathCellnames(path_cell_names);
|
||||
|
||||
ifstream lib_subckts_stream(lib_subckts_filename_);
|
||||
if (lib_subckts_stream.is_open()) {
|
||||
ofstream subckts_stream(subckts_filename_);
|
||||
if (subckts_stream.is_open()) {
|
||||
string line;
|
||||
while (getline(lib_subckts_stream, line)) {
|
||||
// .subckt <cell_name> [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_subckts_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_subckts_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(subckts_filename_);
|
||||
}
|
||||
}
|
||||
else
|
||||
throw FileNotReadable(lib_subckts_filename_);
|
||||
}
|
||||
|
||||
void
|
||||
WriteSpice::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
|
||||
WriteSpice::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
|
||||
WriteSpice::stageFirst()
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
|
||||
Stage
|
||||
WriteSpice::stageLast()
|
||||
{
|
||||
return (path_expanded_.size() + 1) / 2;
|
||||
}
|
||||
|
||||
string
|
||||
WriteSpice::stageName(Stage stage)
|
||||
{
|
||||
string name;
|
||||
stringPrint(name, "stage%d", stage);
|
||||
return name;
|
||||
}
|
||||
|
||||
int
|
||||
WriteSpice::stageGateInputPathIndex(Stage stage)
|
||||
{
|
||||
return stage * 2 - 3;
|
||||
}
|
||||
|
||||
int
|
||||
WriteSpice::stageDrvrPathIndex(Stage stage)
|
||||
{
|
||||
return stage * 2 - 2;
|
||||
}
|
||||
|
||||
int
|
||||
WriteSpice::stageLoadPathIndex(Stage stage)
|
||||
{
|
||||
return stage * 2 - 1;
|
||||
}
|
||||
|
||||
PathRef *
|
||||
WriteSpice::stageGateInputPath(Stage stage)
|
||||
{
|
||||
int path_index = stageGateInputPathIndex(stage);
|
||||
return path_expanded_.path(path_index);
|
||||
}
|
||||
|
||||
PathRef *
|
||||
WriteSpice::stageDrvrPath(Stage stage)
|
||||
{
|
||||
int path_index = stageDrvrPathIndex(stage);
|
||||
return path_expanded_.path(path_index);
|
||||
}
|
||||
|
||||
PathRef *
|
||||
WriteSpice::stageLoadPath(Stage stage)
|
||||
{
|
||||
int path_index = stageLoadPathIndex(stage);
|
||||
return path_expanded_.path(path_index);
|
||||
}
|
||||
|
||||
TimingArc *
|
||||
WriteSpice::stageGateArc(Stage stage)
|
||||
{
|
||||
int path_index = stageDrvrPathIndex(stage);
|
||||
if (path_index >= 0)
|
||||
return path_expanded_.prevArc(path_index);
|
||||
else
|
||||
return NULL;
|
||||
}
|
||||
|
||||
TimingArc *
|
||||
WriteSpice::stageWireArc(Stage stage)
|
||||
{
|
||||
int path_index = stageLoadPathIndex(stage);
|
||||
return path_expanded_.prevArc(path_index);
|
||||
}
|
||||
|
||||
Edge *
|
||||
WriteSpice::stageGateEdge(Stage stage)
|
||||
{
|
||||
PathRef *path = stageGateInputPath(stage);
|
||||
TimingArc *arc = stageGateArc(stage);
|
||||
return path->prevEdge(arc, this);
|
||||
}
|
||||
|
||||
Edge *
|
||||
WriteSpice::stageWireEdge(Stage stage)
|
||||
{
|
||||
PathRef *path = stageLoadPath(stage);
|
||||
TimingArc *arc = stageWireArc(stage);
|
||||
return path->prevEdge(arc, this);
|
||||
}
|
||||
|
||||
Pin *
|
||||
WriteSpice::stageInputPin(Stage stage)
|
||||
{
|
||||
PathRef *path = stageGateInputPath(stage);
|
||||
return path->pin(this);
|
||||
}
|
||||
|
||||
Pin *
|
||||
WriteSpice::stageDrvrPin(Stage stage)
|
||||
{
|
||||
PathRef *path = stageDrvrPath(stage);
|
||||
return path->pin(this);
|
||||
}
|
||||
|
||||
Pin *
|
||||
WriteSpice::stageLoadPin(Stage stage)
|
||||
{
|
||||
PathRef *path = stageLoadPath(stage);
|
||||
return path->pin(this);
|
||||
}
|
||||
|
||||
const char *
|
||||
WriteSpice::stageInputPinName(Stage stage)
|
||||
{
|
||||
const Pin *pin = stageInputPin(stage);
|
||||
return network_->pathName(pin);
|
||||
}
|
||||
|
||||
const char *
|
||||
WriteSpice::stageDrvrPinName(Stage stage)
|
||||
{
|
||||
Pin *pin = stageDrvrPin(stage);
|
||||
return network_->pathName(pin);
|
||||
}
|
||||
|
||||
const char *
|
||||
WriteSpice::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
|
||||
|
|
@ -0,0 +1,37 @@
|
|||
// OpenSTA, Static Timing Analyzer
|
||||
// Copyright (c) 2018, Parallax Software, Inc.
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
#ifndef STA_WRITE_SPICE_H
|
||||
#define STA_WRITE_SPICE_H
|
||||
|
||||
namespace sta {
|
||||
|
||||
// Write a spice deck for path.
|
||||
// Throws FileNotReadable, FileNotWritable, SubcktEndsMissing
|
||||
void
|
||||
writeSpice(Path *path,
|
||||
// Spice file written for path.
|
||||
const char *spice_filename,
|
||||
// Subckts used by path included in spice file.
|
||||
const char *subckts_filename,
|
||||
// File of all cell spice subckt definitions.
|
||||
const char *lib_subckts_filename,
|
||||
// Device model file included in spice file.
|
||||
const char *models_filename,
|
||||
StaState *sta);
|
||||
|
||||
} // namespace
|
||||
#endif
|
||||
14
tcl/StaTcl.i
14
tcl/StaTcl.i
|
|
@ -78,6 +78,7 @@
|
|||
#include "ReportPath.hh"
|
||||
#include "Power.hh"
|
||||
#include "Property.hh"
|
||||
#include "WriteSpice.hh"
|
||||
#include "Sta.hh"
|
||||
|
||||
namespace sta {
|
||||
|
|
@ -4585,6 +4586,19 @@ write_sdc_cmd(const char *filename,
|
|||
Sta::sta()->writeSdc(filename, native, no_timestamp, digits);
|
||||
}
|
||||
|
||||
void
|
||||
write_spice(PathRef *path,
|
||||
const char *spice_filename,
|
||||
const char *subckts_filename,
|
||||
const char *lib_subckts_filename,
|
||||
const char *models_filename)
|
||||
{
|
||||
cmdLinkedNetwork();
|
||||
Sta *sta = Sta::sta();
|
||||
writeSpice(path, spice_filename, subckts_filename,
|
||||
lib_subckts_filename, models_filename, sta);
|
||||
}
|
||||
|
||||
void
|
||||
set_cmd_unit_scale(const char *unit_name,
|
||||
float scale)
|
||||
|
|
|
|||
180
tcl/Util.tcl
180
tcl/Util.tcl
|
|
@ -391,15 +391,13 @@ proc check_percent { cmd_arg arg } {
|
|||
################################################################
|
||||
|
||||
# The builtin Tcl "source" and "unknown" commands are redefined by sta.
|
||||
# The original definition is not clobbered until Final.tcl.
|
||||
# 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[*].
|
||||
proc unknown { args } {
|
||||
proc sta_unknown { args } {
|
||||
global errorCode errorInfo
|
||||
|
||||
set name [lindex $args 0]
|
||||
|
|
@ -434,3 +432,179 @@ proc 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
|
||||
|
|
|
|||
|
|
@ -30,6 +30,7 @@ public:
|
|||
Vector() : std::vector<OBJ>() {}
|
||||
Vector(size_t n) : std::vector<OBJ>(n) {}
|
||||
Vector(size_t n, const OBJ &obj) : std::vector<OBJ>(n, obj) {}
|
||||
|
||||
// Erase an object from the vector (slow).
|
||||
void
|
||||
eraseObject(OBJ obj)
|
||||
|
|
|
|||
Loading…
Reference in New Issue