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