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", "search/Tag.hh",
"dcalc/ArcDcalcWaveforms.hh", "dcalc/ArcDcalcWaveforms.hh",
"power/Power.hh", "power/Power.hh",
"power/VcdParse.hh",
"power/VcdReader.hh", "power/VcdReader.hh",
"power/SaifReader.hh", "power/SaifReader.hh",
"sdf/SdfReader.hh", "sdf/SdfReader.hh",

View File

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

View File

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

View File

@ -227,6 +227,7 @@ DelayCalcBase::setDcalcArgParasiticSlew(ArcDcalcArg &gate,
gate.edge(), gate.edge(),
scene, min_max); scene, min_max);
gate.setInSlew(in_slew); 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 // Lawrence Pillage - “Electronic Circuit & System Simulation Methods” 1998
// McGraw-Hill, Inc. New York, NY. // 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 * ArcDelayCalc *
makePrimaDelayCalc(StaState *sta) makePrimaDelayCalc(StaState *sta)
{ {
@ -176,6 +183,7 @@ PrimaDelayCalc::gateDelay(const Pin *drvr_pin,
ArcDcalcArgSeq dcalc_args; ArcDcalcArgSeq dcalc_args;
dcalc_args.emplace_back(nullptr, drvr_pin, nullptr, arc, in_slew, load_cap, dcalc_args.emplace_back(nullptr, drvr_pin, nullptr, arc, in_slew, load_cap,
parasitic); parasitic);
dcalc_args[0].setSceneArc(scene, min_max);
ArcDcalcResultSeq dcalc_results = gateDelays(dcalc_args, load_pin_index_map, ArcDcalcResultSeq dcalc_results = gateDelays(dcalc_args, load_pin_index_map,
scene, min_max); scene, min_max);
return dcalc_results[0]; return dcalc_results[0];
@ -249,9 +257,10 @@ PrimaDelayCalc::checkArgs(ArcDcalcArgSeq &dcalc_args,
if (output_waveforms->slewAxis()->inBounds(in_slew)) { if (output_waveforms->slewAxis()->inBounds(in_slew)) {
if (output_waveforms->capAxis()->inBounds(dcalc_arg.loadCap())) { if (output_waveforms->capAxis()->inBounds(dcalc_arg.loadCap())) {
output_waveforms_[drvr_idx] = output_waveforms; output_waveforms_[drvr_idx] = output_waveforms;
debugPrint(debug_, "prima", 1, "{} {}", debugPrint(debug_, "prima", 1, "{} {} {}",
dcalc_arg.drvrCell()->name(), 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(); LibertyCell *drvr_cell = dcalc_arg.drvrCell();
drvr_cell->ensureVoltageWaveforms(scenes_); drvr_cell->ensureVoltageWaveforms(scenes_);
} }
@ -355,7 +364,7 @@ PrimaDelayCalc::simulate1(const MatrixSd &G,
v_ = v_prev_ = x_to_v * x_init; v_ = v_prev_ = x_to_v * x_init;
time_step_ = time_step_prev_ = timeStep(); time_step_ = time_step_prev_ = timeStep();
debugPrint(debug_, "ccs_dcalc", 1, "time step {}", debugPrint(debug_, "prima", 1, "time step {}",
delayAsString(time_step_, this)); delayAsString(time_step_, this));
MatrixSd A(order, order); MatrixSd A(order, order);
@ -395,7 +404,7 @@ PrimaDelayCalc::simulate1(const MatrixSd &G,
v_ = x_to_v * x; v_ = x_to_v * x;
const ArcDcalcArg &dcalc_arg = (*dcalc_args_)[0]; 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), delayAsString(time, this),
units_->capacitanceUnit()->asString(ceff_[0]), units_->capacitanceUnit()->asString(ceff_[0]),
voltage(dcalc_arg.drvrPin()), voltage(dcalc_arg.drvrPin()),
@ -420,7 +429,7 @@ PrimaDelayCalc::simulate1(const MatrixSd &G,
double double
PrimaDelayCalc::timeStep() PrimaDelayCalc::timeStep()
{ {
// Needs to use LTE for time step dynamic control. // Should use LTE for dynamic time step control.
return driverResistance() * load_cap_ * .02; return driverResistance() * load_cap_ * .02;
} }
@ -436,7 +445,8 @@ PrimaDelayCalc::driverResistance()
{ {
const Pin *drvr_pin = (*dcalc_args_)[0].drvrPin(); const Pin *drvr_pin = (*dcalc_args_)[0].drvrPin();
LibertyPort *drvr_port = network_->libertyPort(drvr_pin); 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 void
@ -460,61 +470,68 @@ PrimaDelayCalc::initSim()
void void
PrimaDelayCalc::findNodeCount() PrimaDelayCalc::findNodeCount()
{ {
includes_pin_caps_ = parasitics_->includesPinCaps(parasitic_network_);
coupling_cap_multiplier_ = 1.0; coupling_cap_multiplier_ = 1.0;
node_capacitances_.clear();
pin_node_map_.clear(); pin_node_map_.clear();
node_index_map_.clear(); node_index_map_.clear();
node_count_ = 0;
for (ParasiticNode *node : parasitics_->nodes(parasitic_network_)) { // Collect the nodes that enter G by walking out from the drivers through
if (!parasitics_->isExternal(node)) { // resistors. G is conductance-only, so a node with no resistive path to a
size_t node_idx = node_index_map_.size(); // driver has an all-zero row which is dropped to prevent singularity.
node_index_map_[node] = node_idx; ParasiticSet visited_parasitics;
const Pin *pin = parasitics_->pin(node); for (const ArcDcalcArg &dcalc_arg : *dcalc_args_) {
if (pin) { const Parasitic *parasitic = dcalc_arg.parasitic();
pin_node_map_[pin] = node_idx; if (!visited_parasitics.contains(parasitic)) {
debugPrint(debug_, "ccs_dcalc", 1, "pin {} node {}", ParasiticNodeResistorMap resistor_map =
network_->pathName(pin), node_idx); 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); while (!queue.empty()) {
node_capacitances_.push_back(cap); 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 // Add node to network at index (shared by drivers and by the
PrimaDelayCalc::pinCapacitance(ParasiticNode *node) // 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); const Pin *pin = parasitics_->pin(node);
float pin_cap = 0.0;
const Sdc *sdc = scene_->sdc();
if (pin) { if (pin) {
Port *port = network_->port(pin); pin_node_map_[pin] = index;
LibertyPort *lib_port = network_->libertyPort(port); debugPrint(debug_, "prima", 1, "pin {} node {}",
if (lib_port) { network_->pathName(pin), index);
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_);
} }
return pin_cap;
} }
void void
@ -556,6 +573,16 @@ PrimaDelayCalc::setXinit()
x_init_[node_count_ + p] = drvr_init_volt; 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 void
PrimaDelayCalc::stampEqns() PrimaDelayCalc::stampEqns()
{ {
@ -563,40 +590,127 @@ PrimaDelayCalc::stampEqns()
C_.setZero(); C_.setZero();
B_.setZero(); B_.setZero();
for (size_t node_idx = 0; node_idx < node_count_; node_idx++) NetSet drvr_nets(network_);
stampCapacitance(node_idx, node_capacitances_[node_idx]); for (ArcDcalcArg &dcalc_arg : *dcalc_args_) {
const Net *net = dcalc_arg.drvrNet(network_);
drvr_nets.insert(net);
}
resistance_sum_ = 0.0; resistance_sum_ = 0.0;
for (ParasiticResistor *resistor : parasitics_->resistors(parasitic_network_)) { ParasiticSet visited_parasitics;
ParasiticNode *node1 = parasitics_->node1(resistor); for (size_t drvr_idx = 0; drvr_idx < drvr_count_; drvr_idx++) {
ParasiticNode *node2 = parasitics_->node2(resistor); const ArcDcalcArg &dcalc_arg = (*dcalc_args_)[drvr_idx];
// One commercial extractor creates resistors with identical from/to nodes. stampDriver(dcalc_arg, drvr_idx);
if (node1 != node2) { const Parasitic *parasitic = dcalc_arg.parasitic();
size_t node_idx1 = node_index_map_[node1]; if (!visited_parasitics.contains(parasitic)) {
size_t node_idx2 = node_index_map_[node2]; stampResistors(parasitic);
float resistance = parasitics_->value(resistor); stampCapacitors(parasitic, dcalc_arg, drvr_nets);
stampConductance(node_idx1, node_idx2, 1.0 / resistance); visited_parasitics.insert(parasitic);
resistance_sum_ += resistance;
} }
} }
for (size_t drvr_idx = 0; drvr_idx < drvr_count_; drvr_idx++) { if (debug_->check("prima", 3)) {
const ArcDcalcArg &dcalc_arg = (*dcalc_args_)[drvr_idx]; 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()]; 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, drvr_node) = 1.0;
G_.coeffRef(node_count_ + drvr_idx, node_count_ + drvr_idx) = -1.0; G_.coeffRef(node_count_ + drvr_idx, node_count_ + drvr_idx) = -1.0;
// special sauce // special sauce
G_.coeffRef(drvr_node, drvr_node) += 1e-6; G_.coeffRef(drvr_node, drvr_node) += 1e-6;
B_.coeffRef(drvr_node, drvr_idx) = 1.0; 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)) { // Coupling capcacitors.
reportMatrix("G", G_); const Net *drvr_net = dcalc_arg.drvrNet(network_);
reportMatrix("C", C_); for (ParasiticCapacitor *capacitor : parasitics_->capacitors(parasitic)) {
reportMatrix("B", B_); 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. // Grounded resistor.
void void
PrimaDelayCalc::stampConductance(size_t n1, PrimaDelayCalc::stampConductance(size_t n1,
@ -723,7 +837,8 @@ PrimaDelayCalc::measureThresholds(double time)
if ((v_prev < th && th <= v) || (v_prev > th && th >= v)) { if ((v_prev < th && th <= v) || (v_prev > th && th >= v)) {
double t_cross = double t_cross =
time - time_step_ + (th - v_prev) * time_step_ / (v - v_prev); 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)); delayAsString(t_cross, this));
threshold_times_[node_idx][m] = t_cross; threshold_times_[node_idx][m] = t_cross;
} }
@ -771,7 +886,7 @@ PrimaDelayCalc::dcalcResults()
dcalc_result.setGateDelay(gate_delay2); dcalc_result.setGateDelay(gate_delay2);
dcalc_result.setDrvrSlew(drvr_slew2); 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), network_->pathName(drvr_pin), delayAsString(gate_delay, this),
delayAsString(drvr_slew, this)); delayAsString(drvr_slew, this));
@ -784,7 +899,7 @@ PrimaDelayCalc::dcalcResults()
ThresholdTimes &drvr_times = threshold_times_[drvr_node]; ThresholdTimes &drvr_times = threshold_times_[drvr_node];
double wire_delay = wire_times[threshold_vth] - drvr_times[threshold_vth]; double wire_delay = wire_times[threshold_vth] - drvr_times[threshold_vth];
double load_slew = std::abs(wire_times[threshold_vh] - wire_times[threshold_vl]); 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), network_->pathName(load_pin),
drvr_rf_->shortName(), drvr_rf_->shortName(),
delayAsString(wire_delay, this), delayAsString(wire_delay, this),
@ -876,7 +991,7 @@ PrimaDelayCalc::primaReduce()
// solve x_init = Vq * x~_init for x~_init // solve x_init = Vq * x~_init for x~_init
xq_init_ = Vq_.colPivHouseholderQr().solve(x_init_); xq_init_ = Vq_.colPivHouseholderQr().solve(x_init_);
if (debug_->check("ccs_dcalc", 3)) { if (debug_->check("prima", 3)) {
reportMatrix("Vq", Vq_); reportMatrix("Vq", Vq_);
reportMatrix("G~", Gq_); reportMatrix("G~", Gq_);
reportMatrix("C~", Cq_); reportMatrix("C~", Cq_);
@ -939,7 +1054,7 @@ PrimaDelayCalc::primaReduce2()
// solve x_init = Vq * x~_init for x~_init // solve x_init = Vq * x~_init for x~_init
xq_init_ = Vq_.colPivHouseholderQr().solve(x_init_); xq_init_ = Vq_.colPivHouseholderQr().solve(x_init_);
if (debug_->check("ccs_dcalc", 3)) { if (debug_->check("prima", 3)) {
reportMatrix("Vq", Vq_); reportMatrix("Vq", Vq_);
reportMatrix("G~", Gq_); reportMatrix("G~", Gq_);
reportMatrix("C~", Cq_); reportMatrix("C~", Cq_);

View File

@ -132,10 +132,22 @@ protected:
void initSim(); void initSim();
void findLoads(); void findLoads();
void findNodeCount(); void findNodeCount();
void placeNode(ParasiticNode *node,
size_t index);
void setOrder(); void setOrder();
void initCeffIdrvr(); void initCeffIdrvr();
void setXinit(); void setXinit();
std::pair<size_t, bool> nodeIndex(const ParasiticNode *node);
void stampEqns(); 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, void stampConductance(size_t n1,
double g); double g);
void stampConductance(size_t n1, void stampConductance(size_t n1,
@ -146,7 +158,6 @@ protected:
void stampCapacitance(size_t n1, void stampCapacitance(size_t n1,
size_t n2, size_t n2,
double cap); double cap);
float pinCapacitance(ParasiticNode *node);
void setPortCurrents(); void setPortCurrents();
void measureThresholds(double time); void measureThresholds(double time);
double voltage(const Pin *pin); double voltage(const Pin *pin);
@ -194,8 +205,6 @@ protected:
std::vector<OutputWaveforms*> output_waveforms_; std::vector<OutputWaveforms*> output_waveforms_;
double resistance_sum_; double resistance_sum_;
std::vector<double> node_capacitances_;
bool includes_pin_caps_;
float coupling_cap_multiplier_; float coupling_cap_multiplier_;
size_t node_count_; // Parasitic network node count 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. This file summarizes user visible changes for each release.
See ApiChangeLog.txt for changes to the STA api. 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 2026/05/01
---------- ----------

View File

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

View File

@ -284,6 +284,10 @@ protected:
PropertyValue::Type propertyType(std::string_view type); PropertyValue::Type propertyType(std::string_view type);
PropertyValue coercePropertyValue(PropertyValue::Type type, PropertyValue coercePropertyValue(PropertyValue::Type type,
std::string_view value); 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 Library*> registry_library_;
PropertyRegistry<const LibertyLibrary*> registry_liberty_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. // This notice may not be removed or altered from any source distribution.
%include "stdint.i"
%{ %{
#include "power/Power.hh" #include "power/Power.hh"
@ -29,12 +31,16 @@
#include "Sdc.hh" #include "Sdc.hh"
#include "Sta.hh" #include "Sta.hh"
#include "power/SaifReader.hh" #include "power/SaifReader.hh"
#include "power/VcdParse.hh"
#include "power/VcdReader.hh" #include "power/VcdReader.hh"
using namespace sta; using namespace sta;
%} %}
// Match power/VcdParse.hh vcd_null_time for Tcl defaults.
%constant int64_t vcd_null_time = -1;
%inline %{ %inline %{
void void
@ -201,11 +207,13 @@ clock_min_period(const char *mode_name)
void void
read_vcd_file(const char *filename, read_vcd_file(const char *filename,
const char *scope, const char *scope,
const char *mode_name) const char *mode_name,
int64_t begin_time,
int64_t end_time)
{ {
Sta *sta = Sta::sta(); Sta *sta = Sta::sta();
sta->ensureLibLinked(); 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); report_annotated);
} }
void
clear_power()
{
Power *power = Sta::sta()->power();
power->clear();
}
%} // inline %} // inline

View File

@ -238,16 +238,18 @@ proc read_power_activities { args } {
set scope $keys(-scope) set scope $keys(-scope)
} }
sta_warn 305 "read_power_activities is deprecated. Use read_vcd." 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 } { proc read_vcd { args } {
parse_key_args "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 check_argc_eq1 "read_vcd" $args
set filename [file nativename [lindex $args 0]] set filename [file nativename [lindex $args 0]]
@ -259,7 +261,15 @@ proc read_vcd { args } {
if { [info exists keys(-mode)] } { if { [info exists keys(-mode)] } {
set mode_name $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 void
VcdParse::read(const char *filename, 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"); stream_ = gzopen(filename, "r");
if (stream_) { if (stream_) {
Stats stats(debug_, report_); Stats stats(debug_, report_);
@ -51,6 +56,11 @@ VcdParse::read(const char *filename,
reader_ = reader; reader_ = reader;
file_line_ = 0; file_line_ = 0;
stmt_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(); std::string token = getToken();
while (!token.empty()) { while (!token.empty()) {
if (token == "$date") if (token == "$date")
@ -87,7 +97,10 @@ VcdParse::read(const char *filename,
report_->fileError(806, filename_, file_line_, "time out of range {}", report_->fileError(806, filename_, file_line_, "time out of range {}",
token.substr(1)); 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_; prev_time_ = time_;
} }
else if (token[0] == '$') else if (token[0] == '$')
@ -238,7 +251,13 @@ VcdParse::parseVarValues()
} }
token = getToken(); 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 std::string

View File

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

View File

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

View File

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

View File

@ -643,6 +643,8 @@ Properties::getProperty(const Library *lib,
"library", sta_); "library", sta_);
if (value.type() != PropertyValue::Type::none) if (value.type() != PropertyValue::Type::none)
return value; return value;
else if (isUserProperty("library", property))
return value;
else else
throw PropertyUnknown("library", property); throw PropertyUnknown("library", property);
} }
@ -665,6 +667,8 @@ Properties::getProperty(const LibertyLibrary *lib,
sta_); sta_);
if (value.type() != PropertyValue::Type::none) if (value.type() != PropertyValue::Type::none)
return value; return value;
else if (isUserProperty("liberty_library", property))
return value;
else else
throw PropertyUnknown("liberty library", property); throw PropertyUnknown("liberty library", property);
} }
@ -696,6 +700,8 @@ Properties::getProperty(const Cell *cell,
"cell", sta_); "cell", sta_);
if (value.type() != PropertyValue::Type::none) if (value.type() != PropertyValue::Type::none)
return value; return value;
else if (isUserProperty("cell", property))
return value;
else else
throw PropertyUnknown("cell", property); throw PropertyUnknown("cell", property);
} }
@ -737,6 +743,8 @@ Properties::getProperty(const LibertyCell *cell,
"liberty_cell", sta_); "liberty_cell", sta_);
if (value.type() != PropertyValue::Type::none) if (value.type() != PropertyValue::Type::none)
return value; return value;
else if (isUserProperty("liberty_cell", property))
return value;
else else
throw PropertyUnknown("liberty cell", property); throw PropertyUnknown("liberty cell", property);
} }
@ -797,6 +805,8 @@ Properties::getProperty(const Port *port,
"port", sta_); "port", sta_);
if (value.type() != PropertyValue::Type::none) if (value.type() != PropertyValue::Type::none)
return value; return value;
else if (isUserProperty("port", property))
return value;
else else
throw PropertyUnknown("port", property); throw PropertyUnknown("port", property);
} }
@ -898,6 +908,8 @@ Properties::getProperty(const LibertyPort *port,
"liberty_port", sta_); "liberty_port", sta_);
if (value.type() != PropertyValue::Type::none) if (value.type() != PropertyValue::Type::none)
return value; return value;
else if (isUserProperty("liberty_port", property))
return value;
else else
throw PropertyUnknown("liberty port", property); throw PropertyUnknown("liberty port", property);
} }
@ -938,6 +950,8 @@ Properties::getProperty(const Instance *inst,
"instance", sta_); "instance", sta_);
if (value.type() != PropertyValue::Type::none) if (value.type() != PropertyValue::Type::none)
return value; return value;
else if (isUserProperty("instance", property))
return value;
else else
throw PropertyUnknown("instance", property); throw PropertyUnknown("instance", property);
} }
@ -1025,6 +1039,8 @@ Properties::getProperty(const Pin *pin,
PropertyValue value = registry_pin_.getProperty(pin, property, "pin", sta_); PropertyValue value = registry_pin_.getProperty(pin, property, "pin", sta_);
if (value.type() != PropertyValue::Type::none) if (value.type() != PropertyValue::Type::none)
return value; return value;
else if (isUserProperty("pin", property))
return value;
else else
throw PropertyUnknown("pin", property); throw PropertyUnknown("pin", property);
} }
@ -1086,6 +1102,8 @@ Properties::getProperty(const Net *net,
PropertyValue value = registry_net_.getProperty(net, property, "net", sta_); PropertyValue value = registry_net_.getProperty(net, property, "net", sta_);
if (value.type() != PropertyValue::Type::none) if (value.type() != PropertyValue::Type::none)
return value; return value;
else if (isUserProperty("net", property))
return value;
else else
throw PropertyUnknown("net", property); throw PropertyUnknown("net", property);
} }
@ -1191,6 +1209,8 @@ Properties::getProperty(const Clock *clk,
"clock", sta_); "clock", sta_);
if (value.type() != PropertyValue::Type::none) if (value.type() != PropertyValue::Type::none)
return value; return value;
else if (isUserProperty("clock", property))
return value;
else else
throw PropertyUnknown("clock", property); 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, PropertyKey::PropertyKey(const void *object,
std::string_view property) : std::string_view property) :
object_(object), object_(object),
@ -1462,6 +1489,36 @@ template void Properties::defineProperty<Scene>(std::string_view,
template void Properties::defineProperty<Mode>(std::string_view, template void Properties::defineProperty<Mode>(std::string_view,
std::string_view, 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 void
Properties::setProperty(const void *object, 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); properties.defineProperty<Scene>(object_type, property, type);
else if (object_type_view == "mode") else if (object_type_view == "mode")
properties.defineProperty<Mode>(object_type, property, type); 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 else
Sta::sta()->report()->error(2209, "define_property -object_type {} not supported.", Sta::sta()->report()->error(2209, "define_property -object_type {} not supported.",
object_type); object_type);
@ -166,6 +186,26 @@ set_property_cmd(void *object,
properties.setProperty(object, "scene", property, value); properties.setProperty(object, "scene", property, value);
else if (object_type_view == "Mode") else if (object_type_view == "Mode")
properties.setProperty(object, "mode", property, value); 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 else
Sta::sta()->report()->error(2214, "set_property unsupported object type {}.", Sta::sta()->report()->error(2214, "set_property unsupported object type {}.",
object_type); object_type);

View File

@ -72,6 +72,7 @@ public:
void writeSpice(); void writeSpice();
private: private:
void initPowerGnd();
void writeHeader(); void writeHeader();
void writePrintStmt(); void writePrintStmt();
void writeStageInstances(); void writeStageInstances();
@ -150,6 +151,7 @@ private:
using WriteSpice::writeMeasureDelayStmt; using WriteSpice::writeMeasureDelayStmt;
using WriteSpice::writeMeasureSlewStmt; using WriteSpice::writeMeasureSlewStmt;
using WriteSpice::findSlew; using WriteSpice::findSlew;
using WriteSpice::initPowerGnd;
}; };
//////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////
@ -187,7 +189,6 @@ WritePathSpice::WritePathSpice(const Path *path,
path_expanded_(sta), path_expanded_(sta),
written_insts_(network_) written_insts_(network_)
{ {
initPowerGnd();
} }
void void
@ -196,6 +197,8 @@ WritePathSpice::writeSpice()
spice_stream_.open(spice_filename_); spice_stream_.open(spice_filename_);
if (spice_stream_.is_open()) { if (spice_stream_.is_open()) {
path_expanded_.expand(path_, true); path_expanded_.expand(path_, true);
initPowerGnd();
// Find subckt port names as a side-effect of writeSubckts. // Find subckt port names as a side-effect of writeSubckts.
writeSubckts(); writeSubckts();
writeHeader(); writeHeader();
@ -212,6 +215,27 @@ WritePathSpice::writeSpice()
throw FileNotWritable(spice_filename_); 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 void
WritePathSpice::writeHeader() WritePathSpice::writeHeader()
{ {
@ -330,7 +354,7 @@ WritePathSpice::writeInputWaveform()
const TimingArc *next_arc = stageGateArc(input_stage + 1); const TimingArc *next_arc = stageGateArc(input_stage + 1);
float slew0 = findSlew(input_path, rf, next_arc); 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 dt = railToRailSlew(slew0, rf);
float time0 = dt * threshold; float time0 = dt * threshold;
@ -514,11 +538,13 @@ WritePathSpice::writeGateStage(Stage stage)
const Path *drvr_path = stageDrvrPath(stage); const Path *drvr_path = stageDrvrPath(stage);
const RiseFall *drvr_rf = drvr_path->transition(this); 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); const Edge *gate_edge = stageGateEdge(stage);
LibertyPortLogicValues port_values; PortLogicValues port_values;
bool is_clked; 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); port_values, is_clked);
PinSet inputs(network_); PinSet inputs(network_);

View File

@ -76,17 +76,18 @@ WriteSpice::WriteSpice(std::string_view spice_filename,
ckt_sim_(ckt_sim), ckt_sim_(ckt_sim),
scene_(scene), scene_(scene),
min_max_(min_max), min_max_(min_max),
default_library_(network_->defaultLibertyLibrary()), threshold_library_(nullptr),
bdd_(sta), bdd_(sta),
parasitics_(scene->parasitics(min_max)) parasitics_(scene->parasitics(min_max))
{ {
} }
void void
WriteSpice::initPowerGnd() WriteSpice::initPowerGnd(LibertyLibrary *threshold_library)
{ {
threshold_library_ = threshold_library;
bool exists = false; bool exists = false;
default_library_->supplyVoltage(power_name_, power_voltage_, exists); threshold_library_->supplyVoltage(power_name_, power_voltage_, exists);
if (!exists) { if (!exists) {
const OperatingConditions *op_cond = const OperatingConditions *op_cond =
scene_->sdc()->operatingConditions(min_max_); scene_->sdc()->operatingConditions(min_max_);
@ -94,7 +95,7 @@ WriteSpice::initPowerGnd()
op_cond = network_->defaultLibertyLibrary()->defaultOperatingConditions(); op_cond = network_->defaultLibertyLibrary()->defaultOperatingConditions();
power_voltage_ = op_cond->voltage(); power_voltage_ = op_cond->voltage();
} }
default_library_->supplyVoltage(gnd_name_, gnd_voltage_, exists); threshold_library_->supplyVoltage(gnd_name_, gnd_voltage_, exists);
if (!exists) if (!exists)
gnd_voltage_ = 0.0; gnd_voltage_ = 0.0;
} }
@ -309,10 +310,11 @@ WriteSpice::writeSubcktInst(const Instance *inst)
// Power/ground and input voltage sources. // Power/ground and input voltage sources.
void void
WriteSpice::writeSubcktInstVoltSrcs(const Instance *inst, WriteSpice::writeSubcktInstVoltSrcs(const Instance *inst,
LibertyPortLogicValues &port_values, PortLogicValues &port_values,
const PinSet &excluded_input_pins) 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(); const std::string &cell_name = cell->name();
StringSeq &spice_port_names = cell_spice_port_names_[cell_name]; StringSeq &spice_port_names = cell_spice_port_names_[cell_name];
std::string inst_name = network_->pathName(inst); std::string inst_name = network_->pathName(inst);
@ -339,7 +341,7 @@ WriteSpice::writeSubcktInstVoltSrcs(const Instance *inst,
if (port_value == LogicValue::unknown) { if (port_value == LogicValue::unknown) {
bool has_value; bool has_value;
LogicValue value; LogicValue value;
findKeyValue(port_values, port, value, has_value); findKeyValue(port_values, port->name(), value, has_value);
if (has_value) if (has_value)
port_value = value; port_value = value;
} }
@ -732,7 +734,7 @@ WriteSpice::writeWaveformEdge(const RiseFall *rf,
volt0 = power_voltage_; volt0 = power_voltage_;
volt1 = gnd_voltage_; volt1 = gnd_voltage_;
} }
float threshold = default_library_->inputThreshold(rf); float threshold = threshold_library_->inputThreshold(rf);
float dt = railToRailSlew(slew, rf); float dt = railToRailSlew(slew, rf);
float time0 = time - dt * threshold; float time0 = time - dt * threshold;
float time1 = time0 + dt; float time1 = time0 + dt;
@ -745,8 +747,8 @@ float
WriteSpice::railToRailSlew(float slew, WriteSpice::railToRailSlew(float slew,
const RiseFall *rf) const RiseFall *rf)
{ {
float lower = default_library_->slewLowerThreshold(rf); float lower = threshold_library_->slewLowerThreshold(rf);
float upper = default_library_->slewUpperThreshold(rf); float upper = threshold_library_->slewUpperThreshold(rf);
return slew / (upper - lower); return slew / (upper - lower);
} }
@ -756,14 +758,14 @@ WriteSpice::railToRailSlew(float slew,
void void
WriteSpice::gatePortValues(const Pin *input_pin, WriteSpice::gatePortValues(const Pin *input_pin,
const Pin *drvr_pin, const Pin *drvr_pin,
const RiseFall *input_rf,
const RiseFall *drvr_rf, const RiseFall *drvr_rf,
const Edge *gate_edge, const Edge *gate_edge,
// Return values. // Return values.
LibertyPortLogicValues &port_values, PortLogicValues &port_values,
bool &is_clked) bool &is_clked)
{ {
is_clked = false; is_clked = false;
const Instance *inst = network_->instance(input_pin);
const LibertyPort *input_port = network_->libertyPort(input_pin); const LibertyPort *input_port = network_->libertyPort(input_pin);
const LibertyPort *drvr_port = network_->libertyPort(drvr_pin); const LibertyPort *drvr_port = network_->libertyPort(drvr_pin);
const FuncExpr *drvr_func = drvr_port->function(); 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()) if (gate_edge && gate_edge->role()->genericRole() == TimingRole::regClkToQ())
regPortValues(input_pin, drvr_rf, drvr_port, drvr_func, port_values, is_clked); regPortValues(input_pin, drvr_rf, drvr_port, drvr_func, port_values, is_clked);
else else
gatePortValues(inst, drvr_func, input_port, port_values); gatePortValues(drvr_func, input_port, input_rf, drvr_rf, port_values);
} }
} }
void void
WriteSpice::gatePortValues(const Instance *, WriteSpice::gatePortValues(const FuncExpr *expr,
const FuncExpr *expr,
const LibertyPort *input_port, const LibertyPort *input_port,
const RiseFall *input_rf,
const RiseFall *drvr_rf,
// Return values. // Return values.
LibertyPortLogicValues &port_values) PortLogicValues &port_values)
{ {
DdManager *cudd_mgr = bdd_.cuddMgr();
DdNode *bdd = bdd_.funcBdd(expr); DdNode *bdd = bdd_.funcBdd(expr);
DdNode *input_node = bdd_.findNode(input_port); DdNode *input_node = bdd_.findNode(input_port);
unsigned input_node_index = Cudd_NodeReadIndex(input_node); // Cofactors of the driver function wrt the switching (path) input.
DdManager *cudd_mgr = bdd_.cuddMgr(); DdNode *f1 = Cudd_Cofactor(cudd_mgr, bdd, input_node);
DdNode *diff = Cudd_bddBooleanDiff(cudd_mgr, bdd, input_node_index); 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; int *cube;
CUDD_VALUE_TYPE value; CUDD_VALUE_TYPE value;
DdGen *cube_gen = Cudd_FirstCube(cudd_mgr, diff, &cube, &value); DdGen *cube_gen = Cudd_FirstCube(cudd_mgr, care, &cube, &value);
if (!Cudd_IsGenEmpty(cube_gen)) {
LibertyPortSet ports = expr->ports(); LibertyPortSet ports = expr->ports();
for (const LibertyPort *port : ports) { for (const LibertyPort *port : ports) {
if (port != input_port) { if (port != input_port) {
DdNode *port_node = bdd_.findNode(port); DdNode *port_node = bdd_.findNode(port);
int var_index = Cudd_NodeReadIndex(port_node); int var_index = Cudd_NodeReadIndex(port_node);
LogicValue value; LogicValue port_value;
switch (cube[var_index]) { switch (cube[var_index]) {
case 0: case 0:
value = LogicValue::zero; port_value = LogicValue::zero;
break; break;
case 1: case 1:
value = LogicValue::one; port_value = LogicValue::one;
break; break;
case 2: case 2:
default: default:
value = LogicValue::unknown; port_value = LogicValue::unknown;
break; break;
}
port_values[port->name()] = port_value;
} }
port_values[port] = value;
} }
} }
Cudd_GenFree(cube_gen); Cudd_GenFree(cube_gen);
Cudd_Ref(diff); Cudd_RecursiveDeref(cudd_mgr, care);
Cudd_RecursiveDeref(cudd_mgr, f0);
Cudd_RecursiveDeref(cudd_mgr, f1);
bdd_.clearVarMap(); bdd_.clearVarMap();
} }
@ -823,7 +844,7 @@ WriteSpice::regPortValues(const Pin *input_pin,
const LibertyPort *drvr_port, const LibertyPort *drvr_port,
const FuncExpr *drvr_func, const FuncExpr *drvr_func,
// Return values. // Return values.
LibertyPortLogicValues &port_values, PortLogicValues &port_values,
bool &is_clked) bool &is_clked)
{ {
is_clked = false; is_clked = false;
@ -849,7 +870,7 @@ void
WriteSpice::seqPortValues(Sequential *seq, WriteSpice::seqPortValues(Sequential *seq,
const RiseFall *rf, const RiseFall *rf,
// Return values. // Return values.
LibertyPortLogicValues &port_values) PortLogicValues &port_values)
{ {
FuncExpr *data = seq->data(); FuncExpr *data = seq->data();
// SHOULD choose values for all ports of data to make output rise/fall // 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); LibertyPort *port = onePort(data);
if (port) { if (port) {
TimingSense sense = data->portTimingSense(port); TimingSense sense = data->portTimingSense(port);
const std::string &port_name = port->name();
switch (sense) { switch (sense) {
case TimingSense::positive_unate: case TimingSense::positive_unate:
if (rf == RiseFall::rise()) if (rf == RiseFall::rise())
port_values[port] = LogicValue::one; port_values[port_name] = LogicValue::one;
else else
port_values[port] = LogicValue::zero; port_values[port_name] = LogicValue::zero;
break; break;
case TimingSense::negative_unate: case TimingSense::negative_unate:
if (rf == RiseFall::rise()) if (rf == RiseFall::rise())
port_values[port] = LogicValue::zero; port_values[port_name] = LogicValue::zero;
else else
port_values[port] = LogicValue::one; port_values[port_name] = LogicValue::one;
break; break;
case TimingSense::non_unate: case TimingSense::non_unate:
case TimingSense::none: case TimingSense::none:
@ -933,7 +955,7 @@ WriteSpice::writeSubcktInstLoads(const Pin *drvr_pin,
sta::print(spice_stream_, "* Load pins\n"); sta::print(spice_stream_, "* Load pins\n");
PinSeq drvr_loads = drvrLoads(drvr_pin); PinSeq drvr_loads = drvrLoads(drvr_pin);
// Do not sensitize side load gates. // Do not sensitize side load gates.
LibertyPortLogicValues port_values; PortLogicValues port_values;
for (const Pin *load_pin : drvr_loads) { for (const Pin *load_pin : drvr_loads) {
const Instance *load_inst = network_->instance(load_pin); const Instance *load_inst = network_->instance(load_pin);
if (load_pin != path_load && network_->direction(load_pin)->isAnyInput() 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_view prefix)
{ {
std::string from_pin_name = network_->pathName(from_pin); 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); 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, sta::print(spice_stream_, ".measure tran {}_{}_delay_{}\n", prefix,
from_pin_name, to_pin_name); from_pin_name, to_pin_name);
sta::print(spice_stream_, "+trig v({}) val={:.3f} {}=last\n", from_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 pin_name = network_->pathName(pin);
std::string_view spice_rf = spiceTrans(rf); std::string_view spice_rf = spiceTrans(rf);
float lower = power_voltage_ * default_library_->slewLowerThreshold(rf); float lower = power_voltage_ * threshold_library_->slewLowerThreshold(rf);
float upper = power_voltage_ * default_library_->slewUpperThreshold(rf); float upper = power_voltage_ * threshold_library_->slewUpperThreshold(rf);
float threshold1, threshold2; float threshold1, threshold2;
if (rf == RiseFall::rise()) { if (rf == RiseFall::rise()) {
threshold1 = lower; threshold1 = lower;

View File

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

View File

@ -121,7 +121,7 @@ proc get_property_object_type { object_type object_name quiet } {
} }
define_cmd_args "define_property" \ 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 } { proc define_property { args } {
parse_key_args "define_property" args keys {-object_type -type} flags {} 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 path_group_names
power_json power_json
prima3 prima3
prima_singular
read_saif_null_instance read_saif_null_instance
report_checks_sorted report_checks_sorted
report_checks_src_attr report_checks_src_attr
report_json1 report_json1
report_json2 report_json2
suppress_msg suppress_msg
user_properties
vcd_begin_end_time
verilog_attribute verilog_attribute
verilog_well_supplies verilog_well_supplies
verilog_specify verilog_specify
verilog_write_escape verilog_write_escape
verilog_write_gzip verilog_write_gzip
verilog_unconnected_hpin verilog_unconnected_hpin
write_path_spice_arc_sense
} }
define_test_group fast [group_tests all] 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