Merge branch 'master' of https://github.com/The-OpenROAD-Project-private/OpenSTA into secure-sta-test-by-opus
Signed-off-by: Jaehyun Kim <jhkim@precisioninno.com>
This commit is contained in:
commit
422f774b64
3
BUILD
3
BUILD
|
|
@ -183,7 +183,8 @@ genrule(
|
|||
#define STA_VERSION "2.7.0"
|
||||
#define STA_GIT_SHA1 "f21d4a3878e2531e3af4930818d9b5968aad9416"
|
||||
#define SSTA 0
|
||||
#define ZLIB_FOUND' > \"$@\"
|
||||
#define ZLIB_FOUND
|
||||
#define HAVE_CXX_STD_FORMAT 1' > \"$@\"
|
||||
""",
|
||||
visibility = ["//:__subpackages__"],
|
||||
)
|
||||
|
|
|
|||
|
|
@ -410,15 +410,42 @@ find_package(Threads)
|
|||
|
||||
find_package(Eigen3 REQUIRED)
|
||||
|
||||
# fmt library: fallback when std::format is not available (e.g. GCC 11 on Ubuntu 22.04)
|
||||
find_package(fmt QUIET)
|
||||
if(NOT fmt_FOUND)
|
||||
include(FetchContent)
|
||||
FetchContent_Declare(fmt
|
||||
GIT_REPOSITORY https://github.com/fmtlib/fmt.git
|
||||
GIT_TAG 10.2.1
|
||||
)
|
||||
FetchContent_MakeAvailable(fmt)
|
||||
# See if std::format is available and if nor install fmt.
|
||||
include(CheckCXXSourceCompiles)
|
||||
set(_sta_fmt_check_saved_flags "${CMAKE_REQUIRED_FLAGS}")
|
||||
if(MSVC)
|
||||
string(APPEND CMAKE_REQUIRED_FLAGS " /std:c++20")
|
||||
else()
|
||||
string(APPEND CMAKE_REQUIRED_FLAGS " -std=c++20")
|
||||
endif()
|
||||
check_cxx_source_compiles("
|
||||
#include <format>
|
||||
#include <string>
|
||||
int main() { (void)std::format(\"{}\", 42); return 0; }
|
||||
" HAVE_CXX_STD_FORMAT)
|
||||
set(CMAKE_REQUIRED_FLAGS "${_sta_fmt_check_saved_flags}")
|
||||
|
||||
if(HAVE_CXX_STD_FORMAT)
|
||||
message(STATUS "std::format: available")
|
||||
else()
|
||||
# Use spdlog's bundled fmt when available to avoid ODR violations.
|
||||
find_package(spdlog QUIET)
|
||||
if(spdlog_FOUND)
|
||||
message(STATUS "std::format: using spdlog's bundled fmt")
|
||||
set(STA_USE_SPDLOG_FMT TRUE)
|
||||
get_target_property(_spdlog_inc spdlog::spdlog INTERFACE_INCLUDE_DIRECTORIES)
|
||||
set(_fmt_dir "${CMAKE_CURRENT_BINARY_DIR}/_spdlog_fmt")
|
||||
file(MAKE_DIRECTORY "${_fmt_dir}")
|
||||
file(CREATE_LINK "${_spdlog_inc}/spdlog/fmt/bundled" "${_fmt_dir}/fmt" SYMBOLIC)
|
||||
else()
|
||||
message(STATUS "std::format: building fmt library")
|
||||
include(FetchContent)
|
||||
FetchContent_Declare(fmt
|
||||
GIT_REPOSITORY https://github.com/fmtlib/fmt.git
|
||||
GIT_TAG 11.0.2
|
||||
)
|
||||
FetchContent_MakeAvailable(fmt)
|
||||
endif()
|
||||
endif()
|
||||
|
||||
include(cmake/FindCUDD.cmake)
|
||||
|
|
@ -536,12 +563,20 @@ target_sources(OpenSTA
|
|||
|
||||
target_link_libraries(OpenSTA
|
||||
Eigen3::Eigen
|
||||
fmt::fmt
|
||||
${TCL_LIBRARY}
|
||||
${CMAKE_THREAD_LIBS_INIT}
|
||||
${CUDD_LIB}
|
||||
)
|
||||
|
||||
if(NOT HAVE_CXX_STD_FORMAT)
|
||||
if(STA_USE_SPDLOG_FMT)
|
||||
target_include_directories(OpenSTA PUBLIC "${_fmt_dir}")
|
||||
target_link_libraries(OpenSTA spdlog::spdlog)
|
||||
else()
|
||||
target_link_libraries(OpenSTA fmt::fmt)
|
||||
endif()
|
||||
endif()
|
||||
|
||||
if (ZLIB_LIBRARIES)
|
||||
target_link_libraries(OpenSTA ${ZLIB_LIBRARIES})
|
||||
endif()
|
||||
|
|
|
|||
|
|
@ -68,6 +68,30 @@ cd ../..
|
|||
rm -rf v1.14.0.tar.gz googletest-1.14.0
|
||||
EOF
|
||||
|
||||
# Download and build fmt
|
||||
# Ensure the Vault redirect is applied to everything including new installs
|
||||
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 ca-certificates git && \
|
||||
update-ca-trust force-enable
|
||||
# clone fmt compatible version (10.2.1)
|
||||
RUN git config --global http.sslVerify false && \
|
||||
git clone --depth 1 --branch 10.2.1 https://github.com/fmtlib/fmt.git /tmp/fmt
|
||||
RUN source /opt/rh/devtoolset-11/enable && \
|
||||
cd /tmp/fmt && \
|
||||
mkdir build && cd build && \
|
||||
cmake3 .. \
|
||||
-DCMAKE_POSITION_INDEPENDENT_CODE=ON \
|
||||
-DFMT_TEST=OFF \
|
||||
-DCMAKE_BUILD_TYPE=Release \
|
||||
-DFMT_DOC=OFF && \
|
||||
make -j$(nproc) && \
|
||||
make install
|
||||
RUN rm -rf /tmp/fmt
|
||||
|
||||
################################################################
|
||||
|
||||
FROM base-dependencies AS builder
|
||||
|
||||
COPY . /OpenSTA
|
||||
|
|
|
|||
|
|
@ -13,13 +13,14 @@ RUN apt-get update && \
|
|||
gdb \
|
||||
tcl-dev \
|
||||
tcl-tclreadline \
|
||||
libeigen3-dev \
|
||||
swig \
|
||||
bison \
|
||||
flex \
|
||||
automake \
|
||||
autotools-dev \
|
||||
libgtest-dev
|
||||
libgtest-dev \
|
||||
libeigen3-dev \
|
||||
libfmt-dev
|
||||
|
||||
# Download CUDD
|
||||
RUN wget https://raw.githubusercontent.com/davidkebo/cudd/main/cudd_versions/cudd-3.0.0.tar.gz && \
|
||||
|
|
|
|||
|
|
@ -104,6 +104,7 @@ 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
|
||||
zLib 1.2.5 1.2.8 zlib optional
|
||||
libfmt 8.1.1 N/A MIT required if std::format not available
|
||||
```
|
||||
|
||||
The [TCL readline library](https://tclreadline.sourceforge.net/tclreadline.html)
|
||||
|
|
@ -143,6 +144,11 @@ make
|
|||
You can use the "configure --prefix" option and "make install" to install CUDD
|
||||
in a different directory.
|
||||
|
||||
Modern c++ compilers that support c++20 include support for std::format.
|
||||
With older compilers like gcc 11 on Ubuntu 22.04 and Centos7 the fmt library
|
||||
is used instead. If it is not installed locally, the github repository is
|
||||
downloaded and compiled in the build directory.
|
||||
|
||||
### Building with CMake
|
||||
|
||||
Use the following commands to checkout the git repository and build the
|
||||
|
|
|
|||
|
|
@ -87,7 +87,7 @@ CcsCeffDelayCalc::gateDelay(const Pin *drvr_pin,
|
|||
parasitic_ = parasitic;
|
||||
output_waveforms_ = nullptr;
|
||||
|
||||
GateTableModel *table_model = arc->gateTableModel(scene, min_max);
|
||||
const GateTableModel *table_model = arc->gateTableModel(scene, min_max);
|
||||
if (table_model && parasitic) {
|
||||
OutputWaveforms *output_waveforms = table_model->outputWaveforms();
|
||||
Parasitics *parasitics = scene->parasitics(min_max);
|
||||
|
|
@ -112,12 +112,36 @@ CcsCeffDelayCalc::gateDelay(const Pin *drvr_pin,
|
|||
drvr_cell->ensureVoltageWaveforms(scenes_);
|
||||
output_waveforms_ = output_waveforms;
|
||||
ref_time_ = output_waveforms_->referenceTime(in_slew_);
|
||||
debugPrint(debug_, "ccs_dcalc", 1, "{} {}", drvr_cell->name(),
|
||||
debugPrint(debug_, "ccs_dcalc", 1, "{} {}",
|
||||
drvr_cell->name(),
|
||||
drvr_rf_->shortName());
|
||||
|
||||
double gate_delay, drvr_slew;
|
||||
gateDelaySlew(drvr_library, drvr_rf_, gate_delay, drvr_slew);
|
||||
return makeResult(drvr_library, drvr_rf_, gate_delay, drvr_slew,
|
||||
load_pin_index_map);
|
||||
gateDelaySlew(drvr_library, gate_delay, drvr_slew);
|
||||
debugPrint(debug_, "ccs_dcalc", 2, "gate_delay {} drvr_slew {}",
|
||||
delayAsString(gate_delay, this), delayAsString(drvr_slew, this));
|
||||
|
||||
// Fill in pocv parameters.
|
||||
ArcDelay gate_delay2(gate_delay);
|
||||
Slew drvr_slew2(drvr_slew);
|
||||
if (variables_->pocvEnabled()) {
|
||||
double ceff = region_ceff_[0];
|
||||
const Pvt *pvt = pinPvt(drvr_pin_, scene, min_max);
|
||||
table_model->gateDelayPocv(pvt, in_slew_, ceff, min_max,
|
||||
variables_->pocvMode(),
|
||||
gate_delay2, drvr_slew2);
|
||||
}
|
||||
ArcDcalcResult dcalc_result(load_pin_index_map.size());
|
||||
dcalc_result.setGateDelay(gate_delay2);
|
||||
dcalc_result.setDrvrSlew(drvr_slew2);
|
||||
|
||||
for (const auto &[load_pin, load_idx] : load_pin_index_map) {
|
||||
double wire_delay, load_slew;
|
||||
loadDelaySlew(load_pin, drvr_library, drvr_slew, wire_delay, load_slew);
|
||||
dcalc_result.setWireDelay(load_idx, wire_delay);
|
||||
dcalc_result.setLoadSlew(load_idx, load_slew);
|
||||
}
|
||||
return dcalc_result;
|
||||
}
|
||||
}
|
||||
return table_dcalc_->gateDelay(drvr_pin, arc, in_slew, load_cap, parasitic,
|
||||
|
|
@ -126,12 +150,11 @@ CcsCeffDelayCalc::gateDelay(const Pin *drvr_pin,
|
|||
|
||||
void
|
||||
CcsCeffDelayCalc::gateDelaySlew(const LibertyLibrary *drvr_library,
|
||||
const RiseFall *rf,
|
||||
// Return values.
|
||||
double &gate_delay,
|
||||
double &drvr_slew)
|
||||
{
|
||||
initRegions(drvr_library, rf);
|
||||
initRegions(drvr_library, drvr_rf_);
|
||||
findCsmWaveform();
|
||||
ref_time_ = output_waveforms_->referenceTime(in_slew_);
|
||||
gate_delay = region_times_[region_vth_idx_] - ref_time_;
|
||||
|
|
@ -300,33 +323,10 @@ CcsCeffDelayCalc::findCsmWaveform()
|
|||
|
||||
////////////////////////////////////////////////////////////////
|
||||
|
||||
ArcDcalcResult
|
||||
CcsCeffDelayCalc::makeResult(const LibertyLibrary *drvr_library,
|
||||
const RiseFall *rf,
|
||||
double &gate_delay,
|
||||
double &drvr_slew,
|
||||
const LoadPinIndexMap &load_pin_index_map)
|
||||
{
|
||||
ArcDcalcResult dcalc_result(load_pin_index_map.size());
|
||||
debugPrint(debug_, "ccs_dcalc", 2, "gate_delay {} drvr_slew {}",
|
||||
delayAsString(gate_delay, this), delayAsString(drvr_slew, this));
|
||||
dcalc_result.setGateDelay(gate_delay);
|
||||
dcalc_result.setDrvrSlew(drvr_slew);
|
||||
|
||||
for (const auto &[load_pin, load_idx] : load_pin_index_map) {
|
||||
double wire_delay, load_slew;
|
||||
loadDelaySlew(load_pin, drvr_library, rf, drvr_slew, wire_delay, load_slew);
|
||||
dcalc_result.setWireDelay(load_idx, wire_delay);
|
||||
dcalc_result.setLoadSlew(load_idx, load_slew);
|
||||
}
|
||||
return dcalc_result;
|
||||
}
|
||||
|
||||
void
|
||||
CcsCeffDelayCalc::loadDelaySlew(const Pin *load_pin,
|
||||
const LibertyLibrary *drvr_library,
|
||||
const RiseFall *rf,
|
||||
double &drvr_slew,
|
||||
double drvr_slew,
|
||||
// Return values.
|
||||
double &wire_delay,
|
||||
double &load_slew)
|
||||
|
|
@ -349,7 +349,7 @@ CcsCeffDelayCalc::loadDelaySlew(const Pin *load_pin,
|
|||
else
|
||||
loadDelaySlew(load_pin, drvr_slew, elmore, wire_delay, load_slew);
|
||||
|
||||
thresholdAdjust(load_pin, drvr_library, rf, wire_delay, load_slew);
|
||||
thresholdAdjust(load_pin, drvr_library, drvr_rf_, wire_delay, load_slew);
|
||||
}
|
||||
|
||||
void
|
||||
|
|
|
|||
|
|
@ -71,22 +71,15 @@ protected:
|
|||
typedef std::vector<double> Region;
|
||||
|
||||
void gateDelaySlew(const LibertyLibrary *drvr_library,
|
||||
const RiseFall *rf,
|
||||
// Return values.
|
||||
double &gate_delay,
|
||||
double &drvr_slew);
|
||||
void initRegions(const LibertyLibrary *drvr_library,
|
||||
const RiseFall *rf);
|
||||
void findCsmWaveform();
|
||||
ArcDcalcResult makeResult(const LibertyLibrary *drvr_library,
|
||||
const RiseFall *rf,
|
||||
double &gate_delay,
|
||||
double &drvr_slew,
|
||||
const LoadPinIndexMap &load_pin_index_map);
|
||||
void loadDelaySlew(const Pin *load_pin,
|
||||
const LibertyLibrary *drvr_library,
|
||||
const RiseFall *rf,
|
||||
double &drvr_slew,
|
||||
double drvr_slew,
|
||||
// Return values.
|
||||
double &wire_delay,
|
||||
double &load_slew);
|
||||
|
|
|
|||
|
|
@ -448,10 +448,10 @@ DmpAlg::showJacobian()
|
|||
{
|
||||
std::string line = " ";
|
||||
for (int j = 0; j < nr_order_; j++)
|
||||
line += sta::format("{:12}", dmp_param_index_strings[j]);
|
||||
line += sta::format("{:>12}", dmp_param_index_strings[j]);
|
||||
report_->reportLine(line);
|
||||
line.clear();
|
||||
for (int i = 0; i < nr_order_; i++) {
|
||||
line.clear();
|
||||
line += sta::format("{:4} ", dmp_func_index_strings[i]);
|
||||
for (int j = 0; j < nr_order_; j++)
|
||||
line += sta::format("{:12.3e} ", fjac_[i][j]);
|
||||
|
|
@ -551,10 +551,10 @@ DmpAlg::loadDelaySlew(const Pin *,
|
|||
// Use the driver thresholds and rely on thresholdAdjust to
|
||||
// convert the delay and slew to the load's thresholds.
|
||||
try {
|
||||
if (debug_->check("dmp_ceff", 4))
|
||||
showVl();
|
||||
elmore_ = elmore;
|
||||
p3_ = 1.0 / elmore;
|
||||
if (debug_->check("dmp_ceff", 4))
|
||||
showVl();
|
||||
double t_lower = t0_;
|
||||
double t_upper = vlCrossingUpperBound();
|
||||
double load_delay = findVlCrossing(vth_, t_lower, t_upper);
|
||||
|
|
@ -1189,9 +1189,9 @@ DmpZeroC2::init(const LibertyLibrary *drvr_library,
|
|||
}
|
||||
|
||||
void
|
||||
DmpZeroC2::gateDelaySlew( // Return values.
|
||||
double &delay,
|
||||
double &slew)
|
||||
DmpZeroC2::gateDelaySlew(// Return values.
|
||||
double &delay,
|
||||
double &slew)
|
||||
{
|
||||
try {
|
||||
findDriverParams(c1_);
|
||||
|
|
|
|||
|
|
@ -411,27 +411,27 @@ GraphDelayCalc::seedDrvrSlew(Vertex *drvr_vertex,
|
|||
for (const MinMax *min_max : MinMax::range()) {
|
||||
for (const RiseFall *rf : RiseFall::range()) {
|
||||
InputDrive *drive = nullptr;
|
||||
if (network_->isTopLevelPort(drvr_pin)) {
|
||||
Port *port = network_->port(drvr_pin);
|
||||
if (network_->isTopLevelPort(drvr_pin)) {
|
||||
Port *port = network_->port(drvr_pin);
|
||||
drive = sdc->findInputDrive(port);
|
||||
}
|
||||
if (drive) {
|
||||
const LibertyCell *drvr_cell;
|
||||
const LibertyPort *from_port, *to_port;
|
||||
float *from_slews;
|
||||
}
|
||||
if (drive) {
|
||||
const LibertyCell *drvr_cell;
|
||||
const LibertyPort *from_port, *to_port;
|
||||
float *from_slews;
|
||||
drive->driveCell(rf, min_max, drvr_cell, from_port,
|
||||
from_slews, to_port);
|
||||
if (drvr_cell) {
|
||||
if (from_port == nullptr)
|
||||
from_port = driveCellDefaultFromPort(drvr_cell, to_port);
|
||||
findInputDriverDelay(drvr_cell, drvr_pin, drvr_vertex, rf,
|
||||
from_slews, to_port);
|
||||
if (drvr_cell) {
|
||||
if (from_port == nullptr)
|
||||
from_port = driveCellDefaultFromPort(drvr_cell, to_port);
|
||||
findInputDriverDelay(drvr_cell, drvr_pin, drvr_vertex, rf,
|
||||
from_port, from_slews, to_port, scene, min_max);
|
||||
}
|
||||
else
|
||||
}
|
||||
else
|
||||
seedNoDrvrCellSlew(drvr_vertex, drvr_pin, rf, drive, scene, min_max,
|
||||
arc_delay_calc);
|
||||
}
|
||||
else
|
||||
arc_delay_calc);
|
||||
}
|
||||
else
|
||||
seedNoDrvrSlew(drvr_vertex, drvr_pin, rf, scene, min_max, arc_delay_calc);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,6 +4,13 @@ OpenSTA Timing Analyzer Release Notes
|
|||
This file summarizes user visible changes for each release.
|
||||
See ApiChangeLog.txt for changes to the STA api.
|
||||
|
||||
2026/03/23
|
||||
----------
|
||||
|
||||
The write_path_spice command -spice_directory has been changed to
|
||||
-spice_file, which is a prefix for the spice filenames. Successive
|
||||
paths are written in files name <spice_file>_<path_number>.sp.
|
||||
|
||||
Release 3.0.1 2026/03/12
|
||||
------------------------
|
||||
|
||||
|
|
|
|||
3453
doc/OpenSTA.fodt
3453
doc/OpenSTA.fodt
File diff suppressed because it is too large
Load Diff
BIN
doc/OpenSTA.pdf
BIN
doc/OpenSTA.pdf
Binary file not shown.
|
|
@ -35,10 +35,7 @@
|
|||
#include <zlib.h>
|
||||
#endif
|
||||
|
||||
// std::format is not supported in GCC 11 (e.g. Ubuntu 22.04).
|
||||
// Use fmt library as fallback when __cpp_lib_format is not defined.
|
||||
|
||||
#if defined(__cpp_lib_format) && __cpp_lib_format >= 201907L
|
||||
#if HAVE_CXX_STD_FORMAT
|
||||
#include <format>
|
||||
|
||||
namespace sta {
|
||||
|
|
|
|||
|
|
@ -3042,7 +3042,7 @@ OperatingConditions::OperatingConditions(const char *name) :
|
|||
Pvt(0.0, 0.0, 0.0),
|
||||
name_(name),
|
||||
// Default wireload tree.
|
||||
wire_load_tree_(WireloadTree::balanced)
|
||||
wire_load_tree_(WireloadTree::unknown)
|
||||
{
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -79,7 +79,7 @@ LibertyParser::makeDefine(const LibertyAttrValueSeq *values,
|
|||
const std::string &value_type_name = (*values)[2]->stringValue();
|
||||
LibertyAttrType value_type = attrValueType(value_type_name);
|
||||
LibertyGroupType group_type = groupType(group_type_name);
|
||||
define = new LibertyDefine(std::move(define_name), group_type, value_type, line);
|
||||
define = new LibertyDefine(define_name, group_type, value_type, line);
|
||||
LibertyGroup *group = this->group();
|
||||
group->addDefine(define);
|
||||
for (auto value : *values)
|
||||
|
|
@ -168,7 +168,7 @@ LibertyParser::makeSimpleAttr(const std::string name,
|
|||
int line)
|
||||
{
|
||||
LibertySimpleAttr *attr =
|
||||
new LibertySimpleAttr(std::move(name), std::move(*value), line);
|
||||
new LibertySimpleAttr(name, *value, line);
|
||||
delete value;
|
||||
LibertyGroup *group = this->group();
|
||||
group->addAttr(attr);
|
||||
|
|
@ -189,7 +189,7 @@ LibertyParser::makeComplexAttr(const std::string name,
|
|||
}
|
||||
else {
|
||||
LibertyComplexAttr *attr =
|
||||
new LibertyComplexAttr(std::move(name), std::move(*values), line);
|
||||
new LibertyComplexAttr(name, *values, line);
|
||||
delete values;
|
||||
LibertyGroup *group = this->group();
|
||||
group->addAttr(attr);
|
||||
|
|
@ -414,7 +414,7 @@ LibertyGroup::findSubgroups(const std::string type) const
|
|||
const LibertyGroup *
|
||||
LibertyGroup::findSubgroup(const std::string type) const
|
||||
{
|
||||
const LibertyGroupSeq &groups = findKeyValue(subgroup_map_, type);
|
||||
const LibertyGroupSeq groups = findKeyValue(subgroup_map_, type);
|
||||
if (groups.size() >= 1)
|
||||
return groups[0];
|
||||
else
|
||||
|
|
@ -436,7 +436,7 @@ LibertyGroup::findComplexAttrs(const std::string attr_name) const
|
|||
const LibertyComplexAttr *
|
||||
LibertyGroup::findComplexAttr(const std::string attr_name) const
|
||||
{
|
||||
const LibertyComplexAttrSeq &attrs = findKeyValue(complex_attr_map_, attr_name);
|
||||
const LibertyComplexAttrSeq attrs = findKeyValue(complex_attr_map_, attr_name);
|
||||
if (attrs.size() >= 1)
|
||||
return attrs[0];
|
||||
else
|
||||
|
|
|
|||
|
|
@ -116,7 +116,9 @@ GateTableModel::gateDelay(const Pvt *pvt,
|
|||
drvr_slew = findValue(pvt, slew_models_->model(), in_slew, load_cap, 0.0);
|
||||
else
|
||||
drvr_slew = 0.0;
|
||||
// Clip negative slews to zero.
|
||||
// TODO: Check for a better solution than clip negative delays and slews to zero.
|
||||
//if (gate_delay < 0.0)
|
||||
// gate_delay = 0.0;
|
||||
if (drvr_slew < 0.0)
|
||||
drvr_slew = 0.0;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1998,8 +1998,6 @@ TEST_F(LinearModelTest, Table3ReportValue) {
|
|||
TEST_F(LinearModelTest, TableModelReport) {
|
||||
TablePtr tbl = std::make_shared<Table>(42.0f);
|
||||
TableModel model(tbl, nullptr, ScaleFactorType::cell, RiseFall::rise());
|
||||
const Units *units = lib_->units();
|
||||
Report *report_obj = nullptr;
|
||||
// report needs Report*; test order/axes instead
|
||||
EXPECT_EQ(model.order(), 0);
|
||||
EXPECT_EQ(model.axis1(), nullptr);
|
||||
|
|
@ -3290,8 +3288,9 @@ TEST(TestCellTest, FootprintDefault) {
|
|||
TestCell cell(&lib, "CELL1", "test.lib");
|
||||
const char *fp = cell.footprint();
|
||||
// Empty string or nullptr for default
|
||||
if (fp)
|
||||
if (fp) {
|
||||
EXPECT_EQ(fp, "");
|
||||
}
|
||||
}
|
||||
|
||||
TEST(TestCellTest, SetFootprint) {
|
||||
|
|
@ -3305,8 +3304,9 @@ TEST(TestCellTest, UserFunctionClassDefault) {
|
|||
LibertyLibrary lib("test_lib", "test.lib");
|
||||
TestCell cell(&lib, "CELL1", "test.lib");
|
||||
const char *ufc = cell.userFunctionClass();
|
||||
if (ufc)
|
||||
if (ufc) {
|
||||
EXPECT_EQ(ufc, "");
|
||||
}
|
||||
}
|
||||
|
||||
TEST(TestCellTest, SetUserFunctionClass) {
|
||||
|
|
|
|||
|
|
@ -1141,7 +1141,6 @@ TEST(ConcreteNetworkTest, FindCellViaNetwork) {
|
|||
TEST(ConcreteNetworkTest, FindAnyCell) {
|
||||
ConcreteNetwork network;
|
||||
Library *lib1 = network.makeLibrary("lib1", "lib1.lib");
|
||||
Library *lib2 = network.makeLibrary("lib2", "lib2.lib");
|
||||
network.makeCell(lib1, "INV_X1", true, "lib1.lib");
|
||||
Cell *found = network.findAnyCell("INV_X1");
|
||||
EXPECT_NE(found, nullptr);
|
||||
|
|
|
|||
|
|
@ -257,12 +257,12 @@ Parasitics::makeWireloadNetwork(const Pin *drvr_pin,
|
|||
makeWireloadNetworkWorst(parasitic, drvr_pin, net, wireload_cap,
|
||||
wireload_res, fanout);
|
||||
break;
|
||||
case WireloadTree::unknown:
|
||||
case WireloadTree::balanced:
|
||||
makeWireloadNetworkBalanced(parasitic, drvr_pin, wireload_cap, wireload_res,
|
||||
fanout);
|
||||
break;
|
||||
case WireloadTree::best_case:
|
||||
case WireloadTree::unknown:
|
||||
makeWireloadNetworkBest(parasitic, drvr_pin, wireload_cap, wireload_res,
|
||||
fanout);
|
||||
break;
|
||||
|
|
|
|||
|
|
@ -1304,7 +1304,6 @@ TEST_F(StaParasiticsTest, ParasiticNodeResistorMap) {
|
|||
// Test findNode (deprecated) - delegates to findParasiticNode
|
||||
TEST_F(StaParasiticsTest, FindNodeDeprecated) {
|
||||
ASSERT_NO_THROW(( [&](){
|
||||
Parasitics *parasitics = sta_->findParasitics("default");
|
||||
ConcretePiElmore pe(1e-12f, 100.0f, 2e-12f);
|
||||
// findNode on non-network parasitic should work but return nullptr
|
||||
// since it's not a parasitic network
|
||||
|
|
@ -1317,7 +1316,6 @@ TEST_F(StaParasiticsTest, FindNodeDeprecated) {
|
|||
// Test unannotatedLoads through parasitics API with PiElmore
|
||||
TEST_F(StaParasiticsTest, UnannotatedLoadsPiElmore) {
|
||||
ASSERT_NO_THROW(( [&](){
|
||||
Parasitics *parasitics = sta_->findParasitics("default");
|
||||
ConcretePiElmore pe(1e-12f, 100.0f, 2e-12f);
|
||||
// With no network loads, should just return what parasitics->loads returns
|
||||
// which needs a connected pin. With nullptr pin, this will likely crash
|
||||
|
|
|
|||
|
|
@ -1427,8 +1427,12 @@ Power::findSwitchingPower(const Instance *inst,
|
|||
float volt = portVoltage(scene_cell, to_port, scene, MinMax::max());
|
||||
float switching = .5 * load_cap * volt * volt * activity.density();
|
||||
debugPrint(debug_, "power", 2,
|
||||
"switching {}/{} activity = {:.2e} volt = {:.2f} {:.3e}", cell->name(),
|
||||
to_port->name(), activity.density(), volt, switching);
|
||||
"switching {}/{} activity = {:.2e} volt = {:.2f} {:.3e}",
|
||||
cell->name(),
|
||||
to_port->name(),
|
||||
activity.density(),
|
||||
volt,
|
||||
switching);
|
||||
result.incrSwitching(switching);
|
||||
}
|
||||
}
|
||||
|
|
@ -1477,14 +1481,18 @@ Power::findLeakagePower(const Instance *inst,
|
|||
for (const LeakagePower &pwr : scene_cell->leakagePowers()) {
|
||||
LibertyPort *pg_port = pwr.relatedPgPort();
|
||||
if (pg_port == nullptr || pg_port->pwrGndType() == PwrGndType::primary_power) {
|
||||
std::string pg_name = pg_port ? pg_port->name() : "?";
|
||||
LeakageSummary &sum = leakage_summaries[pg_port];
|
||||
float leakage = pwr.power();
|
||||
FuncExpr *when = pwr.when();
|
||||
if (when) {
|
||||
LogicValue when_value = sim->evalExpr(when, inst);
|
||||
if (when_value == LogicValue::one) {
|
||||
debugPrint(debug_, "power", 2, "leakage {}/{} {}=1 {:.3e}", cell->name(),
|
||||
pg_port->name(), when->to_string(), leakage);
|
||||
debugPrint(debug_, "power", 2, "leakage {}/{} {}=1 {:.3e}",
|
||||
cell->name(),
|
||||
pg_name,
|
||||
when->to_string(),
|
||||
leakage);
|
||||
sum.cond_true_leakage = leakage;
|
||||
sum.cond_true_exists = true;
|
||||
}
|
||||
|
|
@ -1492,7 +1500,9 @@ Power::findLeakagePower(const Instance *inst,
|
|||
PwrActivity cond_activity = evalActivity(when, inst);
|
||||
float cond_duty = cond_activity.duty();
|
||||
debugPrint(debug_, "power", 2, "leakage {} {} {} {:.3e} * {:.2f}",
|
||||
cell->name(), pg_port->name(), when->to_string(),
|
||||
cell->name(),
|
||||
pg_name,
|
||||
when->to_string(),
|
||||
leakage, cond_duty);
|
||||
// Leakage power average weighted by duty.
|
||||
sum.cond_leakage += leakage * cond_duty;
|
||||
|
|
@ -1502,8 +1512,10 @@ Power::findLeakagePower(const Instance *inst,
|
|||
}
|
||||
}
|
||||
else {
|
||||
debugPrint(debug_, "power", 2, "leakage {} {} -- {:.3e}", cell->name(),
|
||||
pg_port->name(), leakage);
|
||||
debugPrint(debug_, "power", 2, "leakage {} {} -- {:.3e}",
|
||||
cell->name(),
|
||||
pg_name,
|
||||
leakage);
|
||||
sum.uncond_leakage = leakage;
|
||||
sum.uncond_exists = true;
|
||||
}
|
||||
|
|
@ -1529,8 +1541,10 @@ Power::findLeakagePower(const Instance *inst,
|
|||
// Ignore unconditional leakage unless there are no conditional leakage groups.
|
||||
else if (sum.uncond_exists)
|
||||
leakage = sum.uncond_leakage;
|
||||
debugPrint(debug_, "power", 2, "leakage {}/{} {:.3e}", cell->name(),
|
||||
pg_port->name(), leakage);
|
||||
debugPrint(debug_, "power", 2, "leakage {}/{} {:.3e}",
|
||||
cell->name(),
|
||||
pg_port ? pg_port->name() : "?",
|
||||
leakage);
|
||||
result.incrLeakage(leakage);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -139,7 +139,9 @@ SaifReader::instancePush(const char *instance_name)
|
|||
else {
|
||||
// Inside annotation scope.
|
||||
Instance *parent = path_.empty() ? sdc_network_->topInstance() : path_.back();
|
||||
Instance *child = sdc_network_->findChild(parent, instance_name);
|
||||
Instance *child = parent
|
||||
? sdc_network_->findChild(parent, instance_name)
|
||||
: nullptr;
|
||||
path_.push_back(child);
|
||||
}
|
||||
stringDelete(instance_name);
|
||||
|
|
|
|||
|
|
@ -768,8 +768,7 @@ TEST_F(PowerDesignTest, PowerPerInstance) {
|
|||
InstanceChildIterator *child_iter = network->childIterator(top);
|
||||
int count = 0;
|
||||
while (child_iter->hasNext() && count < 5) {
|
||||
Instance *inst = child_iter->next();
|
||||
PowerResult result = sta_->power(inst, corner);
|
||||
sta_->power(child_iter->next(), corner);
|
||||
count++;
|
||||
}
|
||||
delete child_iter;
|
||||
|
|
|
|||
|
|
@ -35,31 +35,6 @@
|
|||
|
||||
namespace sta {
|
||||
|
||||
static std::string
|
||||
readTextFile(const char *filename)
|
||||
{
|
||||
std::ifstream in(filename);
|
||||
if (!in.is_open())
|
||||
return "";
|
||||
return std::string((std::istreambuf_iterator<char>(in)),
|
||||
std::istreambuf_iterator<char>());
|
||||
}
|
||||
|
||||
static size_t
|
||||
countSubstring(const std::string &text,
|
||||
const std::string &needle)
|
||||
{
|
||||
if (needle.empty())
|
||||
return 0;
|
||||
size_t count = 0;
|
||||
size_t pos = 0;
|
||||
while ((pos = text.find(needle, pos)) != std::string::npos) {
|
||||
++count;
|
||||
pos += needle.size();
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
// RiseFall tests
|
||||
class RiseFallTest : public ::testing::Test {};
|
||||
|
||||
|
|
@ -3542,8 +3517,6 @@ TEST_F(SdcInitTest, DisabledPortsSetAll) {
|
|||
|
||||
// PortExtCap: operations (needs Port* constructor)
|
||||
TEST_F(SdcInitTest, PortExtCapSetGet) {
|
||||
// Need a port to construct PortExtCap
|
||||
Network *network = sta_->cmdNetwork();
|
||||
// PortExtCap default constructor
|
||||
PortExtCap pec;
|
||||
pec.setPinCap(nullptr, 0.1f, RiseFall::rise(), MinMax::max());
|
||||
|
|
|
|||
|
|
@ -25,7 +25,7 @@
|
|||
namespace eval sta {
|
||||
|
||||
define_cmd_args "write_path_spice" { -path_args path_args\
|
||||
-spice_directory spice_directory\
|
||||
-spice_file spice_file\
|
||||
-lib_subckt_file lib_subckts_file\
|
||||
-model_file model_file\
|
||||
-power power\
|
||||
|
|
@ -34,12 +34,13 @@ define_cmd_args "write_path_spice" { -path_args path_args\
|
|||
|
||||
proc write_path_spice { args } {
|
||||
parse_key_args "write_path_spice" args \
|
||||
keys {-spice_directory -lib_subckt_file -model_file \
|
||||
keys {-spice_file -lib_subckt_file -model_file \
|
||||
-power -ground -path_args -simulator} \
|
||||
flags {}
|
||||
|
||||
if { [info exists keys(-spice_directory)] } {
|
||||
set spice_dir [file nativename $keys(-spice_directory)]
|
||||
if { [info exists keys(-spice_file)] } {
|
||||
set spice_file [file nativename $keys(-spice_file)]
|
||||
set spice_dir [file dirname $spice_file]
|
||||
if { ![file exists $spice_dir] } {
|
||||
sta_error 1920 "Directory $spice_dir not found."
|
||||
}
|
||||
|
|
@ -50,7 +51,7 @@ proc write_path_spice { args } {
|
|||
sta_error 1922 "Cannot write in $spice_dir."
|
||||
}
|
||||
} else {
|
||||
sta_error 1923 "No -spice_directory specified."
|
||||
sta_error 1923 "No -spice_file specified."
|
||||
}
|
||||
|
||||
if { [info exists keys(-lib_subckt_file)] } {
|
||||
|
|
@ -96,10 +97,10 @@ proc write_path_spice { args } {
|
|||
set path_index 1
|
||||
foreach path_end $path_ends {
|
||||
set path [$path_end path]
|
||||
set path_name "path_$path_index"
|
||||
set spice_file [file join $spice_dir "$path_name.sp"]
|
||||
set subckt_file [file join $spice_dir "$path_name.subckt"]
|
||||
write_path_spice_cmd $path $spice_file $subckt_file \
|
||||
set path_file "${spice_file}_$path_index"
|
||||
set spice_file1 ${path_file}.sp
|
||||
set subckt_file ${path_file}.subckt
|
||||
write_path_spice_cmd $path $spice_file1 $subckt_file \
|
||||
$lib_subckt_file $model_file $power $ground $ckt_sim
|
||||
incr path_index
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1302,8 +1302,12 @@ TEST_F(SpiceSmokeTest, RiseFallRangeValues) {
|
|||
auto range = RiseFall::range();
|
||||
int idx = 0;
|
||||
for (auto rf : range) {
|
||||
if (idx == 0) EXPECT_EQ(rf, RiseFall::rise());
|
||||
if (idx == 1) EXPECT_EQ(rf, RiseFall::fall());
|
||||
if (idx == 0) {
|
||||
EXPECT_EQ(rf, RiseFall::rise());
|
||||
}
|
||||
if (idx == 1) {
|
||||
EXPECT_EQ(rf, RiseFall::fall());
|
||||
}
|
||||
idx++;
|
||||
}
|
||||
EXPECT_EQ(idx, 2);
|
||||
|
|
@ -1554,7 +1558,6 @@ TEST_F(SpiceDesignTest, LibertyCellTimingArcs) {
|
|||
// Verify pin connectivity for SPICE net writing
|
||||
TEST_F(SpiceDesignTest, PinConnectivity) {
|
||||
Network *network = sta_->cmdNetwork();
|
||||
Instance *top = network->topInstance();
|
||||
|
||||
// The internal net n1 connects and1:ZN to buf1:A
|
||||
Pin *and1_zn = network->findPin("and1/ZN");
|
||||
|
|
|
|||
|
|
@ -42,7 +42,7 @@ close $subckt_fh
|
|||
# C++ WritePathSpice code paths.
|
||||
write_path_spice \
|
||||
-path_args {-sort_by_slack} \
|
||||
-spice_directory $spice_dir \
|
||||
-spice_file [file join $spice_dir path] \
|
||||
-lib_subckt_file $subckt_file \
|
||||
-model_file $model_file \
|
||||
-power VDD \
|
||||
|
|
|
|||
|
|
@ -0,0 +1,14 @@
|
|||
library(min) {
|
||||
technology(cmos);
|
||||
time_unit : "1ns";
|
||||
voltage_unit : "1V";
|
||||
current_unit : "1mA";
|
||||
capacitive_load_unit(1, pf);
|
||||
cell(BUF) {
|
||||
pin(A) { direction : input; }
|
||||
pin(Y) { direction : output;
|
||||
function : "A";
|
||||
timing() { related_pin : "A"; }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1 @@
|
|||
Annotated 0 pin activities.
|
||||
|
|
@ -0,0 +1,16 @@
|
|||
(SAIFILE
|
||||
(SAIFVERSION "2.0")
|
||||
(DIRECTION "backward")
|
||||
(DIVIDER / )
|
||||
(TIMESCALE 1ns)
|
||||
(DURATION 1000)
|
||||
(INSTANCE TOP
|
||||
(INSTANCE child
|
||||
(INSTANCE grandchild
|
||||
(NET
|
||||
(clk (T0 500) (T1 500) (TZ 0) (TX 0) (TB 0) (TC 1000))
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
# read_saif references missing instance
|
||||
read_liberty read_saif_null_instance.lib
|
||||
read_verilog read_saif_null_instance.v
|
||||
link_design top
|
||||
read_saif -scope TOP read_saif_null_instance.saif
|
||||
|
|
@ -0,0 +1,2 @@
|
|||
module top(input clk);
|
||||
endmodule
|
||||
|
|
@ -30,7 +30,8 @@
|
|||
#
|
||||
# This notice may not be removed or altered from any source distribution.
|
||||
|
||||
# regression -help | [-threads threads] [-valgrind] [-report_stats] test1 [test2...]
|
||||
# Usage: regression -help | [-j jobs] [-threads threads] [-valgrind] [-report_stats]
|
||||
# test1 [test2...]
|
||||
|
||||
proc regression_main {} {
|
||||
setup
|
||||
|
|
@ -41,12 +42,13 @@ proc regression_main {} {
|
|||
}
|
||||
|
||||
proc setup {} {
|
||||
global result_dir diff_file failure_file errors
|
||||
global result_dir diff_file failure_file errors failed_tests
|
||||
global use_valgrind valgrind_shared_lib_failure
|
||||
global report_stats
|
||||
global report_stats max_jobs app_path
|
||||
|
||||
set use_valgrind 0
|
||||
set report_stats 0
|
||||
set max_jobs 1
|
||||
|
||||
if { !([file exists $result_dir] && [file isdirectory $result_dir]) } {
|
||||
file mkdir $result_dir
|
||||
|
|
@ -60,20 +62,28 @@ proc setup {} {
|
|||
set errors(fail) 0
|
||||
set errors(no_cmd) 0
|
||||
set errors(no_ok) 0
|
||||
set failed_tests {}
|
||||
set valgrind_shared_lib_failure 0
|
||||
|
||||
if { ![file exists $app_path] } {
|
||||
error "$app_path not found."
|
||||
} elseif { ![file executable $app_path] } {
|
||||
error "$app_path is not executable."
|
||||
}
|
||||
}
|
||||
|
||||
proc parse_args {} {
|
||||
global argv app_options tests test_groups cmd_paths
|
||||
global use_valgrind
|
||||
global result_dir tests
|
||||
global report_stats
|
||||
global report_stats max_jobs
|
||||
|
||||
while { $argv != {} } {
|
||||
set arg [lindex $argv 0]
|
||||
if { $arg == "help" || $arg == "-help" } {
|
||||
puts {Usage: regression [-help] [-threads threads] [-valgrind] [-report_stats] tests...}
|
||||
puts " -threads max|integer - number of threads to use"
|
||||
puts {Usage: regression [-help] [-threads threads] [-j jobs] [-valgrind] [-report_stats] tests...}
|
||||
puts " -j jobs - number of parallel test jobs (processes) to run"
|
||||
puts " -threads max|integer - number of threads the STA uses"
|
||||
puts " -valgrind - run valgrind (linux memory checker)"
|
||||
puts " -report_stats - report run time and memory"
|
||||
puts " Wildcarding for test names is supported (enclose in \"'s)"
|
||||
|
|
@ -84,12 +94,20 @@ proc parse_args {} {
|
|||
} elseif { $arg == "-threads" } {
|
||||
set threads [lindex $argv 1]
|
||||
if { !([string is integer $threads] || $threads == "max") } {
|
||||
puts "Error: -threads arg $threads is not an integer or max."
|
||||
exit 0
|
||||
puts "Error: -threads arg $threads is not an integer or max."
|
||||
exit 0
|
||||
}
|
||||
lappend app_options "-threads"
|
||||
lappend app_options $threads
|
||||
set argv [lrange $argv 2 end]
|
||||
} elseif { $arg == "-j" } {
|
||||
set jobs [lindex $argv 1]
|
||||
if { ![string is integer $jobs] || $jobs < 1 } {
|
||||
puts "Error: -j arg $jobs must be a positive integer."
|
||||
exit 0
|
||||
}
|
||||
set max_jobs $jobs
|
||||
set argv [lrange $argv 2 end]
|
||||
} elseif { $arg == "-valgrind" } {
|
||||
if { ![find_valgrind] } {
|
||||
error "valgrind not found."
|
||||
|
|
@ -131,7 +149,7 @@ proc expand_tests { argv } {
|
|||
if { [info exists test_groups($arg)] } {
|
||||
set tests [concat $tests $test_groups($arg)]
|
||||
} elseif { [string first "*" $arg] != -1 \
|
||||
|| [string first "?" $arg] != -1 } {
|
||||
|| [string first "?" $arg] != -1 } {
|
||||
# Find wildcard matches.
|
||||
foreach test [group_tests "all"] {
|
||||
if [string match $arg $test] {
|
||||
|
|
@ -143,200 +161,296 @@ proc expand_tests { argv } {
|
|||
} else {
|
||||
puts "Error: test $arg not found."
|
||||
incr errors(no_cmd)
|
||||
}
|
||||
}
|
||||
}
|
||||
return $tests
|
||||
}
|
||||
|
||||
proc run_tests {} {
|
||||
global tests errors app_path
|
||||
global tests errors app_path max_jobs
|
||||
|
||||
foreach test $tests {
|
||||
run_test $test
|
||||
if { $max_jobs > 1 } {
|
||||
run_tests_parallel
|
||||
} else {
|
||||
foreach test $tests {
|
||||
run_test $test
|
||||
}
|
||||
}
|
||||
# Macos debug info generated by valgrind.
|
||||
file delete -force "$app_path.dSYM"
|
||||
write_failure_file
|
||||
write_diff_file
|
||||
}
|
||||
|
||||
proc run_test { test } {
|
||||
global result_dir diff_file errors diff_options report_stats
|
||||
global result_dir diff_file errors diff_options
|
||||
|
||||
set cmd_file [test_cmd_file $test]
|
||||
if [file exists $cmd_file] {
|
||||
set ok_file [test_ok_file $test]
|
||||
puts -nonewline $test
|
||||
flush stdout
|
||||
set exit_code 0
|
||||
if { [test_cmd_file_exists $test] } {
|
||||
set cmd [make_cmd_file $test]
|
||||
set log_file [test_log_file $test]
|
||||
foreach file [glob -nocomplain [file join $result_dir $test.*]] {
|
||||
file delete -force $file
|
||||
}
|
||||
puts -nonewline $test
|
||||
flush stdout
|
||||
set test_errors [run_test_app $test $cmd_file $log_file]
|
||||
if { [lindex $test_errors 0] == "ERROR" } {
|
||||
puts " *ERROR* [lrange $test_errors 1 end]"
|
||||
append_failure $test
|
||||
incr errors(error)
|
||||
|
||||
# For some reason seg faults aren't echoed in the log - add them.
|
||||
if { [llength $test_errors] > 1 && [file exists $log_file] } {
|
||||
set log_ch [open $log_file "a"]
|
||||
puts $log_ch $test_errors
|
||||
close $log_ch
|
||||
}
|
||||
|
||||
# Report partial log diff anyway.
|
||||
if [file exists $ok_file] {
|
||||
catch [concat exec diff $diff_options $ok_file $log_file \
|
||||
>> $diff_file]
|
||||
}
|
||||
} else {
|
||||
set error_msg ""
|
||||
if { [lsearch $test_errors "MEMORY"] != -1 } {
|
||||
append error_msg " *MEMORY*"
|
||||
append_failure $test
|
||||
incr errors(memory)
|
||||
}
|
||||
if { [lsearch $test_errors "LEAK"] != -1 } {
|
||||
append error_msg " *LEAK*"
|
||||
append_failure $test
|
||||
incr errors(leak)
|
||||
}
|
||||
if { $report_stats } {
|
||||
append error_msg " [test_stats_summary $test]"
|
||||
}
|
||||
|
||||
if [file exists $ok_file] {
|
||||
# Filter dos '/r's from log file.
|
||||
set tmp_file [file join $result_dir $test.tmp]
|
||||
exec tr -d "\r" < $log_file > $tmp_file
|
||||
file rename -force $tmp_file $log_file
|
||||
if [catch [concat exec diff $diff_options $ok_file $log_file \
|
||||
>> $diff_file]] {
|
||||
puts " *FAIL*$error_msg"
|
||||
append_failure $test
|
||||
incr errors(fail)
|
||||
} else {
|
||||
puts " pass$error_msg"
|
||||
}
|
||||
if { [catch [concat "exec" "$cmd >& $log_file"] result result_options] } {
|
||||
set details [dict get $result_options -errorcode]
|
||||
set exit_signal [lindex $details 2]
|
||||
if { $exit_signal == "SIGSEGV" } {
|
||||
set exit_code 139
|
||||
} else {
|
||||
puts " *NO OK FILE*"
|
||||
append_failure $test
|
||||
incr errors(no_ok)
|
||||
set exit_code 128
|
||||
}
|
||||
}
|
||||
} else {
|
||||
puts "$test *NO CMD FILE*"
|
||||
incr errors(no_cmd)
|
||||
}
|
||||
puts " [test_status $test $exit_code]"
|
||||
}
|
||||
|
||||
################################################################
|
||||
|
||||
# Parallel runs use one pipeline per test; close() yields the real exit status.
|
||||
# (Non-blocking channels must be switched to blocking before close - see Tcl manual.)
|
||||
proc regression_parallel_close_pipe { fh } {
|
||||
fconfigure $fh -blocking 1
|
||||
if { [catch {close $fh} err opts] } {
|
||||
set ec [dict get $opts -errorcode]
|
||||
if { [lindex $ec 0] == "CHILDSTATUS" } {
|
||||
return [lindex $ec 2]
|
||||
}
|
||||
return 128
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
proc regression_pipe_readable { fh test } {
|
||||
global reg_parallel_active reg_parallel_job_done
|
||||
|
||||
read $fh
|
||||
if { [eof $fh] } {
|
||||
fileevent $fh readable {}
|
||||
set exit_code [regression_parallel_close_pipe $fh]
|
||||
puts "$test [test_status $test $exit_code]"
|
||||
incr reg_parallel_active -1
|
||||
incr reg_parallel_job_done
|
||||
}
|
||||
}
|
||||
|
||||
proc test_stats { test } {
|
||||
if { ![catch {open [test_stats_file $test] r} stream] } {
|
||||
gets $stream line1
|
||||
close $stream
|
||||
return $line1
|
||||
} else {
|
||||
return {}
|
||||
proc open_test_pipeline { test } {
|
||||
set cmd [make_cmd_file $test]
|
||||
set log [test_log_file $test]
|
||||
set inner [format {%s > %s 2>&1} $cmd [file nativename $log]]
|
||||
set fh [open [format {|/bin/sh -c %s} [list $inner]] r]
|
||||
fconfigure $fh -blocking 0
|
||||
return $fh
|
||||
}
|
||||
|
||||
proc run_tests_parallel {} {
|
||||
global tests max_jobs reg_parallel_active reg_parallel_job_done
|
||||
|
||||
set reg_parallel_active 0
|
||||
set reg_parallel_job_done 0
|
||||
set test_idx 0
|
||||
set test_count [llength $tests]
|
||||
|
||||
while { $test_idx < $test_count || $reg_parallel_active > 0 } {
|
||||
while { $reg_parallel_active < $max_jobs && $test_idx < $test_count } {
|
||||
set test [lindex $tests $test_idx]
|
||||
incr test_idx
|
||||
if { ![test_cmd_file_exists $test] } {
|
||||
puts -nonewline $test
|
||||
flush stdout
|
||||
puts " [test_status $test 0]"
|
||||
continue
|
||||
}
|
||||
set fh [open_test_pipeline $test]
|
||||
fileevent $fh readable [list regression_pipe_readable $fh $test]
|
||||
incr reg_parallel_active
|
||||
}
|
||||
if { $reg_parallel_active > 0 } {
|
||||
set before $reg_parallel_job_done
|
||||
while { $reg_parallel_job_done == $before } {
|
||||
vwait reg_parallel_job_done
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
proc make_cmd_file { test } {
|
||||
global app_path app_options result_dir use_valgrind report_stats
|
||||
|
||||
foreach file [glob -nocomplain [file join $result_dir $test.*]] {
|
||||
file delete -force $file
|
||||
}
|
||||
|
||||
set cmd_file [test_cmd_file $test]
|
||||
set ok_file [test_ok_file $test]
|
||||
set log_file [test_log_file $test]
|
||||
|
||||
set run_file [test_run_file $test]
|
||||
set run_stream [open $run_file "w"]
|
||||
puts $run_stream "cd [file dirname $cmd_file]"
|
||||
puts $run_stream "include [file tail $cmd_file]"
|
||||
if { $use_valgrind } {
|
||||
puts $run_stream "sta::delete_all_memory"
|
||||
}
|
||||
if { $report_stats } {
|
||||
puts $run_stream "sta::write_stats [test_stats_file $test]"
|
||||
}
|
||||
close $run_stream
|
||||
|
||||
if { $use_valgrind } {
|
||||
global valgrind_options
|
||||
set cmd "valgrind $valgrind_options $app_path $app_options $run_file"
|
||||
} else {
|
||||
set cmd "$app_path $app_options $run_file"
|
||||
}
|
||||
return $cmd
|
||||
}
|
||||
|
||||
proc test_cmd_file_exists { test } {
|
||||
set cmd_file [test_cmd_file $test]
|
||||
return [file exists $cmd_file]
|
||||
}
|
||||
|
||||
proc test_status { test exit_code } {
|
||||
global result_dir diff_options errors
|
||||
global use_valgrind report_stats test_status
|
||||
|
||||
set test_status {}
|
||||
if { ![test_cmd_file_exists $test] } {
|
||||
test_failed $test "no_cmd"
|
||||
} else {
|
||||
set log_file [test_log_file $test]
|
||||
|
||||
if { [file exists $log_file] } {
|
||||
# Check log file for error patterns
|
||||
set log_ch [open $log_file "r"]
|
||||
set log_content [read $log_ch]
|
||||
close $log_ch
|
||||
|
||||
# Check if exit code indicates a segfault or signal termination
|
||||
# Exit codes >= 128 typically indicate termination by a signal
|
||||
# 139 = 128 + 11 (SIGSEGV), 134 = 128 + 6 (SIGABRT), etc.
|
||||
if { $exit_code >= 128
|
||||
|| [string match "*Segmentation fault*" $log_content] \
|
||||
|| [string match "*DEADLYSIGNAL*" $log_content] \
|
||||
|| [string match "*Abort*" $log_content] \
|
||||
|| [string match "*Fatal*" $log_content] } {
|
||||
test_failed $test "error"
|
||||
} elseif { [string match "*heap-use-after-free*" $log_content] } {
|
||||
# ASAN error
|
||||
test_failed $test "memory"
|
||||
}
|
||||
set ok_file [test_ok_file $test]
|
||||
if { [file exists $ok_file] } {
|
||||
if { $use_valgrind } {
|
||||
cleanse_valgrind_logfile $test
|
||||
}
|
||||
if { [catch [concat exec diff $diff_options $ok_file $log_file]] } {
|
||||
if { $test_status == "" } {
|
||||
test_failed $test "fail"
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if { $test_status == "" } {
|
||||
test_failed $test "no_ok"
|
||||
}
|
||||
}
|
||||
} else {
|
||||
# Log file doesn't exist, likely an error
|
||||
test_failed $test "error" "*ERROR* no log file"
|
||||
}
|
||||
}
|
||||
if { $test_status == {} } {
|
||||
append test_status "pass"
|
||||
}
|
||||
if { $report_stats } {
|
||||
append test_status " [test_stats_summary $test]"
|
||||
}
|
||||
return $test_status
|
||||
}
|
||||
|
||||
proc test_exit_code { test } {
|
||||
# Read exit code
|
||||
set test_error ""
|
||||
set exit_code_file [test_exit_code_file $test]
|
||||
if { [file exists $exit_code_file] } {
|
||||
set exit_code_ch [open $exit_code_file "r"]
|
||||
set exit_code [string trim [read $exit_code_ch]]
|
||||
close $exit_code_ch
|
||||
|
||||
if { [string is integer $exit_code] } {
|
||||
return $exit_code
|
||||
}
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
proc test_stats_summary { test } {
|
||||
set stats [test_stats $test]
|
||||
set elapsed_time [lindex $stats 0]
|
||||
set user_time [lindex $stats 1]
|
||||
set memory [lindex $stats 2]
|
||||
if { [string is double $elapsed_time] } {
|
||||
set elapsed [format "%.1fe" $elapsed_time]
|
||||
} else {
|
||||
set elapsed "?"
|
||||
}
|
||||
if { [string is double $user_time] } {
|
||||
set user [format "%.1fu" $user_time]
|
||||
} else {
|
||||
set user "?"
|
||||
}
|
||||
if { [string is double $memory] } {
|
||||
set mem [format "%.0fmb" [expr $memory * 1e-6]]
|
||||
} else {
|
||||
set mem "?"
|
||||
}
|
||||
return "$elapsed $user $mem"
|
||||
}
|
||||
if { ![catch {open [test_stats_file $test] r} stream] } {
|
||||
gets $stream stats
|
||||
close $stream
|
||||
|
||||
proc append_failure { test } {
|
||||
global failure_file
|
||||
set fail_ch [open $failure_file "a"]
|
||||
puts $fail_ch $test
|
||||
close $fail_ch
|
||||
}
|
||||
|
||||
# Return error.
|
||||
proc run_test_app { test cmd_file log_file } {
|
||||
global app_path errorCode use_valgrind
|
||||
if { $use_valgrind } {
|
||||
return [run_test_valgrind $test $cmd_file $log_file]
|
||||
} else {
|
||||
return [run_test_plain $test $cmd_file $log_file]
|
||||
}
|
||||
}
|
||||
|
||||
proc run_test_plain { test cmd_file log_file } {
|
||||
global app_path app_options result_dir errorCode
|
||||
global report_stats
|
||||
|
||||
if { ![file exists $app_path] } {
|
||||
return "ERROR $app_path not found."
|
||||
} elseif { ![file executable $app_path] } {
|
||||
return "ERROR $app_path is not executable."
|
||||
} else {
|
||||
set run_file [test_run_file $test]
|
||||
set run_stream [open $run_file "w"]
|
||||
puts $run_stream "cd [file dirname $cmd_file]"
|
||||
puts $run_stream "include [file tail $cmd_file]"
|
||||
if { $report_stats } {
|
||||
set stat_file [file normalize [test_stats_file $test]]
|
||||
puts $run_stream "sta::write_stats $stat_file"
|
||||
set elapsed_time [lindex $stats 0]
|
||||
set user_time [lindex $stats 1]
|
||||
set memory [lindex $stats 2]
|
||||
if { [string is double $elapsed_time] } {
|
||||
set elapsed [format "%.1fe" $elapsed_time]
|
||||
} else {
|
||||
set elapsed "?"
|
||||
}
|
||||
close $run_stream
|
||||
|
||||
if { [catch [concat exec $app_path $app_options $run_file >& $log_file]] } {
|
||||
set signal [lindex $errorCode 2]
|
||||
set error [lindex $errorCode 3]
|
||||
# Error strings are not consistent across platforms but signal
|
||||
# names are.
|
||||
if { $signal == "SIGSEGV" } {
|
||||
# Save corefiles to regression results directory.
|
||||
set pid [lindex $errorCode 1]
|
||||
set sys_corefile [test_sys_core_file $test $pid]
|
||||
if { [file exists $sys_corefile] } {
|
||||
file copy $sys_corefile [test_core_file $test]
|
||||
}
|
||||
}
|
||||
return "ERROR $error"
|
||||
if { [string is double $user_time] } {
|
||||
set user [format "%.1fu" $user_time]
|
||||
} else {
|
||||
set user "?"
|
||||
}
|
||||
file delete $run_file
|
||||
if { [string is double $memory] } {
|
||||
set mem [format "%.0fmb" [expr $memory * 1e-6]]
|
||||
} else {
|
||||
set mem "?"
|
||||
}
|
||||
return "$elapsed $user $mem"
|
||||
} else {
|
||||
return ""
|
||||
}
|
||||
}
|
||||
|
||||
proc run_test_valgrind { test cmd_file log_file } {
|
||||
global app_path app_options valgrind_options result_dir errorCode
|
||||
proc test_failed { test reason } {
|
||||
global errors test_status failed_tests
|
||||
|
||||
set vg_cmd_file [test_valgrind_cmd_file $test]
|
||||
set vg_stream [open $vg_cmd_file "w"]
|
||||
puts $vg_stream "cd [file dirname $cmd_file]"
|
||||
puts $vg_stream "include [file tail $cmd_file]"
|
||||
puts $vg_stream "sta::delete_all_memory"
|
||||
close $vg_stream
|
||||
|
||||
set cmd [concat exec valgrind $valgrind_options \
|
||||
$app_path $app_options $vg_cmd_file >& $log_file]
|
||||
set error_msg ""
|
||||
if { [catch $cmd] } {
|
||||
set error_msg "ERROR [lindex $errorCode 3]"
|
||||
if { $reason == "error" } {
|
||||
set test_status "*ERROR*"
|
||||
} elseif { $reason == "no_cmd" } {
|
||||
set test_status "*NO CMD FILE*"
|
||||
} elseif { $reason == "memory" } {
|
||||
set test_status "*MEMORY*"
|
||||
} elseif { $reason == "leak" } {
|
||||
set test_status "*LEAK*"
|
||||
} elseif { $reason == "fail" } {
|
||||
set test_status "*FAIL*"
|
||||
} elseif { $reason == "no_ok" } {
|
||||
set test_status "*NO OK FILE*"
|
||||
} else {
|
||||
error "unknown test failure reason $reason"
|
||||
}
|
||||
lappend failed_tests $test
|
||||
incr errors($reason)
|
||||
}
|
||||
|
||||
proc write_failure_file {} {
|
||||
global failure_file failed_tests
|
||||
|
||||
set ch [open $failure_file "w"]
|
||||
foreach test $failed_tests {
|
||||
puts $ch $test
|
||||
}
|
||||
close $ch
|
||||
}
|
||||
|
||||
proc write_diff_file {} {
|
||||
global diff_file diff_options failed_tests
|
||||
|
||||
foreach test $failed_tests {
|
||||
set log_file [test_log_file $test]
|
||||
set ok_file [test_ok_file $test]
|
||||
catch [concat exec diff $diff_options $ok_file $log_file >> $diff_file]
|
||||
}
|
||||
file delete $vg_cmd_file
|
||||
set error_msg [concat $error_msg [cleanse_valgrind_logfile $test $log_file]]
|
||||
return $error_msg
|
||||
}
|
||||
|
||||
# Error messages can be found in "valgrind/memcheck/mc_errcontext.c".
|
||||
|
|
@ -363,25 +477,26 @@ set valgrind_shared_lib_failure_regexp "No malloc'd blocks -- no leaks are possi
|
|||
|
||||
# Scan the log file to separate valgrind notifications and check for
|
||||
# valgrind errors.
|
||||
proc cleanse_valgrind_logfile { test log_file } {
|
||||
proc cleanse_valgrind_logfile { test } {
|
||||
global valgrind_mem_regexp valgrind_leak_regexp
|
||||
global valgrind_shared_lib_failure_regexp
|
||||
global valgrind_shared_lib_failure
|
||||
global valgrind_shared_lib_failure error
|
||||
|
||||
set log_file [test_log_file $test]
|
||||
set tmp_file [test_tmp_file $test]
|
||||
set valgrind_log_file [test_valgrind_file $test]
|
||||
file copy -force $log_file $tmp_file
|
||||
set tmp [open $tmp_file "r"]
|
||||
set log [open $log_file "w"]
|
||||
set valgrind [open $valgrind_log_file "w"]
|
||||
set leaks 0
|
||||
set leak 0
|
||||
set mem_errors 0
|
||||
gets $tmp line
|
||||
while { ![eof $tmp] } {
|
||||
if {[regexp "^==" $line]} {
|
||||
puts $valgrind $line
|
||||
if {[regexp $valgrind_leak_regexp $line]} {
|
||||
set leaks 1
|
||||
set leak 1
|
||||
}
|
||||
if {[regexp $valgrind_mem_regexp $line]} {
|
||||
set mem_errors 1
|
||||
|
|
@ -399,16 +514,12 @@ proc cleanse_valgrind_logfile { test log_file } {
|
|||
close $log
|
||||
close $tmp
|
||||
close $valgrind
|
||||
file delete $tmp_file
|
||||
|
||||
set errors {}
|
||||
if { $mem_errors } {
|
||||
lappend errors "MEMORY"
|
||||
test_failed $test "memory"
|
||||
} elseif { $leak } {
|
||||
test_failed $test "leak"
|
||||
}
|
||||
if { $leaks } {
|
||||
lappend errors "LEAK"
|
||||
}
|
||||
return $errors
|
||||
}
|
||||
|
||||
################################################################
|
||||
|
|
@ -533,11 +644,6 @@ proc test_tmp_file { test } {
|
|||
return [file join $result_dir $test.tmp]
|
||||
}
|
||||
|
||||
proc test_valgrind_cmd_file { test } {
|
||||
global result_dir
|
||||
return [file join $result_dir $test.vg_cmd]
|
||||
}
|
||||
|
||||
proc test_valgrind_file { test } {
|
||||
global result_dir
|
||||
return [file join $result_dir $test.valgrind]
|
||||
|
|
@ -563,6 +669,11 @@ proc test_sys_core_file { test pid } {
|
|||
return [file join [test_cmd_dir $test] "core"]
|
||||
}
|
||||
|
||||
proc test_exit_code_file { test } {
|
||||
global result_dir
|
||||
return [file join $result_dir "$test.exitcode"]
|
||||
}
|
||||
|
||||
################################################################
|
||||
|
||||
# Local Variables:
|
||||
|
|
|
|||
|
|
@ -158,6 +158,7 @@ record_public_tests {
|
|||
path_group_names
|
||||
power_json
|
||||
prima3
|
||||
read_saif_null_instance
|
||||
report_checks_sorted
|
||||
report_checks_src_attr
|
||||
report_json1
|
||||
|
|
|
|||
|
|
@ -6,4 +6,6 @@
|
|||
|
||||
#cmakedefine ZLIB_FOUND
|
||||
|
||||
#cmakedefine01 HAVE_CXX_STD_FORMAT
|
||||
|
||||
#define TCL_READLINE ${TCL_READLINE}
|
||||
|
|
|
|||
|
|
@ -316,7 +316,7 @@ TEST_F(VerilogTest, EscapedWithBackslash) {
|
|||
std::string name = "\\a\\b ";
|
||||
std::string result = instanceVerilogToSta(name);
|
||||
EXPECT_FALSE(result.empty());
|
||||
// The backslash inside should be escaped as \\
|
||||
// The backslash inside should be escaped as double-backslash
|
||||
EXPECT_NE(result.find("\\\\"), std::string::npos);
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue