From 3cdce8bef7e5b9ce0c6f12751180bd65aa9e96f5 Mon Sep 17 00:00:00 2001 From: Meisam Bahadori Date: Wed, 22 Jul 2026 14:46:57 +0200 Subject: [PATCH] pa-62 --- src/include/ngspice/trcvdefs.h | 13 ++++ src/spicelib/analysis/cktdisto.c | 16 +++++ src/spicelib/analysis/dctrcurv.c | 117 +++++++++++++++++++++++++++++++ 3 files changed, 146 insertions(+) diff --git a/src/include/ngspice/trcvdefs.h b/src/include/ngspice/trcvdefs.h index 4e5ae796a..7f448f5a2 100644 --- a/src/include/ngspice/trcvdefs.h +++ b/src/include/ngspice/trcvdefs.h @@ -25,6 +25,14 @@ Modified: 1999 Paolo Nenzi #define TEMP_CODE 1023 #endif +/* Enhancement-62: sweep type code for a generic instance-parameter sweep + (`.dc @inst[param] start stop step`), resolved through the device's own + DEVparam/DEVask interface -- works for any device type that exposes a + settable real instance parameter, including Verilog-A (OSDI) devices. */ +#ifndef PARAM_CODE +#define PARAM_CODE 1024 +#endif + typedef struct { int JOBtype; JOB *JOBnextJob; @@ -38,6 +46,11 @@ typedef struct { IFuid TRCVvName[TRCVNESTLEVEL]; /* source being varied */ GENinstance *TRCVvElt[TRCVNESTLEVEL]; /* pointer to source */ int TRCVvType[TRCVNESTLEVEL]; /* type of element being varied */ + int TRCVvParmId[TRCVNESTLEVEL]; /* Enhancement-62: IFparm id for a + * PARAM_CODE (@inst[param]) sweep */ + double TRCVvNow[TRCVNESTLEVEL]; /* Enhancement-62: current value of a + * PARAM_CODE sweep (devices have no + * generic readback field to consult) */ int TRCVset[TRCVNESTLEVEL]; /* flag to indicate this nest level used */ int TRCVnestLevel; /* number of levels of nesting called for */ int TRCVnestState; /* iteration state during pause */ diff --git a/src/spicelib/analysis/cktdisto.c b/src/spicelib/analysis/cktdisto.c index 252554aa2..ee69251d3 100644 --- a/src/spicelib/analysis/cktdisto.c +++ b/src/spicelib/analysis/cktdisto.c @@ -33,6 +33,22 @@ CKTdisto (CKTcircuit *ckt, int mode) case D_SETUP: + /* Enhancement-62: distortion analysis needs per-device Taylor + coefficients (DEVdisto); Verilog-A (OSDI) devices only expose + first derivatives through the OSDI ABI, so their + nonlinearities are invisible to .disto -- the analysis would + silently report ZERO distortion for them. Warn loudly instead + of returning quietly-wrong numbers. */ + for (i=0;iDEVdisto && ckt->CKThead[i] + && DEVices[i]->DEVpublic.registry_entry ) { + fprintf(stderr, + "Warning: Verilog-A (OSDI) device type '%s' has no distortion model;\n" + " .disto results will NOT include its nonlinearities.\n", + DEVices[i]->DEVpublic.name); + } + } + for (i=0;iDEVdisto && ckt->CKThead[i] ) { error = DEVices[i]->DEVdisto (mode, ckt->CKThead[i], ckt); diff --git a/src/spicelib/analysis/dctrcurv.c b/src/spicelib/analysis/dctrcurv.c index dd312a637..cf8d8bb6e 100644 --- a/src/spicelib/analysis/dctrcurv.c +++ b/src/spicelib/analysis/dctrcurv.c @@ -29,6 +29,75 @@ Modified: 1999 Paolo Nenzi static double actval, actdiff; #endif +/* Enhancement-62: resolve a `@inst[param]` sweep variable to its instance, + device type, and (settable, real-valued) instance-parameter id, through + the generic DEVparam/DEVask tables. Returns OK on success. Instance + lookup walks every device type comparing names case-insensitively (the + sweep name is a raw token, not an interned IFuid, so the DEVnameHash + cannot be used). */ +static int +DCTfindInstParam(CKTcircuit *ckt, const char *name, GENinstance **instOut, + int *typeOut, int *parmOut) +{ + char buf[1024]; + char *lbrack, *rbrack, *parname; + GENmodel *model; + GENinstance *inst; + IFdevice *dev; + int type, k; + + if (!name || name[0] != '@' || strlen(name) >= sizeof(buf)) + return E_NODEV; + strcpy(buf, name + 1); + lbrack = strchr(buf, '['); + rbrack = lbrack ? strchr(lbrack, ']') : NULL; + if (!lbrack || !rbrack || rbrack <= lbrack + 1 || lbrack == buf) + return E_NODEV; + *lbrack = '\0'; + *rbrack = '\0'; + parname = lbrack + 1; + + for (type = 0; type < DEVmaxnum; type++) { + if (!DEVices[type]) + continue; + for (model = ckt->CKThead[type]; model; model = model->GENnextModel) + for (inst = model->GENinstances; inst; inst = inst->GENnextInstance) + if (inst->GENname && cieq(inst->GENname, buf)) { + dev = &DEVices[type]->DEVpublic; + for (k = 0; dev->instanceParms && k < *dev->numInstanceParms; k++) { + IFparm *prm = dev->instanceParms + k; + if ((prm->dataType & IF_SET) + && (prm->dataType & IF_VARTYPES) == IF_REAL + && cieq(prm->keyword, parname)) { + *instOut = inst; + *typeOut = type; + *parmOut = prm->id; + return OK; + } + } + return E_BADPARM; + } + } + return E_NODEV; +} + +/* Enhancement-62: set the swept instance parameter to `val` and refresh the + device (DEVtemperature re-runs per-model/per-instance setup -- for OSDI + devices that is exactly the parameter-change path `alter` + a fresh + analysis would take). */ +static void +DCTsetInstParam(CKTcircuit *ckt, TRCV *job, int i, double val) +{ + IFvalue v; + int type = job->TRCVvElt[i]->GENmodPtr->GENmodType; + + v.rValue = val; + DEVices[type]->DEVparam(job->TRCVvParmId[i], &v, job->TRCVvElt[i], NULL); + job->TRCVvNow[i] = val; + if (DEVices[type]->DEVtemperature) + DEVices[type]->DEVtemperature(ckt->CKThead[type], ckt); +} + int DCtrCurv(CKTcircuit *ckt, int restart) @@ -150,6 +219,27 @@ DCtrCurv(CKTcircuit *ckt, int restart) goto found; } + /* Enhancement-62: `.dc @inst[param] start stop step` -- sweep any + settable real instance parameter of any device (incl. OSDI). */ + if (job->TRCVvName[i] && job->TRCVvName[i][0] == '@') { + GENinstance *pinst; + int ptype, pid; + if (DCTfindInstParam(ckt, job->TRCVvName[i], &pinst, &ptype, &pid) == OK) { + IFvalue old_v; + job->TRCVvElt[i] = pinst; + job->TRCVvType[i] = PARAM_CODE; + job->TRCVvParmId[i] = pid; + if (DEVices[ptype]->DEVask + && DEVices[ptype]->DEVask(ckt, pinst, pid, &old_v, NULL) == OK) + job->TRCVvSave[i] = old_v.rValue; + else + job->TRCVvSave[i] = job->TRCVvStart[i]; + job->TRCVgSave[i] = 1; + DCTsetInstParam(ckt, job, i, job->TRCVvStart[i]); + goto found; + } + } + SPfrontEnd->IFerrorf (ERR_FATAL, "DC Transfer Function: Voltage source, current source, or " "resistor named \"%s\" is not in the circuit", @@ -185,6 +275,8 @@ DCtrCurv(CKTcircuit *ckt, int restart) SPfrontEnd->IFnewUid (ckt, &varUid, NULL, "temp-sweep", UID_OTHER, NULL); else if (job->TRCVvType[0] == rcode) SPfrontEnd->IFnewUid (ckt, &varUid, NULL, "res-sweep", UID_OTHER, NULL); + else if (job->TRCVvType[0] == PARAM_CODE) + SPfrontEnd->IFnewUid (ckt, &varUid, NULL, "param-sweep", UID_OTHER, NULL); else SPfrontEnd->IFnewUid (ckt, &varUid, NULL, "?-sweep", UID_OTHER, NULL); @@ -261,6 +353,18 @@ DCtrCurv(CKTcircuit *ckt, int restart) break; goto nextstep; } + } else if (job->TRCVvType[i] == PARAM_CODE) { /* @inst[param] sweep */ + if (SGN(job->TRCVvStep[i]) * + (job->TRCVvNow[i] - job->TRCVvStop[i]) > + DBL_EPSILON * 1e+03) + { + i++; + firstTime = 1; + ckt->CKTmode = (ckt->CKTmode & MODEUIC) | MODEDCTRANCURVE | MODEINITJCT; + if (i > job->TRCVnestLevel) + break; + goto nextstep; + } } while (--i >= 0) @@ -279,6 +383,8 @@ DCtrCurv(CKTcircuit *ckt, int restart) job->TRCVvStart[i]; RESupdate_conduct((RESinstance *)(job->TRCVvElt[i]), FALSE); DEVices[rcode]->DEVload(job->TRCVvElt[i]->GENmodPtr, ckt); + } else if (job->TRCVvType[i] == PARAM_CODE) { + DCTsetInstParam(ckt, job, i, job->TRCVvStart[i]); } /* Rotate state vectors. */ @@ -331,6 +437,8 @@ DCtrCurv(CKTcircuit *ckt, int restart) } else if (job->TRCVvType[0] == TEMP_CODE) { g_mif_info.circuit.evt_step = ckt->CKTtemp - CONSTCtoK; + } else if (job->TRCVvType[0] == PARAM_CODE) { + g_mif_info.circuit.evt_step = job->TRCVvNow[0]; } /* if first time through, call EVTop immediately and save event results */ @@ -374,6 +482,8 @@ DCtrCurv(CKTcircuit *ckt, int restart) ckt->CKTtime = ((ISRCinstance *)(job->TRCVvElt[0]))->ISRCdcValue; else if (job->TRCVvType[0] == rcode) ckt->CKTtime = ((RESinstance *)(job->TRCVvElt[0]))->RESresist; + else if (job->TRCVvType[0] == PARAM_CODE) + ckt->CKTtime = job->TRCVvNow[0]; else if (job->TRCVvType[0] == TEMP_CODE) ckt->CKTtime = ckt->CKTtemp - CONSTCtoK; @@ -450,6 +560,9 @@ DCtrCurv(CKTcircuit *ckt, int restart) job->TRCVvStep[i]; RESupdate_conduct((RESinstance *)(job->TRCVvElt[i]), FALSE); DEVices[rcode]->DEVload(job->TRCVvElt[i]->GENmodPtr, ckt); + } else if (job->TRCVvType[i] == PARAM_CODE) { /* @inst[param] */ + DCTsetInstParam(ckt, job, i, + job->TRCVvNow[i] + job->TRCVvStep[i]); } else if (job->TRCVvType[i] == TEMP_CODE) { /* temperature */ ckt->CKTtemp += job->TRCVvStep[i]; @@ -500,6 +613,10 @@ DCtrCurv(CKTcircuit *ckt, int restart) ckt->CKTtemp = job->TRCVvSave[i]; inp_evaluate_temper(ft_curckt); CKTtemp(ckt); + } else if (job->TRCVvType[i] == PARAM_CODE) { + /* value restored; the parameter stays marked "given" (the + generic DEVparam interface has no way to clear that) */ + DCTsetInstParam(ckt, job, i, job->TRCVvSave[i]); } SPfrontEnd->OUTendPlot (plot);