Menu

#850 CKTtopologyReduce spurious pruning

v1.0 (example)
open
nobody
None
5
2026-07-13
2026-07-13
snzho
No

CKTtopologyReduce performs spurious pruning because some nodes are mistakenly taken as degree-1 dangling leaves. This happens when a node's only stamped element is a single passive and affects both ASRC and XSPICE elements. Repro netlist is attached.

--- a/src/spicelib/analysis/cktsetup.c
+++ b/src/spicelib/analysis/cktsetup.c
@@ -21,9 +21,16 @@
 /* device headers needed by CKTtopologyReduce() to mark dangling passives */
 #include "../devices/cap/capdefs.h"
 #include "../devices/res/resdefs.h"
+/* asrc (B-source) header: its expression-sensed nodes are not GENnode()

+ * terminals, so CKTtopologyReduce() must count them explicitly (see below) */
+#include "../devices/asrc/asrcdefs.h"

 #ifdef XSPICE
 #include "ngspice/enh.h"
+/* XSPICE code-model header: code-model connections live in the MIF conn[]/

+ * port[] arrays (DEVpublic.terms is 0 for code models), so CKTtopologyReduce()
+ * must count their analog nodes explicitly too (see below) */
+#include "ngspice/mifdefs.h"
 #endif

 #ifdef USE_OMP
@@ -56,7 +63,7 @@
 CKTtopologyReduce(CKTcircuit *ckt)
 {
     int i, t, nterm, maxnode, removed_total = 0, reported = 0;

-    int captype = -1, restype = -1;
+    int captype = -1, restype = -1, asrctype = -1;
     int *degree;
     GENmodel *gmod;
     GENinstance *ginst;
@@ -89,6 +96,8 @@
                 captype = i;
             else if (!strcmp(DEVices[i]->DEVpublic.name, "Resistor"))
                 restype = i;
+            else if (!strcmp(DEVices[i]->DEVpublic.name, "ASRC"))
+                asrctype = i;
         }
         for (gmod = ckt->CKThead[i]; gmod; gmod = gmod->GENnextModel)
             for (ginst = gmod->GENinstances; ginst; ginst = ginst->GENnextInstance) {
@@ -101,6 +110,67 @@
             }
     }


+    /* ASRC (B-source) controlling nodes are sensed through the parse-tree
+     * expression (ASRCvars), not through GENnode() terminals, so the generic
+     * scan above cannot see them.  Count each sensed node as a connection so a
+     * node whose only stamped element is one passive is not mistaken for a
+     * dangling leaf and pruned. Pruning silently forces the sensed voltage to
+     * 0 (e.g. r.x_u1.r2 feeding a sensed op-amp input on the TI TLV9002 model).
+     * VCVS/VCCS controlling nodes are real device terminals, so they are already
+     * counted by the loop above and need no special case here.  ASRCvars entries
+     * for current-controlling variables (IF_INSTANCE) are branch-equation
+     * indices, so bumping their degree is harmless. */
+    if (asrctype >= 0) {
+        ASRCmodel *am;
+        for (am = (ASRCmodel *)ckt->CKThead[asrctype]; am; am = ASRCnextModel(am))
+            for (ASRCinstance *ai = ASRCinstances(am); ai; ai = ASRCnextInstance(ai)) {
+                if (!ai->ASRCtree || !ai->ASRCvars)
+                    continue;
+                for (t = 0; t < ai->ASRCtree->numVars; t++) {
+                    int nd = ai->ASRCvars[t];
+                    if (nd > 0 && nd <= maxnode)
+                        degree[nd]++;
+                }
+            }
+    }
+
+#ifdef XSPICE
+    /* XSPICE code-model connections live in the MIF conn[]/port[] arrays; not in
+     * GENnode() terminals, and DEVpublic.terms is 0 for code models, so the
+     * generic scan above counts none of them. A node wired only to a code-model
+     * analog port plus one passive would look like a degree-1 dangling leaf and
+     * would be wrongly pruned.  Count every analog port node.  Code models are
+     * the device types with DEVpublic.num_conn > 0. Digital/event ports never
+     * set smp_data.pos_node/neg_node (calloc-zeroed), so the range test skips
+     * them. */
+    for (i = 0; i < DEVmaxnum; i++) {
+        if (!DEVices[i] || !ckt->CKThead[i] || DEVices[i]->DEVpublic.num_conn <= 0)
+            continue;
+        for (gmod = ckt->CKThead[i]; gmod; gmod = gmod->GENnextModel)
+            for (ginst = gmod->GENinstances; ginst; ginst = ginst->GENnextInstance) {
+                MIFinstance *mif = (MIFinstance *)ginst;
+                int c, p;
+                for (c = 0; c < mif->num_conn; c++) {
+                    Mif_Conn_Data_t *cd = mif->conn[c];
+                    if (!cd || cd->is_null)
+                        continue;
+                    for (p = 0; p < cd->size; p++) {
+                        Mif_Port_Data_t *pd = cd->port[p];
+                        int nd;
+                        if (!pd || pd->is_null)
+                            continue;
+                        nd = pd->smp_data.pos_node;
+                        if (nd > 0 && nd <= maxnode)
+                            degree[nd]++;
+                        nd = pd->smp_data.neg_node;
+                        if (nd > 0 && nd <= maxnode)
+                            degree[nd]++;
+                    }
+                }
+            }
+    }
+#endif
+
     if (captype < 0 && restype < 0) {
         FREE(degree);
         return;
1 Attachments

Discussion


Log in to post a comment.