Merge remote-tracking branch 'parallaxsw/master'
Signed-off-by: Eder Monteiro <emrmonteiro@precisioninno.com>
This commit is contained in:
commit
32365d9bc4
|
|
@ -14,7 +14,7 @@
|
|||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
cmake_minimum_required (VERSION 3.9)
|
||||
cmake_minimum_required (VERSION 3.10)
|
||||
if(CMAKE_VERSION VERSION_GREATER_EQUAL 3.13)
|
||||
# Use standard target names
|
||||
cmake_policy(SET CMP0078 NEW)
|
||||
|
|
@ -125,6 +125,11 @@ set(STA_SOURCE
|
|||
parasitics/SpefNamespace.cc
|
||||
parasitics/SpefReader.cc
|
||||
parasitics/SpefReaderPvt.hh
|
||||
|
||||
power/Power.cc
|
||||
power/VcdReader.cc
|
||||
power/SaifReader.cc
|
||||
power/VcdParse.cc
|
||||
|
||||
sdc/Clock.cc
|
||||
sdc/ClockGatingCheck.cc
|
||||
|
|
@ -198,11 +203,7 @@ set(STA_SOURCE
|
|||
spice/WriteSpice.cc
|
||||
spice/Xyce.cc
|
||||
|
||||
power/Power.cc
|
||||
power/ReadVcdActivities.cc
|
||||
power/SaifReader.cc
|
||||
power/Vcd.cc
|
||||
power/VcdReader.cc
|
||||
tcl/TclTypeHelpers.cc
|
||||
|
||||
util/Debug.cc
|
||||
util/DispatchQueue.cc
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ RUN sed -i s/mirror.centos.org/vault.centos.org/g /etc/yum.repos.d/*.repo \
|
|||
RUN sed -i s/mirror.centos.org/vault.centos.org/g /etc/yum.repos.d/*.repo \
|
||||
&& sed -i s/^#.*baseurl=http/baseurl=http/g /etc/yum.repos.d/*.repo \
|
||||
&& sed -i s/^mirrorlist=http/#mirrorlist=http/g /etc/yum.repos.d/*.repo \
|
||||
&& yum install -y devtoolset-8 wget cmake3 make eigen3-devel tcl-devel tcl-tclreadline-devel swig3 bison flex zlib-devel \
|
||||
&& yum install -y devtoolset-8 wget cmake3 make eigen3-devel tcl-devel tcl-tclreadline-devel swig3 bison flex zlib-devel valgrind \
|
||||
&& yum clean -y all
|
||||
|
||||
# Download CUDD
|
||||
|
|
|
|||
|
|
@ -16,7 +16,8 @@ RUN apt-get update && \
|
|||
bison \
|
||||
flex \
|
||||
automake \
|
||||
autotools-dev
|
||||
autotools-dev \
|
||||
valgrind
|
||||
|
||||
# Download CUDD
|
||||
RUN wget https://raw.githubusercontent.com/davidkebo/cudd/main/cudd_versions/cudd-3.0.0.tar.gz && \
|
||||
23
README.md
23
README.md
|
|
@ -94,7 +94,7 @@ work, but these are the versions used for development.
|
|||
cmake 3.24.2 3.29.2
|
||||
clang 15.0.0
|
||||
gcc 11.4.0
|
||||
tcl 8.6 8.6.6
|
||||
tcl 8.6 8.6.16
|
||||
swig 4.1.0 4.1.1
|
||||
bison 3.8.2 3.8.2
|
||||
flex 2.6.4 2.6.4
|
||||
|
|
@ -105,7 +105,7 @@ are illegal in c++17.
|
|||
|
||||
External library dependencies:
|
||||
```
|
||||
Ubuntu Macos license
|
||||
Ubuntu Darwin License
|
||||
eigen 3.4.0 3.4.0 MPL2 required
|
||||
cudd 3.0.0 3.0.0 BSD required
|
||||
tclreadline 2.3.8 2.3.8 BSD optional
|
||||
|
|
@ -197,7 +197,7 @@ following command builds a Docker image.
|
|||
|
||||
```
|
||||
cd OpenSTA
|
||||
docker build --file Dockerfile.ubuntu_22.04 --tag OpenSTA .
|
||||
docker build --file Dockerfile.ubuntu22.04 --tag OpenSTA .
|
||||
```
|
||||
|
||||
To run a docker container using the OpenSTA image, use the -v option
|
||||
|
|
@ -237,7 +237,22 @@ case directory.
|
|||
|
||||
* James Cherry
|
||||
|
||||
* William Scott authored the arnoldi delay calculator at Blaze, Inc which was subsequently licensed to Nefelus, Inc that has graciously contributed it to OpenSTA.
|
||||
* William Scott authored the arnoldi delay calculator at Blaze, Inc
|
||||
which was subsequently licensed to Nefelus, Inc that has graciously
|
||||
contributed it to OpenSTA.
|
||||
|
||||
|
||||
## Contributions
|
||||
|
||||
Contributors must sign the Contributor License Agreement (doc/CLA.txt)
|
||||
when submitting pull requests.
|
||||
|
||||
All contributors should read doc/CodingGuidelines.txt for nodes on
|
||||
making code that adheres to the existing naming and formatting style.
|
||||
|
||||
Contributions that claim 4% performance improvements in OpenROAD flow
|
||||
scripts will largely be ignored. Small performance improvements
|
||||
simply do not justify the time requied to audit and verify the changes.
|
||||
|
||||
## License
|
||||
|
||||
|
|
|
|||
|
|
@ -22,7 +22,8 @@
|
|||
# because there doesn't appear to be a way to override
|
||||
# searching OSX system directories before unix directories.
|
||||
|
||||
set(TCL_POSSIBLE_NAMES tcl87 tcl8.7
|
||||
set(TCL_POSSIBLE_NAMES
|
||||
tcl87 tcl8.7
|
||||
tcl86 tcl8.6
|
||||
tcl85 tcl8.5
|
||||
)
|
||||
|
|
@ -30,7 +31,7 @@ set(TCL_POSSIBLE_NAMES tcl87 tcl8.7
|
|||
# tcl lib path guesses.
|
||||
if (NOT TCL_LIB_PATHS)
|
||||
if (CMAKE_SYSTEM_NAME STREQUAL "Darwin")
|
||||
set(TCL_LIB_PATHS /usr/local/lib /opt/homebrew/opt/tcl-tk/lib)
|
||||
set(TCL_LIB_PATHS /opt/homebrew/Cellar/tcl-tk@8/8.6.16/lib /opt/homebrew/opt/tcl-tk/lib /usr/local/lib)
|
||||
set(TCL_NO_DEFAULT_PATH TRUE)
|
||||
elseif (CMAKE_SYSTEM_NAME STREQUAL "Linux")
|
||||
set(TCL_LIB_PATHS /usr/lib /usr/lib64 /usr/local/lib)
|
||||
|
|
|
|||
|
|
@ -628,7 +628,7 @@ GraphDelayCalc::loadSlewsChanged(DrvrLoadSlews &load_slews_prev,
|
|||
for (auto const [pin, index] : load_pin_index_map) {
|
||||
Vertex *load_vertex = graph_->pinLoadVertex(pin);
|
||||
const SlewSeq slews = graph_->slews(load_vertex);
|
||||
const SlewSeq &slews_prev = load_slews_prev[index];
|
||||
const SlewSeq &slews_prev = load_slews_prev[index];
|
||||
for (size_t i = 0; i < slews.size(); i++) {
|
||||
if (!delayEqual(slews[i], slews_prev[i]))
|
||||
return true;
|
||||
|
|
@ -1109,10 +1109,9 @@ GraphDelayCalc::annotateDelaySlew(Edge *edge,
|
|||
float prev_gate_delay1 = delayAsFloat(prev_gate_delay);
|
||||
if (prev_gate_delay1 == 0.0
|
||||
|| (abs(gate_delay1 - prev_gate_delay1) / prev_gate_delay1
|
||||
> incremental_delay_tolerance_)) {
|
||||
> incremental_delay_tolerance_))
|
||||
delay_changed = true;
|
||||
graph_->setArcDelay(edge, arc, ap_index, gate_delay);
|
||||
}
|
||||
graph_->setArcDelay(edge, arc, ap_index, gate_delay);
|
||||
}
|
||||
return delay_changed;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -31,11 +31,14 @@ is now supported by the the read_saif command.
|
|||
read_saif [-scope scope] filename
|
||||
|
||||
The report_checks -group_count option has been renamed to -group_path_count.
|
||||
The report_checks -endpoing_count option has been renamed to -endpoint_path_count.
|
||||
The report_checks -endpoint_count option has been renamed to -endpoint_path_count.
|
||||
|
||||
The report_checks -field hierarchical_pins field reports hierarical pins between
|
||||
The report_checks -field hierarchical_pins field reports hierarchical pins between
|
||||
a driver and a load in the path report.
|
||||
|
||||
The suppress_msg and unsuppress_msg commands allow suppression/unsuppression of
|
||||
warnings/errors by ID. Message IDs can be found in doc/messages.txt.
|
||||
|
||||
Release 2.5.0 2024/01/17
|
||||
-------------------------
|
||||
|
||||
|
|
@ -62,6 +65,11 @@ clocks from the list.
|
|||
|
||||
all_inputs [-no_clocks]
|
||||
|
||||
The report_activity_annotation command reports power activity annotaton
|
||||
from vcd, saif or the set_input_activity command.
|
||||
|
||||
report_activity_annotation [-report_unannotated] [-report_annotated]
|
||||
|
||||
Release 2.4.0 2023/01/19
|
||||
-------------------------
|
||||
|
||||
|
|
|
|||
|
|
@ -97,15 +97,15 @@ private order.
|
|||
friend class Frobulator;
|
||||
}
|
||||
|
||||
Don't declare class variables as const. It means any downstream code
|
||||
that accesses the member cannot modify it, which is overly
|
||||
restrictive.
|
||||
|
||||
Avoid using [] to lookup a map value because it creates a key/null value
|
||||
pair if the lookup fails. Use map::find or sta::Map::findKey instead.
|
||||
|
||||
Avoid nested classes/enums because SWIG has trouble with them.
|
||||
|
||||
Avoid all use of global variables as "caches", even if they are thread local.
|
||||
OpenSTA goes to great lengths to minimize global state variable that prevent
|
||||
multiple instances of the Sta class from coexisting.
|
||||
|
||||
Regression Tests
|
||||
................
|
||||
|
||||
|
|
|
|||
BIN
doc/OpenSTA.odt
BIN
doc/OpenSTA.odt
Binary file not shown.
BIN
doc/OpenSTA.pdf
BIN
doc/OpenSTA.pdf
Binary file not shown.
File diff suppressed because it is too large
Load Diff
Binary file not shown.
File diff suppressed because it is too large
Load Diff
Binary file not shown.
|
|
@ -2,11 +2,11 @@
|
|||
read_liberty sky130hd_tt.lib.gz
|
||||
read_verilog gcd_sky130hd.v
|
||||
link_design gcd
|
||||
|
||||
read_sdc gcd_sky130hd.sdc
|
||||
read_spef gcd_sky130hd.spef
|
||||
# Generate vcd file
|
||||
# iverilog -o gcd_tb gcd_tb.v
|
||||
# vvp gcd_tb
|
||||
read_vcd -scope gcd_tb/gcd1 gcd_sky130hd.vcd
|
||||
read_vcd -scope gcd_tb/gcd1 gcd_sky130hd.vcd.gz
|
||||
report_activity_annotation
|
||||
report_power
|
||||
|
|
|
|||
|
|
@ -34,11 +34,14 @@ public:
|
|||
class ExceptionMsg : public Exception
|
||||
{
|
||||
public:
|
||||
ExceptionMsg(const char *msg);
|
||||
ExceptionMsg(const char *msg,
|
||||
const bool suppressed);
|
||||
virtual const char *what() const noexcept;
|
||||
virtual bool suppressed() const { return suppressed_; }
|
||||
|
||||
private:
|
||||
string msg_;
|
||||
bool suppressed_;
|
||||
};
|
||||
|
||||
class ExceptionLine : public Exception
|
||||
|
|
|
|||
|
|
@ -68,8 +68,7 @@ public:
|
|||
Path *path() { return &path_; }
|
||||
const Path *path() const { return &path_; }
|
||||
PathRef &pathRef() { return path_; }
|
||||
virtual void setPath(PathEnumed *path,
|
||||
const StaState *sta);
|
||||
virtual void setPath(const Path *path);
|
||||
Vertex *vertex(const StaState *sta) const;
|
||||
const MinMax *minMax(const StaState *sta) const;
|
||||
// Synonym for minMax().
|
||||
|
|
@ -256,8 +255,7 @@ public:
|
|||
virtual Slack slackNoCrpr(const StaState *sta) const;
|
||||
virtual int exceptPathCmp(const PathEnd *path_end,
|
||||
const StaState *sta) const;
|
||||
virtual void setPath(PathEnumed *path,
|
||||
const StaState *sta);
|
||||
virtual void setPath(const Path *path);
|
||||
|
||||
protected:
|
||||
PathEndClkConstrained(Path *path,
|
||||
|
|
|
|||
|
|
@ -37,7 +37,7 @@ typedef Map<const char*, PathGroup*, CharPtrLess> PathGroupNamedMap;
|
|||
class PathGroup
|
||||
{
|
||||
public:
|
||||
virtual ~PathGroup();
|
||||
~PathGroup();
|
||||
// Path group that compares compare slacks.
|
||||
static PathGroup *makePathGroupArrival(const char *name,
|
||||
int group_path_count,
|
||||
|
|
@ -59,8 +59,9 @@ public:
|
|||
void insert(PathEnd *path_end);
|
||||
// Push group_path_count into path_ends.
|
||||
void pushEnds(PathEndSeq &path_ends);
|
||||
// Predicates to determine if a PathEnd is worth saving.
|
||||
virtual bool savable(PathEnd *path_end);
|
||||
// Predicate to determine if a PathEnd is worth saving.
|
||||
bool saveable(PathEnd *path_end);
|
||||
bool enumMinSlackUnderMin(PathEnd *path_end);
|
||||
int maxPaths() const { return group_path_count_; }
|
||||
PathGroupIterator *iterator();
|
||||
// This does NOT delete the path ends.
|
||||
|
|
|
|||
|
|
@ -38,17 +38,17 @@ class PwrActivity
|
|||
{
|
||||
public:
|
||||
PwrActivity();
|
||||
PwrActivity(float activity,
|
||||
PwrActivity(float density,
|
||||
float duty,
|
||||
PwrActivityOrigin origin);
|
||||
float activity() const { return activity_; }
|
||||
void setActivity(float activity);
|
||||
float density() const { return density_; }
|
||||
void setDensity(float density);
|
||||
float duty() const { return duty_; }
|
||||
void setDuty(float duty);
|
||||
PwrActivityOrigin origin() { return origin_; }
|
||||
PwrActivityOrigin origin() const { return origin_; }
|
||||
void setOrigin(PwrActivityOrigin origin);
|
||||
const char *originName() const;
|
||||
void set(float activity,
|
||||
void set(float density,
|
||||
float duty,
|
||||
PwrActivityOrigin origin);
|
||||
bool isSet() const;
|
||||
|
|
@ -56,12 +56,11 @@ public:
|
|||
private:
|
||||
void check();
|
||||
|
||||
// In general activity is per clock cycle, NOT per second.
|
||||
float activity_;
|
||||
float duty_;
|
||||
float density_; // transitions / second
|
||||
float duty_; // probability signal is high
|
||||
PwrActivityOrigin origin_;
|
||||
|
||||
static constexpr float min_activity = 1E-10;
|
||||
static constexpr float min_density = 1E-10;
|
||||
};
|
||||
|
||||
class PowerResult
|
||||
|
|
@ -69,12 +68,15 @@ class PowerResult
|
|||
public:
|
||||
PowerResult();
|
||||
void clear();
|
||||
float &internal() { return internal_; }
|
||||
float &switching() { return switching_; }
|
||||
float &leakage() { return leakage_; }
|
||||
float internal() const { return internal_; }
|
||||
float switching() const { return switching_; }
|
||||
float leakage() const { return leakage_; }
|
||||
float total() const;
|
||||
void incr(PowerResult &result);
|
||||
|
||||
void incrInternal(float pwr);
|
||||
void incrSwitching(float pwr);
|
||||
void incrLeakage(float pwr);
|
||||
|
||||
private:
|
||||
float internal_;
|
||||
float switching_;
|
||||
|
|
|
|||
|
|
@ -20,6 +20,7 @@
|
|||
#include <cstdarg>
|
||||
#include <string>
|
||||
#include <mutex>
|
||||
#include <set>
|
||||
|
||||
#include "Machine.hh" // __attribute__
|
||||
|
||||
|
|
@ -119,6 +120,11 @@ public:
|
|||
size_t length);
|
||||
static Report *defaultReport() { return default_; }
|
||||
|
||||
// Suppress message by id.
|
||||
void suppressMsgId(int id);
|
||||
void unsuppressMsgId(int id);
|
||||
bool isSuppressed(int id);
|
||||
|
||||
protected:
|
||||
// All sta print functions have an implicit return printed by this function.
|
||||
virtual void printLine(const char *line,
|
||||
|
|
@ -152,6 +158,7 @@ protected:
|
|||
size_t buffer_length_;
|
||||
std::mutex buffer_lock_;
|
||||
static Report *default_;
|
||||
std::set<int> suppressed_msg_ids_;
|
||||
|
||||
friend class Debug;
|
||||
};
|
||||
|
|
|
|||
|
|
@ -911,6 +911,7 @@ public:
|
|||
PathEnd *prev_end,
|
||||
bool last);
|
||||
void reportPathEnd(PathEnd *end);
|
||||
void reportPathEnds(PathEndSeq *ends);
|
||||
ReportPath *reportPath() { return report_path_; }
|
||||
void reportPath(Path *path);
|
||||
|
||||
|
|
@ -1291,7 +1292,7 @@ public:
|
|||
PowerResult &pad);
|
||||
PowerResult power(const Instance *inst,
|
||||
const Corner *corner);
|
||||
PwrActivity findClkedActivity(const Pin *pin);
|
||||
PwrActivity activity(const Pin *pin);
|
||||
|
||||
void writeTimingModel(const char *lib_name,
|
||||
const char *cell_name,
|
||||
|
|
|
|||
|
|
@ -0,0 +1,61 @@
|
|||
// OpenSTA, Static Timing Analyzer
|
||||
// Copyright (c) 2024, Parallax Software, Inc.
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
#include "ArcDelayCalc.hh"
|
||||
#include "StringSet.hh"
|
||||
#include "StringSeq.hh"
|
||||
|
||||
#include <tcl.h>
|
||||
|
||||
namespace sta {
|
||||
|
||||
#if TCL_MAJOR_VERSION < 9
|
||||
typedef int Tcl_Size;
|
||||
#endif
|
||||
|
||||
StringSet *
|
||||
tclListSetConstChar(Tcl_Obj *const source,
|
||||
Tcl_Interp *interp);
|
||||
|
||||
StringSeq *
|
||||
tclListSeqConstChar(Tcl_Obj *const source,
|
||||
Tcl_Interp *interp);
|
||||
|
||||
StdStringSet *
|
||||
tclListSetStdString(Tcl_Obj *const source,
|
||||
Tcl_Interp *interp);
|
||||
|
||||
void
|
||||
tclArgError(Tcl_Interp *interp,
|
||||
const char *msg,
|
||||
const char *arg);
|
||||
|
||||
void
|
||||
objectListNext(const char *list,
|
||||
const char *type,
|
||||
// Return values.
|
||||
bool &type_match,
|
||||
const char *&next);
|
||||
|
||||
Tcl_Obj *
|
||||
tclArcDcalcArg(ArcDcalcArg &gate,
|
||||
Tcl_Interp *interp);
|
||||
|
||||
ArcDcalcArg
|
||||
arcDcalcArgTcl(Tcl_Obj *obj,
|
||||
Tcl_Interp *interp);
|
||||
|
||||
} // namespace
|
||||
|
|
@ -219,7 +219,7 @@ TableTemplate *
|
|||
LibertyLibrary::findTableTemplate(const char *name,
|
||||
TableTemplateType type)
|
||||
{
|
||||
return template_maps_[int(type)][name];
|
||||
return template_maps_[int(type)].findKey(name);
|
||||
}
|
||||
|
||||
TableTemplateSeq
|
||||
|
|
|
|||
|
|
@ -103,6 +103,7 @@ LibertyReader::init(const char *filename,
|
|||
saved_port_group_ = nullptr;
|
||||
in_bus_ = false;
|
||||
in_bundle_ = false;
|
||||
in_ccsn_ = false;
|
||||
sequential_ = nullptr;
|
||||
statetable_ = nullptr;
|
||||
timing_ = nullptr;
|
||||
|
|
@ -545,6 +546,24 @@ LibertyReader::defineVisitors()
|
|||
defineAttrVisitor("driver_waveform_name", &LibertyReader::visitDriverWaveformName);
|
||||
defineAttrVisitor("driver_waveform_rise", &LibertyReader::visitDriverWaveformRise);
|
||||
defineAttrVisitor("driver_waveform_fall", &LibertyReader::visitDriverWaveformFall);
|
||||
|
||||
// ccsn (not implemented, this is needed to properly ignore ccsn groups)
|
||||
defineGroupVisitor("ccsn_first_stage", &LibertyReader::beginCcsn,
|
||||
&LibertyReader::endCcsn);
|
||||
defineGroupVisitor("ccsn_last_stage", &LibertyReader::beginCcsn,
|
||||
&LibertyReader::endCcsn);
|
||||
defineGroupVisitor("output_voltage_rise", &LibertyReader::beginCcsn,
|
||||
&LibertyReader::endCcsn);
|
||||
defineGroupVisitor("output_voltage_fall", &LibertyReader::beginCcsn,
|
||||
&LibertyReader::endCcsn);
|
||||
defineGroupVisitor("propagated_noise_low", &LibertyReader::beginCcsn,
|
||||
&LibertyReader::endCcsn);
|
||||
defineGroupVisitor("propagated_noise_high", &LibertyReader::beginCcsn,
|
||||
&LibertyReader::endCcsn);
|
||||
defineGroupVisitor("input_ccb", &LibertyReader::beginCcsn,
|
||||
&LibertyReader::endCcsn);
|
||||
defineGroupVisitor("output_ccb", &LibertyReader::beginCcsn,
|
||||
&LibertyReader::endCcsn);
|
||||
}
|
||||
|
||||
void
|
||||
|
|
@ -2711,7 +2730,7 @@ LibertyReader::endOutputCurrentRiseFall(LibertyGroup *group)
|
|||
void
|
||||
LibertyReader::beginVector(LibertyGroup *group)
|
||||
{
|
||||
if (timing_) {
|
||||
if (timing_ && !in_ccsn_) {
|
||||
beginTable(group, TableTemplateType::output_current, current_scale_);
|
||||
scale_factor_type_ = ScaleFactorType::unknown;
|
||||
reference_time_exists_ = false;
|
||||
|
|
@ -4655,9 +4674,9 @@ LibertyReader::makeTable(LibertyAttr *attr,
|
|||
float scale)
|
||||
{
|
||||
if (attr->isComplex()) {
|
||||
makeTableAxis(0);
|
||||
makeTableAxis(1);
|
||||
makeTableAxis(2);
|
||||
makeTableAxis(0, attr);
|
||||
makeTableAxis(1, attr);
|
||||
makeTableAxis(2, attr);
|
||||
if (axis_[0] && axis_[1] && axis_[2]) {
|
||||
// 3D table
|
||||
// Column index1*size(index2) + index2
|
||||
|
|
@ -4682,7 +4701,7 @@ LibertyReader::makeTable(LibertyAttr *attr,
|
|||
delete table;
|
||||
table_ = make_shared<Table1>(values, axis_[0]);
|
||||
}
|
||||
else {
|
||||
else if (axis_[0] == nullptr && axis_[1] == nullptr && axis_[2] == nullptr) {
|
||||
// scalar
|
||||
FloatTable *table = makeFloatTable(attr, 1, 1, scale);
|
||||
float value = (*(*table)[0])[0];
|
||||
|
|
@ -4717,20 +4736,18 @@ LibertyReader::makeFloatTable(LibertyAttr *attr,
|
|||
else
|
||||
libWarn(1258, attr, "%s is not a list of floats.", attr->name());
|
||||
if (row->size() != cols) {
|
||||
libWarn(1259, attr, "table row has %u columns but axis has %d.",
|
||||
// size_t is long on 64 bit ports.
|
||||
static_cast<unsigned>(row->size()),
|
||||
static_cast<unsigned>(cols));
|
||||
libWarn(1259, attr, "table row has %zu columns but axis has %zu.",
|
||||
row->size(),
|
||||
cols);
|
||||
// Fill out row columns with zeros.
|
||||
for (size_t c = row->size(); c < cols; c++)
|
||||
row->push_back(0.0);
|
||||
}
|
||||
}
|
||||
if (table->size() != rows) {
|
||||
libWarn(1260, attr, "table has %u rows but axis has %d.",
|
||||
// size_t is long on 64 bit ports.
|
||||
static_cast<unsigned>(table->size()),
|
||||
static_cast<unsigned>(rows));
|
||||
libWarn(1260, attr, "table has %zu rows but axis has %zu.",
|
||||
table->size(),
|
||||
rows);
|
||||
// Fill with zero'd rows.
|
||||
for (size_t r = table->size(); r < rows; r++) {
|
||||
FloatSeq *row = new FloatSeq;
|
||||
|
|
@ -4744,7 +4761,8 @@ LibertyReader::makeFloatTable(LibertyAttr *attr,
|
|||
}
|
||||
|
||||
void
|
||||
LibertyReader::makeTableAxis(int index)
|
||||
LibertyReader::makeTableAxis(int index,
|
||||
LibertyAttr *attr)
|
||||
{
|
||||
if (axis_values_[index]) {
|
||||
TableAxisVariable var = axis_[index]->variable();
|
||||
|
|
@ -4754,6 +4772,11 @@ LibertyReader::makeTableAxis(int index)
|
|||
scaleFloats(values, scale);
|
||||
axis_[index] = make_shared<TableAxis>(var, values);
|
||||
}
|
||||
else if (axis_[index] && axis_[index]->values() == nullptr) {
|
||||
libWarn(1344, attr, "Table axis and template missing values.");
|
||||
axis_[index] = nullptr;
|
||||
axis_values_[index] = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////
|
||||
|
|
|
|||
|
|
@ -482,6 +482,10 @@ public:
|
|||
void visitDriverWaveformRiseFall(LibertyAttr *attr,
|
||||
const RiseFall *rf);
|
||||
|
||||
// ccsn (not implemented, this is needed to properly ignore ccsn groups)
|
||||
void beginCcsn(LibertyGroup *) { in_ccsn_ = true; }
|
||||
void endCcsn(LibertyGroup *) { in_ccsn_ = false; }
|
||||
|
||||
// Visitors for derived classes to overload.
|
||||
virtual void beginGroup1(LibertyGroup *) {}
|
||||
virtual void beginGroup2(LibertyGroup *) {}
|
||||
|
|
@ -520,7 +524,8 @@ protected:
|
|||
LibraryAttrVisitor visitor);
|
||||
void parseNames(const char *name_str);
|
||||
void clearAxisValues();
|
||||
void makeTableAxis(int index);
|
||||
void makeTableAxis(int index,
|
||||
LibertyAttr *attr);
|
||||
|
||||
StringSeq *parseNameList(const char *name_list);
|
||||
StdStringSeq parseTokenList(const char *token_str,
|
||||
|
|
@ -629,6 +634,7 @@ protected:
|
|||
StringSeq bus_names_;
|
||||
bool in_bus_;
|
||||
bool in_bundle_;
|
||||
bool in_ccsn_;
|
||||
TableAxisVariable axis_var_[3];
|
||||
FloatSeq *axis_values_[3];
|
||||
int type_bit_from_;
|
||||
|
|
|
|||
|
|
@ -78,7 +78,7 @@ proc_redirect read_spef {
|
|||
$coupling_reduction_factor $reduce]
|
||||
}
|
||||
|
||||
define_cmd_args "report_parasitic_annotation" {-report_unannotated}
|
||||
define_cmd_args "report_parasitic_annotation" {[-report_unannotated]}
|
||||
|
||||
proc_redirect report_parasitic_annotation {
|
||||
parse_key_args "report_parasitic_annotation" args \
|
||||
|
|
|
|||
346
power/Power.cc
346
power/Power.cc
|
|
@ -59,13 +59,12 @@
|
|||
// input_voltage : default_VDD_VSS_input;
|
||||
// pin
|
||||
// output_voltage : default_VDD_VSS_output;
|
||||
//
|
||||
// transition_density = activity / clock_period
|
||||
|
||||
namespace sta {
|
||||
|
||||
using std::abs;
|
||||
using std::max;
|
||||
using std::min;
|
||||
using std::isnormal;
|
||||
|
||||
static bool
|
||||
|
|
@ -87,8 +86,8 @@ static EnumNameMap<PwrActivityOrigin> pwr_activity_origin_map =
|
|||
|
||||
Power::Power(StaState *sta) :
|
||||
StaState(sta),
|
||||
global_activity_{0.0, 0.0, PwrActivityOrigin::unknown},
|
||||
input_activity_{0.1, 0.5, PwrActivityOrigin::input},
|
||||
global_activity_(),
|
||||
input_activity_(), // default set in ensureActivities()
|
||||
seq_activity_map_(100, SeqPinHash(network_), SeqPinEqual()),
|
||||
activities_valid_(false),
|
||||
bdd_(sta)
|
||||
|
|
@ -96,41 +95,41 @@ Power::Power(StaState *sta) :
|
|||
}
|
||||
|
||||
void
|
||||
Power::setGlobalActivity(float activity,
|
||||
Power::setGlobalActivity(float density,
|
||||
float duty)
|
||||
{
|
||||
global_activity_.set(activity, duty, PwrActivityOrigin::global);
|
||||
global_activity_.set(density, duty, PwrActivityOrigin::global);
|
||||
activities_valid_ = false;
|
||||
}
|
||||
|
||||
void
|
||||
Power::setInputActivity(float activity,
|
||||
Power::setInputActivity(float density,
|
||||
float duty)
|
||||
{
|
||||
input_activity_.set(activity, duty, PwrActivityOrigin::input);
|
||||
input_activity_.set(density, duty, PwrActivityOrigin::input);
|
||||
activities_valid_ = false;
|
||||
}
|
||||
|
||||
void
|
||||
Power::setInputPortActivity(const Port *input_port,
|
||||
float activity,
|
||||
float density,
|
||||
float duty)
|
||||
{
|
||||
Instance *top_inst = network_->topInstance();
|
||||
const Pin *pin = network_->findPin(top_inst, input_port);
|
||||
if (pin) {
|
||||
user_activity_map_[pin] = {activity, duty, PwrActivityOrigin::user};
|
||||
user_activity_map_[pin] = {density, duty, PwrActivityOrigin::user};
|
||||
activities_valid_ = false;
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
Power::setUserActivity(const Pin *pin,
|
||||
float activity,
|
||||
float density,
|
||||
float duty,
|
||||
PwrActivityOrigin origin)
|
||||
{
|
||||
user_activity_map_[pin] = {activity, duty, origin};
|
||||
user_activity_map_[pin] = {density, duty, origin};
|
||||
activities_valid_ = false;
|
||||
}
|
||||
|
||||
|
|
@ -152,7 +151,7 @@ Power::setActivity(const Pin *pin,
|
|||
{
|
||||
debugPrint(debug_, "power_activity", 3, "set %s %.2e %.2f %s",
|
||||
network_->pathName(pin),
|
||||
activity.activity(),
|
||||
activity.density(),
|
||||
activity.duty(),
|
||||
pwr_activity_origin_map.find(activity.origin()));
|
||||
activity_map_[pin] = activity;
|
||||
|
|
@ -403,7 +402,7 @@ PropActivityVisitor::visit(Vertex *vertex)
|
|||
Vertex *from_vertex = edge->from(graph_);
|
||||
const Pin *from_pin = from_vertex->pin();
|
||||
PwrActivity &from_activity = power_->activity(from_pin);
|
||||
PwrActivity to_activity(from_activity.activity(),
|
||||
PwrActivity to_activity(from_activity.density(),
|
||||
from_activity.duty(),
|
||||
PwrActivityOrigin::propagated);
|
||||
changed = setActivityCheck(pin, to_activity);
|
||||
|
|
@ -426,13 +425,13 @@ PropActivityVisitor::visit(Vertex *vertex)
|
|||
PwrActivity activity2 = power_->findActivity(enable);
|
||||
float p1 = activity1.duty();
|
||||
float p2 = activity2.duty();
|
||||
PwrActivity activity(activity1.activity() * p2 + activity2.activity() * p1,
|
||||
PwrActivity activity(activity1.density() * p2 + activity2.density() * p1,
|
||||
p1 * p2,
|
||||
PwrActivityOrigin::propagated);
|
||||
changed = setActivityCheck(gclk, activity);
|
||||
debugPrint(debug_, "power_activity", 3, "gated_clk %s %.2e %.2f",
|
||||
network_->pathName(gclk),
|
||||
activity.activity(),
|
||||
activity.density(),
|
||||
activity.duty());
|
||||
}
|
||||
}
|
||||
|
|
@ -466,13 +465,17 @@ bool
|
|||
PropActivityVisitor::setActivityCheck(const Pin *pin,
|
||||
PwrActivity &activity)
|
||||
{
|
||||
float min_rf_slew = power_->getMinRfSlew(pin);
|
||||
float max_density = (min_rf_slew > 0.0) ? 1.0 / min_rf_slew : INF;
|
||||
if (activity.density() > max_density)
|
||||
activity.setDensity(max_density);
|
||||
PwrActivity &prev_activity = power_->activity(pin);
|
||||
float activity_delta = abs(activity.activity() - prev_activity.activity());
|
||||
float density_delta = abs(activity.density() - prev_activity.density());
|
||||
float duty_delta = abs(activity.duty() - prev_activity.duty());
|
||||
if (activity_delta > change_tolerance_
|
||||
if (density_delta > change_tolerance_
|
||||
|| duty_delta > change_tolerance_
|
||||
|| activity.origin() != prev_activity.origin()) {
|
||||
max_change_ = max(max_change_, activity_delta);
|
||||
max_change_ = max(max_change_, density_delta);
|
||||
max_change_ = max(max_change_, duty_delta);
|
||||
power_->setActivity(pin, activity);
|
||||
return true;
|
||||
|
|
@ -517,11 +520,11 @@ Power::evalActivity(FuncExpr *expr,
|
|||
else {
|
||||
DdNode *bdd = bdd_.funcBdd(expr);
|
||||
float duty = evalBddDuty(bdd, inst);
|
||||
float activity = evalBddActivity(bdd, inst);
|
||||
float density = evalBddActivity(bdd, inst);
|
||||
|
||||
Cudd_RecursiveDeref(bdd_.cuddMgr(), bdd);
|
||||
bdd_.clearVarMap();
|
||||
return PwrActivity(activity, duty, PwrActivityOrigin::propagated);
|
||||
return PwrActivity(density, duty, PwrActivityOrigin::propagated);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -588,7 +591,7 @@ float
|
|||
Power::evalBddActivity(DdNode *bdd,
|
||||
const Instance *inst)
|
||||
{
|
||||
float activity = 0.0;
|
||||
float density = 0.0;
|
||||
for (const auto [port, var_node] : bdd_.portVarMap()) {
|
||||
const Pin *pin = findLinkPin(inst, port);
|
||||
if (pin) {
|
||||
|
|
@ -598,21 +601,16 @@ Power::evalBddActivity(DdNode *bdd,
|
|||
Cudd_Ref(diff);
|
||||
float diff_duty = evalBddDuty(diff, inst);
|
||||
Cudd_RecursiveDeref(bdd_.cuddMgr(), diff);
|
||||
float var_act = var_activity.activity() * diff_duty;
|
||||
activity += var_act;
|
||||
if (debug_->check("power_activity", 3)) {
|
||||
const Clock *clk = findClk(pin);
|
||||
float clk_period = clk ? clk->period() : 1.0;
|
||||
debugPrint(debug_, "power_activity", 3, "var %s%s %.3e * %.3f = %.3e",
|
||||
port->name(),
|
||||
clk ? "" : " (unclocked)",
|
||||
var_activity.activity() / clk_period,
|
||||
diff_duty,
|
||||
var_act / clk_period);
|
||||
}
|
||||
float var_density = var_activity.density() * diff_duty;
|
||||
density += var_density;
|
||||
debugPrint(debug_, "power_activity", 3, "var %s %.3e * %.3f = %.3e",
|
||||
port->name(),
|
||||
var_activity.density(),
|
||||
diff_duty,
|
||||
var_density);
|
||||
}
|
||||
}
|
||||
return activity;
|
||||
return density;
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////
|
||||
|
|
@ -628,6 +626,13 @@ Power::ensureActivities()
|
|||
activity_map_.clear();
|
||||
seq_activity_map_.clear();
|
||||
|
||||
// Initialize default input activity (after sdc is defined)
|
||||
// unless it has been set by command.
|
||||
if (input_activity_.density() == 0.0) {
|
||||
float min_period = clockMinPeriod();
|
||||
float density = 0.1 / (min_period != 0.0 ? min_period : 0.0);
|
||||
input_activity_.set(density, 0.5, PwrActivityOrigin::input);
|
||||
}
|
||||
ActivitySrchPred activity_srch_pred(this);
|
||||
BfsFwdIterator bfs(BfsIndex::other, &activity_srch_pred, this);
|
||||
seedActivities(bfs);
|
||||
|
|
@ -722,9 +727,17 @@ Power::seedRegOutputActivities(const Instance *reg,
|
|||
PwrActivity activity = evalActivity(seq->data(), reg);
|
||||
// Register output activity cannnot exceed one transition per clock cycle,
|
||||
// but latch output can.
|
||||
if (seq->isRegister()
|
||||
&& activity.activity() > 1.0)
|
||||
activity.setActivity(1.0);
|
||||
if (seq->isRegister()) {
|
||||
FuncExpr *clk_func = seq->clock();
|
||||
if (clk_func->port()) {
|
||||
const Pin *pin = network_->findPin(reg, clk_func->port());
|
||||
const Clock *clk = findClk(pin);
|
||||
if (clk) {
|
||||
if (activity.density() > 1.0 / clk->period())
|
||||
activity.setDensity(1.0 / clk->period());
|
||||
}
|
||||
}
|
||||
}
|
||||
if (invert)
|
||||
activity.setDuty(1.0 - activity.duty());
|
||||
activity.setOrigin(PwrActivityOrigin::propagated);
|
||||
|
|
@ -740,9 +753,8 @@ Power::power(const Instance *inst,
|
|||
const Corner *corner)
|
||||
{
|
||||
PowerResult result;
|
||||
const Clock *inst_clk = findInstClk(inst);
|
||||
findInternalPower(inst, cell, corner, inst_clk, result);
|
||||
findSwitchingPower(inst, cell, corner, inst_clk, result);
|
||||
findInternalPower(inst, cell, corner, result);
|
||||
findSwitchingPower(inst, cell, corner, result);
|
||||
findLeakagePower(inst, cell, corner, result);
|
||||
return result;
|
||||
}
|
||||
|
|
@ -768,7 +780,6 @@ void
|
|||
Power::findInternalPower(const Instance *inst,
|
||||
LibertyCell *cell,
|
||||
const Corner *corner,
|
||||
const Clock *inst_clk,
|
||||
// Return values.
|
||||
PowerResult &result)
|
||||
{
|
||||
|
|
@ -781,7 +792,7 @@ Power::findInternalPower(const Instance *inst,
|
|||
float load_cap = to_port->direction()->isAnyOutput()
|
||||
? graph_delay_calc_->loadCap(to_pin, dcalc_ap)
|
||||
: 0.0;
|
||||
PwrActivity activity = findClkedActivity(to_pin, inst_clk);
|
||||
PwrActivity activity = findActivity(to_pin);
|
||||
if (to_port->direction()->isAnyOutput())
|
||||
findOutputInternalPower(to_port, inst, cell, activity,
|
||||
load_cap, corner, result);
|
||||
|
|
@ -850,18 +861,18 @@ Power::findInputInternalPower(const Pin *pin,
|
|||
else
|
||||
duty = evalActivity(when, inst).duty();
|
||||
}
|
||||
float port_internal = energy * duty * activity.activity();
|
||||
float port_internal = energy * duty * activity.density();
|
||||
debugPrint(debug_, "power", 2, " %3s %6s %.2f %.2f %9.2e %9.2e %s",
|
||||
port->name(),
|
||||
when ? when->asString() : "",
|
||||
activity.activity() * 1e-9,
|
||||
activity.density() * 1e-9,
|
||||
duty,
|
||||
energy,
|
||||
port_internal,
|
||||
related_pg_pin ? related_pg_pin : "no pg_pin");
|
||||
internal += port_internal;
|
||||
}
|
||||
result.internal() += internal;
|
||||
result.incrInternal(internal);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -879,6 +890,27 @@ Power::getSlew(Vertex *vertex,
|
|||
return delayAsFloat(graph_->slew(vertex, rf, dcalc_ap->index()));
|
||||
}
|
||||
|
||||
float
|
||||
Power::getMinRfSlew(const Pin *pin)
|
||||
{
|
||||
Vertex *vertex, *bidir_vertex;
|
||||
graph_->pinVertices(pin, vertex, bidir_vertex);
|
||||
if (vertex) {
|
||||
const MinMax *min_max = MinMax::min();
|
||||
Slew mm_slew = min_max->initValue();
|
||||
for (const DcalcAnalysisPt *dcalc_ap : corners_->dcalcAnalysisPts()) {
|
||||
DcalcAPIndex ap_index = dcalc_ap->index();
|
||||
const Slew &slew1 = graph_->slew(vertex, RiseFall::rise(), ap_index);
|
||||
const Slew &slew2 = graph_->slew(vertex, RiseFall::fall(), ap_index);
|
||||
Slew slew = delayAsFloat(slew1 + slew2) / 2.0;
|
||||
if (delayGreater(slew, mm_slew, min_max, this))
|
||||
mm_slew = slew;
|
||||
}
|
||||
return mm_slew;
|
||||
}
|
||||
return 0.0;
|
||||
}
|
||||
|
||||
LibertyPort *
|
||||
Power::findExprOutPort(FuncExpr *expr)
|
||||
{
|
||||
|
|
@ -936,11 +968,11 @@ Power::findOutputInternalPower(const LibertyPort *to_port,
|
|||
const LibertyPort *from_corner_port = pwr->relatedPort();
|
||||
if (from_corner_port) {
|
||||
const Pin *from_pin = findLinkPin(inst, from_corner_port);
|
||||
float from_activity = findActivity(from_pin).activity();
|
||||
float from_density = findActivity(from_pin).density();
|
||||
float duty = findInputDuty(inst, func, pwr);
|
||||
const char *related_pg_pin = pwr->relatedPgPin();
|
||||
// Note related_pg_pin may be null.
|
||||
pg_duty_sum[related_pg_pin] += from_activity * duty;
|
||||
pg_duty_sum[related_pg_pin] += from_density * duty;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -982,16 +1014,16 @@ Power::findOutputInternalPower(const LibertyPort *to_port,
|
|||
if (duty_sum_iter != pg_duty_sum.end()) {
|
||||
float duty_sum = duty_sum_iter->second;
|
||||
if (duty_sum != 0.0 && from_pin) {
|
||||
float from_activity = findActivity(from_pin).activity();
|
||||
weight = from_activity * duty / duty_sum;
|
||||
float from_density = findActivity(from_pin).density();
|
||||
weight = from_density * duty / duty_sum;
|
||||
}
|
||||
}
|
||||
float port_internal = weight * energy * to_activity.activity();
|
||||
float port_internal = weight * energy * to_activity.density();
|
||||
debugPrint(debug_, "power", 2, "%3s -> %-3s %6s %.3f %.3f %.3f %9.2e %9.2e %s",
|
||||
from_corner_port ? from_corner_port->name() : "-" ,
|
||||
to_port->name(),
|
||||
when ? when->asString() : "",
|
||||
to_activity.activity() * 1e-9,
|
||||
to_activity.density() * 1e-9,
|
||||
duty,
|
||||
weight,
|
||||
energy,
|
||||
|
|
@ -999,7 +1031,7 @@ Power::findOutputInternalPower(const LibertyPort *to_port,
|
|||
related_pg_pin ? related_pg_pin : "no pg_pin");
|
||||
internal += port_internal;
|
||||
}
|
||||
result.internal() += internal;
|
||||
result.incrInternal(internal);
|
||||
}
|
||||
|
||||
float
|
||||
|
|
@ -1023,7 +1055,7 @@ Power::findInputDuty(const Instance *inst,
|
|||
else if (when)
|
||||
return evalActivity(when, inst).duty();
|
||||
else if (search_->isClock(from_vertex))
|
||||
return 1.0;
|
||||
return 0.5;
|
||||
return 0.5;
|
||||
}
|
||||
}
|
||||
|
|
@ -1068,7 +1100,6 @@ void
|
|||
Power::findSwitchingPower(const Instance *inst,
|
||||
LibertyCell *cell,
|
||||
const Corner *corner,
|
||||
const Clock *inst_clk,
|
||||
// Return values.
|
||||
PowerResult &result)
|
||||
{
|
||||
|
|
@ -1082,17 +1113,17 @@ Power::findSwitchingPower(const Instance *inst,
|
|||
float load_cap = to_port->direction()->isAnyOutput()
|
||||
? graph_delay_calc_->loadCap(to_pin, dcalc_ap)
|
||||
: 0.0;
|
||||
PwrActivity activity = findClkedActivity(to_pin, inst_clk);
|
||||
PwrActivity activity = findActivity(to_pin);
|
||||
if (to_port->direction()->isAnyOutput()) {
|
||||
float volt = portVoltage(corner_cell, to_port, dcalc_ap);
|
||||
float switching = .5 * load_cap * volt * volt * activity.activity();
|
||||
float switching = .5 * load_cap * volt * volt * activity.density();
|
||||
debugPrint(debug_, "power", 2, "switching %s/%s activity = %.2e volt = %.2f %.3e",
|
||||
cell->name(),
|
||||
to_port->name(),
|
||||
activity.activity(),
|
||||
activity.density(),
|
||||
volt,
|
||||
switching);
|
||||
result.switching() += switching;
|
||||
result.incrSwitching(switching);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1160,34 +1191,15 @@ Power::findLeakagePower(const Instance *inst,
|
|||
debugPrint(debug_, "power", 2, "leakage %s %.3e",
|
||||
cell->name(),
|
||||
leakage);
|
||||
result.leakage() += leakage;
|
||||
result.incrLeakage(leakage);
|
||||
}
|
||||
|
||||
// External.
|
||||
PwrActivity
|
||||
Power::findClkedActivity(const Pin *pin)
|
||||
Power::pinActivity(const Pin *pin)
|
||||
{
|
||||
const Instance *inst = network_->instance(pin);
|
||||
const Clock *inst_clk = findInstClk(inst);
|
||||
ensureActivities();
|
||||
return findClkedActivity(pin, inst_clk);
|
||||
}
|
||||
|
||||
PwrActivity
|
||||
Power::findClkedActivity(const Pin *pin,
|
||||
const Clock *inst_clk)
|
||||
{
|
||||
PwrActivity activity = findActivity(pin);
|
||||
const Clock *clk = findClk(pin);
|
||||
if (clk == nullptr)
|
||||
clk = inst_clk;
|
||||
if (clk) {
|
||||
float period = clk->period();
|
||||
if (period > 0.0)
|
||||
return PwrActivity(activity.activity() / period,
|
||||
activity.duty(),
|
||||
activity.origin());
|
||||
}
|
||||
return activity;
|
||||
return findActivity(pin);
|
||||
}
|
||||
|
||||
PwrActivity
|
||||
|
|
@ -1204,7 +1216,7 @@ Power::findActivity(const Pin *pin)
|
|||
}
|
||||
const Clock *clk = findClk(pin);
|
||||
float duty = clockDuty(clk);
|
||||
return PwrActivity(2.0, duty, PwrActivityOrigin::clock);
|
||||
return PwrActivity(2.0 / clk->period(), duty, PwrActivityOrigin::clock);
|
||||
}
|
||||
else if (global_activity_.isSet())
|
||||
return global_activity_;
|
||||
|
|
@ -1243,10 +1255,9 @@ Power::findSeqActivity(const Instance *inst,
|
|||
return global_activity_;
|
||||
else if (hasSeqActivity(inst, port)) {
|
||||
PwrActivity &activity = seqActivity(inst, port);
|
||||
if (activity.origin() != PwrActivityOrigin::unknown)
|
||||
return activity;
|
||||
return activity;
|
||||
}
|
||||
return PwrActivity(0.0, 0.0, PwrActivityOrigin::unknown);
|
||||
return PwrActivity();
|
||||
}
|
||||
|
||||
float
|
||||
|
|
@ -1305,6 +1316,131 @@ Power::findClk(const Pin *to_pin)
|
|||
|
||||
////////////////////////////////////////////////////////////////
|
||||
|
||||
void
|
||||
Power::reportActivityAnnotation(bool report_unannotated,
|
||||
bool report_annotated)
|
||||
{
|
||||
size_t vcd_count = 0;
|
||||
size_t saif_count = 0;
|
||||
size_t input_count = 0;
|
||||
for (auto const& [pin, activity] : user_activity_map_) {
|
||||
PwrActivityOrigin origin = activity.origin();
|
||||
switch (origin) {
|
||||
case PwrActivityOrigin::vcd:
|
||||
vcd_count++;
|
||||
break;
|
||||
case PwrActivityOrigin::saif:
|
||||
saif_count++;
|
||||
break;
|
||||
case PwrActivityOrigin::user:
|
||||
input_count++;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (vcd_count > 0)
|
||||
report_->reportLine("vcd %5zu", vcd_count);
|
||||
if (saif_count > 0)
|
||||
report_->reportLine("saif %5zu", saif_count);
|
||||
if (input_count > 0)
|
||||
report_->reportLine("input %5zu", input_count);
|
||||
size_t pin_count = pinCount();
|
||||
size_t unannotated_count = pin_count - vcd_count - saif_count - input_count;
|
||||
report_->reportLine("unannotated %5zu", unannotated_count);
|
||||
|
||||
if (report_annotated) {
|
||||
PinSeq annotated_pins;
|
||||
for (auto const& [pin, activity] : user_activity_map_)
|
||||
annotated_pins.push_back(pin);
|
||||
sort(annotated_pins.begin(), annotated_pins.end(), PinPathNameLess(sdc_network_));
|
||||
report_->reportLine("Annotated pins:");
|
||||
for (const Pin *pin : annotated_pins) {
|
||||
const PwrActivity &activity = user_activity_map_[pin];
|
||||
PwrActivityOrigin origin = activity.origin();
|
||||
const char *origin_name = pwr_activity_origin_map.find(origin);
|
||||
report_->reportLine("%5s %s",
|
||||
origin_name,
|
||||
sdc_network_->pathName(pin));
|
||||
}
|
||||
}
|
||||
if (report_unannotated) {
|
||||
PinSeq unannotated_pins;
|
||||
findUnannotatedPins(network_->topInstance(), unannotated_pins);
|
||||
LeafInstanceIterator *inst_iter = network_->leafInstanceIterator();
|
||||
while (inst_iter->hasNext()) {
|
||||
const Instance *inst = inst_iter->next();
|
||||
findUnannotatedPins(inst, unannotated_pins);
|
||||
}
|
||||
delete inst_iter;
|
||||
|
||||
sort(unannotated_pins.begin(), unannotated_pins.end(),
|
||||
PinPathNameLess(sdc_network_));
|
||||
report_->reportLine("Unannotated pins:");
|
||||
for (const Pin *pin : unannotated_pins) {
|
||||
report_->reportLine(" %s", sdc_network_->pathName(pin));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
Power::findUnannotatedPins(const Instance *inst,
|
||||
PinSeq &unannotated_pins)
|
||||
{
|
||||
InstancePinIterator *pin_iter = network_->pinIterator(inst);
|
||||
while (pin_iter->hasNext()) {
|
||||
const Pin *pin = pin_iter->next();
|
||||
if (!network_->direction(pin)->isInternal()
|
||||
&& user_activity_map_.find(pin) == user_activity_map_.end())
|
||||
unannotated_pins.push_back(pin);
|
||||
}
|
||||
delete pin_iter;
|
||||
}
|
||||
|
||||
// leaf pins - internal pins + top instance pins
|
||||
size_t
|
||||
Power::pinCount()
|
||||
{
|
||||
size_t count = 0;
|
||||
LeafInstanceIterator *leaf_iter = network_->leafInstanceIterator();
|
||||
while (leaf_iter->hasNext()) {
|
||||
Instance *leaf = leaf_iter->next();
|
||||
InstancePinIterator *pin_iter = network_->pinIterator(leaf);
|
||||
while (pin_iter->hasNext()) {
|
||||
const Pin *pin = pin_iter->next();
|
||||
if (!network_->direction(pin)->isInternal())
|
||||
count++;
|
||||
}
|
||||
delete pin_iter;
|
||||
}
|
||||
delete leaf_iter;
|
||||
|
||||
InstancePinIterator *pin_iter = network_->pinIterator(network_->topInstance());
|
||||
while (pin_iter->hasNext()) {
|
||||
pin_iter->next();
|
||||
count++;
|
||||
}
|
||||
delete pin_iter;
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
float
|
||||
Power::clockMinPeriod()
|
||||
{
|
||||
ClockSeq *clks = sdc_->clocks();
|
||||
if (clks && !clks->empty()) {
|
||||
float min_period = INF;
|
||||
for (const Clock *clk : *clks)
|
||||
min_period = min(min_period, clk->period());
|
||||
return min_period;
|
||||
}
|
||||
else
|
||||
return 0.0;
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////
|
||||
|
||||
PowerResult::PowerResult() :
|
||||
internal_(0.0),
|
||||
switching_(0.0),
|
||||
|
|
@ -1326,6 +1462,24 @@ PowerResult::total() const
|
|||
return internal_ + switching_ + leakage_;
|
||||
}
|
||||
|
||||
void
|
||||
PowerResult::incrInternal(float pwr)
|
||||
{
|
||||
internal_ += pwr;
|
||||
}
|
||||
|
||||
void
|
||||
PowerResult::incrSwitching(float pwr)
|
||||
{
|
||||
switching_ += pwr;
|
||||
}
|
||||
|
||||
void
|
||||
PowerResult::incrLeakage(float pwr)
|
||||
{
|
||||
leakage_ += pwr;
|
||||
}
|
||||
|
||||
void
|
||||
PowerResult::incr(PowerResult &result)
|
||||
{
|
||||
|
|
@ -1336,17 +1490,17 @@ PowerResult::incr(PowerResult &result)
|
|||
|
||||
////////////////////////////////////////////////////////////////
|
||||
|
||||
PwrActivity::PwrActivity(float activity,
|
||||
PwrActivity::PwrActivity(float density,
|
||||
float duty,
|
||||
PwrActivityOrigin origin) :
|
||||
activity_(activity),
|
||||
density_(density),
|
||||
duty_(duty),
|
||||
origin_(origin)
|
||||
{
|
||||
}
|
||||
|
||||
PwrActivity::PwrActivity() :
|
||||
activity_(0.0),
|
||||
density_(0.0),
|
||||
duty_(0.0),
|
||||
origin_(PwrActivityOrigin::unknown)
|
||||
{
|
||||
|
|
@ -1354,9 +1508,9 @@ PwrActivity::PwrActivity() :
|
|||
}
|
||||
|
||||
void
|
||||
PwrActivity::setActivity(float activity)
|
||||
PwrActivity::setDensity(float density)
|
||||
{
|
||||
activity_ = activity;
|
||||
density_ = density;
|
||||
}
|
||||
|
||||
void
|
||||
|
|
@ -1372,11 +1526,11 @@ PwrActivity::setOrigin(PwrActivityOrigin origin)
|
|||
}
|
||||
|
||||
void
|
||||
PwrActivity::set(float activity,
|
||||
PwrActivity::set(float density,
|
||||
float duty,
|
||||
PwrActivityOrigin origin)
|
||||
{
|
||||
activity_ = activity;
|
||||
density_ = density;
|
||||
duty_ = duty;
|
||||
origin_ = origin;
|
||||
check();
|
||||
|
|
@ -1385,11 +1539,11 @@ PwrActivity::set(float activity,
|
|||
void
|
||||
PwrActivity::check()
|
||||
{
|
||||
// Activities can get very small from multiplying probabilities
|
||||
// Densities can get very small from multiplying probabilities
|
||||
// through deep chains of logic. Clip them to prevent floating
|
||||
// point anomalies.
|
||||
if (abs(activity_) < min_activity)
|
||||
activity_ = 0.0;
|
||||
if (abs(density_) < min_density)
|
||||
density_ = 0.0;
|
||||
}
|
||||
|
||||
bool
|
||||
|
|
|
|||
|
|
@ -84,15 +84,17 @@ public:
|
|||
void setInputPortActivity(const Port *input_port,
|
||||
float activity,
|
||||
float duty);
|
||||
PwrActivity &activity(const Pin *pin);
|
||||
PwrActivity pinActivity(const Pin *pin);
|
||||
void setUserActivity(const Pin *pin,
|
||||
float activity,
|
||||
float duty,
|
||||
PwrActivityOrigin origin);
|
||||
// Activity is toggles per second.
|
||||
PwrActivity findClkedActivity(const Pin *pin);
|
||||
void reportActivityAnnotation(bool report_unannotated,
|
||||
bool report_annotated);
|
||||
float clockMinPeriod();
|
||||
|
||||
protected:
|
||||
PwrActivity &activity(const Pin *pin);
|
||||
bool inClockNetwork(const Instance *inst);
|
||||
void powerInside(const Instance *hinst,
|
||||
const Corner *corner,
|
||||
|
|
@ -110,6 +112,7 @@ protected:
|
|||
bool hasActivity(const Pin *pin);
|
||||
void setActivity(const Pin *pin,
|
||||
PwrActivity &activity);
|
||||
PwrActivity findActivity(const Pin *pin);
|
||||
|
||||
PowerResult power(const Instance *inst,
|
||||
LibertyCell *cell,
|
||||
|
|
@ -117,7 +120,6 @@ protected:
|
|||
void findInternalPower(const Instance *inst,
|
||||
LibertyCell *cell,
|
||||
const Corner *corner,
|
||||
const Clock *inst_clk,
|
||||
// Return values.
|
||||
PowerResult &result);
|
||||
void findInputInternalPower(const Pin *to_pin,
|
||||
|
|
@ -145,18 +147,15 @@ protected:
|
|||
void findSwitchingPower(const Instance *inst,
|
||||
LibertyCell *cell,
|
||||
const Corner *corner,
|
||||
const Clock *inst_clk,
|
||||
// Return values.
|
||||
PowerResult &result);
|
||||
float getSlew(Vertex *vertex,
|
||||
const RiseFall *rf,
|
||||
const Corner *corner);
|
||||
float getMinRfSlew(const Pin *pin);
|
||||
const Clock *findInstClk(const Instance *inst);
|
||||
const Clock *findClk(const Pin *to_pin);
|
||||
float clockDuty(const Clock *clk);
|
||||
PwrActivity findClkedActivity(const Pin *pin,
|
||||
const Clock *inst_clk);
|
||||
PwrActivity findActivity(const Pin *pin);
|
||||
PwrActivity findSeqActivity(const Instance *inst,
|
||||
LibertyPort *port);
|
||||
float portVoltage(LibertyCell *cell,
|
||||
|
|
@ -198,6 +197,9 @@ protected:
|
|||
const Instance *inst);
|
||||
float evalBddDuty(DdNode *bdd,
|
||||
const Instance *inst);
|
||||
void findUnannotatedPins(const Instance *inst,
|
||||
PinSeq &unannotated_pins);
|
||||
size_t pinCount();
|
||||
|
||||
private:
|
||||
// Port/pin activities set by set_pin_activity.
|
||||
|
|
|
|||
|
|
@ -18,9 +18,9 @@
|
|||
|
||||
%{
|
||||
#include "Sta.hh"
|
||||
#include "Sdc.hh"
|
||||
#include "power/Power.hh"
|
||||
#include "power/VcdReader.hh"
|
||||
#include "power/ReadVcdActivities.hh"
|
||||
#include "power/SaifReader.hh"
|
||||
|
||||
using namespace sta;
|
||||
|
|
@ -102,6 +102,13 @@ set_power_pin_activity(const Pin *pin,
|
|||
return power->setUserActivity(pin, activity, duty, PwrActivityOrigin::user);
|
||||
}
|
||||
|
||||
float
|
||||
clock_min_period()
|
||||
{
|
||||
Power *power = Sta::sta()->power();
|
||||
return power->clockMinPeriod();
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////
|
||||
|
||||
void
|
||||
|
|
@ -113,22 +120,6 @@ read_vcd_file(const char *filename,
|
|||
readVcdActivities(filename, scope, sta);
|
||||
}
|
||||
|
||||
void
|
||||
report_vcd_waveforms(const char *filename)
|
||||
{
|
||||
Sta *sta = Sta::sta();
|
||||
reportVcdWaveforms(filename, sta);
|
||||
}
|
||||
|
||||
// debugging
|
||||
void
|
||||
report_vcd_var_values(const char *filename,
|
||||
const char *var_name)
|
||||
{
|
||||
Sta *sta = Sta::sta();
|
||||
reportVcdVarValues(filename, var_name, sta);
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////
|
||||
|
||||
bool
|
||||
|
|
@ -140,4 +131,13 @@ read_saif_file(const char *filename,
|
|||
return readSaif(filename, scope, sta);
|
||||
}
|
||||
|
||||
void
|
||||
report_activity_annotation_cmd(bool report_unannotated,
|
||||
bool report_annotated)
|
||||
{
|
||||
Power *power = Sta::sta()->power();
|
||||
power->reportActivityAnnotation(report_unannotated,
|
||||
report_annotated);
|
||||
}
|
||||
|
||||
%} // inline
|
||||
|
|
|
|||
|
|
@ -217,46 +217,67 @@ define_cmd_args "set_power_activity" { [-global]\
|
|||
[-input]\
|
||||
[-input_ports ports]\
|
||||
[-pins pins]\
|
||||
[-activity activity]\
|
||||
[-duty duty] }
|
||||
[-activity activity | -density density]\
|
||||
[-duty duty]\
|
||||
[-clock clock]}
|
||||
|
||||
proc set_power_activity { args } {
|
||||
parse_key_args "set_power_activity" args \
|
||||
keys {-input_ports -pins -activity -duty} \
|
||||
keys {-input_ports -pins -activity -density -duty -clock} \
|
||||
flags {-global -input}
|
||||
|
||||
check_argc_eq0 "set_power_activity" $args
|
||||
set activity 0.0
|
||||
if { [info exists keys(-activity)] && [info exists keys(-density)] \
|
||||
|| ![info exists keys(-activity)] && ![info exists keys(-density)] } {
|
||||
sta_error 306 "Specify -activity or -density."
|
||||
}
|
||||
|
||||
set density 0.0
|
||||
if { [info exists keys(-activity)] } {
|
||||
set activity $keys(-activity)
|
||||
check_float "activity" $activity
|
||||
if { $activity < 0.0 } {
|
||||
sta_warn 301 "activity should be 0.0 to 1.0 or 2.0"
|
||||
check_positive_float "activity" $activity
|
||||
if { [info exists keys(-clock)] } {
|
||||
set clk [get_clock_warn "-clock" $keys(-clock)]
|
||||
} else {
|
||||
set clks [get_clocks]
|
||||
if { $clks == {} } {
|
||||
sta_error 307 "-activity requires a clock to be defined"
|
||||
}
|
||||
}
|
||||
set density [expr $activity / [clock_min_period]]
|
||||
}
|
||||
|
||||
if { [info exists keys(-density)] } {
|
||||
set density $keys(-density)
|
||||
check_positive_float "density" $density
|
||||
set density [expr $density / [time_ui_sta 1.0]]
|
||||
if { [info exists keys(-clock)] } {
|
||||
sta_warn 302 "-clock ignored for -density"
|
||||
}
|
||||
}
|
||||
set duty 0.5
|
||||
if { [info exists keys(-duty)] } {
|
||||
set duty $keys(-duty)
|
||||
check_float "duty" $duty
|
||||
if { $duty < 0.0 || $duty > 1.0 } {
|
||||
sta_warn 302 "duty should be 0.0 to 1.0"
|
||||
if { $duty < 0.0 || $duty > 1.0 } {i
|
||||
sta_error 302 "duty should be 0.0 to 1.0"
|
||||
}
|
||||
}
|
||||
|
||||
if { [info exists flags(-global)] } {
|
||||
set_power_global_activity $activity $duty
|
||||
set_power_global_activity $density $duty
|
||||
}
|
||||
if { [info exists flags(-input)] } {
|
||||
set_power_input_activity $activity $duty
|
||||
set_power_input_activity $density $duty
|
||||
}
|
||||
if { [info exists keys(-input_ports)] } {
|
||||
set ports [get_ports_error "input_ports" $keys(-input_ports)]
|
||||
foreach port $ports {
|
||||
if { [get_property $port "direction"] == "input" } {
|
||||
if { [sta::is_clock_src [sta::get_port_pin $port]] } {
|
||||
if { [is_clock_src [sta::get_port_pin $port]] } {
|
||||
sta_warn 303 "activity cannot be set on clock ports."
|
||||
} else {
|
||||
set_power_input_port_activity $port $activity $duty
|
||||
set_power_input_port_activity $port $density $duty
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -264,7 +285,7 @@ proc set_power_activity { args } {
|
|||
if { [info exists keys(-pins)] } {
|
||||
set pins [get_pins $keys(-pins)]
|
||||
foreach pin $pins {
|
||||
set_power_pin_activity $pin $activity $duty
|
||||
set_power_pin_activity $pin $density $duty
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -323,6 +344,20 @@ proc read_saif { args } {
|
|||
|
||||
################################################################
|
||||
|
||||
define_cmd_args "report_activity_annotation" { [-report_unannotated] \
|
||||
[-report_annotated] }
|
||||
|
||||
proc_redirect report_activity_annotation {
|
||||
parse_key_args "report_activity_annotation" args \
|
||||
keys {} flags {-report_unannotated -report_annotated}
|
||||
check_argc_eq0 "report_activity_annotation" $args
|
||||
set report_unannotated [info exists flags(-report_unannotated)]
|
||||
set report_annotated [info exists flags(-report_annotated)];
|
||||
report_activity_annotation_cmd $report_unannotated $report_annotated
|
||||
}
|
||||
|
||||
################################################################
|
||||
|
||||
proc power_find_nan { } {
|
||||
set corner [cmd_corner]
|
||||
foreach inst [network_leaf_instances] {
|
||||
|
|
|
|||
|
|
@ -1,258 +0,0 @@
|
|||
// OpenSTA, Static Timing Analyzer
|
||||
// Copyright (c) 2024, Parallax Software, Inc.
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
#include "ReadVcdActivities.hh"
|
||||
|
||||
#include <inttypes.h>
|
||||
#include <set>
|
||||
|
||||
#include "VcdReader.hh"
|
||||
#include "Debug.hh"
|
||||
#include "Network.hh"
|
||||
#include "VerilogNamespace.hh"
|
||||
#include "ParseBus.hh"
|
||||
#include "Sdc.hh"
|
||||
#include "Power.hh"
|
||||
#include "Sta.hh"
|
||||
|
||||
namespace sta {
|
||||
|
||||
using std::abs;
|
||||
using std::min;
|
||||
using std::to_string;
|
||||
|
||||
class ReadVcdActivities : public StaState
|
||||
{
|
||||
public:
|
||||
ReadVcdActivities(const char *filename,
|
||||
const char *scope,
|
||||
Sta *sta);
|
||||
void readActivities();
|
||||
|
||||
private:
|
||||
void setActivities();
|
||||
void setVarActivity(VcdVar *var,
|
||||
string &var_name,
|
||||
const VcdValues &var_value);
|
||||
void setVarActivity(const char *pin_name,
|
||||
const VcdValues &var_values,
|
||||
int value_bit);
|
||||
void findVarActivity(const VcdValues &var_values,
|
||||
int value_bit,
|
||||
// Return values.
|
||||
double &transition_count,
|
||||
double &activity,
|
||||
double &duty);
|
||||
void checkClkPeriod(const Pin *pin,
|
||||
double transition_count);
|
||||
|
||||
const char *filename_;
|
||||
const char *scope_;
|
||||
Vcd vcd_;
|
||||
double clk_period_;
|
||||
Sta *sta_;
|
||||
Power *power_;
|
||||
std::set<const Pin*> annotated_pins_;
|
||||
|
||||
static constexpr double sim_clk_period_tolerance_ = .1;
|
||||
};
|
||||
|
||||
void
|
||||
readVcdActivities(const char *filename,
|
||||
const char *scope,
|
||||
Sta *sta)
|
||||
{
|
||||
ReadVcdActivities reader(filename, scope, sta);
|
||||
reader.readActivities();
|
||||
}
|
||||
|
||||
ReadVcdActivities::ReadVcdActivities(const char *filename,
|
||||
const char *scope,
|
||||
Sta *sta) :
|
||||
StaState(sta),
|
||||
filename_(filename),
|
||||
scope_(scope),
|
||||
vcd_(sta),
|
||||
clk_period_(0.0),
|
||||
sta_(sta),
|
||||
power_(sta->power())
|
||||
{
|
||||
}
|
||||
|
||||
void
|
||||
ReadVcdActivities::readActivities()
|
||||
{
|
||||
vcd_ = readVcdFile(filename_, sta_);
|
||||
|
||||
clk_period_ = INF;
|
||||
for (Clock *clk : *sta_->sdc()->clocks())
|
||||
clk_period_ = min(static_cast<double>(clk->period()), clk_period_);
|
||||
|
||||
if (vcd_.timeMax() > 0)
|
||||
setActivities();
|
||||
else
|
||||
report_->warn(1450, "VCD max time is zero.");
|
||||
report_->reportLine("Annotated %zu pin activities.", annotated_pins_.size());
|
||||
}
|
||||
|
||||
void
|
||||
ReadVcdActivities::setActivities()
|
||||
{
|
||||
size_t scope_length = strlen(scope_);
|
||||
for (VcdVar *var : vcd_.vars()) {
|
||||
const VcdValues &var_values = vcd_.values(var);
|
||||
if (!var_values.empty()
|
||||
&& (var->type() == VcdVarType::wire
|
||||
|| var->type() == VcdVarType::reg)) {
|
||||
string var_name = var->name();
|
||||
// string::starts_with in c++20
|
||||
if (scope_length) {
|
||||
if (var_name.substr(0, scope_length) == scope_) {
|
||||
var_name = var_name.substr(scope_length + 1);
|
||||
setVarActivity(var, var_name, var_values);
|
||||
}
|
||||
}
|
||||
else
|
||||
setVarActivity(var, var_name, var_values);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
ReadVcdActivities::setVarActivity(VcdVar *var,
|
||||
string &var_name,
|
||||
const VcdValues &var_values)
|
||||
{
|
||||
if (var->width() == 1) {
|
||||
string sta_name = netVerilogToSta(var_name.c_str());
|
||||
setVarActivity(sta_name.c_str(), var_values, 0);
|
||||
}
|
||||
else {
|
||||
bool is_bus, is_range, subscript_wild;
|
||||
string bus_name;
|
||||
int from, to;
|
||||
parseBusName(var_name.c_str(), '[', ']', '\\',
|
||||
is_bus, is_range, bus_name, from, to, subscript_wild);
|
||||
if (is_bus) {
|
||||
string sta_bus_name = netVerilogToSta(bus_name.c_str());
|
||||
int value_bit = 0;
|
||||
if (to < from) {
|
||||
for (int bus_bit = to; bus_bit <= from; bus_bit++) {
|
||||
string pin_name = sta_bus_name;
|
||||
pin_name += '[';
|
||||
pin_name += to_string(bus_bit);
|
||||
pin_name += ']';
|
||||
setVarActivity(pin_name.c_str(), var_values, value_bit);
|
||||
value_bit++;
|
||||
}
|
||||
}
|
||||
else {
|
||||
for (int bus_bit = to; bus_bit >= from; bus_bit--) {
|
||||
string pin_name = sta_bus_name;
|
||||
pin_name += '[';
|
||||
pin_name += to_string(bus_bit);
|
||||
pin_name += ']';
|
||||
setVarActivity(pin_name.c_str(), var_values, value_bit);
|
||||
value_bit++;
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
report_->warn(1451, "problem parsing bus %s.", var_name.c_str());
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
ReadVcdActivities::setVarActivity(const char *pin_name,
|
||||
const VcdValues &var_values,
|
||||
int value_bit)
|
||||
{
|
||||
const Pin *pin = sdc_network_->findPin(pin_name);
|
||||
if (pin) {
|
||||
debugPrint(debug_, "read_vcd_activities", 3, "%s values", pin_name);
|
||||
double transition_count, activity, duty;
|
||||
findVarActivity(var_values, value_bit,
|
||||
transition_count, activity, duty);
|
||||
debugPrint(debug_, "read_vcd_activities", 1,
|
||||
"%s transitions %.1f activity %.2f duty %.2f",
|
||||
pin_name,
|
||||
transition_count,
|
||||
activity,
|
||||
duty);
|
||||
if (sdc_->isLeafPinClock(pin))
|
||||
checkClkPeriod(pin, transition_count);
|
||||
power_->setUserActivity(pin, activity, duty, PwrActivityOrigin::vcd);
|
||||
annotated_pins_.insert(pin);
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
ReadVcdActivities::findVarActivity(const VcdValues &var_values,
|
||||
int value_bit,
|
||||
// Return values.
|
||||
double &transition_count,
|
||||
double &activity,
|
||||
double &duty)
|
||||
{
|
||||
transition_count = 0.0;
|
||||
char prev_value = var_values[0].value(value_bit);
|
||||
VcdTime prev_time = var_values[0].time();
|
||||
VcdTime high_time = 0;
|
||||
for (const VcdValue &var_value : var_values) {
|
||||
VcdTime time = var_value.time();
|
||||
char value = var_value.value(value_bit);
|
||||
debugPrint(debug_, "read_vcd_activities", 3, " %" PRId64 " %c", time, value);
|
||||
if (prev_value == '1')
|
||||
high_time += time - prev_time;
|
||||
if (value != prev_value)
|
||||
transition_count += (value == 'X'
|
||||
|| value == 'Z'
|
||||
|| prev_value == 'X'
|
||||
|| prev_value == 'Z')
|
||||
? .5
|
||||
: 1.0;
|
||||
prev_time = time;
|
||||
prev_value = value;
|
||||
}
|
||||
VcdTime time_max = vcd_.timeMax();
|
||||
if (prev_value == '1')
|
||||
high_time += time_max - prev_time;
|
||||
duty = static_cast<double>(high_time) / time_max;
|
||||
activity = transition_count / (time_max * vcd_.timeScale() / clk_period_);
|
||||
}
|
||||
|
||||
void
|
||||
ReadVcdActivities::checkClkPeriod(const Pin *pin,
|
||||
double transition_count)
|
||||
{
|
||||
VcdTime time_max = vcd_.timeMax();
|
||||
double sim_period = time_max * vcd_.timeScale() / (transition_count / 2.0);
|
||||
|
||||
ClockSet *clks = sdc_->findLeafPinClocks(pin);
|
||||
if (clks) {
|
||||
for (Clock *clk : *clks) {
|
||||
double clk_period = clk->period();
|
||||
if (abs((clk_period - sim_period) / clk_period) > .1)
|
||||
// Warn if sim clock period differs from SDC by 10%.
|
||||
report_->warn(1452, "clock %s vcd period %s differs from SDC clock period %s",
|
||||
clk->name(),
|
||||
delayAsString(sim_period, this),
|
||||
delayAsString(clk_period, this));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -1,28 +0,0 @@
|
|||
// OpenSTA, Static Timing Analyzer
|
||||
// Copyright (c) 2024, Parallax Software, Inc.
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
#pragma once
|
||||
|
||||
namespace sta {
|
||||
|
||||
class Sta;
|
||||
|
||||
void
|
||||
readVcdActivities(const char *filename,
|
||||
const char *scope,
|
||||
Sta *sta);
|
||||
|
||||
} // namespace
|
||||
|
|
@ -23,6 +23,7 @@
|
|||
#include "Debug.hh"
|
||||
#include "Report.hh"
|
||||
#include "Network.hh"
|
||||
#include "PortDirection.hh"
|
||||
#include "Sdc.hh"
|
||||
#include "Power.hh"
|
||||
#include "power/SaifReaderPvt.hh"
|
||||
|
|
@ -62,7 +63,6 @@ SaifReader::SaifReader(const char *filename,
|
|||
escape_('\\'),
|
||||
timescale_(1.0E-9F), // default units of ns
|
||||
duration_(0.0),
|
||||
clk_period_(0.0),
|
||||
in_scope_level_(0),
|
||||
power_(sta->power())
|
||||
{
|
||||
|
|
@ -78,10 +78,6 @@ SaifReader::read()
|
|||
// Use zlib to uncompress gzip'd files automagically.
|
||||
stream_ = gzopen(filename_, "rb");
|
||||
if (stream_) {
|
||||
clk_period_ = INF;
|
||||
for (Clock *clk : *sdc_->clocks())
|
||||
clk_period_ = min(static_cast<double>(clk->period()), clk_period_);
|
||||
|
||||
saif_scope_.clear();
|
||||
in_scope_level_ = 0;
|
||||
annotated_pins_.clear();
|
||||
|
|
@ -178,20 +174,22 @@ SaifReader::setNetDurations(const char *net_name,
|
|||
if (parent) {
|
||||
string unescaped_name = unescaped(net_name);
|
||||
const Pin *pin = sdc_network_->findPin(parent, unescaped_name.c_str());
|
||||
if (pin) {
|
||||
if (pin
|
||||
&& !sdc_network_->isHierarchical(pin)
|
||||
&& !sdc_network_->direction(pin)->isInternal()) {
|
||||
double t1 = durations[static_cast<int>(SaifState::T1)];
|
||||
float duty = t1 / duration_;
|
||||
double tc = durations[static_cast<int>(SaifState::TC)];
|
||||
float activity = tc / (duration_ * timescale_ / clk_period_);
|
||||
float density = tc / (duration_ * timescale_);
|
||||
debugPrint(debug_, "read_saif", 2,
|
||||
"%s duty %.0f / %" PRIu64 " = %.2f tc %.0f activity %.2f",
|
||||
"%s duty %.0f / %" PRIu64 " = %.2f tc %.0f density %.2f",
|
||||
sdc_network_->pathName(pin),
|
||||
t1,
|
||||
duration_,
|
||||
duty,
|
||||
tc,
|
||||
activity);
|
||||
power_->setUserActivity(pin, activity, duty, PwrActivityOrigin::saif);
|
||||
density);
|
||||
power_->setUserActivity(pin, density, duty, PwrActivityOrigin::saif);
|
||||
annotated_pins_.insert(pin);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -96,7 +96,6 @@ private:
|
|||
char escape_;
|
||||
double timescale_;
|
||||
int64_t duration_;
|
||||
double clk_period_;
|
||||
|
||||
vector<string> saif_scope_; // Scope during parsing.
|
||||
size_t in_scope_level_;
|
||||
|
|
|
|||
213
power/Vcd.cc
213
power/Vcd.cc
|
|
@ -1,213 +0,0 @@
|
|||
// OpenSTA, Static Timing Analyzer
|
||||
// Copyright (c) 2024, Parallax Software, Inc.
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
#include "Vcd.hh"
|
||||
|
||||
#include "Report.hh"
|
||||
|
||||
namespace sta {
|
||||
|
||||
Vcd::Vcd(StaState *sta) :
|
||||
StaState(sta),
|
||||
time_scale_(1.0),
|
||||
time_unit_scale_(1.0),
|
||||
max_var_name_length_(0),
|
||||
max_var_width_(0),
|
||||
min_delta_time_(0),
|
||||
time_max_(0)
|
||||
{
|
||||
}
|
||||
|
||||
Vcd::Vcd(const Vcd &vcd) :
|
||||
StaState(vcd),
|
||||
date_(vcd.date_),
|
||||
comment_(vcd.comment_),
|
||||
version_(vcd.version_),
|
||||
time_scale_(vcd.time_scale_),
|
||||
time_unit_(vcd.time_unit_),
|
||||
time_unit_scale_(vcd.time_unit_scale_),
|
||||
vars_(vcd.vars_),
|
||||
var_name_map_(vcd.var_name_map_),
|
||||
max_var_name_length_(vcd.max_var_name_length_),
|
||||
max_var_width_(vcd.max_var_width_),
|
||||
id_values_map_(vcd.id_values_map_),
|
||||
min_delta_time_(vcd.min_delta_time_),
|
||||
time_max_(vcd.time_max_)
|
||||
{
|
||||
}
|
||||
|
||||
Vcd&
|
||||
Vcd::operator=(Vcd &&vcd1)
|
||||
{
|
||||
date_ = vcd1.date_;
|
||||
comment_ = vcd1.comment_;
|
||||
version_ = vcd1.version_;
|
||||
time_scale_ = vcd1.time_scale_;
|
||||
time_unit_ = vcd1.time_unit_;
|
||||
time_unit_scale_ = vcd1.time_unit_scale_;
|
||||
vars_ = vcd1.vars_;
|
||||
var_name_map_ = vcd1.var_name_map_;
|
||||
max_var_name_length_ = vcd1.max_var_name_length_;
|
||||
max_var_width_ = vcd1.max_var_width_;
|
||||
id_values_map_ = vcd1.id_values_map_;
|
||||
min_delta_time_ = vcd1.min_delta_time_;
|
||||
time_max_ = vcd1.time_max_;
|
||||
|
||||
vcd1.vars_.clear();
|
||||
return *this;
|
||||
}
|
||||
|
||||
Vcd::~Vcd()
|
||||
{
|
||||
for (VcdVar *var : vars_)
|
||||
delete var;
|
||||
}
|
||||
|
||||
void
|
||||
Vcd::setTimeUnit(const string &time_unit,
|
||||
double time_unit_scale)
|
||||
{
|
||||
time_unit_ = time_unit;
|
||||
time_unit_scale_ = time_unit_scale;
|
||||
}
|
||||
|
||||
void
|
||||
Vcd::setDate(const string &date)
|
||||
{
|
||||
date_ = date;
|
||||
}
|
||||
|
||||
void
|
||||
Vcd::setComment(const string &comment)
|
||||
{
|
||||
comment_ = comment;
|
||||
}
|
||||
|
||||
void
|
||||
Vcd::setVersion(const string &version)
|
||||
{
|
||||
version_ = version;
|
||||
}
|
||||
|
||||
void
|
||||
Vcd::setTimeScale(double time_scale)
|
||||
{
|
||||
time_scale_ = time_scale;
|
||||
}
|
||||
|
||||
void
|
||||
Vcd::setMinDeltaTime(VcdTime min_delta_time)
|
||||
{
|
||||
min_delta_time_ = min_delta_time;
|
||||
}
|
||||
|
||||
void
|
||||
Vcd::setTimeMax(VcdTime time_max)
|
||||
{
|
||||
time_max_ = time_max;
|
||||
}
|
||||
|
||||
void
|
||||
Vcd::makeVar(string &name,
|
||||
VcdVarType type,
|
||||
int width,
|
||||
string &id)
|
||||
{
|
||||
VcdVar *var = new VcdVar(name, type, width, id);
|
||||
vars_.push_back(var);
|
||||
var_name_map_[name] = var;
|
||||
max_var_name_length_ = std::max(max_var_name_length_, name.size());
|
||||
max_var_width_ = std::max(max_var_width_, width);
|
||||
// Make entry for var ID.
|
||||
id_values_map_[id].clear();
|
||||
}
|
||||
|
||||
VcdVar *
|
||||
Vcd::var(const string name)
|
||||
{
|
||||
return var_name_map_[name];
|
||||
}
|
||||
|
||||
bool
|
||||
Vcd::varIdValid(string &id)
|
||||
{
|
||||
return id_values_map_.find(id) != id_values_map_.end();
|
||||
}
|
||||
|
||||
void
|
||||
Vcd::varAppendValue(string &id,
|
||||
VcdTime time,
|
||||
char value)
|
||||
{
|
||||
VcdValues &values = id_values_map_[id];
|
||||
values.emplace_back(time, value, 0);
|
||||
}
|
||||
|
||||
void
|
||||
Vcd::varAppendBusValue(string &id,
|
||||
VcdTime time,
|
||||
int64_t bus_value)
|
||||
{
|
||||
VcdValues &values = id_values_map_[id];
|
||||
values.emplace_back(time, '\0', bus_value);
|
||||
}
|
||||
|
||||
VcdValues &
|
||||
Vcd::values(VcdVar *var)
|
||||
{
|
||||
if (id_values_map_.find(var->id()) == id_values_map_.end()) {
|
||||
report_->error(1360, "Unknown variable %s ID %s",
|
||||
var->name().c_str(),
|
||||
var->id().c_str());
|
||||
static VcdValues empty;
|
||||
return empty;
|
||||
}
|
||||
else
|
||||
return id_values_map_[var->id()];
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////
|
||||
|
||||
VcdVar::VcdVar(string name,
|
||||
VcdVarType type,
|
||||
int width,
|
||||
string id) :
|
||||
name_(name),
|
||||
type_(type),
|
||||
width_(width),
|
||||
id_(id)
|
||||
{
|
||||
}
|
||||
|
||||
VcdValue::VcdValue(VcdTime time,
|
||||
char value,
|
||||
uint64_t bus_value) :
|
||||
time_(time),
|
||||
value_(value),
|
||||
bus_value_(bus_value)
|
||||
{
|
||||
}
|
||||
|
||||
char
|
||||
VcdValue::value(int value_bit) const
|
||||
{
|
||||
if (value_ == '\0')
|
||||
return ((bus_value_ >> value_bit) & 0x1) ? '1' : '0';
|
||||
else
|
||||
return value_;
|
||||
}
|
||||
|
||||
}
|
||||
158
power/Vcd.hh
158
power/Vcd.hh
|
|
@ -1,158 +0,0 @@
|
|||
// OpenSTA, Static Timing Analyzer
|
||||
// Copyright (c) 2024, Parallax Software, Inc.
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <map>
|
||||
#include <algorithm>
|
||||
|
||||
#include "StaState.hh"
|
||||
|
||||
namespace sta {
|
||||
|
||||
using std::string;
|
||||
using std::vector;
|
||||
using std::map;
|
||||
using std::max;
|
||||
using std::min;
|
||||
|
||||
class VcdVar;
|
||||
class VcdValue;
|
||||
typedef vector<VcdValue> VcdValues;
|
||||
typedef int64_t VcdTime;
|
||||
typedef vector<string> VcdScope;
|
||||
typedef map<string, VcdVar*> VcdNameMap;
|
||||
|
||||
enum class VcdVarType {
|
||||
wire,
|
||||
reg,
|
||||
parameter,
|
||||
integer,
|
||||
real,
|
||||
supply0,
|
||||
supply1,
|
||||
time,
|
||||
tri,
|
||||
triand,
|
||||
trior,
|
||||
trireg,
|
||||
tri0,
|
||||
tri1,
|
||||
wand,
|
||||
wor,
|
||||
unknown
|
||||
};
|
||||
|
||||
class Vcd : public StaState
|
||||
{
|
||||
public:
|
||||
Vcd(StaState *sta);
|
||||
Vcd(const Vcd &vcd);
|
||||
// Move copy assignment.
|
||||
Vcd& operator=(Vcd &&vcd1);
|
||||
~Vcd();
|
||||
VcdVar *var(const string name);
|
||||
VcdValues &values(VcdVar *var);
|
||||
|
||||
const string &date() const { return date_; }
|
||||
void setDate(const string &date);
|
||||
const string &comment() const { return comment_; }
|
||||
void setComment(const string &comment);
|
||||
const string &version() const { return version_; }
|
||||
void setVersion(const string &version);
|
||||
double timeScale() const { return time_scale_; }
|
||||
void setTimeScale(double time_scale);
|
||||
const string &timeUnit() const { return time_unit_; }
|
||||
double timeUnitScale() const { return time_unit_scale_; }
|
||||
void setTimeUnit(const string &time_unit,
|
||||
double time_unit_scale);
|
||||
VcdTime timeMax() const { return time_max_; }
|
||||
void setTimeMax(VcdTime time_max);
|
||||
VcdTime minDeltaTime() const { return min_delta_time_; }
|
||||
void setMinDeltaTime(VcdTime min_delta_time);
|
||||
vector<VcdVar*> vars() { return vars_; }
|
||||
void makeVar(string &name,
|
||||
VcdVarType type,
|
||||
int width,
|
||||
string &id);
|
||||
int maxVarWidth() const { return max_var_width_; }
|
||||
int maxVarNameLength() const { return max_var_name_length_; }
|
||||
bool varIdValid(string &id);
|
||||
void varAppendValue(string &id,
|
||||
VcdTime time,
|
||||
char value);
|
||||
void varAppendBusValue(string &id,
|
||||
VcdTime time,
|
||||
int64_t bus_value);
|
||||
|
||||
private:
|
||||
string date_;
|
||||
string comment_;
|
||||
string version_;
|
||||
double time_scale_;
|
||||
string time_unit_;
|
||||
double time_unit_scale_;
|
||||
|
||||
vector<VcdVar*> vars_;
|
||||
VcdNameMap var_name_map_;
|
||||
size_t max_var_name_length_;
|
||||
int max_var_width_;
|
||||
map<string, VcdValues> id_values_map_;
|
||||
VcdTime min_delta_time_;
|
||||
VcdTime time_max_;
|
||||
};
|
||||
|
||||
class VcdVar
|
||||
{
|
||||
public:
|
||||
VcdVar(string name,
|
||||
VcdVarType type,
|
||||
int width,
|
||||
string id);
|
||||
const string& name() const { return name_; }
|
||||
VcdVarType type() const { return type_; }
|
||||
int width() const { return width_; }
|
||||
const string& id() const { return id_; }
|
||||
|
||||
private:
|
||||
string name_;
|
||||
VcdVarType type_;
|
||||
int width_;
|
||||
string id_;
|
||||
};
|
||||
|
||||
class VcdValue
|
||||
{
|
||||
public:
|
||||
VcdValue(VcdTime time,
|
||||
char value,
|
||||
uint64_t bus_value);
|
||||
VcdTime time() const { return time_; }
|
||||
char value() const { return value_; }
|
||||
uint64_t busValue() const { return bus_value_; }
|
||||
char value(int value_bit) const;
|
||||
|
||||
private:
|
||||
VcdTime time_;
|
||||
// 01XUZ or '\0' when width > 1 to use bus_value_.
|
||||
char value_;
|
||||
uint64_t bus_value_;
|
||||
};
|
||||
|
||||
} // namespace
|
||||
|
|
@ -0,0 +1,318 @@
|
|||
// OpenSTA, Static Timing Analyzer
|
||||
// Copyright (c) 2024, Parallax Software, Inc.
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
#include "VcdParse.hh"
|
||||
|
||||
#include <cctype>
|
||||
#include <cinttypes>
|
||||
|
||||
#include "Stats.hh"
|
||||
#include "Report.hh"
|
||||
#include "Error.hh"
|
||||
#include "EnumNameMap.hh"
|
||||
|
||||
namespace sta {
|
||||
|
||||
using std::isspace;
|
||||
|
||||
// Very imprecise syntax definition
|
||||
// https://en.wikipedia.org/wiki/Value_change_dump#Structure.2FSyntax
|
||||
// Much better syntax definition
|
||||
// https://web.archive.org/web/20120323132708/http://www.beyondttl.com/vcd.php
|
||||
|
||||
void
|
||||
VcdParse::read(const char *filename,
|
||||
VcdReader *reader)
|
||||
{
|
||||
stream_ = gzopen(filename, "r");
|
||||
if (stream_) {
|
||||
Stats stats(debug_, report_);
|
||||
filename_ = filename;
|
||||
reader_ = reader;
|
||||
file_line_ = 1;
|
||||
stmt_line_ = 1;
|
||||
string token = getToken();
|
||||
while (!token.empty()) {
|
||||
if (token == "$date")
|
||||
reader_->setDate(readStmtString());
|
||||
else if (token == "$comment")
|
||||
reader_->setComment(readStmtString());
|
||||
else if (token == "$version")
|
||||
reader_->setVersion(readStmtString());
|
||||
else if (token == "$timescale")
|
||||
parseTimescale();
|
||||
else if (token == "$var")
|
||||
parseVar();
|
||||
else if (token == "$scope")
|
||||
parseScope();
|
||||
else if (token == "$upscope")
|
||||
parseUpscope();
|
||||
else if (token == "$enddefinitions")
|
||||
// empty body
|
||||
readStmtString();
|
||||
else if (token == "$dumpall")
|
||||
parseVarValues();
|
||||
else if (token == "$dumpvars")
|
||||
// Initial values.
|
||||
parseVarValues();
|
||||
else if (token[0] == '$')
|
||||
report_->fileError(800, filename_, stmt_line_, "unhandled vcd command.");
|
||||
else
|
||||
parseVarValues();
|
||||
token = getToken();
|
||||
}
|
||||
gzclose(stream_);
|
||||
stats.report("Read VCD");
|
||||
}
|
||||
else
|
||||
throw FileNotReadable(filename);
|
||||
}
|
||||
|
||||
VcdParse::VcdParse(Report *report,
|
||||
Debug *debug) :
|
||||
reader_(nullptr),
|
||||
file_line_(0),
|
||||
stmt_line_(0),
|
||||
time_(0),
|
||||
prev_time_(0),
|
||||
report_(report),
|
||||
debug_(debug)
|
||||
{
|
||||
}
|
||||
|
||||
void
|
||||
VcdParse::parseTimescale()
|
||||
{
|
||||
vector<string> tokens = readStmtTokens();
|
||||
if (tokens.size() == 1) {
|
||||
size_t last;
|
||||
double time_scale = std::stod(tokens[0], &last);
|
||||
setTimeUnit(tokens[0].substr(last), time_scale);
|
||||
}
|
||||
else if (tokens.size() == 2) {
|
||||
double time_scale = std::stod(tokens[0]);
|
||||
setTimeUnit(tokens[1], time_scale);
|
||||
}
|
||||
else
|
||||
report_->fileError(801, filename_, stmt_line_, "timescale syntax error.");
|
||||
}
|
||||
|
||||
void
|
||||
VcdParse::setTimeUnit(const string &time_unit,
|
||||
double time_scale)
|
||||
{
|
||||
double time_unit_scale = 1.0;
|
||||
if (time_unit == "fs")
|
||||
time_unit_scale = 1e-15;
|
||||
else if (time_unit == "ps")
|
||||
time_unit_scale = 1e-12;
|
||||
else if (time_unit == "ns")
|
||||
time_unit_scale = 1e-9;
|
||||
else
|
||||
report_->fileError(802, filename_, stmt_line_, "Unknown timescale unit.");
|
||||
reader_->setTimeUnit(time_unit, time_unit_scale, time_scale);
|
||||
}
|
||||
|
||||
static EnumNameMap<VcdVarType> vcd_var_type_map =
|
||||
{{VcdVarType::wire, "wire"},
|
||||
{VcdVarType::reg, "reg"},
|
||||
{VcdVarType::parameter, "parameter"},
|
||||
{VcdVarType::integer, "integer"},
|
||||
{VcdVarType::real, "real"},
|
||||
{VcdVarType::supply0, "supply0"},
|
||||
{VcdVarType::supply1, "supply1"},
|
||||
{VcdVarType::time, "time"},
|
||||
{VcdVarType::tri, "tri"},
|
||||
{VcdVarType::triand, "triand"},
|
||||
{VcdVarType::trior, "trior"},
|
||||
{VcdVarType::trireg, "trireg"},
|
||||
{VcdVarType::tri0, "tri0"},
|
||||
{VcdVarType::tri1, "tri1"},
|
||||
{VcdVarType::wand, "wand"},
|
||||
{VcdVarType::wor, "wor"}
|
||||
};
|
||||
|
||||
void
|
||||
VcdParse::parseVar()
|
||||
{
|
||||
vector<string> tokens = readStmtTokens();
|
||||
if (tokens.size() == 4
|
||||
|| tokens.size() == 5) {
|
||||
string type_name = tokens[0];
|
||||
VcdVarType type = vcd_var_type_map.find(type_name, VcdVarType::unknown);
|
||||
if (type == VcdVarType::unknown)
|
||||
report_->fileWarn(1370, filename_, stmt_line_,
|
||||
"Unknown variable type %s.",
|
||||
type_name.c_str());
|
||||
else {
|
||||
size_t width = stoi(tokens[1]);
|
||||
string &id = tokens[2];
|
||||
string name = tokens[3];
|
||||
// iverilog separates bus base name from bit range.
|
||||
if (tokens.size() == 5) {
|
||||
// Preserve space after esacaped name.
|
||||
if (name[0] == '\\')
|
||||
name += ' ';
|
||||
name += tokens[4];
|
||||
}
|
||||
|
||||
reader_->makeVar(scope_, name, type, width, id);
|
||||
}
|
||||
}
|
||||
else
|
||||
report_->fileError(804, filename_, stmt_line_, "Variable syntax error.");
|
||||
}
|
||||
|
||||
void
|
||||
VcdParse::parseScope()
|
||||
{
|
||||
vector<string> tokens = readStmtTokens();
|
||||
string &scope = tokens[1];
|
||||
scope_.push_back(scope);
|
||||
}
|
||||
|
||||
void
|
||||
VcdParse::parseUpscope()
|
||||
{
|
||||
readStmtTokens();
|
||||
scope_.pop_back();
|
||||
}
|
||||
|
||||
void
|
||||
VcdParse::parseVarValues()
|
||||
{
|
||||
string token = getToken();
|
||||
while (!token.empty()) {
|
||||
char char0 = toupper(token[0]);
|
||||
if (char0 == '#' && token.size() > 1) {
|
||||
prev_time_ = time_;
|
||||
time_ = stoll(token.substr(1));
|
||||
if (time_ > prev_time_)
|
||||
reader_->varMinDeltaTime(time_ - prev_time_);
|
||||
}
|
||||
else if (char0 == '0'
|
||||
|| char0 == '1'
|
||||
|| char0 == 'X'
|
||||
|| char0 == 'U'
|
||||
|| char0 == 'Z') {
|
||||
string id = token.substr(1);
|
||||
if (!reader_->varIdValid(id))
|
||||
report_->fileError(805, filename_, stmt_line_,
|
||||
"unknown variable %s", id.c_str());
|
||||
reader_->varAppendValue(id, time_, char0);
|
||||
}
|
||||
else if (char0 == 'B') {
|
||||
char char1 = toupper(token[1]);
|
||||
if (char1 == 'X'
|
||||
|| char1 == 'U'
|
||||
|| char1 == 'Z') {
|
||||
string id = getToken();
|
||||
if (!reader_->varIdValid(id))
|
||||
report_->fileError(806, filename_, stmt_line_,
|
||||
"unknown variable %s", id.c_str());
|
||||
// Bus mixed 0/1/X/U not supported.
|
||||
reader_->varAppendValue(id, time_, char1);
|
||||
}
|
||||
else {
|
||||
string bin = token.substr(1);
|
||||
char *end;
|
||||
int64_t bus_value = strtol(bin.c_str(), &end, 2);
|
||||
string id = getToken();
|
||||
if (!reader_->varIdValid(id))
|
||||
report_->fileError(807, filename_, stmt_line_,
|
||||
"unknown variable %s", id.c_str());
|
||||
else
|
||||
reader_->varAppendBusValue(id, time_, bus_value);
|
||||
}
|
||||
}
|
||||
token = getToken();
|
||||
}
|
||||
reader_->setTimeMax(time_);
|
||||
}
|
||||
|
||||
string
|
||||
VcdParse::readStmtString()
|
||||
{
|
||||
stmt_line_ = file_line_;
|
||||
string line;
|
||||
string token = getToken();
|
||||
while (!token.empty() && token != "$end") {
|
||||
if (!line.empty())
|
||||
line += " ";
|
||||
line += token;
|
||||
token = getToken();
|
||||
}
|
||||
return line;
|
||||
}
|
||||
|
||||
vector<string>
|
||||
VcdParse::readStmtTokens()
|
||||
{
|
||||
stmt_line_ = file_line_;
|
||||
vector<string> tokens;
|
||||
string token = getToken();
|
||||
while (!token.empty() && token != "$end") {
|
||||
tokens.push_back(token);
|
||||
token = getToken();
|
||||
}
|
||||
return tokens;
|
||||
}
|
||||
|
||||
string
|
||||
VcdParse::getToken()
|
||||
{
|
||||
string token;
|
||||
int ch = gzgetc(stream_);
|
||||
if (ch == '\n')
|
||||
file_line_++;
|
||||
// skip whitespace
|
||||
while (ch != EOF && isspace(ch)) {
|
||||
ch = gzgetc(stream_);
|
||||
if (ch == '\n')
|
||||
file_line_++;
|
||||
}
|
||||
while (ch != EOF && !isspace(ch)) {
|
||||
token.push_back(ch);
|
||||
ch = gzgetc(stream_);
|
||||
if (ch == '\n')
|
||||
file_line_++;
|
||||
}
|
||||
if (ch == EOF)
|
||||
return "";
|
||||
else
|
||||
return token;
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////
|
||||
|
||||
char
|
||||
VcdValue::value(int value_bit) const
|
||||
{
|
||||
if (value_ == '\0')
|
||||
return ((bus_value_ >> value_bit) & 0x1) ? '1' : '0';
|
||||
else
|
||||
return value_;
|
||||
}
|
||||
|
||||
void
|
||||
VcdValue::setValue(VcdTime time,
|
||||
char value)
|
||||
{
|
||||
time_ = time;
|
||||
value_ = value;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
|
@ -0,0 +1,136 @@
|
|||
// OpenSTA, Static Timing Analyzer
|
||||
// Copyright (c) 2024, Parallax Software, Inc.
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "Zlib.hh"
|
||||
#include "StaState.hh"
|
||||
|
||||
namespace sta {
|
||||
|
||||
using std::string;
|
||||
using std::vector;
|
||||
|
||||
typedef int64_t VcdTime;
|
||||
typedef vector<string> VcdScope;
|
||||
|
||||
enum class VcdVarType {
|
||||
wire,
|
||||
reg,
|
||||
parameter,
|
||||
integer,
|
||||
real,
|
||||
supply0,
|
||||
supply1,
|
||||
time,
|
||||
tri,
|
||||
triand,
|
||||
trior,
|
||||
trireg,
|
||||
tri0,
|
||||
tri1,
|
||||
wand,
|
||||
wor,
|
||||
unknown
|
||||
};
|
||||
|
||||
class VcdReader;
|
||||
|
||||
class VcdParse : public StaState
|
||||
{
|
||||
public:
|
||||
VcdParse(Report *report,
|
||||
Debug *debug);
|
||||
void read(const char *filename,
|
||||
VcdReader *reader);
|
||||
|
||||
private:
|
||||
void parseTimescale();
|
||||
void setTimeUnit(const string &time_unit,
|
||||
double time_scale);
|
||||
void parseVar();
|
||||
void parseScope();
|
||||
void parseUpscope();
|
||||
void parseVarValues();
|
||||
string getToken();
|
||||
string readStmtString();
|
||||
vector<string> readStmtTokens();
|
||||
|
||||
VcdReader *reader_;
|
||||
gzFile stream_;
|
||||
string token_;
|
||||
const char *filename_;
|
||||
int file_line_;
|
||||
int stmt_line_;
|
||||
|
||||
VcdTime time_;
|
||||
VcdTime prev_time_;
|
||||
VcdScope scope_;
|
||||
|
||||
Report *report_;
|
||||
Debug *debug_;
|
||||
};
|
||||
|
||||
// Abstract class for VcdParse callbacks.
|
||||
class VcdReader
|
||||
{
|
||||
public:
|
||||
virtual ~VcdReader() {}
|
||||
virtual void setDate(const string &date) = 0;
|
||||
virtual void setComment(const string &comment) = 0;
|
||||
virtual void setVersion(const string &version) = 0;
|
||||
virtual void setTimeUnit(const string &time_unit,
|
||||
double time_unit_scale,
|
||||
double time_scale) = 0;
|
||||
virtual void setTimeMax(VcdTime time_max) = 0;
|
||||
virtual void varMinDeltaTime(VcdTime min_delta_time) = 0;
|
||||
virtual bool varIdValid(const string &id) = 0;
|
||||
virtual void makeVar(const VcdScope &scope,
|
||||
const string &name,
|
||||
VcdVarType type,
|
||||
size_t width,
|
||||
const string &id) = 0;
|
||||
virtual void varAppendValue(const string &id,
|
||||
VcdTime time,
|
||||
char value) = 0;
|
||||
virtual void varAppendBusValue(const string &id,
|
||||
VcdTime time,
|
||||
int64_t bus_value) = 0;
|
||||
};
|
||||
|
||||
class VcdValue
|
||||
{
|
||||
public:
|
||||
VcdValue();
|
||||
VcdTime time() const { return time_; }
|
||||
char value() const { return value_; }
|
||||
void setValue(VcdTime time,
|
||||
char value);
|
||||
uint64_t busValue() const { return bus_value_; }
|
||||
char value(int value_bit) const;
|
||||
|
||||
private:
|
||||
VcdTime time_;
|
||||
// 01XUZ or '\0' when width > 1 to use bus_value_.
|
||||
char value_;
|
||||
uint64_t bus_value_;
|
||||
};
|
||||
|
||||
} // namespace
|
||||
|
|
@ -14,437 +14,428 @@
|
|||
// 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 <cctype>
|
||||
#include <cinttypes>
|
||||
|
||||
#include "VcdReader.hh"
|
||||
|
||||
#include "Zlib.hh"
|
||||
#include "Stats.hh"
|
||||
#include "Report.hh"
|
||||
#include "Error.hh"
|
||||
#include "StringUtil.hh"
|
||||
#include "EnumNameMap.hh"
|
||||
#include <inttypes.h>
|
||||
#include <set>
|
||||
|
||||
#include "VcdParse.hh"
|
||||
#include "Debug.hh"
|
||||
#include "Network.hh"
|
||||
#include "PortDirection.hh"
|
||||
#include "VerilogNamespace.hh"
|
||||
#include "ParseBus.hh"
|
||||
#include "Sdc.hh"
|
||||
#include "Power.hh"
|
||||
#include "Sta.hh"
|
||||
|
||||
namespace sta {
|
||||
|
||||
using std::isspace;
|
||||
using std::abs;
|
||||
using std::min;
|
||||
using std::to_string;
|
||||
using std::vector;
|
||||
using std::map;
|
||||
|
||||
// Very imprecise syntax definition
|
||||
// https://en.wikipedia.org/wiki/Value_change_dump#Structure.2FSyntax
|
||||
// Much better syntax definition
|
||||
// https://web.archive.org/web/20120323132708/http://www.beyondttl.com/vcd.php
|
||||
|
||||
class VcdReader : public StaState
|
||||
// Transition count and high time for duty cycle for a group of pins
|
||||
// for one bit of vcd ID.
|
||||
class VcdCount
|
||||
{
|
||||
public:
|
||||
VcdReader(StaState *sta);
|
||||
Vcd read(const char *filename);
|
||||
VcdCount();
|
||||
double transitionCount() const { return transition_count_; }
|
||||
VcdTime highTime(VcdTime time_max) const;
|
||||
void incrCounts(VcdTime time,
|
||||
char value);
|
||||
void incrCounts(VcdTime time,
|
||||
int64_t value);
|
||||
void addPin(const Pin *pin);
|
||||
const PinSeq &pins() const { return pins_; }
|
||||
|
||||
private:
|
||||
void parseTimescale();
|
||||
void setTimeUnit(const string &time_unit);
|
||||
void parseVar();
|
||||
void parseScope();
|
||||
void parseUpscope();
|
||||
void parseVarValues();
|
||||
string getToken();
|
||||
string readStmtString();
|
||||
vector<string> readStmtTokens();
|
||||
|
||||
gzFile stream_;
|
||||
string token_;
|
||||
const char *filename_;
|
||||
int file_line_;
|
||||
int stmt_line_;
|
||||
|
||||
Vcd *vcd_;
|
||||
VcdTime time_;
|
||||
PinSeq pins_;
|
||||
VcdTime prev_time_;
|
||||
VcdScope scope_;
|
||||
char prev_value_;
|
||||
VcdTime high_time_;
|
||||
double transition_count_;
|
||||
};
|
||||
|
||||
Vcd
|
||||
readVcdFile(const char *filename,
|
||||
StaState *sta)
|
||||
|
||||
VcdCount::VcdCount() :
|
||||
prev_time_(-1),
|
||||
prev_value_('\0'),
|
||||
high_time_(0),
|
||||
transition_count_(0)
|
||||
{
|
||||
VcdReader reader(sta);
|
||||
return reader.read(filename);
|
||||
}
|
||||
|
||||
Vcd
|
||||
VcdReader::read(const char *filename)
|
||||
void
|
||||
VcdCount::addPin(const Pin *pin)
|
||||
{
|
||||
Vcd vcd(this);
|
||||
vcd_ = &vcd;
|
||||
stream_ = gzopen(filename, "r");
|
||||
if (stream_) {
|
||||
Stats stats(debug_, report_);
|
||||
filename_ = filename;
|
||||
file_line_ = 1;
|
||||
stmt_line_ = 1;
|
||||
string token = getToken();
|
||||
while (!token.empty()) {
|
||||
if (token == "$date")
|
||||
vcd_->setDate(readStmtString());
|
||||
else if (token == "$comment")
|
||||
vcd_->setComment(readStmtString());
|
||||
else if (token == "$version")
|
||||
vcd_->setVersion(readStmtString());
|
||||
else if (token == "$timescale")
|
||||
parseTimescale();
|
||||
else if (token == "$var")
|
||||
parseVar();
|
||||
else if (token == "$scope")
|
||||
parseScope();
|
||||
else if (token == "$upscope")
|
||||
parseUpscope();
|
||||
else if (token == "$enddefinitions")
|
||||
// empty body
|
||||
readStmtString();
|
||||
else if (token == "$dumpall")
|
||||
parseVarValues();
|
||||
else if (token == "$dumpvars")
|
||||
// Initial values.
|
||||
parseVarValues();
|
||||
else if (token[0] == '$')
|
||||
report_->fileError(800, filename_, stmt_line_, "unhandled vcd command.");
|
||||
else
|
||||
parseVarValues();
|
||||
token = getToken();
|
||||
}
|
||||
gzclose(stream_);
|
||||
stats.report("Read VCD");
|
||||
pins_.push_back(pin);
|
||||
}
|
||||
|
||||
void
|
||||
VcdCount::incrCounts(VcdTime time,
|
||||
char value)
|
||||
{
|
||||
// Initial value does not coontribute to transitions or high time.
|
||||
if (prev_time_ != -1) {
|
||||
if (prev_value_ == '1')
|
||||
high_time_ += time - prev_time_;
|
||||
if (value != prev_value_)
|
||||
transition_count_ += (value == 'X'
|
||||
|| value == 'Z'
|
||||
|| prev_value_ == 'X'
|
||||
|| prev_value_ == 'Z')
|
||||
? .5
|
||||
: 1.0;
|
||||
}
|
||||
prev_time_ = time;
|
||||
prev_value_ = value;
|
||||
}
|
||||
|
||||
VcdTime
|
||||
VcdCount::highTime(VcdTime time_max) const
|
||||
{
|
||||
if (prev_value_ == '1')
|
||||
return high_time_ + time_max - prev_time_;
|
||||
else
|
||||
throw FileNotReadable(filename);
|
||||
return vcd;
|
||||
}
|
||||
|
||||
VcdReader::VcdReader(StaState *sta) :
|
||||
StaState(sta),
|
||||
file_line_(0),
|
||||
stmt_line_(0),
|
||||
vcd_(nullptr),
|
||||
time_(0),
|
||||
prev_time_(0)
|
||||
{
|
||||
}
|
||||
|
||||
void
|
||||
VcdReader::parseTimescale()
|
||||
{
|
||||
vector<string> tokens = readStmtTokens();
|
||||
if (tokens.size() == 1) {
|
||||
size_t last;
|
||||
double time_scale = std::stod(tokens[0], &last);
|
||||
setTimeUnit(tokens[0].substr(last));
|
||||
vcd_->setTimeScale(time_scale * vcd_->timeUnitScale());
|
||||
}
|
||||
else if (tokens.size() == 2) {
|
||||
setTimeUnit(tokens[1]);
|
||||
double time_scale = std::stod(tokens[0]);
|
||||
vcd_->setTimeScale(time_scale * vcd_->timeUnitScale());
|
||||
}
|
||||
else
|
||||
report_->fileError(801, filename_, stmt_line_, "timescale syntax error.");
|
||||
}
|
||||
|
||||
void
|
||||
VcdReader::setTimeUnit(const string &time_unit)
|
||||
{
|
||||
double time_unit_scale = 1.0;
|
||||
if (time_unit == "fs")
|
||||
time_unit_scale = 1e-15;
|
||||
else if (time_unit == "ps")
|
||||
time_unit_scale = 1e-12;
|
||||
else if (time_unit == "ns")
|
||||
time_unit_scale = 1e-9;
|
||||
else
|
||||
report_->fileError(802, filename_, stmt_line_, "Unknown timescale unit.");
|
||||
vcd_->setTimeUnit(time_unit, time_unit_scale);;
|
||||
}
|
||||
|
||||
static EnumNameMap<VcdVarType> vcd_var_type_map =
|
||||
{{VcdVarType::wire, "wire"},
|
||||
{VcdVarType::reg, "reg"},
|
||||
{VcdVarType::parameter, "parameter"},
|
||||
{VcdVarType::integer, "integer"},
|
||||
{VcdVarType::real, "real"},
|
||||
{VcdVarType::supply0, "supply0"},
|
||||
{VcdVarType::supply1, "supply1"},
|
||||
{VcdVarType::time, "time"},
|
||||
{VcdVarType::tri, "tri"},
|
||||
{VcdVarType::triand, "triand"},
|
||||
{VcdVarType::trior, "trior"},
|
||||
{VcdVarType::trireg, "trireg"},
|
||||
{VcdVarType::tri0, "tri0"},
|
||||
{VcdVarType::tri1, "tri1"},
|
||||
{VcdVarType::wand, "wand"},
|
||||
{VcdVarType::wor, "wor"}
|
||||
};
|
||||
|
||||
void
|
||||
VcdReader::parseVar()
|
||||
{
|
||||
vector<string> tokens = readStmtTokens();
|
||||
if (tokens.size() == 4
|
||||
|| tokens.size() == 5) {
|
||||
string type_name = tokens[0];
|
||||
VcdVarType type = vcd_var_type_map.find(type_name, VcdVarType::unknown);
|
||||
if (type == VcdVarType::unknown)
|
||||
report_->fileWarn(1370, filename_, stmt_line_,
|
||||
"Unknown variable type %s.",
|
||||
type_name.c_str());
|
||||
else {
|
||||
int width = stoi(tokens[1]);
|
||||
string &id = tokens[2];
|
||||
string name;
|
||||
|
||||
for (string &context : scope_) {
|
||||
name += context;
|
||||
name += '/';
|
||||
}
|
||||
name += tokens[3];
|
||||
// iverilog separates bus base name from bit range.
|
||||
if (tokens.size() == 5) {
|
||||
// Preserve space after esacaped name.
|
||||
if (name[0] == '\\')
|
||||
name += ' ';
|
||||
name += tokens[4];
|
||||
}
|
||||
|
||||
vcd_->makeVar(name, type, width, id);
|
||||
}
|
||||
}
|
||||
else
|
||||
report_->fileError(804, filename_, stmt_line_, "Variable syntax error.");
|
||||
}
|
||||
|
||||
void
|
||||
VcdReader::parseScope()
|
||||
{
|
||||
vector<string> tokens = readStmtTokens();
|
||||
string &scope = tokens[1];
|
||||
scope_.push_back(scope);
|
||||
}
|
||||
|
||||
void
|
||||
VcdReader::parseUpscope()
|
||||
{
|
||||
readStmtTokens();
|
||||
scope_.pop_back();
|
||||
}
|
||||
|
||||
void
|
||||
VcdReader::parseVarValues()
|
||||
{
|
||||
string token = getToken();
|
||||
while (!token.empty()) {
|
||||
char char0 = toupper(token[0]);
|
||||
if (char0 == '#' && token.size() > 1) {
|
||||
prev_time_ = time_;
|
||||
time_ = stoll(token.substr(1));
|
||||
if (time_ > prev_time_)
|
||||
vcd_->setMinDeltaTime(min(time_ - prev_time_, vcd_->minDeltaTime()));
|
||||
}
|
||||
else if (char0 == '0'
|
||||
|| char0 == '1'
|
||||
|| char0 == 'X'
|
||||
|| char0 == 'U'
|
||||
|| char0 == 'Z') {
|
||||
string id = token.substr(1);
|
||||
if (!vcd_->varIdValid(id))
|
||||
report_->fileError(805, filename_, stmt_line_,
|
||||
"unknown variable %s", id.c_str());
|
||||
vcd_->varAppendValue(id, time_, char0);
|
||||
}
|
||||
else if (char0 == 'B') {
|
||||
char char1 = toupper(token[1]);
|
||||
if (char1 == 'X'
|
||||
|| char1 == 'U'
|
||||
|| char1 == 'Z') {
|
||||
string id = getToken();
|
||||
if (!vcd_->varIdValid(id))
|
||||
report_->fileError(806, filename_, stmt_line_,
|
||||
"unknown variable %s", id.c_str());
|
||||
// Bus mixed 0/1/X/U not supported.
|
||||
vcd_->varAppendValue(id, time_, char1);
|
||||
}
|
||||
else {
|
||||
string bin = token.substr(1);
|
||||
char *end;
|
||||
int64_t bus_value = strtol(bin.c_str(), &end, 2);
|
||||
string id = getToken();
|
||||
if (!vcd_->varIdValid(id))
|
||||
report_->fileError(807, filename_, stmt_line_,
|
||||
"unknown variable %s", id.c_str());
|
||||
else
|
||||
vcd_->varAppendBusValue(id, time_, bus_value);
|
||||
}
|
||||
}
|
||||
token = getToken();
|
||||
}
|
||||
vcd_->setTimeMax(time_);
|
||||
}
|
||||
|
||||
string
|
||||
VcdReader::readStmtString()
|
||||
{
|
||||
stmt_line_ = file_line_;
|
||||
string line;
|
||||
string token = getToken();
|
||||
while (!token.empty() && token != "$end") {
|
||||
if (!line.empty())
|
||||
line += " ";
|
||||
line += token;
|
||||
token = getToken();
|
||||
}
|
||||
return line;
|
||||
}
|
||||
|
||||
vector<string>
|
||||
VcdReader::readStmtTokens()
|
||||
{
|
||||
stmt_line_ = file_line_;
|
||||
vector<string> tokens;
|
||||
string token = getToken();
|
||||
while (!token.empty() && token != "$end") {
|
||||
tokens.push_back(token);
|
||||
token = getToken();
|
||||
}
|
||||
return tokens;
|
||||
}
|
||||
|
||||
string
|
||||
VcdReader::getToken()
|
||||
{
|
||||
string token;
|
||||
int ch = gzgetc(stream_);
|
||||
if (ch == '\n')
|
||||
file_line_++;
|
||||
// skip whitespace
|
||||
while (ch != EOF && isspace(ch)) {
|
||||
ch = gzgetc(stream_);
|
||||
if (ch == '\n')
|
||||
file_line_++;
|
||||
}
|
||||
while (ch != EOF && !isspace(ch)) {
|
||||
token.push_back(ch);
|
||||
ch = gzgetc(stream_);
|
||||
if (ch == '\n')
|
||||
file_line_++;
|
||||
}
|
||||
if (ch == EOF)
|
||||
return "";
|
||||
else
|
||||
return token;
|
||||
return high_time_;
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////
|
||||
|
||||
static void
|
||||
reportWaveforms(Vcd &vcd,
|
||||
Report *report);
|
||||
|
||||
void
|
||||
reportVcdWaveforms(const char *filename,
|
||||
StaState *sta)
|
||||
// VcdCount[bit]
|
||||
typedef vector<VcdCount> VcdCounts;
|
||||
// ID -> VcdCount[bit]
|
||||
typedef map<string, VcdCounts> VcdIdCountsMap;
|
||||
|
||||
class VcdCountReader : public VcdReader
|
||||
{
|
||||
public:
|
||||
VcdCountReader(const char *scope,
|
||||
Network *sdc_network,
|
||||
Report *report,
|
||||
Debug *debug);
|
||||
VcdTime timeMax() const { return time_max_; }
|
||||
const VcdIdCountsMap &countMap() const { return vcd_count_map_; }
|
||||
double timeScale() const { return time_scale_; }
|
||||
|
||||
// VcdParse callbacks.
|
||||
void setDate(const string &) override {}
|
||||
void setComment(const string &) override {}
|
||||
void setVersion(const string &) override {}
|
||||
void setTimeUnit(const string &time_unit,
|
||||
double time_unit_scale,
|
||||
double time_scale) override;
|
||||
void setTimeMax(VcdTime time_max) override;
|
||||
void varMinDeltaTime(VcdTime) override {}
|
||||
bool varIdValid(const string &id) override;
|
||||
void makeVar(const VcdScope &scope,
|
||||
const string &name,
|
||||
VcdVarType type,
|
||||
size_t width,
|
||||
const string &id) override;
|
||||
void varAppendValue(const string &id,
|
||||
VcdTime time,
|
||||
char value) override;
|
||||
void varAppendBusValue(const string &id,
|
||||
VcdTime time,
|
||||
int64_t bus_value) override;
|
||||
|
||||
private:
|
||||
void addVarPin(const string &pin_name,
|
||||
const string &id,
|
||||
size_t width,
|
||||
size_t bit_idx);
|
||||
|
||||
const char *scope_;
|
||||
Network *sdc_network_;
|
||||
Report *report_;
|
||||
Debug *debug_;
|
||||
|
||||
double time_scale_;
|
||||
VcdTime time_max_;
|
||||
VcdIdCountsMap vcd_count_map_;
|
||||
};
|
||||
|
||||
VcdCountReader::VcdCountReader(const char *scope,
|
||||
Network *sdc_network,
|
||||
Report *report,
|
||||
Debug *debug) :
|
||||
scope_(scope),
|
||||
sdc_network_(sdc_network),
|
||||
report_(report),
|
||||
debug_(debug),
|
||||
time_scale_(1.0),
|
||||
time_max_(0.0)
|
||||
{
|
||||
Vcd vcd = readVcdFile(filename, sta);
|
||||
reportWaveforms(vcd, sta->report());
|
||||
}
|
||||
|
||||
static void
|
||||
reportWaveforms(Vcd &vcd,
|
||||
Report *report)
|
||||
void
|
||||
VcdCountReader::setTimeUnit(const string &,
|
||||
double time_unit_scale,
|
||||
double time_scale)
|
||||
{
|
||||
report->reportLine("Date: %s", vcd.date().c_str());
|
||||
report->reportLine("Timescale: %.2f%s", vcd.timeScale(), vcd.timeUnit().c_str());
|
||||
// Characters per time sample.
|
||||
int zoom = (vcd.maxVarWidth() + 7) / 4;
|
||||
int time_delta = vcd.minDeltaTime();
|
||||
time_scale_ = time_scale * time_unit_scale;
|
||||
}
|
||||
|
||||
int max_var_name_length = vcd.maxVarNameLength();
|
||||
for (VcdVar *var : vcd.vars()) {
|
||||
string line;
|
||||
stringPrint(line, " %-*s",
|
||||
static_cast<int>(max_var_name_length),
|
||||
var->name().c_str());
|
||||
const VcdValues &var_values = vcd.values(var);
|
||||
if (!var_values.empty()) {
|
||||
size_t value_index = 0;
|
||||
VcdValue var_value = var_values[value_index];
|
||||
VcdValue prev_var_value = var_values[value_index];
|
||||
VcdTime next_value_time = var_values[value_index + 1].time();
|
||||
for (double time = 0.0; time < vcd.timeMax(); time += time_delta) {
|
||||
if (time >= next_value_time) {
|
||||
if (value_index < var_values.size() - 1)
|
||||
value_index++;
|
||||
var_value = var_values[value_index];
|
||||
if (value_index < var_values.size())
|
||||
next_value_time = var_values[value_index + 1].time();
|
||||
}
|
||||
if (var_value.value()) {
|
||||
// 01UZX
|
||||
char value = var_value.value();
|
||||
char prev_value = prev_var_value.value();
|
||||
if (var->width() == 1) {
|
||||
if (value == '0' || value == '1') {
|
||||
for (int z = 0; z < zoom; z++) {
|
||||
if (z == 0
|
||||
&& value != prev_value
|
||||
&& (prev_value == '0'
|
||||
|| prev_value == '1'))
|
||||
line += (prev_value == '1') ? "╲" : "╱";
|
||||
else
|
||||
line += (value == '1') ? "▔" : "▁";
|
||||
}
|
||||
}
|
||||
else {
|
||||
string field;
|
||||
stringPrint(field, "%-*c", zoom, value);
|
||||
line += field;
|
||||
void
|
||||
VcdCountReader::setTimeMax(VcdTime time_max)
|
||||
{
|
||||
time_max_ = time_max;
|
||||
}
|
||||
|
||||
bool
|
||||
VcdCountReader::varIdValid(const string &)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
void
|
||||
VcdCountReader::makeVar(const VcdScope &scope,
|
||||
const string &name,
|
||||
VcdVarType type,
|
||||
size_t width,
|
||||
const string &id)
|
||||
{
|
||||
if (type == VcdVarType::wire
|
||||
|| type == VcdVarType::reg) {
|
||||
string path_name;
|
||||
bool first = true;
|
||||
for (const string &context : scope) {
|
||||
if (!first)
|
||||
path_name += '/';
|
||||
path_name += context;
|
||||
first = false;
|
||||
}
|
||||
size_t scope_length = strlen(scope_);
|
||||
// string::starts_with in c++20
|
||||
if (scope_length == 0
|
||||
|| path_name.substr(0, scope_length) == scope_) {
|
||||
path_name += '/';
|
||||
path_name += name;
|
||||
// Strip the scope from the name.
|
||||
string var_scoped = path_name.substr(scope_length + 1);
|
||||
if (width == 1) {
|
||||
string pin_name = netVerilogToSta(var_scoped.c_str());
|
||||
addVarPin(pin_name, id, width, 0);
|
||||
}
|
||||
else {
|
||||
bool is_bus, is_range, subscript_wild;
|
||||
string bus_name;
|
||||
int from, to;
|
||||
parseBusName(var_scoped.c_str(), '[', ']', '\\',
|
||||
is_bus, is_range, bus_name, from, to, subscript_wild);
|
||||
if (is_bus) {
|
||||
string sta_bus_name = netVerilogToSta(bus_name.c_str());
|
||||
int bit_idx = 0;
|
||||
if (to < from) {
|
||||
for (int bus_bit = to; bus_bit <= from; bus_bit++) {
|
||||
string pin_name = sta_bus_name;
|
||||
pin_name += '[';
|
||||
pin_name += to_string(bus_bit);
|
||||
pin_name += ']';
|
||||
addVarPin(pin_name, id, width, bit_idx);
|
||||
bit_idx++;
|
||||
}
|
||||
}
|
||||
else {
|
||||
string field;
|
||||
stringPrint(field, "%-*c", zoom, value);
|
||||
line += field;
|
||||
for (int bus_bit = to; bus_bit >= from; bus_bit--) {
|
||||
string pin_name = sta_bus_name;
|
||||
pin_name += '[';
|
||||
pin_name += to_string(bus_bit);
|
||||
pin_name += ']';
|
||||
addVarPin(pin_name, id, width, bit_idx);
|
||||
bit_idx++;
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
// bus
|
||||
string field;
|
||||
stringPrint(field, "%-*" PRIX64, zoom, var_value.busValue());
|
||||
line += field;
|
||||
}
|
||||
prev_var_value = var_value;
|
||||
else
|
||||
report_->warn(1451, "problem parsing bus %s.", var_scoped.c_str());
|
||||
}
|
||||
}
|
||||
report->reportLineString(line);
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
reportVcdVarValues(const char *filename,
|
||||
const char *var_name,
|
||||
StaState *sta)
|
||||
VcdCountReader::addVarPin(const string &pin_name,
|
||||
const string &id,
|
||||
size_t width,
|
||||
size_t bit_idx)
|
||||
{
|
||||
Vcd vcd = readVcdFile(filename, sta);
|
||||
VcdVar *var = vcd.var(var_name);
|
||||
if (var) {
|
||||
Report *report = sta->report();
|
||||
for (const VcdValue &var_value : vcd.values(var)) {
|
||||
double time = var_value.time() * vcd.timeUnitScale();
|
||||
char value = var_value.value();
|
||||
if (value == '\0')
|
||||
report->reportLine("%.2e %" PRIu64,
|
||||
time, var_value.busValue());
|
||||
else
|
||||
report->reportLine("%.2e %c", time, value);
|
||||
const Pin *pin = sdc_network_->findPin(pin_name.c_str());
|
||||
if (pin
|
||||
&& !sdc_network_->isHierarchical(pin)
|
||||
&& !sdc_network_->direction(pin)->isInternal()) {
|
||||
VcdCounts &vcd_counts = vcd_count_map_[id];
|
||||
vcd_counts.resize(width);
|
||||
vcd_counts[bit_idx].addPin(pin);
|
||||
debugPrint(debug_, "read_vcd_activities", 2, "id %s pin %s",
|
||||
id.c_str(),
|
||||
pin_name.c_str());
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
VcdCountReader::varAppendValue(const string &id,
|
||||
VcdTime time,
|
||||
char value)
|
||||
{
|
||||
auto itr = vcd_count_map_.find(id);
|
||||
if (itr != vcd_count_map_.end()) {
|
||||
VcdCounts &vcd_counts = itr->second;
|
||||
if (debug_->check("read_vcd_activities", 3)) {
|
||||
for (size_t bit_idx = 0; bit_idx < vcd_counts.size(); bit_idx++) {
|
||||
VcdCount &vcd_count = vcd_counts[bit_idx];
|
||||
for (const Pin *pin : vcd_count.pins()) {
|
||||
debugPrint(debug_, "read_vcd_activities", 3, "%s time %" PRIu64 " value %c",
|
||||
sdc_network_->pathName(pin),
|
||||
time,
|
||||
value);
|
||||
}
|
||||
}
|
||||
}
|
||||
for (size_t bit_idx = 0; bit_idx < vcd_counts.size(); bit_idx++) {
|
||||
VcdCount &vcd_count = vcd_counts[bit_idx];
|
||||
vcd_count.incrCounts(time, value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
VcdCountReader::varAppendBusValue(const string &id,
|
||||
VcdTime time,
|
||||
int64_t bus_value)
|
||||
{
|
||||
auto itr = vcd_count_map_.find(id);
|
||||
if (itr != vcd_count_map_.end()) {
|
||||
VcdCounts &vcd_counts = itr->second;
|
||||
for (size_t bit_idx = 0; bit_idx < vcd_counts.size(); bit_idx++) {
|
||||
char bit_value = ((bus_value >> bit_idx) & 0x1) ? '1' : '0';
|
||||
VcdCount &vcd_count = vcd_counts[bit_idx];
|
||||
if (debug_->check("read_vcd_activities", 3)) {
|
||||
for (const Pin *pin : vcd_count.pins()) {
|
||||
debugPrint(debug_, "read_vcd_activities", 3, "%s time %" PRIu64 " value %c",
|
||||
sdc_network_->pathName(pin),
|
||||
time,
|
||||
bit_value);
|
||||
}
|
||||
}
|
||||
vcd_count.incrCounts(time, bit_value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////
|
||||
|
||||
class ReadVcdActivities : public StaState
|
||||
{
|
||||
public:
|
||||
ReadVcdActivities(const char *filename,
|
||||
const char *scope,
|
||||
Sta *sta);
|
||||
void readActivities();
|
||||
|
||||
private:
|
||||
void setActivities();
|
||||
void checkClkPeriod(const Pin *pin,
|
||||
double transition_count);
|
||||
|
||||
const char *filename_;
|
||||
VcdCountReader vcd_reader_;
|
||||
VcdParse vcd_parse_;
|
||||
|
||||
Power *power_;
|
||||
std::set<const Pin*> annotated_pins_;
|
||||
|
||||
static constexpr double sim_clk_period_tolerance_ = .1;
|
||||
};
|
||||
|
||||
void
|
||||
readVcdActivities(const char *filename,
|
||||
const char *scope,
|
||||
Sta *sta)
|
||||
{
|
||||
ReadVcdActivities reader(filename, scope, sta);
|
||||
reader.readActivities();
|
||||
}
|
||||
|
||||
ReadVcdActivities::ReadVcdActivities(const char *filename,
|
||||
const char *scope,
|
||||
Sta *sta) :
|
||||
StaState(sta),
|
||||
filename_(filename),
|
||||
vcd_reader_(scope, sdc_network_, report_, debug_),
|
||||
vcd_parse_(report_, debug_),
|
||||
power_(sta->power())
|
||||
{
|
||||
}
|
||||
|
||||
void
|
||||
ReadVcdActivities::readActivities()
|
||||
{
|
||||
ClockSeq *clks = sdc_->clocks();
|
||||
if (clks->empty())
|
||||
report_->error(805, "No clocks have been defined.");
|
||||
|
||||
vcd_parse_.read(filename_, &vcd_reader_);
|
||||
|
||||
if (vcd_reader_.timeMax() > 0)
|
||||
setActivities();
|
||||
else
|
||||
report_->warn(1450, "VCD max time is zero.");
|
||||
report_->reportLine("Annotated %zu pin activities.", annotated_pins_.size());
|
||||
}
|
||||
|
||||
void
|
||||
ReadVcdActivities::setActivities()
|
||||
{
|
||||
VcdTime time_max = vcd_reader_.timeMax();
|
||||
double time_scale = vcd_reader_.timeScale();
|
||||
for (auto& [id, vcd_counts] : vcd_reader_.countMap()) {
|
||||
for (const VcdCount &vcd_count : vcd_counts) {
|
||||
double transition_count = vcd_count.transitionCount();
|
||||
VcdTime high_time = vcd_count.highTime(time_max);
|
||||
float duty = static_cast<double>(high_time) / time_max;
|
||||
float density = transition_count / (time_max * time_scale);
|
||||
if (debug_->check("read_vcd_activities", 1)) {
|
||||
for (const Pin *pin : vcd_count.pins()) {
|
||||
debugPrint(debug_, "read_vcd_activities", 1,
|
||||
"%s transitions %.1f activity %.2f duty %.2f",
|
||||
sdc_network_->pathName(pin),
|
||||
transition_count,
|
||||
density,
|
||||
duty);
|
||||
}
|
||||
}
|
||||
for (const Pin *pin : vcd_count.pins()) {
|
||||
power_->setUserActivity(pin, density, duty, PwrActivityOrigin::vcd);
|
||||
if (sdc_->isLeafPinClock(pin))
|
||||
checkClkPeriod(pin, transition_count);
|
||||
annotated_pins_.insert(pin);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
ReadVcdActivities::checkClkPeriod(const Pin *pin,
|
||||
double transition_count)
|
||||
{
|
||||
VcdTime time_max = vcd_reader_.timeMax();
|
||||
double time_scale = vcd_reader_.timeScale();
|
||||
double sim_period = time_max * time_scale / (transition_count / 2.0);
|
||||
ClockSet *clks = sdc_->findLeafPinClocks(pin);
|
||||
if (clks) {
|
||||
for (Clock *clk : *clks) {
|
||||
double clk_period = clk->period();
|
||||
if (abs((clk_period - sim_period) / clk_period) > sim_clk_period_tolerance_)
|
||||
// Warn if sim clock period differs from SDC by more than 10%.
|
||||
report_->warn(1452, "clock %s vcd period %s differs from SDC clock period %s",
|
||||
clk->name(),
|
||||
delayAsString(sim_period, this),
|
||||
delayAsString(clk_period, this));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -16,24 +16,13 @@
|
|||
|
||||
#pragma once
|
||||
|
||||
#include "Vcd.hh"
|
||||
|
||||
namespace sta {
|
||||
|
||||
class StaState;
|
||||
|
||||
Vcd
|
||||
readVcdFile(const char *filename,
|
||||
StaState *sta);
|
||||
class Sta;
|
||||
|
||||
void
|
||||
reportVcdWaveforms(const char *filename,
|
||||
StaState *sta);
|
||||
|
||||
void
|
||||
reportVcdVarValues(const char *filename,
|
||||
const char *var_name,
|
||||
StaState *sta);
|
||||
readVcdActivities(const char *filename,
|
||||
const char *scope,
|
||||
Sta *sta);
|
||||
|
||||
} // namespace
|
||||
|
||||
|
|
|
|||
|
|
@ -73,6 +73,9 @@ proc source_ { filename echo verbose } {
|
|||
sta_error 340 "cannot open '$filename'."
|
||||
} else {
|
||||
if { [file extension $filename] == ".gz" } {
|
||||
if { [info commands zlib] == "" } {
|
||||
sta_error 339 "tcl version > 8.6 required for zlib support."
|
||||
}
|
||||
zlib push gunzip $stream
|
||||
}
|
||||
# Save file and line in recursive call to source.
|
||||
|
|
|
|||
|
|
@ -56,7 +56,7 @@ proc_redirect read_sdf {
|
|||
################################################################
|
||||
|
||||
define_cmd_args "report_annotated_delay" \
|
||||
{[-cell] [-net] [-from_in_ports] [-to_out_ports] [-max_lines liness]\
|
||||
{[-cell] [-net] [-from_in_ports] [-to_out_ports] [-max_lines lines]\
|
||||
[-list_annotated] [-list_not_annotated] [-constant_arcs]}
|
||||
|
||||
proc_redirect report_annotated_delay {
|
||||
|
|
@ -92,7 +92,7 @@ proc_redirect report_annotated_delay {
|
|||
|
||||
define_cmd_args "report_annotated_check" \
|
||||
{[-setup] [-hold] [-recovery] [-removal] [-nochange] [-width] [-period]\
|
||||
[-max_skew] [-max_lines liness] [-list_annotated] [-list_not_annotated]\
|
||||
[-max_skew] [-max_lines lines] [-list_annotated] [-list_not_annotated]\
|
||||
[-constant_arcs]}
|
||||
|
||||
proc_redirect report_annotated_check {
|
||||
|
|
|
|||
|
|
@ -51,8 +51,7 @@ PathEnd::~PathEnd()
|
|||
}
|
||||
|
||||
void
|
||||
PathEnd::setPath(PathEnumed *path,
|
||||
const StaState *)
|
||||
PathEnd::setPath(const Path *path)
|
||||
{
|
||||
path_.init(path);
|
||||
}
|
||||
|
|
@ -523,8 +522,7 @@ PathEndClkConstrained::PathEndClkConstrained(Path *path,
|
|||
}
|
||||
|
||||
void
|
||||
PathEndClkConstrained::setPath(PathEnumed *path,
|
||||
const StaState *)
|
||||
PathEndClkConstrained::setPath(const Path *path)
|
||||
{
|
||||
path_.init(path);
|
||||
crpr_valid_ = false;
|
||||
|
|
|
|||
|
|
@ -377,7 +377,7 @@ PathEnumFaninVisitor::makeDivertedPathEnd(Path *after_div,
|
|||
path_enum_->makeDivertedPath(path_end_->path(), &before_div_, after_div,
|
||||
div_arc, div_path, after_div_copy);
|
||||
div_end = path_end_->copy();
|
||||
div_end->setPath(div_path, this);
|
||||
div_end->setPath(div_path);
|
||||
}
|
||||
|
||||
void
|
||||
|
|
|
|||
|
|
@ -93,14 +93,13 @@ PathGroup::~PathGroup()
|
|||
}
|
||||
|
||||
bool
|
||||
PathGroup::savable(PathEnd *path_end)
|
||||
PathGroup::saveable(PathEnd *path_end)
|
||||
{
|
||||
float threshold;
|
||||
{
|
||||
LockGuard lock(lock_);
|
||||
threshold = threshold_;
|
||||
}
|
||||
bool savable = false;
|
||||
if (compare_slack_) {
|
||||
// Crpr increases the slack, so check the slack
|
||||
// without crpr first because it is expensive to find.
|
||||
|
|
@ -110,17 +109,49 @@ PathGroup::savable(PathEnd *path_end)
|
|||
&& delayLessEqual(slack, slack_max_, sta_)) {
|
||||
// Now check with crpr.
|
||||
slack = path_end->slack(sta_);
|
||||
savable = delayLessEqual(slack, threshold, sta_)
|
||||
return delayLessEqual(slack, threshold, sta_)
|
||||
&& delayLessEqual(slack, slack_max_, sta_)
|
||||
&& delayGreaterEqual(slack, slack_min_, sta_);
|
||||
}
|
||||
}
|
||||
else {
|
||||
const Arrival &arrival = path_end->dataArrivalTime(sta_);
|
||||
savable = !delayIsInitValue(arrival, min_max_)
|
||||
return !delayIsInitValue(arrival, min_max_)
|
||||
&& delayGreaterEqual(arrival, threshold, min_max_, sta_);
|
||||
}
|
||||
return savable;
|
||||
return false;
|
||||
}
|
||||
|
||||
// endpoint_path_count > 1 with slack_min requires
|
||||
// saving endpoints with slack > slack_min so that
|
||||
// path enumeration can find them. Use the patg end
|
||||
// with the min(max) delay to prune ends that cannot
|
||||
// onion peel down to slack_min.
|
||||
bool
|
||||
PathGroup::enumMinSlackUnderMin(PathEnd *path_end)
|
||||
{
|
||||
if (compare_slack_
|
||||
&& endpoint_path_count_ > 1
|
||||
&& slack_min_ > -INF) {
|
||||
const Path *path = path_end->path();
|
||||
PathAnalysisPt *other_ap = path->pathAnalysisPt(sta_)->tgtClkAnalysisPt();
|
||||
const Tag *tag = path->tag(sta_);
|
||||
VertexPathIterator other_iter(path->vertex(sta_),
|
||||
path->transition(sta_),
|
||||
other_ap, sta_);
|
||||
while (other_iter.hasNext()) {
|
||||
PathVertex *other = other_iter.next();
|
||||
if (tagMatchCrpr(other->tag(sta_), tag)) {
|
||||
PathEnd *end_min = path_end->copy();
|
||||
end_min->setPath(other);
|
||||
bool slack_under = fuzzyGreater(end_min->slackNoCrpr(sta_), slack_min_);
|
||||
delete end_min;
|
||||
if (slack_under)
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void
|
||||
|
|
@ -565,7 +596,7 @@ void
|
|||
MakePathEnds1::visitPathEnd(PathEnd *path_end,
|
||||
PathGroup *group)
|
||||
{
|
||||
if (group->savable(path_end)) {
|
||||
if (group->saveable(path_end)) {
|
||||
// Only keep the path end with the smallest slack/latest arrival.
|
||||
PathEnd *worst_end = ends_.findKey(group);
|
||||
if (worst_end) {
|
||||
|
|
@ -588,7 +619,7 @@ MakePathEnds1::vertexEnd(Vertex *)
|
|||
PathGroup *group;
|
||||
PathEnd *end;
|
||||
group_iter.next(group, end);
|
||||
// visitPathEnd already confirmed slack is savable.
|
||||
// visitPathEnd already confirmed slack is saveable.
|
||||
if (end) {
|
||||
group->insert(end);
|
||||
// Clear ends_ for next vertex.
|
||||
|
|
@ -701,7 +732,8 @@ MakePathEndsAll::vertexEnd(Vertex *)
|
|||
path_end->path()->tag(sta_)->index());
|
||||
// Give the group a copy of the path end because
|
||||
// it may delete it during pruning.
|
||||
if (group->savable(path_end)) {
|
||||
if (group->saveable(path_end)
|
||||
|| group->enumMinSlackUnderMin(path_end)) {
|
||||
group->insert(path_end->copy());
|
||||
unique_ends.insert(path_end);
|
||||
n++;
|
||||
|
|
@ -788,7 +820,8 @@ PathGroups::enumPathEnds(PathGroup *group,
|
|||
PathGroupIterator *end_iter = group->iterator();
|
||||
while (end_iter->hasNext()) {
|
||||
PathEnd *end = end_iter->next();
|
||||
if (group->savable(end))
|
||||
if (group->saveable(end)
|
||||
|| group->enumMinSlackUnderMin(end))
|
||||
path_enum.insert(end);
|
||||
}
|
||||
delete end_iter;
|
||||
|
|
@ -797,7 +830,10 @@ PathGroups::enumPathEnds(PathGroup *group,
|
|||
// Parallel path enumeratation to find the endpoint_path_count/max path ends.
|
||||
for (int n = 0; path_enum.hasNext() && n < group_path_count; n++) {
|
||||
PathEnd *end = path_enum.next();
|
||||
group->insert(end);
|
||||
if (group->saveable(end))
|
||||
group->insert(end);
|
||||
else
|
||||
delete end;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -780,7 +780,7 @@ getProperty(const Port *port,
|
|||
else if (stringEqual(property, "activity")) {
|
||||
const Instance *top_inst = network->topInstance();
|
||||
const Pin *pin = network->findPin(top_inst, port);
|
||||
PwrActivity activity = sta->findClkedActivity(pin);
|
||||
PwrActivity activity = sta->activity(pin);
|
||||
return PropertyValue(&activity);
|
||||
}
|
||||
|
||||
|
|
@ -998,7 +998,7 @@ getProperty(const Pin *pin,
|
|||
return PropertyValue(&clks);
|
||||
}
|
||||
else if (stringEqual(property, "activity")) {
|
||||
PwrActivity activity = sta->findClkedActivity(pin);
|
||||
PwrActivity activity = sta->activity(pin);
|
||||
return PropertyValue(&activity);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -55,6 +55,15 @@
|
|||
|
||||
namespace sta {
|
||||
|
||||
static void
|
||||
hierPinsAbove(const Net *net,
|
||||
const Network *network,
|
||||
PinSeq &pins_above);
|
||||
static void
|
||||
hierPinsAbove(const Pin *pin,
|
||||
const Network *network,
|
||||
PinSeq &pins_above);
|
||||
|
||||
static PinSeq
|
||||
hierPinsThruEdge(const Edge *edge,
|
||||
const Network *network,
|
||||
|
|
@ -326,14 +335,18 @@ void
|
|||
ReportPath::reportPathEnds(PathEndSeq *ends)
|
||||
{
|
||||
reportPathEndHeader();
|
||||
PathEndSeq::Iterator end_iter(ends);
|
||||
PathEnd *prev_end = nullptr;
|
||||
while (end_iter.hasNext()) {
|
||||
PathEnd *end = end_iter.next();
|
||||
reportEndpointHeader(end, prev_end);
|
||||
end->reportFull(this);
|
||||
reportBlankLine();
|
||||
prev_end = end;
|
||||
if (ends && !ends->empty()) {
|
||||
PathEndSeq::Iterator end_iter(ends);
|
||||
PathEnd *prev_end = nullptr;
|
||||
while (end_iter.hasNext()) {
|
||||
PathEnd *end = end_iter.next();
|
||||
reportPathEnd(end, prev_end, !end_iter.hasNext());
|
||||
prev_end = end;
|
||||
}
|
||||
}
|
||||
else {
|
||||
if (format_ != ReportPathFormat::json)
|
||||
report_->reportLine("No paths found.");
|
||||
}
|
||||
reportPathEndFooter();
|
||||
}
|
||||
|
|
@ -1086,9 +1099,9 @@ ReportPath::reportJson(const PathEnd *end,
|
|||
const Pin *startpoint = expanded.startPath()->vertex(this)->pin();
|
||||
const Pin *endpoint = expanded.endPath()->vertex(this)->pin();
|
||||
stringAppend(result, " \"startpoint\": \"%s\",\n",
|
||||
network_->pathName(startpoint));
|
||||
sdc_network_->pathName(startpoint));
|
||||
stringAppend(result, " \"endpoint\": \"%s\",\n",
|
||||
network_->pathName(endpoint));
|
||||
sdc_network_->pathName(endpoint));
|
||||
|
||||
const ClockEdge *src_clk_edge = end->sourceClkEdge(this);
|
||||
const PathVertex *tgt_clk_path = end->targetClkPath();
|
||||
|
|
@ -1171,10 +1184,51 @@ ReportPath::reportJson(const PathExpanded &expanded,
|
|||
for (size_t i = 0; i < expanded.size(); i++) {
|
||||
const PathRef *path = expanded.path(i);
|
||||
const Pin *pin = path->vertex(this)->pin();
|
||||
const Net *net = network_->net(pin);
|
||||
const Instance *inst = network_->instance(pin);
|
||||
const RiseFall *rf = path->transition(this);
|
||||
DcalcAnalysisPt *dcalc_ap = path->pathAnalysisPt(this)->dcalcAnalysisPt();
|
||||
bool is_driver = network_->isDriver(pin);
|
||||
|
||||
stringAppend(result, "%*s {\n", indent, "");
|
||||
|
||||
if (inst) {
|
||||
stringAppend(result, "%*s \"instance\": \"%s\",\n",
|
||||
indent, "",
|
||||
sdc_network_->pathName(inst));
|
||||
Cell *cell = network_->cell(inst);
|
||||
if (cell)
|
||||
stringAppend(result, "%*s \"cell\": \"%s\",\n",
|
||||
indent, "",
|
||||
sdc_network_->name(cell));
|
||||
stringAppend(result, "%*s \"verilog_src\": \"%s\",\n",
|
||||
indent, "",
|
||||
sdc_network_->getAttribute(inst, "src").c_str());
|
||||
}
|
||||
|
||||
stringAppend(result, "%*s \"pin\": \"%s\",\n",
|
||||
indent, "",
|
||||
network_->pathName(pin));
|
||||
sdc_network_->pathName(pin));
|
||||
|
||||
if (net) {
|
||||
stringAppend(result, "%*s \"net\": \"%s\",\n",
|
||||
indent, "",
|
||||
sdc_network_->pathName(net));
|
||||
}
|
||||
|
||||
PinSeq pins_above;
|
||||
hierPinsAbove(pin, network_, pins_above);
|
||||
if (!pins_above.empty()) {
|
||||
stringAppend(result, "%*s \"hier_pins\": [\n", indent, "");
|
||||
for (const Pin *hpin : pins_above) {
|
||||
stringAppend(result, "%*s \"%s\"%s\n",
|
||||
indent, "",
|
||||
sdc_network_->pathName(hpin),
|
||||
(hpin != pins_above.back()) ? "," : "");
|
||||
}
|
||||
stringAppend(result, "%*s ],\n", indent, "");
|
||||
}
|
||||
|
||||
double x, y;
|
||||
bool exists;
|
||||
network_->location(pin, x, y, exists);
|
||||
|
|
@ -1186,6 +1240,10 @@ ReportPath::reportJson(const PathExpanded &expanded,
|
|||
stringAppend(result, "%*s \"arrival\": %.3e,\n",
|
||||
indent, "",
|
||||
delayAsFloat(path->arrival(this)));
|
||||
if (is_driver)
|
||||
stringAppend(result, "%*s \"capacitance\": %.3e,\n",
|
||||
indent, "",
|
||||
graph_delay_calc_->loadCap(pin, rf, dcalc_ap));
|
||||
stringAppend(result, "%*s \"slew\": %.3e\n",
|
||||
indent, "",
|
||||
delayAsFloat(path->slew(this)));
|
||||
|
|
@ -3481,15 +3539,6 @@ ReportPath::latchDesc(const RiseFall *clk_rf) const
|
|||
|
||||
////////////////////////////////////////////////////////////////
|
||||
|
||||
static void
|
||||
hierPinsAbove(const Net *net,
|
||||
const Network *network,
|
||||
PinSeq &pins_above);
|
||||
static void
|
||||
hierPinsAbove(const Pin *pin,
|
||||
const Network *network,
|
||||
PinSeq &pins_above);
|
||||
|
||||
static PinSeq
|
||||
hierPinsThruEdge(const Edge *edge,
|
||||
const Network *network,
|
||||
|
|
@ -3557,6 +3606,7 @@ hierPinsAbove(const Net *net,
|
|||
if (hpin_net)
|
||||
hierPinsAbove(hpin_net, network, pins_above);
|
||||
}
|
||||
delete term_iter;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -38,6 +38,7 @@ class ReportPath : public StaState
|
|||
public:
|
||||
explicit ReportPath(StaState *sta);
|
||||
virtual ~ReportPath();
|
||||
ReportPathFormat pathFormat() const { return format_; }
|
||||
void setPathFormat(ReportPathFormat format);
|
||||
void setReportFieldOrder(StringSeq *field_names);
|
||||
void setReportFields(bool report_input_pin,
|
||||
|
|
|
|||
|
|
@ -509,6 +509,13 @@ report_path_cmd(PathRef *path)
|
|||
Sta::sta()->reportPath(path);
|
||||
}
|
||||
|
||||
void
|
||||
report_path_ends(PathEndSeq *ends)
|
||||
{
|
||||
Sta::sta()->reportPathEnds(ends);
|
||||
delete ends;
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////
|
||||
|
||||
void
|
||||
|
|
|
|||
|
|
@ -420,14 +420,9 @@ define_cmd_args "report_checks" \
|
|||
|
||||
proc_redirect report_checks {
|
||||
global sta_report_unconstrained_paths
|
||||
|
||||
parse_report_path_options "report_checks" args "full" 0
|
||||
set path_ends [find_timing_paths_cmd "report_checks" args]
|
||||
if { $path_ends == {} } {
|
||||
report_line "No paths found."
|
||||
} else {
|
||||
report_path_ends $path_ends
|
||||
}
|
||||
report_path_ends $path_ends
|
||||
}
|
||||
|
||||
################################################################
|
||||
|
|
@ -1091,19 +1086,6 @@ proc parse_path_group_arg { group_names } {
|
|||
return $names
|
||||
}
|
||||
|
||||
proc report_path_ends { path_ends } {
|
||||
report_path_end_header
|
||||
set prev_end "NULL"
|
||||
set end_count [llength $path_ends]
|
||||
set i 0
|
||||
foreach path_end $path_ends {
|
||||
report_path_end2 $path_end $prev_end [expr $i == ($end_count - 1)]
|
||||
set prev_end $path_end
|
||||
incr i
|
||||
}
|
||||
report_path_end_footer
|
||||
}
|
||||
|
||||
################################################################
|
||||
|
||||
define_cmd_args "report_clock_min_period" \
|
||||
|
|
|
|||
|
|
@ -2555,6 +2555,12 @@ Sta::reportPathEnd(PathEnd *end,
|
|||
report_path_->reportPathEnd(end, prev_end, last);
|
||||
}
|
||||
|
||||
void
|
||||
Sta::reportPathEnds(PathEndSeq *ends)
|
||||
{
|
||||
report_path_->reportPathEnds(ends);
|
||||
}
|
||||
|
||||
void
|
||||
Sta::reportPath(Path *path)
|
||||
{
|
||||
|
|
@ -5690,10 +5696,10 @@ Sta::power(const Instance *inst,
|
|||
}
|
||||
|
||||
PwrActivity
|
||||
Sta::findClkedActivity(const Pin *pin)
|
||||
Sta::activity(const Pin *pin)
|
||||
{
|
||||
powerPreamble();
|
||||
return power_->findClkedActivity(pin);
|
||||
return power_->pinActivity(pin);
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////
|
||||
|
|
|
|||
|
|
@ -20,6 +20,13 @@
|
|||
fprintf(stderr, "Error: out of memory.\n");
|
||||
exit(1);
|
||||
}
|
||||
catch (ExceptionMsg &excp) {
|
||||
if (!excp.suppressed()) {
|
||||
Tcl_ResetResult(interp);
|
||||
Tcl_AppendResult(interp, "Error: ", excp.what(), nullptr);
|
||||
}
|
||||
return TCL_ERROR;
|
||||
}
|
||||
catch (std::exception &excp) {
|
||||
Tcl_ResetResult(interp);
|
||||
Tcl_AppendResult(interp, "Error: ", excp.what(), nullptr);
|
||||
|
|
|
|||
|
|
@ -39,22 +39,26 @@
|
|||
#include "PathEnd.hh"
|
||||
#include "SearchClass.hh"
|
||||
#include "CircuitSim.hh"
|
||||
#include "ArcDelayCalc.hh"
|
||||
#include "Property.hh"
|
||||
#include "Sta.hh"
|
||||
#include "TclTypeHelpers.hh"
|
||||
|
||||
namespace sta {
|
||||
|
||||
typedef MinPulseWidthCheckSeq::Iterator MinPulseWidthCheckSeqIterator;
|
||||
typedef MinMaxAll MinMaxAllNull;
|
||||
|
||||
#if TCL_MAJOR_VERSION < 9
|
||||
typedef int Tcl_Size;
|
||||
#endif
|
||||
|
||||
template <class TYPE>
|
||||
Vector<TYPE> *
|
||||
tclListSeqPtr(Tcl_Obj *const source,
|
||||
swig_type_info *swig_type,
|
||||
Tcl_Interp *interp)
|
||||
{
|
||||
int argc;
|
||||
Tcl_Size argc;
|
||||
Tcl_Obj **argv;
|
||||
|
||||
if (Tcl_ListObjGetElements(interp, source, &argc, &argv) == TCL_OK
|
||||
|
|
@ -78,7 +82,7 @@ tclListSeq(Tcl_Obj *const source,
|
|||
swig_type_info *swig_type,
|
||||
Tcl_Interp *interp)
|
||||
{
|
||||
int argc;
|
||||
Tcl_Size argc;
|
||||
Tcl_Obj **argv;
|
||||
|
||||
std::vector<TYPE> seq;
|
||||
|
|
@ -100,7 +104,7 @@ tclListSetPtr(Tcl_Obj *const source,
|
|||
swig_type_info *swig_type,
|
||||
Tcl_Interp *interp)
|
||||
{
|
||||
int argc;
|
||||
Tcl_Size argc;
|
||||
Tcl_Obj **argv;
|
||||
if (Tcl_ListObjGetElements(interp, source, &argc, &argv) == TCL_OK
|
||||
&& argc > 0) {
|
||||
|
|
@ -123,7 +127,7 @@ tclListSet(Tcl_Obj *const source,
|
|||
swig_type_info *swig_type,
|
||||
Tcl_Interp *interp)
|
||||
{
|
||||
int argc;
|
||||
Tcl_Size argc;
|
||||
Tcl_Obj **argv;
|
||||
if (Tcl_ListObjGetElements(interp, source, &argc, &argv) == TCL_OK
|
||||
&& argc > 0) {
|
||||
|
|
@ -147,7 +151,7 @@ tclListNetworkSet(Tcl_Obj *const source,
|
|||
Tcl_Interp *interp,
|
||||
const Network *network)
|
||||
{
|
||||
int argc;
|
||||
Tcl_Size argc;
|
||||
Tcl_Obj **argv;
|
||||
if (Tcl_ListObjGetElements(interp, source, &argc, &argv) == TCL_OK
|
||||
&& argc > 0) {
|
||||
|
|
@ -171,7 +175,7 @@ tclListNetworkSet1(Tcl_Obj *const source,
|
|||
Tcl_Interp *interp,
|
||||
const Network *network)
|
||||
{
|
||||
int argc;
|
||||
Tcl_Size argc;
|
||||
Tcl_Obj **argv;
|
||||
SET_TYPE set(network);
|
||||
if (Tcl_ListObjGetElements(interp, source, &argc, &argv) == TCL_OK
|
||||
|
|
@ -186,68 +190,6 @@ tclListNetworkSet1(Tcl_Obj *const source,
|
|||
return set;
|
||||
}
|
||||
|
||||
static StringSet *
|
||||
tclListSetConstChar(Tcl_Obj *const source,
|
||||
Tcl_Interp *interp)
|
||||
{
|
||||
int argc;
|
||||
Tcl_Obj **argv;
|
||||
|
||||
if (Tcl_ListObjGetElements(interp, source, &argc, &argv) == TCL_OK) {
|
||||
StringSet *set = new StringSet;
|
||||
for (int i = 0; i < argc; i++) {
|
||||
int length;
|
||||
const char *str = Tcl_GetStringFromObj(argv[i], &length);
|
||||
set->insert(str);
|
||||
}
|
||||
return set;
|
||||
}
|
||||
else
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
static StringSeq *
|
||||
tclListSeqConstChar(Tcl_Obj *const source,
|
||||
Tcl_Interp *interp)
|
||||
{
|
||||
int argc;
|
||||
Tcl_Obj **argv;
|
||||
|
||||
if (Tcl_ListObjGetElements(interp, source, &argc, &argv) == TCL_OK) {
|
||||
StringSeq *seq = new StringSeq;
|
||||
for (int i = 0; i < argc; i++) {
|
||||
int length;
|
||||
const char *str = Tcl_GetStringFromObj(argv[i], &length);
|
||||
seq->push_back(str);
|
||||
}
|
||||
return seq;
|
||||
}
|
||||
else
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
#ifdef UNUSED
|
||||
static StdStringSet *
|
||||
tclListSetStdString(Tcl_Obj *const source,
|
||||
Tcl_Interp *interp)
|
||||
{
|
||||
int argc;
|
||||
Tcl_Obj **argv;
|
||||
|
||||
if (Tcl_ListObjGetElements(interp, source, &argc, &argv) == TCL_OK) {
|
||||
StdStringSet *set = new StdStringSet;
|
||||
for (int i = 0; i < argc; i++) {
|
||||
int length;
|
||||
const char *str = Tcl_GetStringFromObj(argv[i], &length);
|
||||
set->insert(str);
|
||||
}
|
||||
return set;
|
||||
}
|
||||
else
|
||||
return nullptr;
|
||||
}
|
||||
#endif
|
||||
|
||||
////////////////////////////////////////////////////////////////
|
||||
|
||||
// Sequence out to tcl list.
|
||||
|
|
@ -313,123 +255,6 @@ setPtrTclList(SET_TYPE *set,
|
|||
|
||||
////////////////////////////////////////////////////////////////
|
||||
|
||||
static void
|
||||
tclArgError(Tcl_Interp *interp,
|
||||
const char *msg,
|
||||
const char *arg)
|
||||
{
|
||||
// Swig does not add try/catch around arg parsing so this cannot use Report::error.
|
||||
string error_msg = "Error: ";
|
||||
error_msg += msg;
|
||||
char *error = stringPrint(error_msg.c_str(), arg);
|
||||
Tcl_SetResult(interp, error, TCL_VOLATILE);
|
||||
stringDelete(error);
|
||||
}
|
||||
|
||||
static void
|
||||
objectListNext(const char *list,
|
||||
const char *type,
|
||||
// Return values.
|
||||
bool &type_match,
|
||||
const char *&next)
|
||||
{
|
||||
// Default return values (failure).
|
||||
type_match = false;
|
||||
next = nullptr;
|
||||
// _hexaddress_p_type
|
||||
const char *s = list;
|
||||
char ch = *s++;
|
||||
if (ch == '_') {
|
||||
while (*s && isxdigit(*s))
|
||||
s++;
|
||||
if ((s - list - 1) == sizeof(void*) * 2
|
||||
&& *s && *s++ == '_'
|
||||
&& *s && *s++ == 'p'
|
||||
&& *s && *s++ == '_') {
|
||||
const char *t = type;
|
||||
while (*s && *s != ' ') {
|
||||
if (*s != *t)
|
||||
return;
|
||||
s++;
|
||||
t++;
|
||||
}
|
||||
type_match = true;
|
||||
if (*s)
|
||||
next = s + 1;
|
||||
else
|
||||
next = nullptr;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef UNUSED
|
||||
static Tcl_Obj *
|
||||
tclArcDcalcArg(ArcDcalcArg &gate,
|
||||
Tcl_Interp *interp)
|
||||
{
|
||||
Sta *sta = Sta::sta();
|
||||
const Network *network = sta->network();
|
||||
const Instance *drvr = network->instance(gate.drvrPin());
|
||||
const TimingArc *arc = gate.arc();
|
||||
|
||||
Tcl_Obj *list = Tcl_NewListObj(0, nullptr);
|
||||
Tcl_Obj *obj;
|
||||
|
||||
const char *inst_name = network->pathName(drvr);
|
||||
obj = Tcl_NewStringObj(inst_name, strlen(inst_name));
|
||||
Tcl_ListObjAppendElement(interp, list, obj);
|
||||
|
||||
const char *from_name = arc->from()->name();
|
||||
obj = Tcl_NewStringObj(from_name, strlen(from_name));
|
||||
Tcl_ListObjAppendElement(interp, list, obj);
|
||||
|
||||
const char *from_edge = arc->fromEdge()->asString();
|
||||
obj = Tcl_NewStringObj(from_edge, strlen(from_edge));
|
||||
Tcl_ListObjAppendElement(interp, list, obj);
|
||||
|
||||
const char *to_name = arc->to()->name();
|
||||
obj = Tcl_NewStringObj(to_name, strlen(to_name));
|
||||
Tcl_ListObjAppendElement(interp, list, obj);
|
||||
|
||||
const char *to_edge = arc->toEdge()->asString();
|
||||
obj = Tcl_NewStringObj(to_edge, strlen(to_edge));
|
||||
Tcl_ListObjAppendElement(interp, list, obj);
|
||||
|
||||
const char *input_delay = delayAsString(gate.inputDelay(), sta, 3);
|
||||
obj = Tcl_NewStringObj(input_delay, strlen(input_delay));
|
||||
Tcl_ListObjAppendElement(interp, list, obj);
|
||||
|
||||
return list;
|
||||
}
|
||||
|
||||
static ArcDcalcArg
|
||||
arcDcalcArgTcl(Tcl_Obj *obj,
|
||||
Tcl_Interp *interp)
|
||||
{
|
||||
Sta *sta = Sta::sta();
|
||||
sta->ensureGraph();
|
||||
int list_argc;
|
||||
Tcl_Obj **list_argv;
|
||||
if (Tcl_ListObjGetElements(interp, obj, &list_argc, &list_argv) == TCL_OK) {
|
||||
const char *input_delay = "0.0";
|
||||
int length;
|
||||
if (list_argc == 6)
|
||||
input_delay = Tcl_GetStringFromObj(list_argv[5], &length);
|
||||
if (list_argc == 5 || list_argc == 6) {
|
||||
return makeArcDcalcArg(Tcl_GetStringFromObj(list_argv[0], &length),
|
||||
Tcl_GetStringFromObj(list_argv[1], &length),
|
||||
Tcl_GetStringFromObj(list_argv[2], &length),
|
||||
Tcl_GetStringFromObj(list_argv[3], &length),
|
||||
Tcl_GetStringFromObj(list_argv[4], &length),
|
||||
input_delay, sta);
|
||||
}
|
||||
else
|
||||
sta->report()->warn(2140, "Delay calc arg requires 5 or 6 args.");
|
||||
}
|
||||
return ArcDcalcArg();
|
||||
}
|
||||
#endif
|
||||
|
||||
} // namespace
|
||||
|
||||
using namespace sta;
|
||||
|
|
@ -913,7 +738,7 @@ using namespace sta;
|
|||
}
|
||||
|
||||
%typemap(in) FloatSeq* {
|
||||
int argc;
|
||||
Tcl_Size argc;
|
||||
Tcl_Obj **argv;
|
||||
FloatSeq *floats = nullptr;
|
||||
|
||||
|
|
@ -958,7 +783,7 @@ using namespace sta;
|
|||
}
|
||||
|
||||
%typemap(in) IntSeq* {
|
||||
int argc;
|
||||
Tcl_Size argc;
|
||||
Tcl_Obj **argv;
|
||||
IntSeq *ints = nullptr;
|
||||
|
||||
|
|
@ -1286,6 +1111,10 @@ using namespace sta;
|
|||
Tcl_SetObjResult(interp, obj);
|
||||
}
|
||||
|
||||
%typemap(in) PathEndSeq* {
|
||||
$1 = tclListSeqPtr<PathEnd*>($input, SWIGTYPE_p_PathEnd, interp);
|
||||
}
|
||||
|
||||
%typemap(out) PathEndSeq* {
|
||||
Tcl_Obj *list = Tcl_NewListObj(0, nullptr);
|
||||
const PathEndSeq *path_ends = $1;
|
||||
|
|
@ -1541,7 +1370,7 @@ using namespace sta;
|
|||
Tcl_Obj *obj;
|
||||
const char *str;
|
||||
|
||||
str = stringPrintTmp("%.5e", activity.activity());
|
||||
str = stringPrintTmp("%.5e", activity.density());
|
||||
obj = Tcl_NewStringObj(str, strlen(str));
|
||||
Tcl_ListObjAppendElement(interp, list, obj);
|
||||
|
||||
|
|
@ -1586,7 +1415,7 @@ using namespace sta;
|
|||
|
||||
%typemap(in) ArcDcalcArgSeq {
|
||||
Tcl_Obj *const source = $input;
|
||||
int argc;
|
||||
Tcl_Size argc;
|
||||
Tcl_Obj **argv;
|
||||
|
||||
Sta *sta = Sta::sta();
|
||||
|
|
|
|||
|
|
@ -0,0 +1,201 @@
|
|||
// OpenSTA, Static Timing Analyzer
|
||||
// Copyright (c) 2024, Parallax Software, Inc.
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
#include "TclTypeHelpers.hh"
|
||||
|
||||
#include "Liberty.hh"
|
||||
#include "Network.hh"
|
||||
#include "Sta.hh"
|
||||
|
||||
namespace sta {
|
||||
|
||||
StringSet *
|
||||
tclListSetConstChar(Tcl_Obj *const source,
|
||||
Tcl_Interp *interp)
|
||||
{
|
||||
Tcl_Size argc;
|
||||
Tcl_Obj **argv;
|
||||
|
||||
if (Tcl_ListObjGetElements(interp, source, &argc, &argv) == TCL_OK) {
|
||||
StringSet *set = new StringSet;
|
||||
for (int i = 0; i < argc; i++) {
|
||||
int length;
|
||||
const char *str = Tcl_GetStringFromObj(argv[i], &length);
|
||||
set->insert(str);
|
||||
}
|
||||
return set;
|
||||
}
|
||||
else
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
StringSeq *
|
||||
tclListSeqConstChar(Tcl_Obj *const source,
|
||||
Tcl_Interp *interp)
|
||||
{
|
||||
Tcl_Size argc;
|
||||
Tcl_Obj **argv;
|
||||
|
||||
if (Tcl_ListObjGetElements(interp, source, &argc, &argv) == TCL_OK) {
|
||||
StringSeq *seq = new StringSeq;
|
||||
for (int i = 0; i < argc; i++) {
|
||||
int length;
|
||||
const char *str = Tcl_GetStringFromObj(argv[i], &length);
|
||||
seq->push_back(str);
|
||||
}
|
||||
return seq;
|
||||
}
|
||||
else
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
StdStringSet *
|
||||
tclListSetStdString(Tcl_Obj *const source,
|
||||
Tcl_Interp *interp)
|
||||
{
|
||||
Tcl_Size argc;
|
||||
Tcl_Obj **argv;
|
||||
|
||||
if (Tcl_ListObjGetElements(interp, source, &argc, &argv) == TCL_OK) {
|
||||
StdStringSet *set = new StdStringSet;
|
||||
for (int i = 0; i < argc; i++) {
|
||||
int length;
|
||||
const char *str = Tcl_GetStringFromObj(argv[i], &length);
|
||||
set->insert(str);
|
||||
}
|
||||
return set;
|
||||
}
|
||||
else
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
|
||||
void
|
||||
tclArgError(Tcl_Interp *interp,
|
||||
const char *msg,
|
||||
const char *arg)
|
||||
{
|
||||
// Swig does not add try/catch around arg parsing so this cannot use Report::error.
|
||||
string error_msg = "Error: ";
|
||||
error_msg += msg;
|
||||
char *error = stringPrint(error_msg.c_str(), arg);
|
||||
Tcl_SetResult(interp, error, TCL_VOLATILE);
|
||||
stringDelete(error);
|
||||
}
|
||||
|
||||
void
|
||||
objectListNext(const char *list,
|
||||
const char *type,
|
||||
// Return values.
|
||||
bool &type_match,
|
||||
const char *&next)
|
||||
{
|
||||
// Default return values (failure).
|
||||
type_match = false;
|
||||
next = nullptr;
|
||||
// _hexaddress_p_type
|
||||
const char *s = list;
|
||||
char ch = *s++;
|
||||
if (ch == '_') {
|
||||
while (*s && isxdigit(*s))
|
||||
s++;
|
||||
if ((s - list - 1) == sizeof(void*) * 2
|
||||
&& *s && *s++ == '_'
|
||||
&& *s && *s++ == 'p'
|
||||
&& *s && *s++ == '_') {
|
||||
const char *t = type;
|
||||
while (*s && *s != ' ') {
|
||||
if (*s != *t)
|
||||
return;
|
||||
s++;
|
||||
t++;
|
||||
}
|
||||
type_match = true;
|
||||
if (*s)
|
||||
next = s + 1;
|
||||
else
|
||||
next = nullptr;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Tcl_Obj *
|
||||
tclArcDcalcArg(ArcDcalcArg &gate,
|
||||
Tcl_Interp *interp)
|
||||
{
|
||||
Sta *sta = Sta::sta();
|
||||
const Network *network = sta->network();
|
||||
const Instance *drvr = network->instance(gate.drvrPin());
|
||||
const TimingArc *arc = gate.arc();
|
||||
|
||||
Tcl_Obj *list = Tcl_NewListObj(0, nullptr);
|
||||
Tcl_Obj *obj;
|
||||
|
||||
const char *inst_name = network->pathName(drvr);
|
||||
obj = Tcl_NewStringObj(inst_name, strlen(inst_name));
|
||||
Tcl_ListObjAppendElement(interp, list, obj);
|
||||
|
||||
const char *from_name = arc->from()->name();
|
||||
obj = Tcl_NewStringObj(from_name, strlen(from_name));
|
||||
Tcl_ListObjAppendElement(interp, list, obj);
|
||||
|
||||
const char *from_edge = arc->fromEdge()->asString();
|
||||
obj = Tcl_NewStringObj(from_edge, strlen(from_edge));
|
||||
Tcl_ListObjAppendElement(interp, list, obj);
|
||||
|
||||
const char *to_name = arc->to()->name();
|
||||
obj = Tcl_NewStringObj(to_name, strlen(to_name));
|
||||
Tcl_ListObjAppendElement(interp, list, obj);
|
||||
|
||||
const char *to_edge = arc->toEdge()->asString();
|
||||
obj = Tcl_NewStringObj(to_edge, strlen(to_edge));
|
||||
Tcl_ListObjAppendElement(interp, list, obj);
|
||||
|
||||
const char *input_delay = delayAsString(gate.inputDelay(), sta, 3);
|
||||
obj = Tcl_NewStringObj(input_delay, strlen(input_delay));
|
||||
Tcl_ListObjAppendElement(interp, list, obj);
|
||||
|
||||
return list;
|
||||
}
|
||||
|
||||
ArcDcalcArg
|
||||
arcDcalcArgTcl(Tcl_Obj *obj,
|
||||
Tcl_Interp *interp)
|
||||
{
|
||||
Sta *sta = Sta::sta();
|
||||
sta->ensureGraph();
|
||||
int list_argc;
|
||||
Tcl_Obj **list_argv;
|
||||
if (Tcl_ListObjGetElements(interp, obj, &list_argc, &list_argv) == TCL_OK) {
|
||||
const char *input_delay = "0.0";
|
||||
int length;
|
||||
if (list_argc == 6)
|
||||
input_delay = Tcl_GetStringFromObj(list_argv[5], &length);
|
||||
if (list_argc == 5 || list_argc == 6) {
|
||||
return makeArcDcalcArg(Tcl_GetStringFromObj(list_argv[0], &length),
|
||||
Tcl_GetStringFromObj(list_argv[1], &length),
|
||||
Tcl_GetStringFromObj(list_argv[2], &length),
|
||||
Tcl_GetStringFromObj(list_argv[3], &length),
|
||||
Tcl_GetStringFromObj(list_argv[4], &length),
|
||||
input_delay, sta);
|
||||
}
|
||||
else
|
||||
sta->report()->warn(2140, "Delay calc arg requires 5 or 6 args.");
|
||||
}
|
||||
return ArcDcalcArg();
|
||||
}
|
||||
|
||||
} // namespace
|
||||
29
tcl/Util.tcl
29
tcl/Util.tcl
|
|
@ -192,10 +192,13 @@ proc sta_warn { msg_id msg } {
|
|||
proc sta_error { msg_id msg } {
|
||||
variable sdc_file
|
||||
variable sdc_line
|
||||
if { [info exists sdc_file] } {
|
||||
error "Error: [file tail $sdc_file] line $sdc_line, $msg"
|
||||
} else {
|
||||
error "Error: $msg"
|
||||
|
||||
if { ! [is_suppressed $msg_id] } {
|
||||
if { [info exists sdc_file] } {
|
||||
error "Error: [file tail $sdc_file] line $sdc_line, $msg"
|
||||
} else {
|
||||
error "Error: $msg"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -207,6 +210,24 @@ proc sta_warn_error { msg_id warn_error msg } {
|
|||
}
|
||||
}
|
||||
|
||||
define_cmd_args "suppress_msg" msg_ids
|
||||
|
||||
proc suppress_msg { args } {
|
||||
foreach msg_id $args {
|
||||
check_integer "msg_id" $msg_id
|
||||
suppress_msg_id $msg_id
|
||||
}
|
||||
}
|
||||
|
||||
define_cmd_args "unsuppress_msg" msg_ids
|
||||
|
||||
proc unsuppress_msg { args } {
|
||||
foreach msg_id $args {
|
||||
check_integer "msg_id" $msg_id
|
||||
unsuppress_msg_id $msg_id
|
||||
}
|
||||
}
|
||||
|
||||
# Defined by StaTcl.i
|
||||
define_cmd_args "elapsed_run_time" {}
|
||||
define_cmd_args "user_run_time" {}
|
||||
|
|
|
|||
Binary file not shown.
|
|
@ -0,0 +1,2 @@
|
|||
# read ccsn library and ensure no unnecessary warnings
|
||||
read_liberty asap7_ccsn.lib.gz
|
||||
|
|
@ -2,11 +2,11 @@ Warning: gcd_sky130hd.v line 527, module sky130_fd_sc_hd__tapvpwrvgnd_1 not foun
|
|||
Group Internal Switching Leakage Total
|
||||
Power Power Power Power (Watts)
|
||||
----------------------------------------------------------------
|
||||
Sequential 3.07e-04 4.75e-05 2.96e-10 3.54e-04 40.1%
|
||||
Combinational 1.58e-04 2.04e-04 6.86e-10 3.62e-04 41.0%
|
||||
Sequential 3.07e-04 4.76e-05 2.96e-10 3.54e-04 40.0%
|
||||
Combinational 1.59e-04 2.05e-04 6.86e-10 3.64e-04 41.1%
|
||||
Clock 4.68e-05 1.20e-04 2.30e-11 1.67e-04 18.9%
|
||||
Macro 0.00e+00 0.00e+00 0.00e+00 0.00e+00 0.0%
|
||||
Pad 0.00e+00 0.00e+00 0.00e+00 0.00e+00 0.0%
|
||||
----------------------------------------------------------------
|
||||
Total 5.11e-04 3.72e-04 1.00e-09 8.84e-04 100.0%
|
||||
57.9% 42.1% 0.0%
|
||||
Total 5.12e-04 3.73e-04 1.00e-09 8.85e-04 100.0%
|
||||
57.8% 42.2% 0.0%
|
||||
|
|
|
|||
|
|
@ -1,5 +1,7 @@
|
|||
Warning: gcd_sky130hd.v line 527, module sky130_fd_sc_hd__tapvpwrvgnd_1 not found. Creating black box for TAP_11.
|
||||
Annotated 937 pin activities.
|
||||
vcd 937
|
||||
unannotated 0
|
||||
Group Internal Switching Leakage Total
|
||||
Power Power Power Power (Watts)
|
||||
----------------------------------------------------------------
|
||||
|
|
|
|||
|
|
@ -75,6 +75,9 @@ proc parse_args {} {
|
|||
lappend app_options $threads
|
||||
set argv [lrange $argv 2 end]
|
||||
} elseif { $arg == "-valgrind" } {
|
||||
if { ![find_valgrind] } {
|
||||
error "valgrind not found."
|
||||
}
|
||||
set use_valgrind 1
|
||||
set argv [lrange $argv 1 end]
|
||||
} elseif { $arg == "-report_stats" } {
|
||||
|
|
@ -92,6 +95,18 @@ proc parse_args {} {
|
|||
}
|
||||
}
|
||||
|
||||
# Find valgrind in $PATH.
|
||||
proc find_valgrind {} {
|
||||
global env
|
||||
|
||||
foreach dir [regsub -all ":" $env(PATH) " "] {
|
||||
if { [file executable [file join $dir "valgrind"]] } {
|
||||
return 1
|
||||
}
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
proc expand_tests { argv } {
|
||||
global test_groups errors
|
||||
|
||||
|
|
|
|||
|
|
@ -112,27 +112,31 @@ proc list_delete { list delete } {
|
|||
|
||||
# Record tests in sta/examples
|
||||
record_example_tests {
|
||||
sdf_delays
|
||||
delay_calc
|
||||
min_max_delays
|
||||
spef_parasitics
|
||||
multi_corner
|
||||
power
|
||||
power_vcd
|
||||
sdf_delays
|
||||
spef_parasitics
|
||||
}
|
||||
|
||||
record_sta_tests {
|
||||
prima3
|
||||
verilog_attribute
|
||||
liberty_arcs_one2one_1
|
||||
liberty_arcs_one2one_2
|
||||
get_is_memory
|
||||
get_filter
|
||||
get_is_memory
|
||||
get_lib_pins_of_objects
|
||||
get_noargs
|
||||
get_objrefs
|
||||
get_lib_pins_of_objects
|
||||
report_checks_src_attr
|
||||
liberty_arcs_one2one_1
|
||||
liberty_arcs_one2one_2
|
||||
liberty_ccsn
|
||||
liberty_latch3
|
||||
prima3
|
||||
report_checks_src_attr
|
||||
report_json1
|
||||
report_json2
|
||||
suppress_msg
|
||||
verilog_attribute
|
||||
}
|
||||
|
||||
define_test_group fast [group_tests all]
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
Startpoint: in (input port clocked by clk)
|
||||
Endpoint: _1415_ (rising edge-triggered flip-flop clocked by clk)
|
||||
Startpoint: _1415_ (rising edge-triggered flip-flop clocked by clk)
|
||||
Endpoint: _1416_[0] (rising edge-triggered flip-flop clocked by clk)
|
||||
Path Group: clk
|
||||
Path Type: max
|
||||
|
||||
|
|
@ -7,22 +7,22 @@ Path Type: max
|
|||
---------------------------------------------------------------------------------------------------------------
|
||||
0.00 0.00 0.00 clock clk (rise edge)
|
||||
0.00 0.00 clock network delay (ideal)
|
||||
0.00 0.00 v input external delay
|
||||
0.00 0.00 0.00 0.00 v in (in)
|
||||
in (net)
|
||||
0.00 0.00 0.00 v _1415_/D (sky130_fd_sc_hd__dfrtp_1) synthesis/tests/counter.v:22.3-28.6
|
||||
0.00 data arrival time
|
||||
0.00 0.00 0.00 ^ _1415_/CLK (sky130_fd_sc_hd__dfrtp_1) synthesis/tests/counter.v:22.3-28.6
|
||||
0.00 0.04 0.33 0.33 v _1415_/Q (sky130_fd_sc_hd__dfrtp_1) synthesis/tests/counter.v:22.3-28.6
|
||||
mid (net) synthesis/tests/counter.v:22.3-28.6
|
||||
0.04 0.00 0.33 v _1416_[0]/D (sky130_fd_sc_hd__dfrtp_1) synthesis/tests/counter.v:22.3-28.6
|
||||
0.33 data arrival time
|
||||
|
||||
0.00 10.00 10.00 clock clk (rise edge)
|
||||
0.00 10.00 clock network delay (ideal)
|
||||
0.00 10.00 clock reconvergence pessimism
|
||||
10.00 ^ _1415_/CLK (sky130_fd_sc_hd__dfrtp_1)
|
||||
-0.10 9.90 library setup time
|
||||
9.90 data required time
|
||||
10.00 ^ _1416_[0]/CLK (sky130_fd_sc_hd__dfrtp_1)
|
||||
-0.12 9.88 library setup time
|
||||
9.88 data required time
|
||||
---------------------------------------------------------------------------------------------------------------
|
||||
9.90 data required time
|
||||
-0.00 data arrival time
|
||||
9.88 data required time
|
||||
-0.33 data arrival time
|
||||
---------------------------------------------------------------------------------------------------------------
|
||||
9.90 slack (MET)
|
||||
9.55 slack (MET)
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,78 @@
|
|||
{"checks": [
|
||||
{
|
||||
"type": "check",
|
||||
"path_group": "clk",
|
||||
"path_type": "max",
|
||||
"startpoint": "_1415_/Q",
|
||||
"endpoint": "_1416_[0]/D",
|
||||
"source_clock": "clk",
|
||||
"source_clock_edge": "rise",
|
||||
"source_path": [
|
||||
{
|
||||
"instance": "",
|
||||
"cell": "counter",
|
||||
"verilog_src": "",
|
||||
"pin": "clk",
|
||||
"arrival": 0.000e+00,
|
||||
"capacitance": 3.742e-15,
|
||||
"slew": 0.000e+00
|
||||
},
|
||||
{
|
||||
"instance": "_1415_",
|
||||
"cell": "sky130_fd_sc_hd__dfrtp_1",
|
||||
"verilog_src": "synthesis/tests/counter.v:22.3-28.6",
|
||||
"pin": "_1415_/CLK",
|
||||
"net": "clk",
|
||||
"arrival": 0.000e+00,
|
||||
"slew": 0.000e+00
|
||||
},
|
||||
{
|
||||
"instance": "_1415_",
|
||||
"cell": "sky130_fd_sc_hd__dfrtp_1",
|
||||
"verilog_src": "synthesis/tests/counter.v:22.3-28.6",
|
||||
"pin": "_1415_/Q",
|
||||
"net": "mid",
|
||||
"arrival": 3.296e-10,
|
||||
"capacitance": 1.949e-15,
|
||||
"slew": 3.612e-11
|
||||
},
|
||||
{
|
||||
"instance": "_1416_[0]",
|
||||
"cell": "sky130_fd_sc_hd__dfrtp_1",
|
||||
"verilog_src": "synthesis/tests/counter.v:22.3-28.6",
|
||||
"pin": "_1416_[0]/D",
|
||||
"net": "mid",
|
||||
"arrival": 3.296e-10,
|
||||
"slew": 3.612e-11
|
||||
}
|
||||
],
|
||||
"target_clock": "clk",
|
||||
"target_clock_edge": "rise",
|
||||
"target_clock_path": [
|
||||
{
|
||||
"instance": "",
|
||||
"cell": "counter",
|
||||
"verilog_src": "",
|
||||
"pin": "clk",
|
||||
"arrival": 0.000e+00,
|
||||
"capacitance": 3.742e-15,
|
||||
"slew": 0.000e+00
|
||||
},
|
||||
{
|
||||
"instance": "_1416_[0]",
|
||||
"cell": "sky130_fd_sc_hd__dfrtp_1",
|
||||
"verilog_src": "synthesis/tests/counter.v:22.3-28.6",
|
||||
"pin": "_1416_[0]/CLK",
|
||||
"net": "clk",
|
||||
"arrival": 0.000e+00,
|
||||
"slew": 0.000e+00
|
||||
}
|
||||
],
|
||||
"data_arrival_time": 3.296e-10,
|
||||
"crpr": 0.000e+00,
|
||||
"margin": 1.207e-10,
|
||||
"required_time": 9.879e-09,
|
||||
"slack": 9.550e-09
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
# report_checks -format json
|
||||
read_liberty ../examples/sky130hd_tt.lib.gz
|
||||
read_verilog verilog_attribute.v
|
||||
link_design counter
|
||||
create_clock -name clk -period 10 clk
|
||||
set_input_delay -clock clk 0 [all_inputs -no_clocks]
|
||||
report_checks -path_group clk -format json
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
{"checks": [
|
||||
]
|
||||
}
|
||||
|
|
@ -0,0 +1,6 @@
|
|||
# report_checks -format json with no paths
|
||||
read_liberty ../examples/sky130hd_tt.lib.gz
|
||||
read_verilog verilog_attribute.v
|
||||
link_design counter
|
||||
create_clock -name clk -period 10
|
||||
report_checks -path_group clk -format json
|
||||
|
|
@ -0,0 +1,12 @@
|
|||
Warning: suppress_msg.tcl line 18, cmd warn 1
|
||||
caught Error: suppress_msg.tcl line 18, cmd error 1
|
||||
Warning: cmd warn 2
|
||||
caught Error: cmd error 2
|
||||
after error
|
||||
caught
|
||||
caught
|
||||
after error
|
||||
Warning: suppress_msg.tcl line 51, cmd warn 7
|
||||
caught Error: suppress_msg.tcl line 51, cmd error 7
|
||||
Warning: cmd warn 8
|
||||
caught Error: cmd error 8
|
||||
|
|
@ -0,0 +1,56 @@
|
|||
# suppress and unsuppress message ids
|
||||
|
||||
# Run sta_warn/sta_error to test TCL side suppression
|
||||
proc sta_cmd { msg } {
|
||||
sta::sta_warn 1 "cmd warn $msg"
|
||||
sta::sta_error 2 "cmd error $msg"
|
||||
puts "after error"
|
||||
}
|
||||
|
||||
# Run report_warn/report_error to test C++ side suppression
|
||||
proc report_cmd { msg } {
|
||||
sta::report_warn 1 "cmd warn $msg"
|
||||
sta::report_error 2 "cmd error $msg"
|
||||
puts "after error"
|
||||
}
|
||||
|
||||
# Ensure that TCL side messages are displayed as usual
|
||||
catch { sta_cmd 1 } error
|
||||
puts "caught $error"
|
||||
|
||||
# Ensure that C++ side messages are displayed as usual
|
||||
catch { report_cmd 2 } error
|
||||
puts "caught $error"
|
||||
|
||||
# Suppress messages
|
||||
suppress_msg 1 2
|
||||
|
||||
# Ensure that TCL side messages are suppressed
|
||||
catch { sta_cmd 3 } error
|
||||
puts "caught $error"
|
||||
|
||||
# Ensure that C++ side messages are suppressed
|
||||
catch { report_cmd 4 } error
|
||||
puts "caught $error"
|
||||
|
||||
# Continue on error to avoid having to catch
|
||||
set sta_continue_on_error 1
|
||||
|
||||
# Ensure that TCL side messages are suppressed
|
||||
# TCL side will make it to "after error"
|
||||
sta_cmd 5
|
||||
|
||||
# Ensure that C++ side messages are suppressed
|
||||
# C++ will not make it to "after error" as the whole cmd is cancelled
|
||||
report_cmd 6
|
||||
|
||||
# Unsuppress messages
|
||||
unsuppress_msg 1 2
|
||||
|
||||
# Ensure that TCL side messages are displayed as usual
|
||||
catch { sta_cmd 7 } error
|
||||
puts "caught $error"
|
||||
|
||||
# Ensure that C++ side messages are displayed as usual
|
||||
catch { report_cmd 8 } error
|
||||
puts "caught $error"
|
||||
|
|
@ -11,11 +11,19 @@ module counter(clk, reset, in, out);
|
|||
(* src = "synthesis/tests/counter.v:18.14-18.19" *)
|
||||
input reset;
|
||||
input in;
|
||||
wire mid;
|
||||
(* bottom_bound = 1'sh0 *)
|
||||
(* src = "synthesis/tests/counter.v:22.3-28.6", attr1 = "test_attr1", attr2 = "test_attr2" *)
|
||||
sky130_fd_sc_hd__dfrtp_1 _1415_ (
|
||||
.CLK(clk),
|
||||
.D(in),
|
||||
.Q(mid),
|
||||
.RESET_B(reset)
|
||||
);
|
||||
(* src = "synthesis/tests/counter.v:22.3-28.6" *)
|
||||
sky130_fd_sc_hd__dfrtp_1 \_1416_[0] (
|
||||
.CLK(clk),
|
||||
.D(mid),
|
||||
.Q(out),
|
||||
.RESET_B(reset)
|
||||
);
|
||||
|
|
|
|||
|
|
@ -28,9 +28,11 @@ Exception::Exception() :
|
|||
{
|
||||
}
|
||||
|
||||
ExceptionMsg::ExceptionMsg(const char *msg) :
|
||||
ExceptionMsg::ExceptionMsg(const char *msg,
|
||||
const bool suppressed) :
|
||||
Exception(),
|
||||
msg_(msg)
|
||||
msg_(msg),
|
||||
suppressed_(suppressed)
|
||||
{
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -169,59 +169,71 @@ Report::printBufferLine()
|
|||
////////////////////////////////////////////////////////////////
|
||||
|
||||
void
|
||||
Report::warn(int /* id */,
|
||||
Report::warn(int id,
|
||||
const char *fmt,
|
||||
...)
|
||||
{
|
||||
va_list args;
|
||||
va_start(args, fmt);
|
||||
printToBuffer("Warning: ");
|
||||
printToBufferAppend(fmt, args);
|
||||
printBufferLine();
|
||||
va_end(args);
|
||||
// Skip suppressed messages.
|
||||
if (!isSuppressed(id)) {
|
||||
va_list args;
|
||||
va_start(args, fmt);
|
||||
printToBuffer("Warning: ");
|
||||
printToBufferAppend(fmt, args);
|
||||
printBufferLine();
|
||||
va_end(args);
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
Report::vwarn(int /* id */,
|
||||
Report::vwarn(int id,
|
||||
const char *fmt,
|
||||
va_list args)
|
||||
{
|
||||
printToBuffer("Warning: ");
|
||||
printToBufferAppend(fmt, args);
|
||||
printBufferLine();
|
||||
// Skip suppressed messages.
|
||||
if (!isSuppressed(id)) {
|
||||
printToBuffer("Warning: ");
|
||||
printToBufferAppend(fmt, args);
|
||||
printBufferLine();
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
Report::fileWarn(int /* id */,
|
||||
Report::fileWarn(int id,
|
||||
const char *filename,
|
||||
int line,
|
||||
const char *fmt,
|
||||
...)
|
||||
{
|
||||
va_list args;
|
||||
va_start(args, fmt);
|
||||
printToBuffer("Warning: %s line %d, ", filename, line);
|
||||
printToBufferAppend(fmt, args);
|
||||
printBufferLine();
|
||||
va_end(args);
|
||||
// Skip suppressed messages.
|
||||
if (!isSuppressed(id)) {
|
||||
va_list args;
|
||||
va_start(args, fmt);
|
||||
printToBuffer("Warning: %s line %d, ", filename, line);
|
||||
printToBufferAppend(fmt, args);
|
||||
printBufferLine();
|
||||
va_end(args);
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
Report::vfileWarn(int /* id */,
|
||||
Report::vfileWarn(int id,
|
||||
const char *filename,
|
||||
int line,
|
||||
const char *fmt,
|
||||
va_list args)
|
||||
{
|
||||
printToBuffer("Warning: %s line %d, ", filename, line);
|
||||
printToBufferAppend(fmt, args);
|
||||
printBufferLine();
|
||||
// Skip suppressed messages.
|
||||
if (!isSuppressed(id)) {
|
||||
printToBuffer("Warning: %s line %d, ", filename, line);
|
||||
printToBufferAppend(fmt, args);
|
||||
printBufferLine();
|
||||
}
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////
|
||||
|
||||
void
|
||||
Report::error(int /* id */,
|
||||
Report::error(int id,
|
||||
const char *fmt, ...)
|
||||
{
|
||||
va_list args;
|
||||
|
|
@ -229,21 +241,21 @@ Report::error(int /* id */,
|
|||
// No prefix msg, no \n.
|
||||
printToBuffer(fmt, args);
|
||||
va_end(args);
|
||||
throw ExceptionMsg(buffer_);
|
||||
throw ExceptionMsg(buffer_, isSuppressed(id));
|
||||
}
|
||||
|
||||
void
|
||||
Report::verror(int /* id */,
|
||||
Report::verror(int id,
|
||||
const char *fmt,
|
||||
va_list args)
|
||||
{
|
||||
// No prefix msg, no \n.
|
||||
printToBuffer(fmt, args);
|
||||
throw ExceptionMsg(buffer_);
|
||||
throw ExceptionMsg(buffer_, isSuppressed(id));
|
||||
}
|
||||
|
||||
void
|
||||
Report::fileError(int /* id */,
|
||||
Report::fileError(int id,
|
||||
const char *filename,
|
||||
int line,
|
||||
const char *fmt,
|
||||
|
|
@ -255,11 +267,11 @@ Report::fileError(int /* id */,
|
|||
printToBuffer("%s line %d, ", filename, line);
|
||||
printToBufferAppend(fmt, args);
|
||||
va_end(args);
|
||||
throw ExceptionMsg(buffer_);
|
||||
throw ExceptionMsg(buffer_, isSuppressed(id));
|
||||
}
|
||||
|
||||
void
|
||||
Report::vfileError(int /* id */,
|
||||
Report::vfileError(int id,
|
||||
const char *filename,
|
||||
int line,
|
||||
const char *fmt,
|
||||
|
|
@ -268,7 +280,7 @@ Report::vfileError(int /* id */,
|
|||
// No prefix msg, no \n.
|
||||
printToBuffer("%s line %d, ", filename, line);
|
||||
printToBufferAppend(fmt, args);
|
||||
throw ExceptionMsg(buffer_);
|
||||
throw ExceptionMsg(buffer_, isSuppressed(id));
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////
|
||||
|
|
@ -304,6 +316,26 @@ Report::fileCritical(int /* id */,
|
|||
|
||||
////////////////////////////////////////////////////////////////
|
||||
|
||||
void
|
||||
Report::suppressMsgId(int id)
|
||||
{
|
||||
suppressed_msg_ids_.insert(id);
|
||||
}
|
||||
|
||||
void
|
||||
Report::unsuppressMsgId(int id)
|
||||
{
|
||||
suppressed_msg_ids_.erase(id);
|
||||
}
|
||||
|
||||
bool
|
||||
Report::isSuppressed(int id)
|
||||
{
|
||||
return suppressed_msg_ids_.find(id) != suppressed_msg_ids_.end();
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////
|
||||
|
||||
void
|
||||
Report::logBegin(const char *filename)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -31,14 +31,16 @@ using ::Tcl_GetChannelType;
|
|||
|
||||
extern "C" {
|
||||
|
||||
#if TCL_MAJOR_VERSION >= 9
|
||||
#define CONST84 const
|
||||
#endif
|
||||
|
||||
static int
|
||||
encapOutputProc(ClientData instanceData,
|
||||
CONST84 char *buf,
|
||||
int toWrite,
|
||||
int *errorCodePtr);
|
||||
static int
|
||||
encapCloseProc(ClientData instanceData, Tcl_Interp *interp);
|
||||
static int
|
||||
encapSetOptionProc(ClientData instanceData,
|
||||
Tcl_Interp *interp,
|
||||
CONST84 char *optionName,
|
||||
|
|
@ -53,11 +55,6 @@ encapInputProc(ClientData instanceData,
|
|||
char *buf,
|
||||
int bufSize,
|
||||
int *errorCodePtr);
|
||||
static int
|
||||
encapSeekProc(ClientData instanceData,
|
||||
long offset,
|
||||
int seekMode,
|
||||
int *errorCodePtr);
|
||||
static void
|
||||
encapWatchProc(ClientData instanceData, int mask);
|
||||
static int
|
||||
|
|
@ -66,36 +63,55 @@ encapGetHandleProc(ClientData instanceData,
|
|||
ClientData *handlePtr);
|
||||
static int
|
||||
encapBlockModeProc(ClientData instanceData, int mode);
|
||||
|
||||
#if TCL_MAJOR_VERSION < 9
|
||||
static int
|
||||
encapCloseProc(ClientData instanceData, Tcl_Interp *interp);
|
||||
static int
|
||||
encapSeekProc(ClientData instanceData,
|
||||
long offset,
|
||||
int seekMode,
|
||||
int *errorCodePtr);
|
||||
#endif
|
||||
|
||||
} // extern "C"
|
||||
|
||||
Tcl_ChannelType tcl_encap_type_stdout = {
|
||||
const_cast<char *>("file"),
|
||||
TCL_CHANNEL_VERSION_4,
|
||||
encapCloseProc,
|
||||
encapInputProc,
|
||||
encapOutputProc,
|
||||
encapSeekProc,
|
||||
encapSetOptionProc,
|
||||
encapGetOptionProc,
|
||||
encapWatchProc,
|
||||
encapGetHandleProc,
|
||||
nullptr, // close2Proc
|
||||
encapBlockModeProc,
|
||||
nullptr, // flushProc
|
||||
nullptr, // handlerProc
|
||||
nullptr, // wideSeekProc
|
||||
nullptr, // threadActionProc
|
||||
nullptr // truncateProc
|
||||
const_cast<char*>("file"),
|
||||
TCL_CHANNEL_VERSION_5,
|
||||
#if TCL_MAJOR_VERSION < 9
|
||||
encapCloseProc,
|
||||
#else
|
||||
nullptr, // closeProc unused
|
||||
#endif
|
||||
encapInputProc,
|
||||
encapOutputProc,
|
||||
#if TCL_MAJOR_VERSION < 9
|
||||
encapSeekProc,
|
||||
#else
|
||||
nullptr, // close2Proc
|
||||
#endif
|
||||
encapSetOptionProc,
|
||||
encapGetOptionProc,
|
||||
encapWatchProc,
|
||||
encapGetHandleProc,
|
||||
nullptr, // close2Proc
|
||||
encapBlockModeProc,
|
||||
nullptr, // flushProc
|
||||
nullptr, // handlerProc
|
||||
nullptr, // wideSeekProc
|
||||
nullptr, // threadActionProc
|
||||
nullptr // truncateProc
|
||||
};
|
||||
|
||||
////////////////////////////////////////////////////////////////
|
||||
|
||||
ReportTcl::ReportTcl() :
|
||||
Report(), interp_(nullptr),
|
||||
tcl_stdout_(nullptr),
|
||||
tcl_stderr_(nullptr),
|
||||
tcl_encap_stdout_(nullptr),
|
||||
tcl_encap_stderr_(nullptr)
|
||||
Report(), interp_(nullptr),
|
||||
tcl_stdout_(nullptr),
|
||||
tcl_stderr_(nullptr),
|
||||
tcl_encap_stdout_(nullptr),
|
||||
tcl_encap_stderr_(nullptr)
|
||||
{
|
||||
}
|
||||
|
||||
|
|
@ -228,17 +244,6 @@ encapInputProc(ClientData,
|
|||
return -1;
|
||||
}
|
||||
|
||||
static int
|
||||
encapCloseProc(ClientData instanceData,
|
||||
Tcl_Interp *)
|
||||
{
|
||||
ReportTcl *report = reinterpret_cast<ReportTcl *>(instanceData);
|
||||
report->logEnd();
|
||||
report->redirectFileEnd();
|
||||
report->redirectStringEnd();
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int
|
||||
encapSetOptionProc(ClientData,
|
||||
Tcl_Interp *,
|
||||
|
|
@ -257,15 +262,6 @@ encapGetOptionProc(ClientData,
|
|||
return 0;
|
||||
}
|
||||
|
||||
static int
|
||||
encapSeekProc(ClientData,
|
||||
long,
|
||||
int,
|
||||
int *)
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
|
||||
static void
|
||||
encapWatchProc(ClientData, int)
|
||||
{
|
||||
|
|
@ -286,4 +282,28 @@ encapBlockModeProc(ClientData,
|
|||
return 0;
|
||||
}
|
||||
|
||||
#if TCL_MAJOR_VERSION < 9
|
||||
|
||||
static int
|
||||
encapCloseProc(ClientData instanceData,
|
||||
Tcl_Interp *)
|
||||
{
|
||||
ReportTcl *report = reinterpret_cast<ReportTcl *>(instanceData);
|
||||
report->logEnd();
|
||||
report->redirectFileEnd();
|
||||
report->redirectStringEnd();
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int
|
||||
encapSeekProc(ClientData,
|
||||
long,
|
||||
int,
|
||||
int *)
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
} // namespace sta
|
||||
|
|
|
|||
18
util/Util.i
18
util/Util.i
|
|
@ -148,6 +148,24 @@ report_line(const char *msg)
|
|||
printf("%s\n", msg);
|
||||
}
|
||||
|
||||
void
|
||||
suppress_msg_id(int id)
|
||||
{
|
||||
Sta::sta()->report()->suppressMsgId(id);
|
||||
}
|
||||
|
||||
void
|
||||
unsuppress_msg_id(int id)
|
||||
{
|
||||
Sta::sta()->report()->unsuppressMsgId(id);
|
||||
}
|
||||
|
||||
int
|
||||
is_suppressed(int id)
|
||||
{
|
||||
return Sta::sta()->report()->isSuppressed(id);
|
||||
}
|
||||
|
||||
void
|
||||
fflush()
|
||||
{
|
||||
|
|
|
|||
|
|
@ -1822,7 +1822,7 @@ VerilogReader::linkNetwork(const char *top_cell_name,
|
|||
VerilogModule *module = this->module(top_cell);
|
||||
if (module) {
|
||||
// Seed the recursion for expansion with the top level instance.
|
||||
Instance *top_instance = network_->makeInstance(top_cell, "", nullptr);
|
||||
Instance *top_instance = network_->makeInstance(top_cell, top_cell_name, nullptr);
|
||||
VerilogBindingTbl bindings(zero_net_name_, one_net_name_);
|
||||
VerilogNetSeq::Iterator port_iter(module->ports());
|
||||
while (port_iter.hasNext()) {
|
||||
|
|
|
|||
Loading…
Reference in New Issue