From d429f24bb3289bef9c5cfc4eeb1b4910717dd665 Mon Sep 17 00:00:00 2001 From: Justin Fisher Date: Sun, 2 Aug 2026 15:14:51 +0200 Subject: [PATCH] fixed 555-timer-2 convergence issue --- src/include/ngspice/cktdefs.h | 8 ++++++++ src/include/ngspice/optdefs.h | 10 ++++++++++ src/include/ngspice/tskdefs.h | 6 ++++++ src/maths/ni/niconv.c | 13 +++++++++++-- src/osdi/osditrunc.c | 4 ++-- src/spicelib/analysis/cktdojob.c | 2 ++ src/spicelib/analysis/cktsopt.c | 8 ++++++++ src/spicelib/analysis/dctran.c | 17 +++++++++++++++-- 8 files changed, 62 insertions(+), 6 deletions(-) diff --git a/src/include/ngspice/cktdefs.h b/src/include/ngspice/cktdefs.h index 44ad13abf..9c3184458 100644 --- a/src/include/ngspice/cktdefs.h +++ b/src/include/ngspice/cktdefs.h @@ -151,6 +151,14 @@ struct CKTcircuit { /* `.option nostaterestore` opt-out of the dctran failed-attempt * device-state restore (CKTstate0 <- CKTstate1 on timepoint retry). */ int CKTstateRestoreOff; + /* `.option noorderguard` opt-out of the dctran non-uniform-history + * predictor guard (forced CKTorder=1 on >10x dt-history ratio). */ + int CKTorderGuardOff; + /* `.option currentsettle` opt-IN to honoring device convTest + * CKTnoncon reports in NIconvTest (upstream trunk 0638aaa16 + * current-settling enforcement). Default 0 = ngspice-46 + * solution-only convergence semantics. */ + int CKTcurrentSettle; /* Per-iteration count of huge-finite Jacobian entries clipped by * sanitize_jacobian during CKTload. When > 0, the model evaluation * is in a numerical regime (e.g. BSIM-BULK near a singular operating diff --git a/src/include/ngspice/optdefs.h b/src/include/ngspice/optdefs.h index 81e6ce998..9dd04a6aa 100644 --- a/src/include/ngspice/optdefs.h +++ b/src/include/ngspice/optdefs.h @@ -97,6 +97,8 @@ enum { * SPICE3 solution-only behaviour. */ OPT_NOSTATERESTORE, /* `.option nostaterestore` opts out of the dctran * failed-attempt device-state restore. */ + OPT_NOORDERGUARD, /* `.option noorderguard` opts out of the dctran + * non-uniform-history predictor order guard. */ OPT_EQNS, OPT_REORDTIME, OPT_METHOD, @@ -156,6 +158,14 @@ enum { OPT_NODTCLEAR, /* `.option nodtclear` disables the small-dt * CKTnoncon clear in niiter.c. See CKTdtClearOff * comment in cktdefs.h. */ + OPT_CURRENTSETTLE, /* `.option currentsettle` opts IN to NIconvTest + * honoring device convTest CKTnoncon reports. + * NOTE: appended at the enum TAIL deliberately — + * the region between OPT_GMIN=1 and + * OPT_SYNCTIME=58 is full; inserting there + * collides auto-numbered members with the + * OPT_SYNCTIME anchor (duplicate-case build + * error in cktacct.c). */ }; #ifdef XSPICE diff --git a/src/include/ngspice/tskdefs.h b/src/include/ngspice/tskdefs.h index e5b3e0644..fa67f95f6 100644 --- a/src/include/ngspice/tskdefs.h +++ b/src/include/ngspice/tskdefs.h @@ -75,6 +75,12 @@ struct TSKtask { unsigned int TSKnoStateRestore:1; /* `.option nostaterestore` disables * the dctran failed-attempt state * restore (CKTstate0 <- CKTstate1) */ + unsigned int TSKnoOrderGuard:1; /* `.option noorderguard` disables + * the dctran non-uniform-history + * predictor order guard */ + unsigned int TSKcurrentSettle:1; /* `.option currentsettle` enables + * device-current settling + * enforcement in NIconvTest */ unsigned int TSKtryToCompact:1; /* flag for LTRA lines */ unsigned int TSKbadMos3:1; /* flag for MOS3 models */ unsigned int TSKkeepOpInfo:1; /* flag for small signal analyses */ diff --git a/src/maths/ni/niconv.c b/src/maths/ni/niconv.c index c67f5edd4..c824ee9b2 100644 --- a/src/maths/ni/niconv.c +++ b/src/maths/ni/niconv.c @@ -82,8 +82,17 @@ NIconvTest(CKTcircuit *ckt) #ifdef NEWCONV i = CKTconvTest(ckt); /* The individual testers, called by CKTconvTest, set - * ckt->CKTnoncon and ckt->CKTtroubleElt appropriately. */ - if (ckt->CKTnoncon != 0) { + * ckt->CKTnoncon and ckt->CKTtroubleElt appropriately. + * + * Honoring those reports here (upstream trunk 0638aaa16, + * "Continue Newton iteration until currents settle") activates a + * device-current settling test that had been dead code since + * spice3f5. It regresses legacy decks whose devices genuinely + * oscillate at tight tolerances yet simulated acceptably for + * decades under the solution-only test (e.g. the bundled + * 555-timer-2 example at its own RELTOL=1e-4). Enforce only on + * `.option currentsettle`; the default matches ngspice-46. */ + if (ckt->CKTcurrentSettle && ckt->CKTnoncon != 0) { ckt->CKTtroubleNode = 0; return(1); } diff --git a/src/osdi/osditrunc.c b/src/osdi/osditrunc.c index e35f489a6..655848305 100644 --- a/src/osdi/osditrunc.c +++ b/src/osdi/osditrunc.c @@ -68,8 +68,8 @@ int OSDItrunc(GENmodel *in_model, CKTcircuit *ckt, double *timestep) { * the most recently accepted step so that fast edges are resolved * gradually. * - * Was 2.0× originally, tightened to 1.5× for pinb/net_7 oscillation - * (commit 143a0805f) and now to 1.2× for foundry_a ULP driver_lv_2v5_tb + * Was 2.0× originally, tightened to 1.5× for pinb/net_7 oscillation, + * and now to 1.2× for foundry_a ULP driver_lv_2v5_tb * to address residual pinb failure at t ≈ 1.369 µs. At 1.5×, dt * could grow from ~10 ps post-breakpoint to ~5 ns (the user's max * step) in ~15 accepted steps; once Newton had to land a 0.5 V swing diff --git a/src/spicelib/analysis/cktdojob.c b/src/spicelib/analysis/cktdojob.c index 52b55e91d..50a26c99b 100644 --- a/src/spicelib/analysis/cktdojob.c +++ b/src/spicelib/analysis/cktdojob.c @@ -101,6 +101,8 @@ CKTdoJob(CKTcircuit* ckt, int reset, TSKtask* task) ckt->CKTosdiStepRejectOff = task->TSKnoOsdiStepReject; ckt->CKTdtClearOff = task->TSKnoDtClear; ckt->CKTstateRestoreOff = task->TSKnoStateRestore; + ckt->CKTorderGuardOff = task->TSKnoOrderGuard; + ckt->CKTcurrentSettle = task->TSKcurrentSettle; ckt->CKTosdiVlim = task->TSKosdiVlim; ckt->CKTosdiVlimVds = task->TSKosdiVlimVds; ckt->CKTosdiVlimVgs = task->TSKosdiVlimVgs; diff --git a/src/spicelib/analysis/cktsopt.c b/src/spicelib/analysis/cktsopt.c index 32cbf2c8a..8e471b5e3 100644 --- a/src/spicelib/analysis/cktsopt.c +++ b/src/spicelib/analysis/cktsopt.c @@ -53,6 +53,12 @@ CKTsetOpt(CKTcircuit *ckt, JOB *anal, int opt, IFvalue *val) case OPT_NOSTATERESTORE: task->TSKnoStateRestore = (val->iValue != 0); break; + case OPT_NOORDERGUARD: + task->TSKnoOrderGuard = (val->iValue != 0); + break; + case OPT_CURRENTSETTLE: + task->TSKcurrentSettle = (val->iValue != 0); + break; case OPT_GMIN: task->TSKgmin = val->rValue; break; @@ -304,6 +310,8 @@ static IFparm OPTtbl[] = { { "noosdistepreject", OPT_NOOSDISTEPREJECT, IF_SET|IF_FLAG, "Disable OSDI axis-3 step rejection (osdiload.c sanitize_jacobian + REJECT_STEP)" }, { "nodtclear", OPT_NODTCLEAR, IF_SET|IF_FLAG, "Disable small-dt CKTnoncon clear (niiter.c); revert to abort-from-spurious-noncon at dt<1ps" }, { "nostaterestore", OPT_NOSTATERESTORE, IF_SET|IF_FLAG, "Disable dctran failed-attempt device-state restore (CKTstate0 <- CKTstate1 on retry)" }, + { "noorderguard", OPT_NOORDERGUARD, IF_SET|IF_FLAG, "Disable dctran non-uniform-history predictor order guard (forced order-1 on >10x dt ratio)" }, + { "currentsettle", OPT_CURRENTSETTLE, IF_SET|IF_FLAG, "Enable device-current settling enforcement in NIconvTest (trunk 0638aaa16 behaviour; default is ngspice-46 solution-only)" }, { "gmin", OPT_GMIN,IF_SET|IF_REAL,"Minimum conductance" }, { "gshunt", OPT_GSHUNT,IF_SET|IF_REAL,"Shunt conductance" }, { "reltol", OPT_RELTOL,IF_SET|IF_REAL ,"Relative error tolerence"}, diff --git a/src/spicelib/analysis/dctran.c b/src/spicelib/analysis/dctran.c index 7a8e8dd2a..419891550 100644 --- a/src/spicelib/analysis/dctran.c +++ b/src/spicelib/analysis/dctran.c @@ -710,8 +710,21 @@ resume: * * Threshold of 10× chosen so smooth dt-growth sequences * (e.g. the 1.5× cap, factor 1.5 per step) never trigger - * — only genuine breakpoint-induced discontinuities. */ - if (ckt->CKTorder > 1 && + * — only genuine breakpoint-induced discontinuities. + * + * OSDI circuits only (CKTosdiPresent), like the NIiter Δv + * limiter: the failure mode this guards against was observed + * on OSDI/BSIM-BULK drivers, while for native-device decks + * the forced order-1 steps inflate LTE at breakpoint-dense + * switching (smaller dt), and combined with the axis-4 + * residual check this walled the bundled 555-timer-2 example + * at t=3.27ms (RELTOL=1e-4). Stock predictor behaviour is + * restored for non-OSDI circuits. + * + * Opt-out via `.option noorderguard` (diagnostic: lets a + * suspect deck run with the stock always-order-2 predictor). */ + if (ckt->CKTosdiPresent && + !ckt->CKTorderGuardOff && ckt->CKTorder > 1 && ckt->CKTdeltaOld[1] > 0.0 && ckt->CKTdeltaOld[2] > 0.0) { double r = ckt->CKTdeltaOld[1] / ckt->CKTdeltaOld[2]; if (r > 10.0 || r < 0.1) {