From ef0b69091f0e1394fe9710962c6d747c80d41812 Mon Sep 17 00:00:00 2001 From: Deepashree Sengupta Date: Mon, 27 Jul 2026 15:48:57 +0000 Subject: [PATCH 1/9] PrimaDelayCalc: handle degenerate parasitic networks (fixes STA-1752 "G matrix is singular") (#476) * support for filter in get_scene/mode Signed-off-by: dsengupta0628 * fix singular G matrix issue for degenerate nets Signed-off-by: dsengupta0628 * fix the mistake on existing regression- was accidentally modified Signed-off-by: dsengupta0628 * make changes accounting for future SI support and address reviews Signed-off-by: dsengupta0628 * simplify comment Signed-off-by: dsengupta0628 * address feedbacks Signed-off-by: dsengupta0628 --------- Signed-off-by: dsengupta0628 --- dcalc/PrimaDelayCalc.cc | 94 ++++++++++++++++++++++++++++++---------- dcalc/PrimaDelayCalc.hh | 3 ++ test/prima_singular.ok | 28 ++++++++++++ test/prima_singular.spef | 52 ++++++++++++++++++++++ test/prima_singular.tcl | 29 +++++++++++++ test/prima_singular.v | 11 +++++ test/regression_vars.tcl | 1 + 7 files changed, 195 insertions(+), 23 deletions(-) create mode 100644 test/prima_singular.ok create mode 100644 test/prima_singular.spef create mode 100644 test/prima_singular.tcl create mode 100644 test/prima_singular.v diff --git a/dcalc/PrimaDelayCalc.cc b/dcalc/PrimaDelayCalc.cc index dabe7ae1..cc931ed1 100644 --- a/dcalc/PrimaDelayCalc.cc +++ b/dcalc/PrimaDelayCalc.cc @@ -467,35 +467,79 @@ PrimaDelayCalc::findNodeCount() pin_node_map_.clear(); node_index_map_.clear(); - 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. + ParasiticNodeResistorMap resistor_map = + parasitics_->parasiticNodeResistorMap(parasitic_network_); + 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_network_, drvr_pin); + if (drvr_node && !parasitics_->isExternal(drvr_node) + && !node_index_map_.contains(drvr_node)) + placeNode(drvr_node, node_capacitances_.size(), queue); + } + 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.0; + placeNode(next_node, shorted ? node_index : node_capacitances_.size(), + queue); + } } - double cap = parasitics_->nodeGndCap(node) + pinCapacitance(node); - node_capacitances_.push_back(cap); } } + // Lump each coupling capacitor to ground at its internal (non-external) + // nodes that made it into the network. 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; + auto itr = node_index_map_.find(node1); + if (itr != node_index_map_.end()) + node_capacitances_[itr->second] += cap; } ParasiticNode *node2 = parasitics_->node2(capacitor); if (node2 && !parasitics_->isExternal(node2)) { - size_t node_idx = node_index_map_[node2]; - node_capacitances_[node_idx] += cap; + auto itr = node_index_map_.find(node2); + if (itr != node_index_map_.end()) + node_capacitances_[itr->second] += cap; } } - node_count_ = node_index_map_.size(); + node_count_ = node_capacitances_.size(); +} + +// Add node to the conductance system at index (shared by drivers and by the +// resistor walk); a merged short reuses its near node's index. Accumulates the +// node's ground capacitance and queues it for the walk. +void +PrimaDelayCalc::placeNode(ParasiticNode *node, + size_t index, + std::vector &queue) +{ + node_index_map_[node] = index; + if (index == node_capacitances_.size()) + node_capacitances_.push_back(0.0); + node_capacitances_[index] += + parasitics_->nodeGndCap(node) + pinCapacitance(node); + const Pin *pin = parasitics_->pin(node); + if (pin) { + pin_node_map_[pin] = index; + debugPrint(debug_, "ccs_dcalc", 1, "pin {} node {}", + network_->pathName(pin), index); + } + queue.push_back(node); } float @@ -568,13 +612,17 @@ PrimaDelayCalc::stampEqns() 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); + auto itr1 = node_index_map_.find(parasitics_->node1(resistor)); + auto itr2 = node_index_map_.find(parasitics_->node2(resistor)); + // Skip a resistor with a node left out of the network. + if (itr1 == node_index_map_.end() || itr2 == node_index_map_.end()) + continue; + size_t node_idx1 = itr1->second; + size_t node_idx2 = itr2->second; + 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; } diff --git a/dcalc/PrimaDelayCalc.hh b/dcalc/PrimaDelayCalc.hh index 4d5b1346..5ef00ae0 100644 --- a/dcalc/PrimaDelayCalc.hh +++ b/dcalc/PrimaDelayCalc.hh @@ -132,6 +132,9 @@ protected: void initSim(); void findLoads(); void findNodeCount(); + void placeNode(ParasiticNode *node, + size_t index, + std::vector &queue); void setOrder(); void initCeffIdrvr(); void setXinit(); 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..df3640ec 100644 --- a/test/regression_vars.tcl +++ b/test/regression_vars.tcl @@ -161,6 +161,7 @@ record_public_tests { path_group_names power_json prima3 + prima_singular read_saif_null_instance report_checks_sorted report_checks_src_attr From f476e269b94c932eb5d4f82189d075fed15b9686 Mon Sep 17 00:00:00 2001 From: Deepashree Sengupta Date: Mon, 27 Jul 2026 15:50:40 +0000 Subject: [PATCH 2/9] Extend user-defined properties to all basic object types (#479) * support for filter in get_scene/mode Signed-off-by: dsengupta0628 * extend user defined property to all 10 objects Signed-off-by: dsengupta0628 * address reviews Signed-off-by: dsengupta0628 --------- Signed-off-by: dsengupta0628 --- include/sta/Property.hh | 4 +++ search/Property.cc | 57 ++++++++++++++++++++++++++++++++++++++++ search/Property.i | 40 ++++++++++++++++++++++++++++ tcl/Property.tcl | 2 +- test/regression_vars.tcl | 1 + test/user_properties.ok | 17 ++++++++++++ test/user_properties.tcl | 44 +++++++++++++++++++++++++++++++ 7 files changed, 164 insertions(+), 1 deletion(-) create mode 100644 test/user_properties.ok create mode 100644 test/user_properties.tcl 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/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/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/regression_vars.tcl b/test/regression_vars.tcl index df3640ec..4598051d 100644 --- a/test/regression_vars.tcl +++ b/test/regression_vars.tcl @@ -168,6 +168,7 @@ record_public_tests { report_json1 report_json2 suppress_msg + user_properties verilog_attribute verilog_well_supplies verilog_specify 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 +} From 87ce5680dfa92aa98f07b587df5deb032f6593af Mon Sep 17 00:00:00 2001 From: Brian Degnan <12240229+bpdegnan@users.noreply.github.com> Date: Mon, 27 Jul 2026 13:23:15 -0400 Subject: [PATCH 3/9] write_path_spice: match side input values to the path arc's transitions (#475) * write_path_spice: match side input values to the path arc's transitions gatePortValues() chose side input values from the first CUDD cube of the Boolean difference d(f)/d(input), which sensitizes the gate but ignores the transition directions of the arc the path used: - For non-unate gates whose Boolean difference is a tautology (xor2, xnor2) every side variable came back don't-care, and the unknown value fell through to tie-low in writeSubcktInstVoltSrcs() -- wrong whenever the path used the when-condition requiring the side high. - For mux select arcs the cube was an arbitrary data assignment, unrelated to the output edge the path reported. Either way the written deck's gate drives the opposite direction from the reported path: the simulated chain switches with inverted polarity from that gate onward, edge-qualified arrival measurements fail, and the deck sums delays from the wrong rise/fall tables. Constrain the side input condition to the cofactor pair matching this arc -- f1 & !f0 when the input and driver edges agree (non-inverting), f0 & !f1 when they differ (inverting) -- threading the gate input RiseFall from the path stage into gatePortValues(). Also release the CUDD nodes that were previously leaked (the old code Cudd_Ref'd the Boolean difference after the generator was freed and never deref'd it). Fixes #474 Co-Authored-By: Claude Fable 5 * test: write_path_spice arc-sense regression for #474, and outlined in #475 --------- Co-authored-by: Brian Degnan Co-authored-by: Claude Fable 5 --- spice/WritePathSpice.cc | 4 +- spice/WriteSpice.cc | 71 ++++++++++++------- spice/WriteSpice.hh | 3 + test/regression_vars.tcl | 1 + test/write_path_spice_arc_sense.cells.spice | 40 +++++++++++ test/write_path_spice_arc_sense.lib.gz | Bin 0 -> 6793 bytes test/write_path_spice_arc_sense.models.spice | 6 ++ test/write_path_spice_arc_sense.ok | 51 +++++++++++++ test/write_path_spice_arc_sense.tcl | 20 ++++++ test/write_path_spice_arc_sense.v | 7 ++ 10 files changed, 177 insertions(+), 26 deletions(-) create mode 100644 test/write_path_spice_arc_sense.cells.spice create mode 100644 test/write_path_spice_arc_sense.lib.gz create mode 100644 test/write_path_spice_arc_sense.models.spice create mode 100644 test/write_path_spice_arc_sense.ok create mode 100644 test/write_path_spice_arc_sense.tcl create mode 100644 test/write_path_spice_arc_sense.v diff --git a/spice/WritePathSpice.cc b/spice/WritePathSpice.cc index 1b90af87..d98e2fd6 100644 --- a/spice/WritePathSpice.cc +++ b/spice/WritePathSpice.cc @@ -514,11 +514,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; 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..c08039b6 100644 --- a/spice/WriteSpice.cc +++ b/spice/WriteSpice.cc @@ -756,6 +756,7 @@ 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. @@ -771,7 +772,7 @@ 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(inst, drvr_func, input_port, input_rf, drvr_rf, port_values); } } @@ -779,41 +780,61 @@ void WriteSpice::gatePortValues(const Instance *, const FuncExpr *expr, const LibertyPort *input_port, + const RiseFall *input_rf, + const RiseFall *drvr_rf, // Return values. LibertyPortLogicValues &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] = 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(); } diff --git a/spice/WriteSpice.hh b/spice/WriteSpice.hh index a1fd64a3..b44e6c52 100644 --- a/spice/WriteSpice.hh +++ b/spice/WriteSpice.hh @@ -139,6 +139,7 @@ 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. @@ -154,6 +155,8 @@ protected: void gatePortValues(const Instance *inst, const FuncExpr *expr, const LibertyPort *input_port, + const RiseFall *input_rf, + const RiseFall *drvr_rf, // Return values. LibertyPortLogicValues &port_values); void writeSubcktInstLoads(const Pin *drvr_pin, diff --git a/test/regression_vars.tcl b/test/regression_vars.tcl index 4598051d..c9df3429 100644 --- a/test/regression_vars.tcl +++ b/test/regression_vars.tcl @@ -175,6 +175,7 @@ record_public_tests { 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/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 0000000000000000000000000000000000000000..b1c64428bddae81a7aec6a935838c91716977ec5 GIT binary patch literal 6793 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 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 From f29b6a43c618b3e7e02a66509973e9a4024ed165 Mon Sep 17 00:00:00 2001 From: James Cherry Date: Mon, 27 Jul 2026 16:02:29 -0700 Subject: [PATCH 4/9] prima merge noise branch changes and rm iterator uses Signed-off-by: James Cherry --- dcalc/PrimaDelayCalc.cc | 263 +++++++++++++++++++++++++--------------- dcalc/PrimaDelayCalc.hh | 16 ++- 2 files changed, 174 insertions(+), 105 deletions(-) diff --git a/dcalc/PrimaDelayCalc.cc b/dcalc/PrimaDelayCalc.cc index cc931ed1..db30ef1f 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) { @@ -460,105 +467,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; // 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. - ParasiticNodeResistorMap resistor_map = - parasitics_->parasiticNodeResistorMap(parasitic_network_); - 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_network_, drvr_pin); - if (drvr_node && !parasitics_->isExternal(drvr_node) - && !node_index_map_.contains(drvr_node)) - placeNode(drvr_node, node_capacitances_.size(), queue); - } - 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.0; - placeNode(next_node, shorted ? node_index : node_capacitances_.size(), - queue); + 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); } } + 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); } } - - // Lump each coupling capacitor to ground at its internal (non-external) - // nodes that made it into the network. - 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)) { - auto itr = node_index_map_.find(node1); - if (itr != node_index_map_.end()) - node_capacitances_[itr->second] += cap; - } - ParasiticNode *node2 = parasitics_->node2(capacitor); - if (node2 && !parasitics_->isExternal(node2)) { - auto itr = node_index_map_.find(node2); - if (itr != node_index_map_.end()) - node_capacitances_[itr->second] += cap; - } - } - node_count_ = node_capacitances_.size(); } -// Add node to the conductance system at index (shared by drivers and by the -// resistor walk); a merged short reuses its near node's index. Accumulates the -// node's ground capacitance and queues it for the walk. +// 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, - std::vector &queue) + size_t index) { node_index_map_[node] = index; - if (index == node_capacitances_.size()) - node_capacitances_.push_back(0.0); - node_capacitances_[index] += - parasitics_->nodeGndCap(node) + pinCapacitance(node); const Pin *pin = parasitics_->pin(node); if (pin) { pin_node_map_[pin] = index; debugPrint(debug_, "ccs_dcalc", 1, "pin {} node {}", network_->pathName(pin), index); } - queue.push_back(node); -} - -float -PrimaDelayCalc::pinCapacitance(ParasiticNode *node) -{ - 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_); - } - return pin_cap; } void @@ -600,6 +570,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() { @@ -607,35 +587,23 @@ 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]); - - resistance_sum_ = 0.0; - for (ParasiticResistor *resistor : parasitics_->resistors(parasitic_network_)) { - auto itr1 = node_index_map_.find(parasitics_->node1(resistor)); - auto itr2 = node_index_map_.find(parasitics_->node2(resistor)); - // Skip a resistor with a node left out of the network. - if (itr1 == node_index_map_.end() || itr2 == node_index_map_.end()) - continue; - size_t node_idx1 = itr1->second; - size_t node_idx2 = itr2->second; - 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; - } + 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; + ParasiticSet visited_parasitics; for (size_t drvr_idx = 0; drvr_idx < drvr_count_; drvr_idx++) { const ArcDcalcArg &dcalc_arg = (*dcalc_args_)[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; + 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); + } } if (debug_->check("ccs_dcalc", 3)) { @@ -645,6 +613,101 @@ PrimaDelayCalc::stampEqns() } } +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); + } + } + } + + // 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, diff --git a/dcalc/PrimaDelayCalc.hh b/dcalc/PrimaDelayCalc.hh index 5ef00ae0..dbae2d10 100644 --- a/dcalc/PrimaDelayCalc.hh +++ b/dcalc/PrimaDelayCalc.hh @@ -133,12 +133,21 @@ protected: void findLoads(); void findNodeCount(); void placeNode(ParasiticNode *node, - size_t index, - std::vector &queue); + 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, @@ -149,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); @@ -197,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 From 7fdc304e1223a02ea30db4da382e9ce7c495a9fc Mon Sep 17 00:00:00 2001 From: Akash Levy Date: Wed, 29 Jul 2026 08:44:51 -0700 Subject: [PATCH 5/9] FEATURE: Add `read_vcd -begin_time`/`-end_time` activity windowing (#466) * Add read_vcd -begin_time/-end_time activity windowing. Limit VCD transition and duty counting to an optional time window so activity annotation can ignore regions outside the interval of interest. Co-authored-by: Cursor * Format VcdCount::setFilter parameters one per line. Co-authored-by: Cursor * Address VCD begin/end review: VcdTime, sentinel, rename. Use VcdTime and vcd_null_time instead of int64_t/-1, rename VcdCount filter bounds to begin/end_time, rename the regression to vcd_begin_end_time, and document read_vcd -begin_time/-end_time in ChangeLog.txt. Co-authored-by: Cursor --------- Co-authored-by: Cursor Co-authored-by: James Cherry <34749589+jjcherry56@users.noreply.github.com> --- doc/ChangeLog.txt | 9 +++++ power/Power.i | 20 ++++++++- power/Power.tcl | 18 +++++++-- power/VcdParse.cc | 25 ++++++++++-- power/VcdParse.hh | 12 +++++- power/VcdReader.cc | 81 +++++++++++++++++++++++++++++++------ power/VcdReader.hh | 4 ++ test/regression_vars.tcl | 1 + test/vcd_begin_end_time.ok | 60 +++++++++++++++++++++++++++ test/vcd_begin_end_time.tcl | 70 ++++++++++++++++++++++++++++++++ test/vcd_begin_end_time.v | 14 +++++++ test/vcd_begin_end_time.vcd | 26 ++++++++++++ 12 files changed, 317 insertions(+), 23 deletions(-) create mode 100644 test/vcd_begin_end_time.ok create mode 100644 test/vcd_begin_end_time.tcl create mode 100644 test/vcd_begin_end_time.v create mode 100644 test/vcd_begin_end_time.vcd 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/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..360a13d7 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/test/regression_vars.tcl b/test/regression_vars.tcl index c9df3429..e9976ce6 100644 --- a/test/regression_vars.tcl +++ b/test/regression_vars.tcl @@ -169,6 +169,7 @@ record_public_tests { report_json2 suppress_msg user_properties + vcd_begin_end_time verilog_attribute verilog_well_supplies verilog_specify 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 From c00b847b209f993f3b219889fa9f3fa0ae127f0a Mon Sep 17 00:00:00 2001 From: James Cherry Date: Tue, 28 Jul 2026 19:44:13 -0700 Subject: [PATCH 6/9] prima mcmm bugs Signed-off-by: James Cherry --- dcalc/ArcDcalcWaveforms.cc | 5 ++-- dcalc/ArcDelayCalc.cc | 9 ++++++- dcalc/DelayCalcBase.cc | 1 + dcalc/PrimaDelayCalc.cc | 30 ++++++++++++---------- include/sta/ArcDelayCalc.hh | 4 ++- spice/WritePathSpice.cc | 30 +++++++++++++++++++--- spice/WriteSpice.cc | 51 ++++++++++++++++++++----------------- spice/WriteSpice.hh | 17 +++++++------ 8 files changed, 95 insertions(+), 52 deletions(-) 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 db30ef1f..a5ea608c 100644 --- a/dcalc/PrimaDelayCalc.cc +++ b/dcalc/PrimaDelayCalc.cc @@ -183,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]; @@ -256,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_); } @@ -362,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); @@ -402,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()), @@ -427,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; } @@ -443,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 @@ -526,7 +529,7 @@ PrimaDelayCalc::placeNode(ParasiticNode *node, const Pin *pin = parasitics_->pin(node); if (pin) { pin_node_map_[pin] = index; - debugPrint(debug_, "ccs_dcalc", 1, "pin {} node {}", + debugPrint(debug_, "prima", 1, "pin {} node {}", network_->pathName(pin), index); } } @@ -606,7 +609,7 @@ PrimaDelayCalc::stampEqns() } } - if (debug_->check("ccs_dcalc", 3)) { + if (debug_->check("prima", 3)) { reportMatrix("G", G_); reportMatrix("C", C_); reportMatrix("B", B_); @@ -834,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; } @@ -882,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)); @@ -895,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), @@ -987,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_); @@ -1050,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/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/spice/WritePathSpice.cc b/spice/WritePathSpice.cc index d98e2fd6..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; @@ -518,7 +542,7 @@ WritePathSpice::writeGateStage(Stage 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, input_rf, drvr_rf, gate_edge, port_values, is_clked); diff --git a/spice/WriteSpice.cc b/spice/WriteSpice.cc index c08039b6..e3aafb53 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); } @@ -760,7 +762,7 @@ WriteSpice::gatePortValues(const Pin *input_pin, const RiseFall *drvr_rf, const Edge *gate_edge, // Return values. - LibertyPortLogicValues &port_values, + PortLogicValues &port_values, bool &is_clked) { is_clked = false; @@ -783,7 +785,7 @@ WriteSpice::gatePortValues(const Instance *, 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); @@ -827,7 +829,7 @@ WriteSpice::gatePortValues(const Instance *, port_value = LogicValue::unknown; break; } - port_values[port] = port_value; + port_values[port->name()] = port_value; } } } @@ -844,7 +846,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; @@ -870,7 +872,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 @@ -878,18 +880,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: @@ -954,7 +957,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() @@ -978,9 +981,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, @@ -996,8 +999,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 b44e6c52..b1349d15 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, @@ -143,14 +144,14 @@ protected: 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, @@ -158,7 +159,7 @@ protected: 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, @@ -179,7 +180,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_; From b548398c6cef8c91542685aa66646f243f9f1fc0 Mon Sep 17 00:00:00 2001 From: James Cherry Date: Wed, 29 Jul 2026 08:57:06 -0700 Subject: [PATCH 7/9] WriteSpice::gatePortValues unused rm Instance arg Signed-off-by: James Cherry --- spice/WriteSpice.cc | 6 ++---- spice/WriteSpice.hh | 3 +-- 2 files changed, 3 insertions(+), 6 deletions(-) diff --git a/spice/WriteSpice.cc b/spice/WriteSpice.cc index e3aafb53..e04d8967 100644 --- a/spice/WriteSpice.cc +++ b/spice/WriteSpice.cc @@ -766,7 +766,6 @@ WriteSpice::gatePortValues(const Pin *input_pin, 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(); @@ -774,13 +773,12 @@ 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, input_rf, drvr_rf, 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, diff --git a/spice/WriteSpice.hh b/spice/WriteSpice.hh index b1349d15..5cfb5d6b 100644 --- a/spice/WriteSpice.hh +++ b/spice/WriteSpice.hh @@ -153,8 +153,7 @@ protected: // Return 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, From 29b78841e5c57b5ad7e623bf08adc7b052280093 Mon Sep 17 00:00:00 2001 From: dsengupta0628 Date: Thu, 30 Jul 2026 14:46:28 +0000 Subject: [PATCH 8/9] add missing new file for succesful OpenROAd bazel build Signed-off-by: dsengupta0628 --- BUILD.bazel | 1 + 1 file changed, 1 insertion(+) 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", From 0e887a7003ef25b33455ae84069b88a9bf4d333e Mon Sep 17 00:00:00 2001 From: dsengupta0628 Date: Fri, 31 Jul 2026 01:30:46 +0000 Subject: [PATCH 9/9] use relative name in swig correctly Signed-off-by: dsengupta0628 --- power/Power.tcl | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/power/Power.tcl b/power/Power.tcl index 360a13d7..e9ae0084 100644 --- a/power/Power.tcl +++ b/power/Power.tcl @@ -239,7 +239,7 @@ proc read_power_activities { args } { } sta_warn 305 "read_power_activities is deprecated. Use read_vcd." read_vcd_file $filename $scope [cmd_mode_name] \ - $sta::vcd_null_time $sta::vcd_null_time + $::sta::vcd_null_time $::sta::vcd_null_time } ################################################################ @@ -261,11 +261,11 @@ proc read_vcd { args } { if { [info exists keys(-mode)] } { set mode_name $keys(-mode) } - set begin_time $sta::vcd_null_time + 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 + set end_time $::sta::vcd_null_time if { [info exists keys(-end_time)] } { set end_time $keys(-end_time) }