Merge pull request #397 from The-OpenROAD-Project-staging/sta_update_0729

Latest upstream OpenSTA 7/29
This commit is contained in:
Matt Liberty 2026-07-31 17:38:21 +00:00 committed by GitHub
commit 60b59a15bd
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
38 changed files with 1056 additions and 166 deletions

View File

@ -348,6 +348,7 @@ cc_library(
"search/Tag.hh",
"dcalc/ArcDcalcWaveforms.hh",
"power/Power.hh",
"power/VcdParse.hh",
"power/VcdReader.hh",
"power/SaifReader.hh",
"sdf/SdfReader.hh",

View File

@ -45,8 +45,9 @@ ArcDcalcWaveforms::inputWaveform(ArcDcalcArg &dcalc_arg,
Graph *graph = sta->graph();
Report *report = sta->report();
const Pin *in_pin = dcalc_arg.inPin();
LibertyPort *port = network->libertyPort(in_pin);
if (port) {
LibertyPort *link_port = network->libertyPort(in_pin);
if (link_port) {
LibertyPort *port = link_port->scenePort(scene, min_max);
const RiseFall *in_rf = dcalc_arg.inEdge();
DriverWaveform *driver_waveform = port->driverWaveform(in_rf);
if (driver_waveform) {

View File

@ -186,7 +186,7 @@ ArcDcalcArg::drvrCell() const
return arc_->to()->libertyCell();
}
const LibertyLibrary *
LibertyLibrary *
ArcDcalcArg::drvrLibrary() const
{
return arc_->to()->libertyLibrary();
@ -198,6 +198,13 @@ ArcDcalcArg::drvrEdge() const
return arc_->toEdge()->asRiseFall();
}
void
ArcDcalcArg::setSceneArc(const Scene *scene,
const MinMax *min_max)
{
arc_ = arc_->sceneArc(scene->libertyIndex(min_max));
}
const Net *
ArcDcalcArg::drvrNet(const Network *network) const
{

View File

@ -227,6 +227,7 @@ DelayCalcBase::setDcalcArgParasiticSlew(ArcDcalcArg &gate,
gate.edge(),
scene, min_max);
gate.setInSlew(in_slew);
gate.setSceneArc(scene, min_max);
}
}

View File

@ -48,6 +48,13 @@ namespace sta {
// Lawrence Pillage - “Electronic Circuit & System Simulation Methods” 1998
// McGraw-Hill, Inc. New York, NY.
// "PRIMA: Passive Reduced-order Interconnect Macromodeling Algorithm",
// Altan Odabasioglu, Mustafa Celik, and Lawrence T. Pileggi
// IEEE Transactions on Computer-Aided Design of Integrated Circuits and Systems,
// vol. 17, no. 8, August 1998
using ParasiticSet = std::set<const Parasitic*>;
ArcDelayCalc *
makePrimaDelayCalc(StaState *sta)
{
@ -176,6 +183,7 @@ PrimaDelayCalc::gateDelay(const Pin *drvr_pin,
ArcDcalcArgSeq dcalc_args;
dcalc_args.emplace_back(nullptr, drvr_pin, nullptr, arc, in_slew, load_cap,
parasitic);
dcalc_args[0].setSceneArc(scene, min_max);
ArcDcalcResultSeq dcalc_results = gateDelays(dcalc_args, load_pin_index_map,
scene, min_max);
return dcalc_results[0];
@ -249,9 +257,10 @@ PrimaDelayCalc::checkArgs(ArcDcalcArgSeq &dcalc_args,
if (output_waveforms->slewAxis()->inBounds(in_slew)) {
if (output_waveforms->capAxis()->inBounds(dcalc_arg.loadCap())) {
output_waveforms_[drvr_idx] = output_waveforms;
debugPrint(debug_, "prima", 1, "{} {}",
debugPrint(debug_, "prima", 1, "{} {} {}",
dcalc_arg.drvrCell()->name(),
dcalc_arg.drvrEdge()->to_string().c_str());
dcalc_arg.drvrEdge()->to_string().c_str(),
scene->name());
LibertyCell *drvr_cell = dcalc_arg.drvrCell();
drvr_cell->ensureVoltageWaveforms(scenes_);
}
@ -355,7 +364,7 @@ PrimaDelayCalc::simulate1(const MatrixSd &G,
v_ = v_prev_ = x_to_v * x_init;
time_step_ = time_step_prev_ = timeStep();
debugPrint(debug_, "ccs_dcalc", 1, "time step {}",
debugPrint(debug_, "prima", 1, "time step {}",
delayAsString(time_step_, this));
MatrixSd A(order, order);
@ -395,7 +404,7 @@ PrimaDelayCalc::simulate1(const MatrixSd &G,
v_ = x_to_v * x;
const ArcDcalcArg &dcalc_arg = (*dcalc_args_)[0];
debugPrint(debug_, "ccs_dcalc", 3, "{} ceff {} VDrvr {:.4f} Idrvr {}",
debugPrint(debug_, "prima", 3, "{} ceff {} VDrvr {:.4f} Idrvr {}",
delayAsString(time, this),
units_->capacitanceUnit()->asString(ceff_[0]),
voltage(dcalc_arg.drvrPin()),
@ -420,7 +429,7 @@ PrimaDelayCalc::simulate1(const MatrixSd &G,
double
PrimaDelayCalc::timeStep()
{
// Needs to use LTE for time step dynamic control.
// Should use LTE for dynamic time step control.
return driverResistance() * load_cap_ * .02;
}
@ -436,7 +445,8 @@ PrimaDelayCalc::driverResistance()
{
const Pin *drvr_pin = (*dcalc_args_)[0].drvrPin();
LibertyPort *drvr_port = network_->libertyPort(drvr_pin);
return drvr_port->driveResistance(drvr_rf_, min_max_);
LibertyPort *scene_port = drvr_port->scenePort(scene_, min_max_);
return scene_port->driveResistance(drvr_rf_, min_max_);
}
void
@ -460,61 +470,68 @@ PrimaDelayCalc::initSim()
void
PrimaDelayCalc::findNodeCount()
{
includes_pin_caps_ = parasitics_->includesPinCaps(parasitic_network_);
coupling_cap_multiplier_ = 1.0;
node_capacitances_.clear();
pin_node_map_.clear();
node_index_map_.clear();
node_count_ = 0;
for (ParasiticNode *node : parasitics_->nodes(parasitic_network_)) {
if (!parasitics_->isExternal(node)) {
size_t node_idx = node_index_map_.size();
node_index_map_[node] = node_idx;
const Pin *pin = parasitics_->pin(node);
if (pin) {
pin_node_map_[pin] = node_idx;
debugPrint(debug_, "ccs_dcalc", 1, "pin {} node {}",
network_->pathName(pin), node_idx);
// Collect the nodes that enter G by walking out from the drivers through
// resistors. G is conductance-only, so a node with no resistive path to a
// driver has an all-zero row which is dropped to prevent singularity.
ParasiticSet visited_parasitics;
for (const ArcDcalcArg &dcalc_arg : *dcalc_args_) {
const Parasitic *parasitic = dcalc_arg.parasitic();
if (!visited_parasitics.contains(parasitic)) {
ParasiticNodeResistorMap resistor_map =
parasitics_->parasiticNodeResistorMap(parasitic);
std::vector<ParasiticNode *> queue;
for (size_t drvr_idx = 0; drvr_idx < drvr_count_; drvr_idx++) {
const Pin *drvr_pin = (*dcalc_args_)[drvr_idx].drvrPin();
ParasiticNode *drvr_node =
parasitics_->findParasiticNode(parasitic, drvr_pin);
if (drvr_node && !parasitics_->isExternal(drvr_node)
&& !node_index_map_.contains(drvr_node)) {
placeNode(drvr_node, node_count_++);
queue.push_back(drvr_node);
}
}
double cap = parasitics_->nodeGndCap(node) + pinCapacitance(node);
node_capacitances_.push_back(cap);
while (!queue.empty()) {
ParasiticNode *node = queue.back();
queue.pop_back();
size_t node_index = node_index_map_[node];
auto resistor_itr = resistor_map.find(node);
if (resistor_itr != resistor_map.end()) {
for (ParasiticResistor *resistor : resistor_itr->second) {
ParasiticNode *next_node = parasitics_->otherNode(resistor, node);
if (next_node
&& !parasitics_->isExternal(next_node)
&& !node_index_map_.contains(next_node)) {
bool shorted = parasitics_->value(resistor) == 0;
placeNode(next_node, shorted ? node_index : node_count_++);
queue.push_back(next_node);
}
}
}
}
visited_parasitics.insert(parasitic);
}
}
for (ParasiticCapacitor *capacitor : parasitics_->capacitors(parasitic_network_)) {
float cap = parasitics_->value(capacitor) * coupling_cap_multiplier_;
ParasiticNode *node1 = parasitics_->node1(capacitor);
if (node1 && !parasitics_->isExternal(node1)) {
size_t node_idx = node_index_map_[node1];
node_capacitances_[node_idx] += cap;
}
ParasiticNode *node2 = parasitics_->node2(capacitor);
if (node2 && !parasitics_->isExternal(node2)) {
size_t node_idx = node_index_map_[node2];
node_capacitances_[node_idx] += cap;
}
}
node_count_ = node_index_map_.size();
}
float
PrimaDelayCalc::pinCapacitance(ParasiticNode *node)
// Add node to network at index (shared by drivers and by the
// resistor walk). A merged short reuses the near node's index.
void
PrimaDelayCalc::placeNode(ParasiticNode *node,
size_t index)
{
node_index_map_[node] = index;
const Pin *pin = parasitics_->pin(node);
float pin_cap = 0.0;
const Sdc *sdc = scene_->sdc();
if (pin) {
Port *port = network_->port(pin);
LibertyPort *lib_port = network_->libertyPort(port);
if (lib_port) {
if (!includes_pin_caps_)
pin_cap = sdc->pinCapacitance(pin, drvr_rf_, scene_, min_max_);
}
else if (network_->isTopLevelPort(pin))
pin_cap = sdc->portExtCap(port, drvr_rf_, min_max_);
pin_node_map_[pin] = index;
debugPrint(debug_, "prima", 1, "pin {} node {}",
network_->pathName(pin), index);
}
return pin_cap;
}
void
@ -556,6 +573,16 @@ PrimaDelayCalc::setXinit()
x_init_[node_count_ + p] = drvr_init_volt;
}
std::pair<size_t, bool>
PrimaDelayCalc::nodeIndex(const ParasiticNode *node)
{
auto node_index = node_index_map_.find(node);
if (node_index != node_index_map_.end())
return {node_index->second, true};
else
return {0, false};
}
void
PrimaDelayCalc::stampEqns()
{
@ -563,40 +590,127 @@ PrimaDelayCalc::stampEqns()
C_.setZero();
B_.setZero();
for (size_t node_idx = 0; node_idx < node_count_; node_idx++)
stampCapacitance(node_idx, node_capacitances_[node_idx]);
NetSet drvr_nets(network_);
for (ArcDcalcArg &dcalc_arg : *dcalc_args_) {
const Net *net = dcalc_arg.drvrNet(network_);
drvr_nets.insert(net);
}
resistance_sum_ = 0.0;
for (ParasiticResistor *resistor : parasitics_->resistors(parasitic_network_)) {
ParasiticNode *node1 = parasitics_->node1(resistor);
ParasiticNode *node2 = parasitics_->node2(resistor);
// One commercial extractor creates resistors with identical from/to nodes.
if (node1 != node2) {
size_t node_idx1 = node_index_map_[node1];
size_t node_idx2 = node_index_map_[node2];
float resistance = parasitics_->value(resistor);
stampConductance(node_idx1, node_idx2, 1.0 / resistance);
resistance_sum_ += resistance;
ParasiticSet visited_parasitics;
for (size_t drvr_idx = 0; drvr_idx < drvr_count_; drvr_idx++) {
const ArcDcalcArg &dcalc_arg = (*dcalc_args_)[drvr_idx];
stampDriver(dcalc_arg, drvr_idx);
const Parasitic *parasitic = dcalc_arg.parasitic();
if (!visited_parasitics.contains(parasitic)) {
stampResistors(parasitic);
stampCapacitors(parasitic, dcalc_arg, drvr_nets);
visited_parasitics.insert(parasitic);
}
}
for (size_t drvr_idx = 0; drvr_idx < drvr_count_; drvr_idx++) {
const ArcDcalcArg &dcalc_arg = (*dcalc_args_)[drvr_idx];
if (debug_->check("prima", 3)) {
reportMatrix("G", G_);
reportMatrix("C", C_);
reportMatrix("B", B_);
}
}
void
PrimaDelayCalc::stampDriver(const ArcDcalcArg &dcalc_arg,
size_t drvr_idx)
{
size_t drvr_node = pin_node_map_[dcalc_arg.drvrPin()];
G_.coeffRef(node_count_ + drvr_idx, drvr_node) = 1.0;
G_.coeffRef(node_count_ + drvr_idx, node_count_ + drvr_idx) = -1.0;
// special sauce
G_.coeffRef(drvr_node, drvr_node) += 1e-6;
B_.coeffRef(drvr_node, drvr_idx) = 1.0;
}
void
PrimaDelayCalc::stampResistors(const Parasitic *parasitic)
{
for (ParasiticResistor *resistor : parasitics_->resistors(parasitic)) {
auto [node_idx1, exsits1] = nodeIndex(parasitics_->node1(resistor));
auto [node_idx2, exsits2] = nodeIndex(parasitics_->node2(resistor));
// Skip a resistor with a node left out of the network.
if (exsits1 && exsits2) {
float resistance = parasitics_->value(resistor);
// Skip a self loop / merged short (same index) or a non-positive (short)
// resistance; stamping 1/resistance would be infinite.
if (node_idx1 != node_idx2 && resistance > 0.0) {
stampConductance(node_idx1, node_idx2, 1.0 / resistance);
resistance_sum_ += resistance;
}
}
}
}
void
PrimaDelayCalc::stampCapacitors(const Parasitic *parasitic,
const ArcDcalcArg &dcalc_arg,
NetSet &drvr_nets)
{
const RiseFall *drvr_rf = dcalc_arg.drvrEdge();
bool includes_pin_caps = parasitics_->includesPinCaps(parasitic);
// Grounded capacitors.
for (ParasiticNode *node : parasitics_->nodes(parasitic)) {
if (!parasitics_->isExternal(node)) {
auto [node_idx, exists] = nodeIndex(node);
if (exists) {
double cap = parasitics_->nodeGndCap(node);
const Pin *pin = parasitics_->pin(node);
if (pin)
cap += pinCapacitance(pin, drvr_rf, includes_pin_caps);
stampCapacitance(node_idx, cap);
}
}
}
if (debug_->check("ccs_dcalc", 3)) {
reportMatrix("G", G_);
reportMatrix("C", C_);
reportMatrix("B", B_);
// Coupling capcacitors.
const Net *drvr_net = dcalc_arg.drvrNet(network_);
for (ParasiticCapacitor *capacitor : parasitics_->capacitors(parasitic)) {
ParasiticNode *node1 = parasitics_->node1(capacitor);
ParasiticNode *node2 = parasitics_->node2(capacitor);
float cap = parasitics_->value(capacitor);
const Net *net1 = node1 ? parasitics_->net(node1, network_) : nullptr;
const Net *net2 = node2 ? parasitics_->net(node2, network_) : nullptr;
if (net2 == drvr_net) {
std::swap(net1, net2);
std::swap(node1, node2);
}
auto [node_idx1, exists1] = nodeIndex(node1);
if (exists1) {
if (net2 && drvr_nets.contains(net2)) {
auto [node_idx2, exists2] = nodeIndex(node2);
if (exists2)
// Stamp half the capacitance because the coupled net will do the same.
stampCapacitance(node_idx1, node_idx2, cap * .5);
}
else
stampCapacitance(node_idx1, cap);
}
}
}
float
PrimaDelayCalc::pinCapacitance(const Pin *pin,
const RiseFall *rf,
bool includes_pin_caps)
{
Port *port = network_->port(pin);
LibertyPort *lib_port = network_->libertyPort(port);
const Sdc *sdc = scene_->sdc();
if (lib_port) {
if (!includes_pin_caps)
return sdc->pinCapacitance(pin, rf, scene_, min_max_);
}
else if (network_->isTopLevelPort(pin))
return sdc->portExtCap(port, rf, min_max_);
return 0.0;
}
// Grounded resistor.
void
PrimaDelayCalc::stampConductance(size_t n1,
@ -723,7 +837,8 @@ PrimaDelayCalc::measureThresholds(double time)
if ((v_prev < th && th <= v) || (v_prev > th && th >= v)) {
double t_cross =
time - time_step_ + (th - v_prev) * time_step_ / (v - v_prev);
debugPrint(debug_, "ccs_measure", 1, "node {} cross {:.2f} {}", node_idx, th,
debugPrint(debug_, "prima_measure", 1, "node {} cross {:.2f} {}",
node_idx, th,
delayAsString(t_cross, this));
threshold_times_[node_idx][m] = t_cross;
}
@ -771,7 +886,7 @@ PrimaDelayCalc::dcalcResults()
dcalc_result.setGateDelay(gate_delay2);
dcalc_result.setDrvrSlew(drvr_slew2);
debugPrint(debug_, "ccs_dcalc", 2, "{} gate delay {} slew {}",
debugPrint(debug_, "prima", 2, "{} gate delay {} slew {}",
network_->pathName(drvr_pin), delayAsString(gate_delay, this),
delayAsString(drvr_slew, this));
@ -784,7 +899,7 @@ PrimaDelayCalc::dcalcResults()
ThresholdTimes &drvr_times = threshold_times_[drvr_node];
double wire_delay = wire_times[threshold_vth] - drvr_times[threshold_vth];
double load_slew = std::abs(wire_times[threshold_vh] - wire_times[threshold_vl]);
debugPrint(debug_, "ccs_dcalc", 2, "load {} {} delay {} slew {}",
debugPrint(debug_, "prima", 2, "load {} {} delay {} slew {}",
network_->pathName(load_pin),
drvr_rf_->shortName(),
delayAsString(wire_delay, this),
@ -876,7 +991,7 @@ PrimaDelayCalc::primaReduce()
// solve x_init = Vq * x~_init for x~_init
xq_init_ = Vq_.colPivHouseholderQr().solve(x_init_);
if (debug_->check("ccs_dcalc", 3)) {
if (debug_->check("prima", 3)) {
reportMatrix("Vq", Vq_);
reportMatrix("G~", Gq_);
reportMatrix("C~", Cq_);
@ -939,7 +1054,7 @@ PrimaDelayCalc::primaReduce2()
// solve x_init = Vq * x~_init for x~_init
xq_init_ = Vq_.colPivHouseholderQr().solve(x_init_);
if (debug_->check("ccs_dcalc", 3)) {
if (debug_->check("prima", 3)) {
reportMatrix("Vq", Vq_);
reportMatrix("G~", Gq_);
reportMatrix("C~", Cq_);

View File

@ -132,10 +132,22 @@ protected:
void initSim();
void findLoads();
void findNodeCount();
void placeNode(ParasiticNode *node,
size_t index);
void setOrder();
void initCeffIdrvr();
void setXinit();
std::pair<size_t, bool> nodeIndex(const ParasiticNode *node);
void stampEqns();
void stampDriver(const ArcDcalcArg &dcalc_arg,
size_t drvr_idx);
void stampResistors(const Parasitic *parasitic);
void stampCapacitors(const Parasitic *parasitic,
const ArcDcalcArg &dcalc_arg,
NetSet &drvr_nets);
float pinCapacitance(const Pin *pin,
const RiseFall *rf,
bool includes_pin_caps);
void stampConductance(size_t n1,
double g);
void stampConductance(size_t n1,
@ -146,7 +158,6 @@ protected:
void stampCapacitance(size_t n1,
size_t n2,
double cap);
float pinCapacitance(ParasiticNode *node);
void setPortCurrents();
void measureThresholds(double time);
double voltage(const Pin *pin);
@ -194,8 +205,6 @@ protected:
std::vector<OutputWaveforms*> output_waveforms_;
double resistance_sum_;
std::vector<double> node_capacitances_;
bool includes_pin_caps_;
float coupling_cap_multiplier_;
size_t node_count_; // Parasitic network node count

View File

@ -4,6 +4,15 @@ OpenSTA Timing Analyzer Release Notes
This file summarizes user visible changes for each release.
See ApiChangeLog.txt for changes to the STA api.
2026/07/20
----------
The read_vcd command supports -begin_time / -end_time to limit
activity annotation to a VCD time window.
read_vcd [-scope scope] [-mode mode_name]
[-begin_time begin_time] [-end_time end_time] filename
2026/05/01
----------

View File

@ -76,11 +76,13 @@ public:
const Pin *drvrPin() const { return drvr_pin_; }
Vertex *drvrVertex(const Graph *graph) const;
LibertyCell *drvrCell() const;
const LibertyLibrary *drvrLibrary() const;
LibertyLibrary *drvrLibrary() const;
const RiseFall *drvrEdge() const;
const Net *drvrNet(const Network *network) const;
Edge *edge() const { return edge_; }
const TimingArc *arc() const { return arc_; }
void setSceneArc(const Scene *scene,
const MinMax *min_max);
const Slew &inSlew() const { return in_slew_; }
float inSlewFlt() const;
void setInSlew(Slew in_slew);

View File

@ -284,6 +284,10 @@ protected:
PropertyValue::Type propertyType(std::string_view type);
PropertyValue coercePropertyValue(PropertyValue::Type type,
std::string_view value);
// True if a user-defined property of this name was declared (via
// defineProperty) on this object type.
bool isUserProperty(std::string_view object_type,
std::string_view property);
PropertyRegistry<const Library*> registry_library_;
PropertyRegistry<const LibertyLibrary*> registry_liberty_library_;

View File

@ -22,6 +22,8 @@
//
// This notice may not be removed or altered from any source distribution.
%include "stdint.i"
%{
#include "power/Power.hh"
@ -29,12 +31,16 @@
#include "Sdc.hh"
#include "Sta.hh"
#include "power/SaifReader.hh"
#include "power/VcdParse.hh"
#include "power/VcdReader.hh"
using namespace sta;
%}
// Match power/VcdParse.hh vcd_null_time for Tcl defaults.
%constant int64_t vcd_null_time = -1;
%inline %{
void
@ -201,11 +207,13 @@ clock_min_period(const char *mode_name)
void
read_vcd_file(const char *filename,
const char *scope,
const char *mode_name)
const char *mode_name,
int64_t begin_time,
int64_t end_time)
{
Sta *sta = Sta::sta();
sta->ensureLibLinked();
readVcdActivities(filename, scope, mode_name, sta);
readVcdActivities(filename, scope, mode_name, begin_time, end_time, sta);
}
////////////////////////////////////////////////////////////////
@ -228,4 +236,12 @@ report_activity_annotation_cmd(bool report_unannotated,
report_annotated);
}
void
clear_power()
{
Power *power = Sta::sta()->power();
power->clear();
}
%} // inline

View File

@ -238,16 +238,18 @@ proc read_power_activities { args } {
set scope $keys(-scope)
}
sta_warn 305 "read_power_activities is deprecated. Use read_vcd."
read_vcd_file $filename $scope
read_vcd_file $filename $scope [cmd_mode_name] \
$::sta::vcd_null_time $::sta::vcd_null_time
}
################################################################
define_cmd_args "read_vcd" { [-scope scope] [-mode mode_name] filename }
define_cmd_args "read_vcd" \
{[-scope scope] [-mode mode_name] [-begin_time begin_time] [-end_time end_time] filename}
proc read_vcd { args } {
parse_key_args "read_vcd" args \
keys {-scope -mode_name} flags {}
keys {-scope -mode -begin_time -end_time} flags {}
check_argc_eq1 "read_vcd" $args
set filename [file nativename [lindex $args 0]]
@ -259,7 +261,15 @@ proc read_vcd { args } {
if { [info exists keys(-mode)] } {
set mode_name $keys(-mode)
}
read_vcd_file $filename $scope $mode_name
set begin_time $::sta::vcd_null_time
if { [info exists keys(-begin_time)] } {
set begin_time $keys(-begin_time)
}
set end_time $::sta::vcd_null_time
if { [info exists keys(-end_time)] } {
set end_time $keys(-end_time)
}
read_vcd_file $filename $scope $mode_name $begin_time $end_time
}
################################################################

View File

@ -42,8 +42,13 @@ namespace sta {
void
VcdParse::read(const char *filename,
VcdReader *reader)
VcdReader *reader,
VcdTime begin_time,
VcdTime end_time)
{
begin_time_ = begin_time;
end_time_ = end_time;
stream_ = gzopen(filename, "r");
if (stream_) {
Stats stats(debug_, report_);
@ -51,6 +56,11 @@ VcdParse::read(const char *filename,
reader_ = reader;
file_line_ = 0;
stmt_line_ = 0;
// If user specified a start time, set it now.
if (begin_time != vcd_null_time) {
reader_->setTimeMin(begin_time);
}
std::string token = getToken();
while (!token.empty()) {
if (token == "$date")
@ -87,7 +97,10 @@ VcdParse::read(const char *filename,
report_->fileError(806, filename_, file_line_, "time out of range {}",
token.substr(1));
}
reader_->setTimeMin(time_);
// Set time min to start time if it is not set at beginning
if (begin_time == vcd_null_time) {
reader_->setTimeMin(time_);
}
prev_time_ = time_;
}
else if (token[0] == '$')
@ -238,7 +251,13 @@ VcdParse::parseVarValues()
}
token = getToken();
}
reader_->setTimeMax(time_);
// Set time_max to end_time if specified, otherwise use actual parsed time
if (end_time_ != vcd_null_time) {
reader_->setTimeMax(end_time_);
} else {
reader_->setTimeMax(time_);
}
}
std::string

View File

@ -36,6 +36,9 @@ namespace sta {
using VcdTime = int64_t;
using VcdScope = std::vector<std::string>;
// Sentinel for an unset begin/end time window bound.
constexpr VcdTime vcd_null_time = -1;
enum class VcdVarType {
wire,
reg,
@ -64,7 +67,9 @@ public:
VcdParse(Report *report,
Debug *debug);
void read(const char *filename,
VcdReader *reader);
VcdReader *reader,
VcdTime begin_time,
VcdTime end_time);
private:
void parseTimescale();
@ -87,6 +92,11 @@ private:
VcdTime time_ = 0;
VcdTime prev_time_ = 0;
// Arguments to VcdParse
VcdTime begin_time_ = vcd_null_time;
VcdTime end_time_ = vcd_null_time;
VcdScope scope_;
Report *report_;

View File

@ -52,48 +52,89 @@ public:
VcdTime highTime(VcdTime time_max) const;
void incrCounts(VcdTime time,
char value);
void incrCounts(VcdTime time,
int64_t value);
void addPin(const Pin *pin);
const PinSeq &pins() const { return pins_; }
static void setFilter(VcdTime begin,
VcdTime end);
private:
VcdTime clippedIntervalStart() const;
PinSeq pins_;
VcdTime prev_time_ = -1;
VcdTime prev_time_ = vcd_null_time;
char prev_value_ = '\0';
VcdTime high_time_ = 0;
double transition_count_ = 0;
static VcdTime begin_time_;
static VcdTime end_time_;
};
// Define static members
VcdTime VcdCount::begin_time_ = vcd_null_time;
VcdTime VcdCount::end_time_ = vcd_null_time;
void
VcdCount::addPin(const Pin *pin)
{
pins_.push_back(pin);
}
VcdTime
VcdCount::clippedIntervalStart() const
{
// Clip prev_time_ to begin_time if signal went high before the window.
return (begin_time_ != vcd_null_time && prev_time_ < begin_time_)
? begin_time_ : prev_time_;
}
void
VcdCount::setFilter(VcdTime begin,
VcdTime end)
{
begin_time_ = begin;
end_time_ = end;
}
void
VcdCount::incrCounts(VcdTime time,
char value)
{
// Initial value does not coontribute to transitions or high time.
if (prev_time_ != -1) {
if (prev_value_ == '1')
high_time_ += time - prev_time_;
// Determine if this time point is within the filter window
bool in_window = (begin_time_ == vcd_null_time || time >= begin_time_)
&& (end_time_ == vcd_null_time || time <= end_time_);
// Initial value does not contribute to transitions or high time.
if (prev_time_ != vcd_null_time && in_window) {
if (prev_value_ == '1') {
VcdTime interval_start = clippedIntervalStart();
if (time > interval_start)
high_time_ += time - interval_start;
}
if (value != prev_value_)
transition_count_ +=
(value == 'X' || value == 'Z' || prev_value_ == 'X' || prev_value_ == 'Z')
? .5
: 1.0;
}
prev_time_ = time;
prev_value_ = value;
// Update state for transitions before or within the window.
// This prevents values after window boundaries corrupting high time.
if (end_time_ == vcd_null_time || time <= end_time_) {
prev_time_ = time;
prev_value_ = value;
}
}
VcdTime
VcdCount::highTime(VcdTime time_max) const
{
if (prev_value_ == '1')
return high_time_ + time_max - prev_time_;
if (prev_value_ == '1') {
VcdTime interval_start = clippedIntervalStart();
if (time_max > interval_start)
return high_time_ + time_max - interval_start;
else
return high_time_;
}
else
return high_time_;
}
@ -180,12 +221,14 @@ VcdCountReader::setTimeUnit(std::string_view ,
void
VcdCountReader::setTimeMin(VcdTime time)
{
debugPrint(debug_, "read_vcd", 1, "setTimeMin called with time {}", time);
time_min_ = time;
}
void
VcdCountReader::setTimeMax(VcdTime time)
{
debugPrint(debug_, "read_vcd", 1, "setTimeMax called with time {}", time);
time_max_ = time;
}
@ -335,6 +378,8 @@ class ReadVcdActivities : public StaState
public:
ReadVcdActivities(std::string_view filename,
std::string_view scope,
VcdTime begin_time,
VcdTime end_time,
const Sdc *sdc,
Sta *sta);
void readActivities();
@ -345,6 +390,8 @@ private:
double transition_count);
const std::string filename_;
VcdTime begin_time_;
VcdTime end_time_;
std::set<const Pin *> annotated_pins_;
VcdCountReader vcd_reader_;
@ -359,20 +406,26 @@ void
readVcdActivities(std::string_view filename,
std::string_view scope,
std::string_view mode_name,
VcdTime begin_time,
VcdTime end_time,
Sta *sta)
{
const Mode *mode = sta->findMode(mode_name);
const Sdc *sdc = mode->sdc();
ReadVcdActivities reader(filename, scope, sdc, sta);
ReadVcdActivities reader(filename, scope, begin_time, end_time, sdc, sta);
reader.readActivities();
}
ReadVcdActivities::ReadVcdActivities(std::string_view filename,
std::string_view scope,
VcdTime begin_time,
VcdTime end_time,
const Sdc *sdc,
Sta *sta) :
StaState(sta),
filename_(filename),
begin_time_(begin_time),
end_time_(end_time),
vcd_reader_(scope,
sdc_network_,
report_,
@ -391,7 +444,9 @@ ReadVcdActivities::readActivities()
if (clks.empty())
report_->error(820, "No clocks have been defined.");
vcd_parse_.read(filename_.c_str(), &vcd_reader_);
// Set the time window filter once globally
VcdCount::setFilter(begin_time_, end_time_);
vcd_parse_.read(filename_.c_str(), &vcd_reader_, begin_time_, end_time_);
if (vcd_reader_.timeMax() > 0)
setActivities();

View File

@ -26,6 +26,8 @@
#include <string>
#include "VcdParse.hh"
namespace sta {
class Sta;
@ -34,6 +36,8 @@ void
readVcdActivities(std::string_view filename,
std::string_view scope,
std::string_view mode_name,
VcdTime begin_time,
VcdTime end_time,
Sta *sta);
} // namespace sta

View File

@ -643,6 +643,8 @@ Properties::getProperty(const Library *lib,
"library", sta_);
if (value.type() != PropertyValue::Type::none)
return value;
else if (isUserProperty("library", property))
return value;
else
throw PropertyUnknown("library", property);
}
@ -665,6 +667,8 @@ Properties::getProperty(const LibertyLibrary *lib,
sta_);
if (value.type() != PropertyValue::Type::none)
return value;
else if (isUserProperty("liberty_library", property))
return value;
else
throw PropertyUnknown("liberty library", property);
}
@ -696,6 +700,8 @@ Properties::getProperty(const Cell *cell,
"cell", sta_);
if (value.type() != PropertyValue::Type::none)
return value;
else if (isUserProperty("cell", property))
return value;
else
throw PropertyUnknown("cell", property);
}
@ -737,6 +743,8 @@ Properties::getProperty(const LibertyCell *cell,
"liberty_cell", sta_);
if (value.type() != PropertyValue::Type::none)
return value;
else if (isUserProperty("liberty_cell", property))
return value;
else
throw PropertyUnknown("liberty cell", property);
}
@ -797,6 +805,8 @@ Properties::getProperty(const Port *port,
"port", sta_);
if (value.type() != PropertyValue::Type::none)
return value;
else if (isUserProperty("port", property))
return value;
else
throw PropertyUnknown("port", property);
}
@ -898,6 +908,8 @@ Properties::getProperty(const LibertyPort *port,
"liberty_port", sta_);
if (value.type() != PropertyValue::Type::none)
return value;
else if (isUserProperty("liberty_port", property))
return value;
else
throw PropertyUnknown("liberty port", property);
}
@ -938,6 +950,8 @@ Properties::getProperty(const Instance *inst,
"instance", sta_);
if (value.type() != PropertyValue::Type::none)
return value;
else if (isUserProperty("instance", property))
return value;
else
throw PropertyUnknown("instance", property);
}
@ -1025,6 +1039,8 @@ Properties::getProperty(const Pin *pin,
PropertyValue value = registry_pin_.getProperty(pin, property, "pin", sta_);
if (value.type() != PropertyValue::Type::none)
return value;
else if (isUserProperty("pin", property))
return value;
else
throw PropertyUnknown("pin", property);
}
@ -1086,6 +1102,8 @@ Properties::getProperty(const Net *net,
PropertyValue value = registry_net_.getProperty(net, property, "net", sta_);
if (value.type() != PropertyValue::Type::none)
return value;
else if (isUserProperty("net", property))
return value;
else
throw PropertyUnknown("net", property);
}
@ -1191,6 +1209,8 @@ Properties::getProperty(const Clock *clk,
"clock", sta_);
if (value.type() != PropertyValue::Type::none)
return value;
else if (isUserProperty("clock", property))
return value;
else
throw PropertyUnknown("clock", property);
}
@ -1420,6 +1440,13 @@ Properties::coercePropertyValue(PropertyValue::Type type,
}
}
bool
Properties::isUserProperty(std::string_view object_type,
std::string_view property)
{
return prop_types_.contains({std::string(object_type), std::string(property)});
}
PropertyKey::PropertyKey(const void *object,
std::string_view property) :
object_(object),
@ -1462,6 +1489,36 @@ template void Properties::defineProperty<Scene>(std::string_view,
template void Properties::defineProperty<Mode>(std::string_view,
std::string_view,
std::string_view);
template void Properties::defineProperty<Library>(std::string_view,
std::string_view,
std::string_view);
template void Properties::defineProperty<LibertyLibrary>(std::string_view,
std::string_view,
std::string_view);
template void Properties::defineProperty<Cell>(std::string_view,
std::string_view,
std::string_view);
template void Properties::defineProperty<LibertyCell>(std::string_view,
std::string_view,
std::string_view);
template void Properties::defineProperty<Port>(std::string_view,
std::string_view,
std::string_view);
template void Properties::defineProperty<LibertyPort>(std::string_view,
std::string_view,
std::string_view);
template void Properties::defineProperty<Instance>(std::string_view,
std::string_view,
std::string_view);
template void Properties::defineProperty<Pin>(std::string_view,
std::string_view,
std::string_view);
template void Properties::defineProperty<Net>(std::string_view,
std::string_view,
std::string_view);
template void Properties::defineProperty<Clock>(std::string_view,
std::string_view,
std::string_view);
void
Properties::setProperty(const void *object,

View File

@ -149,6 +149,26 @@ define_property_cmd(const char *object_type,
properties.defineProperty<Scene>(object_type, property, type);
else if (object_type_view == "mode")
properties.defineProperty<Mode>(object_type, property, type);
else if (object_type_view == "library")
properties.defineProperty<Library>(object_type, property, type);
else if (object_type_view == "liberty_library")
properties.defineProperty<LibertyLibrary>(object_type, property, type);
else if (object_type_view == "cell")
properties.defineProperty<Cell>(object_type, property, type);
else if (object_type_view == "liberty_cell")
properties.defineProperty<LibertyCell>(object_type, property, type);
else if (object_type_view == "port")
properties.defineProperty<Port>(object_type, property, type);
else if (object_type_view == "liberty_port")
properties.defineProperty<LibertyPort>(object_type, property, type);
else if (object_type_view == "instance")
properties.defineProperty<Instance>(object_type, property, type);
else if (object_type_view == "pin")
properties.defineProperty<Pin>(object_type, property, type);
else if (object_type_view == "net")
properties.defineProperty<Net>(object_type, property, type);
else if (object_type_view == "clock")
properties.defineProperty<Clock>(object_type, property, type);
else
Sta::sta()->report()->error(2209, "define_property -object_type {} not supported.",
object_type);
@ -166,6 +186,26 @@ set_property_cmd(void *object,
properties.setProperty(object, "scene", property, value);
else if (object_type_view == "Mode")
properties.setProperty(object, "mode", property, value);
else if (object_type_view == "Library")
properties.setProperty(object, "library", property, value);
else if (object_type_view == "LibertyLibrary")
properties.setProperty(object, "liberty_library", property, value);
else if (object_type_view == "Cell")
properties.setProperty(object, "cell", property, value);
else if (object_type_view == "LibertyCell")
properties.setProperty(object, "liberty_cell", property, value);
else if (object_type_view == "Port")
properties.setProperty(object, "port", property, value);
else if (object_type_view == "LibertyPort")
properties.setProperty(object, "liberty_port", property, value);
else if (object_type_view == "Instance")
properties.setProperty(object, "instance", property, value);
else if (object_type_view == "Pin")
properties.setProperty(object, "pin", property, value);
else if (object_type_view == "Net")
properties.setProperty(object, "net", property, value);
else if (object_type_view == "Clock")
properties.setProperty(object, "clock", property, value);
else
Sta::sta()->report()->error(2214, "set_property unsupported object type {}.",
object_type);

View File

@ -72,6 +72,7 @@ public:
void writeSpice();
private:
void initPowerGnd();
void writeHeader();
void writePrintStmt();
void writeStageInstances();
@ -150,6 +151,7 @@ private:
using WriteSpice::writeMeasureDelayStmt;
using WriteSpice::writeMeasureSlewStmt;
using WriteSpice::findSlew;
using WriteSpice::initPowerGnd;
};
////////////////////////////////////////////////////////////////
@ -187,7 +189,6 @@ WritePathSpice::WritePathSpice(const Path *path,
path_expanded_(sta),
written_insts_(network_)
{
initPowerGnd();
}
void
@ -196,6 +197,8 @@ WritePathSpice::writeSpice()
spice_stream_.open(spice_filename_);
if (spice_stream_.is_open()) {
path_expanded_.expand(path_, true);
initPowerGnd();
// Find subckt port names as a side-effect of writeSubckts.
writeSubckts();
writeHeader();
@ -212,6 +215,27 @@ WritePathSpice::writeSpice()
throw FileNotWritable(spice_filename_);
}
void
WritePathSpice::initPowerGnd()
{
Scene *scene = path_->scene(this);
const MinMax *min_max = path_->minMax(this);
LibertyLibrary *threshold_lib = nullptr;
for (size_t i = 0; i < path_expanded_.size(); i++) {
const Path *path = path_expanded_.path(i);
const Pin *pin = path->pin(this);
const LibertyPort *port = network_->libertyPort(pin);
if (port) {
threshold_lib = port->scenePort(scene, min_max)->libertyLibrary();
break;
}
}
if (threshold_lib)
initPowerGnd(threshold_lib);
else
report_->error(1606, "No instance with Liberty cell found in path.");
}
void
WritePathSpice::writeHeader()
{
@ -330,7 +354,7 @@ WritePathSpice::writeInputWaveform()
const TimingArc *next_arc = stageGateArc(input_stage + 1);
float slew0 = findSlew(input_path, rf, next_arc);
float threshold = default_library_->inputThreshold(rf);
float threshold = threshold_library_->inputThreshold(rf);
float dt = railToRailSlew(slew0, rf);
float time0 = dt * threshold;
@ -514,11 +538,13 @@ WritePathSpice::writeGateStage(Stage stage)
const Path *drvr_path = stageDrvrPath(stage);
const RiseFall *drvr_rf = drvr_path->transition(this);
const Path *gate_input_path = stageGateInputPath(stage);
const RiseFall *input_rf = gate_input_path->transition(this);
const Edge *gate_edge = stageGateEdge(stage);
LibertyPortLogicValues port_values;
PortLogicValues port_values;
bool is_clked;
gatePortValues(input_pin, drvr_pin, drvr_rf, gate_edge,
gatePortValues(input_pin, drvr_pin, input_rf, drvr_rf, gate_edge,
port_values, is_clked);
PinSet inputs(network_);

View File

@ -76,17 +76,18 @@ WriteSpice::WriteSpice(std::string_view spice_filename,
ckt_sim_(ckt_sim),
scene_(scene),
min_max_(min_max),
default_library_(network_->defaultLibertyLibrary()),
threshold_library_(nullptr),
bdd_(sta),
parasitics_(scene->parasitics(min_max))
{
}
void
WriteSpice::initPowerGnd()
WriteSpice::initPowerGnd(LibertyLibrary *threshold_library)
{
threshold_library_ = threshold_library;
bool exists = false;
default_library_->supplyVoltage(power_name_, power_voltage_, exists);
threshold_library_->supplyVoltage(power_name_, power_voltage_, exists);
if (!exists) {
const OperatingConditions *op_cond =
scene_->sdc()->operatingConditions(min_max_);
@ -94,7 +95,7 @@ WriteSpice::initPowerGnd()
op_cond = network_->defaultLibertyLibrary()->defaultOperatingConditions();
power_voltage_ = op_cond->voltage();
}
default_library_->supplyVoltage(gnd_name_, gnd_voltage_, exists);
threshold_library_->supplyVoltage(gnd_name_, gnd_voltage_, exists);
if (!exists)
gnd_voltage_ = 0.0;
}
@ -309,10 +310,11 @@ WriteSpice::writeSubcktInst(const Instance *inst)
// Power/ground and input voltage sources.
void
WriteSpice::writeSubcktInstVoltSrcs(const Instance *inst,
LibertyPortLogicValues &port_values,
PortLogicValues &port_values,
const PinSet &excluded_input_pins)
{
LibertyCell *cell = network_->libertyCell(inst);
LibertyCell *link_cell = network_->libertyCell(inst);
LibertyCell *cell = link_cell->sceneCell(scene_, min_max_);
const std::string &cell_name = cell->name();
StringSeq &spice_port_names = cell_spice_port_names_[cell_name];
std::string inst_name = network_->pathName(inst);
@ -339,7 +341,7 @@ WriteSpice::writeSubcktInstVoltSrcs(const Instance *inst,
if (port_value == LogicValue::unknown) {
bool has_value;
LogicValue value;
findKeyValue(port_values, port, value, has_value);
findKeyValue(port_values, port->name(), value, has_value);
if (has_value)
port_value = value;
}
@ -732,7 +734,7 @@ WriteSpice::writeWaveformEdge(const RiseFall *rf,
volt0 = power_voltage_;
volt1 = gnd_voltage_;
}
float threshold = default_library_->inputThreshold(rf);
float threshold = threshold_library_->inputThreshold(rf);
float dt = railToRailSlew(slew, rf);
float time0 = time - dt * threshold;
float time1 = time0 + dt;
@ -745,8 +747,8 @@ float
WriteSpice::railToRailSlew(float slew,
const RiseFall *rf)
{
float lower = default_library_->slewLowerThreshold(rf);
float upper = default_library_->slewUpperThreshold(rf);
float lower = threshold_library_->slewLowerThreshold(rf);
float upper = threshold_library_->slewUpperThreshold(rf);
return slew / (upper - lower);
}
@ -756,14 +758,14 @@ WriteSpice::railToRailSlew(float slew,
void
WriteSpice::gatePortValues(const Pin *input_pin,
const Pin *drvr_pin,
const RiseFall *input_rf,
const RiseFall *drvr_rf,
const Edge *gate_edge,
// Return values.
LibertyPortLogicValues &port_values,
PortLogicValues &port_values,
bool &is_clked)
{
is_clked = false;
const Instance *inst = network_->instance(input_pin);
const LibertyPort *input_port = network_->libertyPort(input_pin);
const LibertyPort *drvr_port = network_->libertyPort(drvr_pin);
const FuncExpr *drvr_func = drvr_port->function();
@ -771,49 +773,68 @@ WriteSpice::gatePortValues(const Pin *input_pin,
if (gate_edge && gate_edge->role()->genericRole() == TimingRole::regClkToQ())
regPortValues(input_pin, drvr_rf, drvr_port, drvr_func, port_values, is_clked);
else
gatePortValues(inst, drvr_func, input_port, port_values);
gatePortValues(drvr_func, input_port, input_rf, drvr_rf, port_values);
}
}
void
WriteSpice::gatePortValues(const Instance *,
const FuncExpr *expr,
WriteSpice::gatePortValues(const FuncExpr *expr,
const LibertyPort *input_port,
const RiseFall *input_rf,
const RiseFall *drvr_rf,
// Return values.
LibertyPortLogicValues &port_values)
PortLogicValues &port_values)
{
DdManager *cudd_mgr = bdd_.cuddMgr();
DdNode *bdd = bdd_.funcBdd(expr);
DdNode *input_node = bdd_.findNode(input_port);
unsigned input_node_index = Cudd_NodeReadIndex(input_node);
DdManager *cudd_mgr = bdd_.cuddMgr();
DdNode *diff = Cudd_bddBooleanDiff(cudd_mgr, bdd, input_node_index);
// Cofactors of the driver function wrt the switching (path) input.
DdNode *f1 = Cudd_Cofactor(cudd_mgr, bdd, input_node);
Cudd_Ref(f1);
DdNode *f0 = Cudd_Cofactor(cudd_mgr, bdd, Cudd_Not(input_node));
Cudd_Ref(f0);
// The side inputs must sensitize the path with the polarity of this
// arc, not just any sensitization: for non-unate gates (xor/xnor, mux
// select arcs) the side values decide whether the gate inverts, so a
// cube of the plain Boolean difference (f1 XOR f0) can put the gate on
// the arc opposite to the one the path used.
// input and driver edges agree (non-inverting): f1 & ~f0
// input and driver edges differ (inverting): f0 & ~f1
DdNode *care = (input_rf == drvr_rf)
? Cudd_bddAnd(cudd_mgr, f1, Cudd_Not(f0))
: Cudd_bddAnd(cudd_mgr, f0, Cudd_Not(f1));
Cudd_Ref(care);
int *cube;
CUDD_VALUE_TYPE value;
DdGen *cube_gen = Cudd_FirstCube(cudd_mgr, diff, &cube, &value);
LibertyPortSet ports = expr->ports();
for (const LibertyPort *port : ports) {
if (port != input_port) {
DdNode *port_node = bdd_.findNode(port);
int var_index = Cudd_NodeReadIndex(port_node);
LogicValue value;
switch (cube[var_index]) {
case 0:
value = LogicValue::zero;
break;
case 1:
value = LogicValue::one;
break;
case 2:
default:
value = LogicValue::unknown;
break;
DdGen *cube_gen = Cudd_FirstCube(cudd_mgr, care, &cube, &value);
if (!Cudd_IsGenEmpty(cube_gen)) {
LibertyPortSet ports = expr->ports();
for (const LibertyPort *port : ports) {
if (port != input_port) {
DdNode *port_node = bdd_.findNode(port);
int var_index = Cudd_NodeReadIndex(port_node);
LogicValue port_value;
switch (cube[var_index]) {
case 0:
port_value = LogicValue::zero;
break;
case 1:
port_value = LogicValue::one;
break;
case 2:
default:
port_value = LogicValue::unknown;
break;
}
port_values[port->name()] = port_value;
}
port_values[port] = value;
}
}
Cudd_GenFree(cube_gen);
Cudd_Ref(diff);
Cudd_RecursiveDeref(cudd_mgr, care);
Cudd_RecursiveDeref(cudd_mgr, f0);
Cudd_RecursiveDeref(cudd_mgr, f1);
bdd_.clearVarMap();
}
@ -823,7 +844,7 @@ WriteSpice::regPortValues(const Pin *input_pin,
const LibertyPort *drvr_port,
const FuncExpr *drvr_func,
// Return values.
LibertyPortLogicValues &port_values,
PortLogicValues &port_values,
bool &is_clked)
{
is_clked = false;
@ -849,7 +870,7 @@ void
WriteSpice::seqPortValues(Sequential *seq,
const RiseFall *rf,
// Return values.
LibertyPortLogicValues &port_values)
PortLogicValues &port_values)
{
FuncExpr *data = seq->data();
// SHOULD choose values for all ports of data to make output rise/fall
@ -857,18 +878,19 @@ WriteSpice::seqPortValues(Sequential *seq,
LibertyPort *port = onePort(data);
if (port) {
TimingSense sense = data->portTimingSense(port);
const std::string &port_name = port->name();
switch (sense) {
case TimingSense::positive_unate:
if (rf == RiseFall::rise())
port_values[port] = LogicValue::one;
port_values[port_name] = LogicValue::one;
else
port_values[port] = LogicValue::zero;
port_values[port_name] = LogicValue::zero;
break;
case TimingSense::negative_unate:
if (rf == RiseFall::rise())
port_values[port] = LogicValue::zero;
port_values[port_name] = LogicValue::zero;
else
port_values[port] = LogicValue::one;
port_values[port_name] = LogicValue::one;
break;
case TimingSense::non_unate:
case TimingSense::none:
@ -933,7 +955,7 @@ WriteSpice::writeSubcktInstLoads(const Pin *drvr_pin,
sta::print(spice_stream_, "* Load pins\n");
PinSeq drvr_loads = drvrLoads(drvr_pin);
// Do not sensitize side load gates.
LibertyPortLogicValues port_values;
PortLogicValues port_values;
for (const Pin *load_pin : drvr_loads) {
const Instance *load_inst = network_->instance(load_pin);
if (load_pin != path_load && network_->direction(load_pin)->isAnyInput()
@ -957,9 +979,9 @@ WriteSpice::writeMeasureDelayStmt(const Pin *from_pin,
std::string_view prefix)
{
std::string from_pin_name = network_->pathName(from_pin);
float from_threshold = power_voltage_ * default_library_->inputThreshold(from_rf);
float from_threshold = power_voltage_ * threshold_library_->inputThreshold(from_rf);
std::string to_pin_name = network_->pathName(to_pin);
float to_threshold = power_voltage_ * default_library_->inputThreshold(to_rf);
float to_threshold = power_voltage_ * threshold_library_->inputThreshold(to_rf);
sta::print(spice_stream_, ".measure tran {}_{}_delay_{}\n", prefix,
from_pin_name, to_pin_name);
sta::print(spice_stream_, "+trig v({}) val={:.3f} {}=last\n", from_pin_name,
@ -975,8 +997,8 @@ WriteSpice::writeMeasureSlewStmt(const Pin *pin,
{
std::string pin_name = network_->pathName(pin);
std::string_view spice_rf = spiceTrans(rf);
float lower = power_voltage_ * default_library_->slewLowerThreshold(rf);
float upper = power_voltage_ * default_library_->slewUpperThreshold(rf);
float lower = power_voltage_ * threshold_library_->slewLowerThreshold(rf);
float upper = power_voltage_ * threshold_library_->slewUpperThreshold(rf);
float threshold1, threshold2;
if (rf == RiseFall::rise()) {
threshold1 = lower;

View File

@ -43,7 +43,8 @@ namespace sta {
using ParasiticNodeMap = std::map<const ParasiticNode*, int>;
using CellSpicePortNames = std::map<std::string, StringSeq, std::less<>>;
using LibertyPortLogicValues = std::map<const LibertyPort*, LogicValue>;
// Use port name so lookup works across scenes.
using PortLogicValues = std::map<std::string, LogicValue>;
// Utilities for writing a spice deck.
class WriteSpice : public StaState
@ -61,7 +62,7 @@ public:
const StaState *sta);
protected:
void initPowerGnd();
void initPowerGnd(LibertyLibrary *threshold_library);
void writeHeader(std::string &title,
float max_time,
float time_step);
@ -73,7 +74,7 @@ protected:
StringSeq &tokens);
void writeSubcktInst(const Instance *inst);
void writeSubcktInstVoltSrcs(const Instance *inst,
LibertyPortLogicValues &port_values,
PortLogicValues &port_values,
const PinSet &excluded_input_pins);
float pgPortVoltage(const LibertyPort *pg_port);
void writeVoltageSource(std::string_view inst_name,
@ -120,7 +121,7 @@ protected:
void seqPortValues(Sequential *seq,
const RiseFall *rf,
// Return values.
LibertyPortLogicValues &port_values);
PortLogicValues &port_values);
LibertyPort *onePort(FuncExpr *expr);
void writeMeasureDelayStmt(const Pin *from_pin,
const RiseFall *from_rf,
@ -139,23 +140,25 @@ protected:
void gatePortValues(const Pin *input_pin,
const Pin *drvr_pin,
const RiseFall *input_rf,
const RiseFall *drvr_rf,
const Edge *gate_edge,
// Return values.
LibertyPortLogicValues &port_values,
PortLogicValues &port_values,
bool &is_clked);
void regPortValues(const Pin *input_pin,
const RiseFall *drvr_rf,
const LibertyPort *drvr_port,
const FuncExpr *drvr_func,
// Return values.
LibertyPortLogicValues &port_values,
PortLogicValues &port_values,
bool &is_clked);
void gatePortValues(const Instance *inst,
const FuncExpr *expr,
void gatePortValues(const FuncExpr *expr,
const LibertyPort *input_port,
const RiseFall *input_rf,
const RiseFall *drvr_rf,
// Return values.
LibertyPortLogicValues &port_values);
PortLogicValues &port_values);
void writeSubcktInstLoads(const Pin *drvr_pin,
const Pin *path_load,
const PinSet &excluded_input_pins,
@ -176,7 +179,7 @@ protected:
const MinMax *min_max_;
std::ofstream spice_stream_;
LibertyLibrary *default_library_;
LibertyLibrary *threshold_library_;
float power_voltage_;
float gnd_voltage_;
float max_time_;

View File

@ -121,7 +121,7 @@ proc get_property_object_type { object_type object_name quiet } {
}
define_cmd_args "define_property" \
{-object_type scene|mode -type bool|float|string property}
{-object_type scene|mode|library|liberty_library|cell|liberty_cell|port|liberty_port|instance|pin|net|clock -type bool|float|string property}
proc define_property { args } {
parse_key_args "define_property" args keys {-object_type -type} flags {}

28
test/prima_singular.ok Normal file
View File

@ -0,0 +1,28 @@
Startpoint: r0 (rising edge-triggered flip-flop clocked by clk)
Endpoint: t0 (rising edge-triggered flip-flop clocked by clk)
Path Group: clk
Path Type: max
Delay Time Description
---------------------------------------------------------
0.00 0.00 clock clk (rise edge)
0.00 0.00 clock network delay (propagated)
0.00 0.00 ^ r0/CLK (DFFHQx4_ASAP7_75t_R)
68.24 68.24 ^ r0/Q (DFFHQx4_ASAP7_75t_R)
52.16 120.40 ^ u0/Y (BUFx2_ASAP7_75t_R)
18.40 138.80 ^ t0/D (DFFHQx4_ASAP7_75t_R)
138.80 data arrival time
500.00 500.00 clock clk (rise edge)
0.00 500.00 clock network delay (propagated)
0.00 500.00 clock reconvergence pessimism
500.00 ^ t0/CLK (DFFHQx4_ASAP7_75t_R)
-22.18 477.82 library setup time
477.82 data required time
---------------------------------------------------------
477.82 data required time
-138.80 data arrival time
---------------------------------------------------------
339.02 slack (MET)

52
test/prima_singular.spef Normal file
View File

@ -0,0 +1,52 @@
*SPEF "IEEE 1481-1998"
*DESIGN "top"
*DATE "2026"
*VENDOR "OpenSTA test"
*PROGRAM "hand written"
*VERSION "1.0.1c"
*DESIGN_FLOW "MISSING_NETS"
*DIVIDER /
*DELIMITER :
*BUS_DELIMITER [ ]
*T_UNIT 1.0 PS
*C_UNIT 1.0 FF
*R_UNIT 1.0 KOHM
*L_UNIT 1.0 UH
// Each buffer output net z<i> is degenerate two ways:
// - z<i>:2 z<i>:3 have ground cap but NO resistor (floating islands)
// - u<i>:Y -- z<i>:1 is a 0 KOHM resistor (an ideal short)
// Both used to make PrimaDelayCalc::primaReduce() factorize a singular G
// (STA-1752). node_count_ = 5 > prima_order_ (default 3) selects the
// primaReduce() path that factorizes the pure G matrix.
*D_NET z0 40.2
*CONN
*I u0:Y O
*I t0:D I *L .0086
*CAP
1 u0:Y 6.7
2 t0:D 6.7
3 z0:1 6.7
4 z0:2 6.7
5 z0:3 6.7
*RES
6 u0:Y z0:1 0
7 z0:1 t0:D 2.42
*END
*D_NET z1 40.2
*CONN
*I u1:Y O
*I t1:D I *L .0086
*CAP
1 u1:Y 6.7
2 t1:D 6.7
3 z1:1 6.7
4 z1:2 6.7
5 z1:3 6.7
*RES
6 u1:Y z1:1 0
7 z1:1 t1:D 2.42
*END

29
test/prima_singular.tcl Normal file
View File

@ -0,0 +1,29 @@
# Prima delay calc on degenerate parasitic networks (STA-1752 regression).
#
# Each buffer output net has both a floating (resistor-less) node and a
# zero-resistance short. Either one used to make PrimaDelayCalc's conductance
# matrix G singular, raising STA-1752 "G matrix is singular". Single threaded
# this surfaced as a Tcl error; multi threaded the error was thrown from a
# DispatchQueue worker and aborted with SIGABRT. findNodeCount() now drops
# isolated nodes and merges shorted nodes, so the delay is computed correctly.
#
# This test runs single threaded and checks the reported path. To exercise the
# historical multi-threaded crash path set STA_TEST_THREADS to the number of
# parallel buffers (2); the run must still complete without aborting. (A BFS
# level is dispatched to workers only when its vertex count >= the thread count,
# so more threads than buffers runs inline on the main thread.)
read_liberty asap7_small.lib.gz
read_verilog prima_singular.v
link_design top
create_clock -name clk -period 500 clk
set_input_delay -clock clk 1 [list in0 in1]
set_input_transition 10 [list clk in0 in1]
set_propagated_clock clk
read_spef prima_singular.spef
sta::set_delay_calculator prima
if { [info exists ::env(STA_TEST_THREADS)] } {
sta::set_thread_count $::env(STA_TEST_THREADS)
} else {
sta::set_thread_count 1
}
report_checks -group_path_count 1

11
test/prima_singular.v Normal file
View File

@ -0,0 +1,11 @@
module top (clk, in0, in1, out0, out1);
input clk, in0, in1;
output out0, out1;
wire q0, q1, z0, z1;
DFFHQx4_ASAP7_75t_R r0 (.D(in0), .CLK(clk), .Q(q0));
BUFx2_ASAP7_75t_R u0 (.A(q0), .Y(z0));
DFFHQx4_ASAP7_75t_R t0 (.D(z0), .CLK(clk), .Q(out0));
DFFHQx4_ASAP7_75t_R r1 (.D(in1), .CLK(clk), .Q(q1));
BUFx2_ASAP7_75t_R u1 (.A(q1), .Y(z1));
DFFHQx4_ASAP7_75t_R t1 (.D(z1), .CLK(clk), .Q(out1));
endmodule

View File

@ -161,18 +161,22 @@ record_public_tests {
path_group_names
power_json
prima3
prima_singular
read_saif_null_instance
report_checks_sorted
report_checks_src_attr
report_json1
report_json2
suppress_msg
user_properties
vcd_begin_end_time
verilog_attribute
verilog_well_supplies
verilog_specify
verilog_write_escape
verilog_write_gzip
verilog_unconnected_hpin
write_path_spice_arc_sense
}
define_test_group fast [group_tests all]

17
test/user_properties.ok Normal file
View File

@ -0,0 +1,17 @@
[get_property u1/Z owner]
alice
[get_property u2/ZN owner] (unset)
<>
[get_property r1q weight]
3.500000
[get_property u1z weight] (unset)
<>
[get_property u2 crit]
1
[get_property u1 crit] (unset)
<>
[get_property clk1 grp]
main
[get_pins -filter {owner == alice} *]
Z
Error: pin objects do not have a no_such_prop property.

44
test/user_properties.tcl Normal file
View File

@ -0,0 +1,44 @@
# User-defined properties on pin/net/instance/clock object types.
read_liberty ../examples/nangate45_typ.lib.gz
read_verilog ../examples/example1.v
link_design top
create_clock -name clk1 -period 10 {clk1}
# pin: string property, set on one pin, left unset on another.
define_property -object_type pin -type string owner
set_property [get_pins u1/Z] owner alice
puts {[get_property u1/Z owner]}
puts [get_property [get_pins u1/Z] owner]
puts {[get_property u2/ZN owner] (unset)}
puts "<[get_property [get_pins u2/ZN] owner]>"
# net: float property.
define_property -object_type net -type float weight
set_property [get_nets r1q] weight 3.5
puts {[get_property r1q weight]}
puts [get_property [get_nets r1q] weight]
puts {[get_property u1z weight] (unset)}
puts "<[get_property [get_nets u1z] weight]>"
# instance: bool property.
define_property -object_type instance -type bool crit
set_property [get_cells u2] crit true
puts {[get_property u2 crit]}
puts [get_property [get_cells u2] crit]
puts {[get_property u1 crit] (unset)}
puts "<[get_property [get_cells u1] crit]>"
# clock: string property.
define_property -object_type clock -type string grp
set_property [get_clocks clk1] grp main
puts {[get_property clk1 grp]}
puts [get_property [get_clocks clk1] grp]
# -filter skips objects the property was never set on (no error).
puts {[get_pins -filter {owner == alice} *]}
report_object_names [get_pins -filter {owner == alice} *]
# An undefined property still errors.
if {[catch {get_property [get_pins u1/Z] no_such_prop} msg]} {
puts $msg
}

View File

@ -0,0 +1,60 @@
Annotated 2 pin activities.
Pin Name Activity Duty Cycle
--------------------------------------------------------
u_inv/Y 0.0666665 0.667
u_inv/A 0.0666665 0.333
Annotated 2 pin activities.
Pin Name Activity Duty Cycle
--------------------------------------------------------
u_inv/Y 0.1 1.000
u_inv/A 0.1 0.000
Annotated 2 pin activities.
Pin Name Activity Duty Cycle
--------------------------------------------------------
u_inv/Y 0.2 0.000
u_inv/A 0.2 1.000
Annotated 2 pin activities.
Pin Name Activity Duty Cycle
--------------------------------------------------------
u_inv/Y 0.1 1.000
u_inv/A 0.1 0.000
Annotated 2 pin activities.
Pin Name Activity Duty Cycle
--------------------------------------------------------
u_inv/Y 0.25 0.500
u_inv/A 0.25 0.500
Annotated 2 pin activities.
Pin Name Activity Duty Cycle
--------------------------------------------------------
u_inv/Y 0.125 0.750
u_inv/A 0.125 0.250
Annotated 2 pin activities.
Pin Name Activity Duty Cycle
--------------------------------------------------------
u_inv/Y 0.125 0.250
u_inv/A 0.125 0.750
Annotated 2 pin activities.
Pin Name Activity Duty Cycle
--------------------------------------------------------
u_inv/Y 0.25 0.500
u_inv/A 0.25 0.500
Annotated 2 pin activities.
Pin Name Activity Duty Cycle
--------------------------------------------------------
u_inv/Y 0.125 0.250
u_inv/A 0.125 0.750
Annotated 2 pin activities.
Pin Name Activity Duty Cycle
--------------------------------------------------------
u_inv/Y 0.125 0.750
u_inv/A 0.125 0.250

View File

@ -0,0 +1,70 @@
# Report pin activities
proc report_activities { } {
set pins [get_pins -hierarchical *]
set clk_freq [expr 1.0 / (10 * 1e-12)]
puts "Pin Name Activity Duty Cycle"
puts "--------------------------------------------------------"
foreach pin $pins {
set prop [get_property $pin activity]
set transitions_per_sec [lindex $prop 0]
set duty [lindex $prop 1]
set activity [expr double($transitions_per_sec) / [expr $clk_freq * 2]]
puts "[get_full_name $pin] $activity $duty"
}
puts ""
}
# Setup
read_liberty asap7_invbuf.lib.gz
read_verilog vcd_begin_end_time.v
link_design top
# Define clock period in ps
create_clock -name vclk -period 10
# Full VCD reading works (normal behavior)
# VCD changes at time 50 and 100 (inverter)
sta::clear_power
read_vcd vcd_begin_end_time.vcd -scope top
report_activities
# Read VCD from start to first transition point
sta::clear_power
read_vcd vcd_begin_end_time.vcd -scope top -end_time 50
report_activities
# Read VCD from first transition point to second transition point
sta::clear_power
read_vcd vcd_begin_end_time.vcd -scope top -begin_time 50 -end_time 100
report_activities
# Read VCD from second transition point to end
sta::clear_power
read_vcd vcd_begin_end_time.vcd -scope top -begin_time 100
report_activities
# Read VCD around the first transition point
sta::clear_power
read_vcd vcd_begin_end_time.vcd -scope top -begin_time 40 -end_time 60
report_activities
sta::clear_power
read_vcd vcd_begin_end_time.vcd -scope top -begin_time 20 -end_time 60
report_activities
sta::clear_power
read_vcd vcd_begin_end_time.vcd -scope top -begin_time 40 -end_time 80
report_activities
# Read VCD around the second transition point (should mirror the first)
sta::clear_power
read_vcd vcd_begin_end_time.vcd -scope top -begin_time 90 -end_time 110
report_activities
sta::clear_power
read_vcd vcd_begin_end_time.vcd -scope top -begin_time 70 -end_time 110
report_activities
sta::clear_power
read_vcd vcd_begin_end_time.vcd -scope top -begin_time 90 -end_time 130
report_activities

14
test/vcd_begin_end_time.v Normal file
View File

@ -0,0 +1,14 @@
`timescale 1ps/1ps
module top (
input wire A,
input wire clk,
output wire Y
);
INVx2_ASAP7_75t_R u_inv (
.A(A),
.Y(Y)
);
endmodule

View File

@ -0,0 +1,26 @@
$date
Mon Mar 16 2026
$end
$version
VCD Test File
$end
$timescale
1ps
$end
$scope module top $end
$var wire 1 ! A $end
$var wire 1 " Y $end
$upscope $end
$enddefinitions $end
#0
$dumpvars
0!
1"
$end
#50
1!
0"
#100
0!
1"
#150

View File

@ -0,0 +1,40 @@
* Copyright 2020 The SkyWater PDK Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* SPDX-License-Identifier: Apache-2.0
.subckt sky130_fd_sc_hd__xor2_1 A B VGND VNB VPB VPWR X
X0 a_35_297# A VGND VNB sky130_fd_pr__nfet_01v8 w=650000u l=150000u
X1 VGND B a_35_297# VNB sky130_fd_pr__nfet_01v8 w=650000u l=150000u
X2 X a_35_297# VGND VNB sky130_fd_pr__nfet_01v8 w=650000u l=150000u
X3 a_285_297# B VPWR VPB sky130_fd_pr__pfet_01v8_hvt w=1e+06u l=150000u
X4 VPWR A a_285_297# VPB sky130_fd_pr__pfet_01v8_hvt w=1e+06u l=150000u
X5 a_35_297# B a_117_297# VPB sky130_fd_pr__pfet_01v8_hvt w=1e+06u l=150000u
X6 a_117_297# A VPWR VPB sky130_fd_pr__pfet_01v8_hvt w=1e+06u l=150000u
X7 a_285_47# B X VNB sky130_fd_pr__nfet_01v8 w=650000u l=150000u
X8 a_285_297# a_35_297# X VPB sky130_fd_pr__pfet_01v8_hvt w=1e+06u l=150000u
X9 VGND A a_285_47# VNB sky130_fd_pr__nfet_01v8 w=650000u l=150000u
.ends
* Absorber stubs: write_path_spice's lib_subckt reader (findCellSubckts) treats
* the last token of every device line as a subckt-call name, so a flat
* transistor netlist makes it look for the parameter "l=150000u" and the
* closing ".ends" as if they were cells. These empty subckts satisfy that
* lookup; only the sky130_fd_sc_hd__xor2_1 subckt above is real. (Unrelated to
* the arc-sense fix under test.)
.subckt l=150000u
.ends
.subckt .ends
.ends

Binary file not shown.

View File

@ -0,0 +1,6 @@
* Placeholder SPICE model file for the write_path_spice_arc_sense regression.
*
* write_path_spice requires a -model_file and emits it as a ".include" line in
* the generated deck, but does not read its contents. The regression only
* diffs the written deck, so no transistor models are needed here. Supply the
* real SKY130 sky130_fd_pr models if you want to simulate the deck in ngspice.

View File

@ -0,0 +1,51 @@
Warning 1171: write_path_spice_arc_sense.lib.gz line 23, default_fanout_load is 0.0.
* Path from a v to x ^
.include "write_path_spice_arc_sense.models.spice"
.include "write_path_spice_arc_sense.sp_1.subckt"
.tran 1e-13 3.33e-09
.print tran v(a) v(x0/B) v(x0/X) v(x)
**************
* Input source
**************
v1 a 0 pwl(
+0.000e+00 1.800e+00
+1.667e-11 0.000e+00
+3.333e-09 0.000e+00
+)
*****************
* Stage instances
*****************
xstage1 a x0/B stage1
xstage2 x0/B x0/X x stage2
***************
* Stage subckts
***************
.subckt stage1 a x0/B
* Net a
* Net has no parasitics.
R1 a x0/B 1.000e-04
.ends
.subckt stage2 x0/B x0/X x
* Gate x0 B -> X
xx0 x0/A x0/B x0/VGND x0/VNB x0/VPB x0/VPWR x0/X sky130_fd_sc_hd__xor2_1
v1 x0/A 0 1.800
v2 x0/VGND 0 0.000
v3 x0/VNB 0 0.000
v4 x0/VPB 0 1.800
v5 x0/VPWR 0 1.800
* Load pins
* Net x
* Net has no parasitics.
R1 x0/X x 1.000e-04
.ends
.end

View File

@ -0,0 +1,20 @@
# write_path_spice ties non-unate side inputs to the sensitized arc (issue #474)
source helpers.tcl
read_liberty write_path_spice_arc_sense.lib.gz
read_verilog write_path_spice_arc_sense.v
link_design repro
create_clock -name vclk -period 10
set_input_delay -clock vclk 0 [all_inputs]
set_output_delay -clock vclk 0 [all_outputs]
# Force the inverting arc B(fall) -> X(rise), which the liberty defines only
# under "when A" (A=1). The xor2 side input A is the unconstrained port s, so
# a correct deck must tie x0/A high (v1 x0/A 0 1.800). Before the fix the
# Boolean-difference cube ignored the arc direction and tied it low (0.000).
set spice_file [make_result_file "write_path_spice_arc_sense.sp"]
write_path_spice -path_args {-path_delay max -fall_from [get_ports a] -rise_to [get_ports x]} \
-spice_file $spice_file \
-lib_subckt_file write_path_spice_arc_sense.cells.spice \
-model_file write_path_spice_arc_sense.models.spice \
-power VPWR -ground VGND \
-simulator ngspice
report_file ${spice_file}_1.sp

View File

@ -0,0 +1,7 @@
// Minimal repro for the write_path_spice non-unate side-input tie bug.
// One xor2: the timed path enters pin B, the side input A comes from an
// unconstrained port, so STA knows no constant for it and write_path_spice
// must pick a tie that matches the arc it sensitized.
module repro (input a, input s, output x);
sky130_fd_sc_hd__xor2_1 x0 (.A(s), .B(a), .X(x));
endmodule