diff --git a/BUILD.bazel b/BUILD.bazel index e432d0be..998f56db 100644 --- a/BUILD.bazel +++ b/BUILD.bazel @@ -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", diff --git a/dcalc/ArcDcalcWaveforms.cc b/dcalc/ArcDcalcWaveforms.cc index 184607e6..61513e33 100644 --- a/dcalc/ArcDcalcWaveforms.cc +++ b/dcalc/ArcDcalcWaveforms.cc @@ -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) { diff --git a/dcalc/ArcDelayCalc.cc b/dcalc/ArcDelayCalc.cc index b615ca47..ecbd1655 100644 --- a/dcalc/ArcDelayCalc.cc +++ b/dcalc/ArcDelayCalc.cc @@ -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 { diff --git a/dcalc/DelayCalcBase.cc b/dcalc/DelayCalcBase.cc index 214aec7d..ed6566b5 100644 --- a/dcalc/DelayCalcBase.cc +++ b/dcalc/DelayCalcBase.cc @@ -227,6 +227,7 @@ DelayCalcBase::setDcalcArgParasiticSlew(ArcDcalcArg &gate, gate.edge(), scene, min_max); gate.setInSlew(in_slew); + gate.setSceneArc(scene, min_max); } } diff --git a/dcalc/PrimaDelayCalc.cc b/dcalc/PrimaDelayCalc.cc index dabe7ae1..a5ea608c 100644 --- a/dcalc/PrimaDelayCalc.cc +++ b/dcalc/PrimaDelayCalc.cc @@ -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; + 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 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 +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_); diff --git a/dcalc/PrimaDelayCalc.hh b/dcalc/PrimaDelayCalc.hh index 4d5b1346..dbae2d10 100644 --- a/dcalc/PrimaDelayCalc.hh +++ b/dcalc/PrimaDelayCalc.hh @@ -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 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 output_waveforms_; double resistance_sum_; - std::vector node_capacitances_; - bool includes_pin_caps_; float coupling_cap_multiplier_; size_t node_count_; // Parasitic network node count diff --git a/doc/ChangeLog.txt b/doc/ChangeLog.txt index c456d0e0..add039d0 100644 --- a/doc/ChangeLog.txt +++ b/doc/ChangeLog.txt @@ -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 ---------- diff --git a/include/sta/ArcDelayCalc.hh b/include/sta/ArcDelayCalc.hh index eacad445..d2bf03af 100644 --- a/include/sta/ArcDelayCalc.hh +++ b/include/sta/ArcDelayCalc.hh @@ -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); diff --git a/include/sta/Property.hh b/include/sta/Property.hh index 84ad5cfc..780740d7 100644 --- a/include/sta/Property.hh +++ b/include/sta/Property.hh @@ -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 registry_library_; PropertyRegistry registry_liberty_library_; diff --git a/power/Power.i b/power/Power.i index 8c2c42e3..8b60b98a 100644 --- a/power/Power.i +++ b/power/Power.i @@ -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 diff --git a/power/Power.tcl b/power/Power.tcl index 71cf563c..e9ae0084 100644 --- a/power/Power.tcl +++ b/power/Power.tcl @@ -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 } ################################################################ diff --git a/power/VcdParse.cc b/power/VcdParse.cc index 2a841427..0243ac7b 100644 --- a/power/VcdParse.cc +++ b/power/VcdParse.cc @@ -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 diff --git a/power/VcdParse.hh b/power/VcdParse.hh index 66f5d28f..9c572680 100644 --- a/power/VcdParse.hh +++ b/power/VcdParse.hh @@ -36,6 +36,9 @@ namespace sta { using VcdTime = int64_t; using VcdScope = std::vector; +// 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_; diff --git a/power/VcdReader.cc b/power/VcdReader.cc index a300c90f..4b3948d2 100644 --- a/power/VcdReader.cc +++ b/power/VcdReader.cc @@ -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 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(); diff --git a/power/VcdReader.hh b/power/VcdReader.hh index e3b0bafc..f115d73c 100644 --- a/power/VcdReader.hh +++ b/power/VcdReader.hh @@ -26,6 +26,8 @@ #include +#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 diff --git a/search/Property.cc b/search/Property.cc index ee3ddc11..25008bd5 100644 --- a/search/Property.cc +++ b/search/Property.cc @@ -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(std::string_view, template void Properties::defineProperty(std::string_view, std::string_view, std::string_view); +template void Properties::defineProperty(std::string_view, + std::string_view, + std::string_view); +template void Properties::defineProperty(std::string_view, + std::string_view, + std::string_view); +template void Properties::defineProperty(std::string_view, + std::string_view, + std::string_view); +template void Properties::defineProperty(std::string_view, + std::string_view, + std::string_view); +template void Properties::defineProperty(std::string_view, + std::string_view, + std::string_view); +template void Properties::defineProperty(std::string_view, + std::string_view, + std::string_view); +template void Properties::defineProperty(std::string_view, + std::string_view, + std::string_view); +template void Properties::defineProperty(std::string_view, + std::string_view, + std::string_view); +template void Properties::defineProperty(std::string_view, + std::string_view, + std::string_view); +template void Properties::defineProperty(std::string_view, + std::string_view, + std::string_view); void Properties::setProperty(const void *object, diff --git a/search/Property.i b/search/Property.i index c282f5fb..6d12eef7 100644 --- a/search/Property.i +++ b/search/Property.i @@ -149,6 +149,26 @@ define_property_cmd(const char *object_type, properties.defineProperty(object_type, property, type); else if (object_type_view == "mode") properties.defineProperty(object_type, property, type); + else if (object_type_view == "library") + properties.defineProperty(object_type, property, type); + else if (object_type_view == "liberty_library") + properties.defineProperty(object_type, property, type); + else if (object_type_view == "cell") + properties.defineProperty(object_type, property, type); + else if (object_type_view == "liberty_cell") + properties.defineProperty(object_type, property, type); + else if (object_type_view == "port") + properties.defineProperty(object_type, property, type); + else if (object_type_view == "liberty_port") + properties.defineProperty(object_type, property, type); + else if (object_type_view == "instance") + properties.defineProperty(object_type, property, type); + else if (object_type_view == "pin") + properties.defineProperty(object_type, property, type); + else if (object_type_view == "net") + properties.defineProperty(object_type, property, type); + else if (object_type_view == "clock") + properties.defineProperty(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); diff --git a/spice/WritePathSpice.cc b/spice/WritePathSpice.cc index 1b90af87..7a8973dc 100644 --- a/spice/WritePathSpice.cc +++ b/spice/WritePathSpice.cc @@ -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_); diff --git a/spice/WriteSpice.cc b/spice/WriteSpice.cc index 6d3f9395..e04d8967 100644 --- a/spice/WriteSpice.cc +++ b/spice/WriteSpice.cc @@ -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; diff --git a/spice/WriteSpice.hh b/spice/WriteSpice.hh index a1fd64a3..5cfb5d6b 100644 --- a/spice/WriteSpice.hh +++ b/spice/WriteSpice.hh @@ -43,7 +43,8 @@ namespace sta { using ParasiticNodeMap = std::map; using CellSpicePortNames = std::map>; -using LibertyPortLogicValues = std::map; +// Use port name so lookup works across scenes. +using PortLogicValues = std::map; // 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_; diff --git a/tcl/Property.tcl b/tcl/Property.tcl index 4df5b196..b0d30bd1 100644 --- a/tcl/Property.tcl +++ b/tcl/Property.tcl @@ -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 {} diff --git a/test/prima_singular.ok b/test/prima_singular.ok new file mode 100644 index 00000000..cbdab0c9 --- /dev/null +++ b/test/prima_singular.ok @@ -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) + + diff --git a/test/prima_singular.spef b/test/prima_singular.spef new file mode 100644 index 00000000..1b65a320 --- /dev/null +++ b/test/prima_singular.spef @@ -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 is degenerate two ways: +// - z:2 z:3 have ground cap but NO resistor (floating islands) +// - u:Y -- z: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 + diff --git a/test/prima_singular.tcl b/test/prima_singular.tcl new file mode 100644 index 00000000..d6f2ef8c --- /dev/null +++ b/test/prima_singular.tcl @@ -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 diff --git a/test/prima_singular.v b/test/prima_singular.v new file mode 100644 index 00000000..1073a84d --- /dev/null +++ b/test/prima_singular.v @@ -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 diff --git a/test/regression_vars.tcl b/test/regression_vars.tcl index fcb2c291..e9976ce6 100644 --- a/test/regression_vars.tcl +++ b/test/regression_vars.tcl @@ -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] diff --git a/test/user_properties.ok b/test/user_properties.ok new file mode 100644 index 00000000..b7645df2 --- /dev/null +++ b/test/user_properties.ok @@ -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. diff --git a/test/user_properties.tcl b/test/user_properties.tcl new file mode 100644 index 00000000..fb917c09 --- /dev/null +++ b/test/user_properties.tcl @@ -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 +} diff --git a/test/vcd_begin_end_time.ok b/test/vcd_begin_end_time.ok new file mode 100644 index 00000000..f6d6667f --- /dev/null +++ b/test/vcd_begin_end_time.ok @@ -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 + diff --git a/test/vcd_begin_end_time.tcl b/test/vcd_begin_end_time.tcl new file mode 100644 index 00000000..974f4be9 --- /dev/null +++ b/test/vcd_begin_end_time.tcl @@ -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 diff --git a/test/vcd_begin_end_time.v b/test/vcd_begin_end_time.v new file mode 100644 index 00000000..089d6545 --- /dev/null +++ b/test/vcd_begin_end_time.v @@ -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 diff --git a/test/vcd_begin_end_time.vcd b/test/vcd_begin_end_time.vcd new file mode 100644 index 00000000..0518ecc3 --- /dev/null +++ b/test/vcd_begin_end_time.vcd @@ -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 diff --git a/test/write_path_spice_arc_sense.cells.spice b/test/write_path_spice_arc_sense.cells.spice new file mode 100644 index 00000000..a4c832ae --- /dev/null +++ b/test/write_path_spice_arc_sense.cells.spice @@ -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 diff --git a/test/write_path_spice_arc_sense.lib.gz b/test/write_path_spice_arc_sense.lib.gz new file mode 100644 index 00000000..b1c64428 Binary files /dev/null and b/test/write_path_spice_arc_sense.lib.gz differ diff --git a/test/write_path_spice_arc_sense.models.spice b/test/write_path_spice_arc_sense.models.spice new file mode 100644 index 00000000..655d398d --- /dev/null +++ b/test/write_path_spice_arc_sense.models.spice @@ -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. diff --git a/test/write_path_spice_arc_sense.ok b/test/write_path_spice_arc_sense.ok new file mode 100644 index 00000000..dbbe1e7d --- /dev/null +++ b/test/write_path_spice_arc_sense.ok @@ -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 diff --git a/test/write_path_spice_arc_sense.tcl b/test/write_path_spice_arc_sense.tcl new file mode 100644 index 00000000..f3ea8d96 --- /dev/null +++ b/test/write_path_spice_arc_sense.tcl @@ -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 diff --git a/test/write_path_spice_arc_sense.v b/test/write_path_spice_arc_sense.v new file mode 100644 index 00000000..7f02204f --- /dev/null +++ b/test/write_path_spice_arc_sense.v @@ -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