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:
Jaehyun Kim 2026-03-29 22:33:08 +09:00
commit 422f774b64
36 changed files with 2274 additions and 2060 deletions

3
BUILD
View File

@ -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__"],
)

View File

@ -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()

View File

@ -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

View File

@ -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 && \

View File

@ -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

View File

@ -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

View File

@ -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);

View File

@ -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_);

View File

@ -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);
}
}

View File

@ -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
------------------------

File diff suppressed because it is too large Load Diff

Binary file not shown.

View File

@ -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 {

View File

@ -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)
{
}

View File

@ -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

View File

@ -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;
}

View File

@ -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) {

View File

@ -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);

View File

@ -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;

View File

@ -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

View File

@ -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);
}
}

View File

@ -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);

View File

@ -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;

View File

@ -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());

View File

@ -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
}

View File

@ -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");

View File

@ -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 \

View File

@ -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"; }
}
}
}

View File

@ -0,0 +1 @@
Annotated 0 pin activities.

View File

@ -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))
)
)
)
)
)

View File

@ -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

View File

@ -0,0 +1,2 @@
module top(input clk);
endmodule

View File

@ -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:

View File

@ -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

View File

@ -6,4 +6,6 @@
#cmakedefine ZLIB_FOUND
#cmakedefine01 HAVE_CXX_STD_FORMAT
#define TCL_READLINE ${TCL_READLINE}

View File

@ -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);
}