PrimaDelayCalc: handle degenerate parasitic networks (fixes STA-1752 "G matrix is singular") (#476)

* support for filter in get_scene/mode

Signed-off-by: dsengupta0628 <dsengupta@precisioninno.com>

* fix singular G matrix issue for degenerate nets

Signed-off-by: dsengupta0628 <dsengupta@precisioninno.com>

* fix the mistake on existing regression- was accidentally modified

Signed-off-by: dsengupta0628 <dsengupta@precisioninno.com>

* make changes accounting for future SI support and address reviews

Signed-off-by: dsengupta0628 <dsengupta@precisioninno.com>

* simplify comment

Signed-off-by: dsengupta0628 <dsengupta@precisioninno.com>

* address feedbacks

Signed-off-by: dsengupta0628 <dsengupta@precisioninno.com>

---------

Signed-off-by: dsengupta0628 <dsengupta@precisioninno.com>
This commit is contained in:
Deepashree Sengupta 2026-07-27 15:48:57 +00:00 committed by GitHub
parent 99eb809fff
commit ef0b69091f
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
7 changed files with 195 additions and 23 deletions

View File

@ -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<ParasiticNode *> queue;
for (size_t drvr_idx = 0; drvr_idx < drvr_count_; drvr_idx++) {
const Pin *drvr_pin = (*dcalc_args_)[drvr_idx].drvrPin();
ParasiticNode *drvr_node =
parasitics_->findParasiticNode(parasitic_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<ParasiticNode*> &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;
}

View File

@ -132,6 +132,9 @@ protected:
void initSim();
void findLoads();
void findNodeCount();
void placeNode(ParasiticNode *node,
size_t index,
std::vector<ParasiticNode*> &queue);
void setOrder();
void initCeffIdrvr();
void setXinit();

28
test/prima_singular.ok Normal file
View File

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

52
test/prima_singular.spef Normal file
View File

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

29
test/prima_singular.tcl Normal file
View File

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

11
test/prima_singular.v Normal file
View File

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

View File

@ -161,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