fixed memory leaks

This commit is contained in:
Justin Fisher 2026-08-02 12:54:50 +02:00
parent 8fd86075c1
commit 347c51c523
27 changed files with 208 additions and 63 deletions

View File

@ -443,9 +443,9 @@ RCROSS2 B0 A24 0.001
**
**INCLUDING FILE: ./proj1/process.models....
*
* Typical N Typical P - from process corners (taken from tsmc025_corners.bsim3 fron NCSU)
* Typical N Typical P - from process corners (taken from foundry_a.bsim3 fron NCSU)
*
* TSMC 0.25u 5M 1P process. 2.5V transistor models
* foundry_a 0.25u 5M 1P process. 2.5V transistor models
.MODEL Nmod NMOS LEVEL=8

View File

@ -441,9 +441,9 @@ RCROSS2 B0 A24 0.001
**
**INCLUDING FILE: ./proj1/process.models....
*
* Typical N Typical P - from process corners (taken from tsmc025_corners.bsim3 fron NCSU)
* Typical N Typical P - from process corners (taken from foundry_a.bsim3 fron NCSU)
*
* TSMC 0.25u 5M 1P process. 2.5V transistor models
* foundry_a 0.25u 5M 1P process. 2.5V transistor models
.MODEL Nmod NMOS LEVEL=8

View File

@ -192,7 +192,7 @@ WHY:
rule is kept outside HS mode. (8) The single-to-brace quote conversion now
skips `.del`, `.include`, and `.inc ` lines (which take literal file paths),
matching the existing `.lib` skip, to avoid handing paths to numparam as
expressions (STEP 24, observed on GF55 bcd55 .alter decks).
expressions (STEP 24, observed on foundry_c bcd55 .alter decks).
CHANGE:
@@ -1072,9 +1080,22 @@
@ -582,10 +582,10 @@ WHY:
midpoint 1.3999999999999999e-08), while integer comparisons remain unambiguous
(STEP 12). (4) When an identifier matches a function keyword, it is now
treated as a function call ONLY if followed by `(`; otherwise it is treated as
a parameter name, so GF55 decks that use `var` as a subckt parameter aren't
a parameter name, so foundry_c decks that use `var` as a subckt parameter aren't
shadowed by the built-in `var` function (STEP 17). (5) A unary `+` following a
binary operator (e.g. `0.67*+2e-8`) is now accepted as a no-op sign, symmetric
with the existing unary-minus handling, fixing GF55 "Misplaced operator"
with the existing unary-minus handling, fixing foundry_c "Misplaced operator"
failures. (6) Added the `nupa_eval_with_scope()` implementation: pushes a
fresh symbol-table scope, populates it via attrib(), runs formula(), then
frees the scope with del_attrib WITHOUT promoting locals to globals, used by
@ -662,7 +662,7 @@ CHANGE:
+ * `vec`, `min`, `max`, `pow`, `table_param`). Only treat
+ * it as a function call if it's followed by `(` (after
+ * optional whitespace). Otherwise fall back to treating
+ * it as a parameter name — foundry decks (GF55 bcd55
+ * it as a parameter name — foundry decks (foundry_c bcd55
+ * diode_rr.inc) use `var` as a subckt parameter, and
+ * shadowing it with the built-in function broke
+ * `vrb='var'` and similar chains. */

View File

@ -497,18 +497,24 @@ com_measure_when(
}
if (has_d2) {
/* The loop index runs over d's length; d2 may be shorter
* (e.g. a length-1 measure-result vector on the right hand
* side of WHEN v(x)=NAME). Clamp to d2's last element so
* a short vector reads as a held constant instead of
* running past its allocation. */
int i2 = (i < d2->v_length) ? i : d2->v_length - 1;
if (ac_check) {
if (d2->v_compdata)
value2 = get_value(meas, d2, i); //d->v_compdata[i].cx_real;
value2 = get_value(meas, d2, i2); //d->v_compdata[i].cx_real;
else
value2 = d2->v_realdata[i];
value2 = d2->v_realdata[i2];
} else if (sp_check) {
if (d2->v_compdata)
value2 = get_value(meas, d2, i); //d->v_compdata[i].cx_real;
value2 = get_value(meas, d2, i2); //d->v_compdata[i].cx_real;
else
value2 = d2->v_realdata[i];
value2 = d2->v_realdata[i2];
} else {
value2 = d2->v_realdata[i];
value2 = d2->v_realdata[i2];
}
} else {
value2 = NAN;

View File

@ -100,7 +100,7 @@ struct function_env
const char *accept;
} *functions;
/* Hash on `name` for O(1) lookup in find_function. Foundry PDKs
* register hundreds-to-thousands of .funcs (Samsung 14LPU's TT
* register hundreds-to-thousands of .funcs (foundry_b 14LPU's TT
* corner has ~1300), and find_function is called once per `(` in
* every line during macro expansion the linear scan was billions
* of strcmp on real decks. */
@ -3725,8 +3725,8 @@ static void inp_stripcomments_line(char *s, bool cs, bool inc)
/* outside of .control section, and not in PS mode */
else if (!cs && (c == '$') && !newcompat.ps) {
/* HSPICE treats '$' as an end-of-line comment regardless of
* the preceding character foundry decks (Samsung 14LPU,
* TSMC, GF) routinely write `...)'$ comment` or `...=10u$ ...`
* the preceding character foundry decks (foundry_b 14LPU,
* foundry_a, GF) routinely write `...)'$ comment` or `...=10u$ ...`
* with no separator. In ngbehavior=hs / hsa, accept that.
*
* Outside HS mode keep the original conservative rule (only
@ -4000,7 +4000,7 @@ static void inp_fix_for_numparam(
* conversion that would otherwise hand the path to
* numparam as an expression and trigger
* Number format error: "../path...} <section>"
* Observed on GF55 bcd55 sample_netlist/isoednfet_5p0_lr.sp
* Observed on foundry_c bcd55 sample_netlist/isoednfet_5p0_lr.sp
* which uses `.del lib '../models/design_wrapper.lib'` in
* .alter blocks. Matches the existing `.lib` skip above. */
if (ciprefix(".del", c->line) ||
@ -5644,7 +5644,7 @@ static void inp_sort_params(struct card *param_cards,
* scanning every other param's expression for occurrences of each
* param's name
* Both passes are now O(N) + O(total expression chars), enabling real
* PDK use (Samsung 14LPU's TT corner had N~2700 per subckt, called
* PDK use (foundry_b 14LPU's TT corner had N~2700 per subckt, called
* ~500 times, totalling many billions of ops in the old code).
*
* Encoding: hash stores `(void *)(intptr_t)(i + 1)` so that the NULL

View File

@ -27,6 +27,10 @@ extern bool ft_batchmode;
extern bool rflag;
/* Set by INPevaluate's HSPICE-compat bare-.param resolution path
(inpeval.c); see the substitution pre-pass below. */
extern char *inpeval_last_param_name;
/* measure in interactive mode:
meas command inside .control ... .endc loop or manually entered.
meas has to be followed by the standard tokens (see measure_extract_variables()).
@ -44,6 +48,7 @@ com_meas(wordlist *wl)
wordlist *wl_index;
struct dvec *d;
int err = 0;
double subst_val;
int fail;
double result = 0;
@ -76,7 +81,8 @@ com_meas(wordlist *wl)
vec_found = wl_index->wl_word;
/* token may be already a value, maybe 'LAST', which we have to keep, or maybe a vector */
if (!cieq(vec_found, "LAST")) {
INPevaluate(&vec_found, &err, 1);
char *orig_word = wl_index->wl_word;
subst_val = INPevaluate(&vec_found, &err, 1);
/* if not a valid number */
if (err) {
/* check if vec_found is a valid vector */
@ -86,8 +92,18 @@ com_meas(wordlist *wl)
if (d && (d->v_length == 1) && (d->v_numdims == 1)) {
/* get its value */
wl_index->wl_word = tprintf("%e", d->v_realdata[0]);
tfree(vec_found);
tfree(orig_word);
}
} else if (inpeval_last_param_name) {
/* INPevaluate resolved a bare .param identifier (e.g.
a prior .measure result registered via
nupa_add_param). Write the value back so the
measure parser sees a number otherwise the
identifier survives as text and com_measure2
treats it as a full vector, which mis-measures
(and over-reads a length-1 result vector). */
wl_index->wl_word = tprintf("%e", subst_val);
tfree(orig_word);
}
}
}
@ -95,7 +111,7 @@ com_meas(wordlist *wl)
else if ((equal_ptr = strchr(token, '=')) != NULL) {
vec_found = equal_ptr + 1;
if (!cieq(vec_found, "LAST")) {
INPevaluate(&vec_found, &err, 1);
subst_val = INPevaluate(&vec_found, &err, 1);
if (err) {
d = vec_get(vec_found);
/* Only if we have a single valued vector, replacing
@ -106,6 +122,13 @@ com_meas(wordlist *wl)
tprintf("%.*s=%e", lhs_len, token, d->v_realdata[0]);
tfree(token);
}
} else if (inpeval_last_param_name) {
/* bare .param identifier resolved — write the value
back (see whole-token case above) */
int lhs_len = (int)(equal_ptr - token);
wl_index->wl_word =
tprintf("%.*s=%e", lhs_len, token, subst_val);
tfree(token);
}
}
} else {

View File

@ -749,7 +749,7 @@ nupa_eval(struct card *card)
} else if (c == 'B') { /* substitute braces line */
/* nupa_substitute() may reallocate line buffer. */
/* HSPICE-style OSDI model cards (Samsung 14LPU et al.) embed
/* HSPICE-style OSDI model cards (foundry_b 14LPU et al.) embed
* `{...}` expressions on the right-hand side of `.model`
* params that reference per-instance geometry symbols (`l`,
* `w`, `nf`, `m`, `xnf`). These cannot be evaluated at

View File

@ -205,7 +205,7 @@ static Table *load_table_file(const char *path) {
* a malloc'd absolute path the caller must free, or a copy of the
* input if no search is needed.
*
* Foundry PDKs (Samsung 14LPU) reference tables by paths relative to
* Foundry PDKs (foundry_b 14LPU) reference tables by paths relative to
* the .lib file that uses them `./RF_COMPONENTS/foo.table` is
* relative to the directory of fets_rf.lib (or wherever the call
* originated), NOT to the user's cwd or ngspice's sourcepath.

View File

@ -1,9 +1,9 @@
/*
* HSPICE table_param() implementation.
*
* Foundry PDKs (Samsung 14LPU, TSMC, GF) use HSPICE's table_param()
* Foundry PDKs (foundry_b 14LPU, foundry_a, GF) use HSPICE's table_param()
* extensively for table-file-based parameter lookup self-heating
* thermal resistance, RF parasitics, etc. Samsung's TT corner alone
* thermal resistance, RF parasitics, etc. foundry_b's TT corner alone
* references it ~1300 times.
*
* Syntax:

View File

@ -1210,7 +1210,7 @@ formula(dico_t *dico, const char *s, const char *s_end, bool *perror)
* `vec`, `min`, `max`, `pow`, `table_param`). Only treat
* it as a function call if it's followed by `(` (after
* optional whitespace). Otherwise fall back to treating
* it as a parameter name foundry decks (GF55 bcd55
* it as a parameter name foundry decks (foundry_c bcd55
* diode_rr.inc) use `var` as a subckt parameter, and
* shadowing it with the built-in function broke
* `vrb='var'` and similar chains. */
@ -1260,7 +1260,7 @@ formula(dico_t *dico, const char *s, const char *s_end, bool *perror)
/* Symmetric to the `c == '-'` case above: a unary `+` directly
* following a binary operator (e.g. `0.67*+2e-8`) is a no-op
* sign drop it and re-read the next token. Foundry decks
* (GF55 bcd55 fixed_corner_bcdlite.inc) use this idiom for
* (foundry_c bcd55 fixed_corner_bcdlite.inc) use this idiom for
* explicit-sign literals: `(sw5)*(0.67*+2e-8)`. Without this,
* the unary-plus form failed with "Misplaced operator" while
* the unary-minus form parsed fine. */

View File

@ -442,7 +442,7 @@ inp_subcktexpand(struct card *deck) {
* name (typically the 5th token, after the 4 node terminals) names
* the type. ngspice's subckt expander only handles the .subckt
* case; if no .subckt matches, we'd error here. But foundry PDKs
* (Samsung 14LPU, TSMC, GF) routinely embed VA-module diagnostic
* (foundry_b 14LPU, foundry_a, GF) routinely embed VA-module diagnostic
* instances like `xesd_monitor d g s b esd_nfet_monitor ...`
* inside their FET subckts, expecting HSPICE-style dispatch.
*
@ -605,7 +605,7 @@ get_model_bins(char *curr_line, float *fwmin, float *fwmax,
parameter. HSPICE applies a subcircuit's `scale` parameter as the
element scale factor for the MOSFETs inside it, multiplying their
W/L before the model bins and simulates. Foundry MOS macro subckts
rely on this (e.g. TSMC `nch_mac ... scale='scale_mos'`). Detected
rely on this (e.g. foundry_a `nch_mac ... scale='scale_mos'`). Detected
by a word-boundary "scale" immediately followed (after optional
whitespace) by '=', so names like `l_scale=` / `noscale=` don't
match. */

View File

@ -143,6 +143,14 @@ struct CKTcircuit {
* opt-out. */
int CKTosdiStepReject;
int CKTosdiStepRejectOff;
/* Set by OSDIsetup when the circuit contains at least one OSDI
* (Verilog-A) device. Gates OSDI-motivated Newton machinery the
* per-node Δv limiter in NIiter so that circuits built purely
* from native SPICE devices keep stock SPICE3 iteration behaviour. */
int CKTosdiPresent;
/* `.option nostaterestore` opt-out of the dctran failed-attempt
* device-state restore (CKTstate0 <- CKTstate1 on timepoint retry). */
int CKTstateRestoreOff;
/* 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

View File

@ -95,6 +95,8 @@ enum {
OPT_NORESIDCHECK, /* `.option noresidcheck` opts out of axis-4 dual-norm
* convergence (residual-vector check); revert to
* SPICE3 solution-only behaviour. */
OPT_NOSTATERESTORE, /* `.option nostaterestore` opts out of the dctran
* failed-attempt device-state restore. */
OPT_EQNS,
OPT_REORDTIME,
OPT_METHOD,

View File

@ -1,7 +1,7 @@
/*
* OSDI deferred-evaluation side table.
*
* HSPICE-style PDK model cards (Samsung 14LPU et al.) embed expressions
* HSPICE-style PDK model cards (foundry_b 14LPU et al.) embed expressions
* like
* cgbn={((l<=1e-07)*(1e-012)+(l>1e-07)*(...))}
* directly on the right-hand side of `.model` parameters. The expression
@ -44,7 +44,7 @@
* `snap_*` capture the subckt-instance scope visible at register time.
* HSPICE-style PDKs put `.model` cards INSIDE a `.subckt` body and let
* the model's expressions reference subckt-scope `.params` (e.g.
* Samsung 14LPU's `vsat1=...*(1+vsat_nfet/...)*velsat_mult` where
* foundry_b 14LPU's `vsat1=...*(1+vsat_nfet/...)*velsat_mult` where
* `vsat_nfet`/`velsat_mult`/`xl_nfet` are passed to the subckt via
* its `params:` list). By register time the subckt scope has been
* resolved per-instance (subckt expansion produced model names like
@ -90,7 +90,7 @@ void osdi_defer_clear(void);
* INPgetModBin once the model line's lmin/lmax tokens have been
* parsed, so that OSDIsetup's pre-eval pass can pick a default
* L within the bin's range (midpoint). Without this, default
* L=30nm makes Samsung-PDK expressions like
* L=30nm makes foundry_b-PDK expressions like
* `vsat1='(l==14n)*X + (l==16n)*Y'` evaluate to 0 and BSIM-CMG
* rejects "vsat1 = 0". Matched against the runtime model name
* (after subckt-path prefix is stripped). */

View File

@ -72,6 +72,9 @@ struct TSKtask {
unsigned int TSKnoDtClear:1; /* `.option nodtclear` disables the
* small-dt CKTnoncon clear in
* niiter.c */
unsigned int TSKnoStateRestore:1; /* `.option nostaterestore` disables
* the dctran failed-attempt state
* restore (CKTstate0 <- CKTstate1) */
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 */

View File

@ -160,6 +160,71 @@ NIiter(CKTcircuit *ckt, int maxIter)
return (E_ITERLIM);
}
/* Axis 4 — row-relative KCL residual convergence check.
* f = G*x - b is computed here, in the load->factor window
* where the matrix still holds the device Jacobian, and each
* row is compared against its own current scale
* s_n = sum_j |G_nj*x_j| + |b_n| (plus the gmin-stepping
* diagonal term for rows that receive it, so continuation
* systems are measured against what is actually solved):
*
* |f_n| <= 10*reltol * s_n + abstol
*
* The row-relative form is what makes this enforceable where
* the earlier absolute-norm attempt was not: lenient SPICE3
* |dx|-only accepts that real PDK operating points depend on
* carry roundoff-scale RELATIVE residuals and pass untouched,
* while a false solution (e.g. a multi-million-fin OSDI
* device whose hundreds-of-siemens row satisfies |dx|<vntol
* long before KCL holds dynamic gmin stepping was observed
* "completing" 1.6 V off the rail at high temperature) shows
* an O(1) relative residual and is sent back for further
* Newton iterations instead of being accepted. Threshold
* 10*reltol: KCL must close to within an order of magnitude
* of the voltage-convergence precision dimensionless, no
* voltage-range or technology assumptions. NIconvTest gates
* on CKTresidConverged (niconv.c). Sparse-matrix path only;
* `.option noresidcheck` opts out. */
ckt->CKTresidConverged = 1;
#ifdef KLU
if (!ckt->CKTresidCheckDisabled && !ckt->CKTmatrix->CKTkluMODE)
#else
if (!ckt->CKTresidCheckDisabled)
#endif
{
int rn, rsize = SMPmatSize(ckt->CKTmatrix);
double *rf = TMALLOC(double, (size_t) rsize + 1);
double *rs = TMALLOC(double, (size_t) rsize + 1);
double *rg = TMALLOC(double, (size_t) rsize + 1);
double rtol = 10.0 * ckt->CKTreltol;
double rvtol = 10.0 * ckt->CKTvoltTol;
char *rskip = ckt->CKTmatrix->gmin_skip;
SMPmultiplyAbs(ckt->CKTmatrix, rf, rs, rg, ckt->CKTrhsOld);
for (rn = 1; rn <= rsize; rn++) {
double gterm = (rskip && rskip[rn]) ? 0.0 :
ckt->CKTdiagGmin * ckt->CKTrhsOld[rn];
double fr = rf[rn] + gterm - ckt->CKTrhs[rn];
/* Tolerance = what a solution within per-unknown
* tolerance could produce on this row: the relative
* part scales with the row's own current magnitude,
* the row-norm part (|G|_1 * vntol) is the attainable
* precision of a stiff row an LU solve cannot push
* a row residual below rownorm * solution-roundoff,
* and demanding it burns iterations at breakpoint
* corners where row values pass through zero. */
double tol_ = rtol * (rs[rn] + fabs(gterm) +
fabs(ckt->CKTrhs[rn]))
+ rvtol * rg[rn] + ckt->CKTabstol;
if (fabs(fr) > tol_) {
ckt->CKTresidConverged = 0;
break;
}
}
FREE(rf);
FREE(rs);
FREE(rg);
}
/* printf("after loading, before solving\n"); */
/* CKTdump(ckt); */
@ -322,18 +387,9 @@ NIiter(CKTcircuit *ckt, int maxIter)
memcpy(OldCKTstate0, ckt->CKTstate0,
(size_t) ckt->CKTnumStates * sizeof(double));
/* Axis 4 placeholder — the |f|-magnitude (residual-norm) half
* of dual-norm convergence stays disabled. A correct true-KCL
* residual check (f = G*x - b via SMPmultiply in the pre-factor
* window) was implemented and verified CORRECT, but enforcing it
* by default over-rejects real PDK operating points that rely on
* ngspice's lenient SPICE3 |dx|-only convergence (TSMC22 OP
* diverged, Samsung slowed badly) while a clean circuit (0.9V
* inverter) was byte-identical. So treat residual as always-
* passed; NIconvTest gates on |dx| only. The CKTresidConverged
* / CKTresidCheckDisabled fields + niconv.c gate + `.option
* noresidcheck` plumbing remain for a future opt-in form. */
ckt->CKTresidConverged = 1;
/* Axis 4: CKTresidConverged was computed in the load->factor
* window above (row-relative KCL residual); by this point the
* matrix is factored and no longer holds the Jacobian. */
startTime = SPfrontEnd->IFseconds();
SMPsolve(ckt->CKTmatrix, ckt->CKTrhs, ckt->CKTrhsSpare);
@ -355,7 +411,7 @@ NIiter(CKTcircuit *ckt, int maxIter)
ckt->CKTrhsOld[0] = 0;
/* Newton-step limiter (simulator-side $limit substitute).
* Always-on during transient (and DC OP) to clamp per-node
* Active during transient (and DC OP) to clamp per-node
* |Δv| to ±CKTabsDv between Newton iterations. Matches
* the default behaviour of Spectre/HSPICE DEVlimvds-style
* limiters which fire on every iteration as a model-
@ -363,6 +419,17 @@ NIiter(CKTcircuit *ckt, int maxIter)
* 0.5 V) a model-parameter-agnostic tolerance, not a
* voltage rail.
*
* OSDI circuits only (CKTosdiPresent): the limiter exists
* as a substitute for model-supplied $limit calls that
* OSDI/Verilog-A models may lack. Applying it to every
* circuit regressed non-OSDI decks whose behavioral
* macromodels legitimately need multi-kV Newton steps
* (PSpice opamp libs: TABLE sources swing to ±3.5 kV)
* the clamp forced a crawl whose small |Δx| the axis-4
* residual check then correctly refused, so OP, gmin and
* source stepping all failed. Native SPICE devices carry
* their own pnjlim/limvds limiting and never needed this.
*
* Skipped on iteration 1 (no previous iterate to compare).
* Skipped when CKTnodes is NULL (matrix not yet built).
*
@ -370,7 +437,7 @@ NIiter(CKTcircuit *ckt, int maxIter)
* $limit synthesis pass, the model's own limiters apply
* inside descr->eval() this simulator-side limiter then
* sees already-limited Δv and does nothing additional. */
if (iterno > 1 && ckt->CKTnodes != NULL) {
if (ckt->CKTosdiPresent && iterno > 1 && ckt->CKTnodes != NULL) {
double dv_max = (ckt->CKTabsDv > 0) ? ckt->CKTabsDv : 0.5;
/* Compute current iteration's max|Δv|. Needed both
* for the Stage A scalar scaling and for the Stage B
@ -403,7 +470,7 @@ NIiter(CKTcircuit *ckt, int maxIter)
* Stagnation = current max|Δv| not dropping by at
* least 30% from prev iteration. Unconditional
* halving past iter 3 was preventing convergence
* on the TSMC22 ULP driver_lv_2v5_tb VSN node: the
* on the foundry_a ULP driver_lv_2v5_tb VSN node: the
* inductor-coupled supply (L1 = 2.7 nH) naturally
* needs ~100 mV swings to track each switching
* transition, but dv_max would collapse to 8 mV by

View File

@ -215,7 +215,7 @@ static int is_ident_cont(int c) {
*
* Reserved set:
* l, w, nf, m, xnf standard HSPICE instance params
* l_calc Samsung/foundry "computed length" (= l + p_la
* l_calc foundry_b/foundry "computed length" (= l + p_la
* in the foundry subckt; defaults to l alone) */
static bool expr_refs_instance_geom(const char *expr) {
static const char *const RES[] = {

View File

@ -319,6 +319,11 @@ int OSDIsetup(SMPmatrix *matrix, GENmodel *inModel, CKTcircuit *ckt,
OsdiSimParas sim_params_ = get_simparams(ckt);
OsdiSimParas *sim_params = &sim_params_;
/* Mark the circuit as containing OSDI devices — gates OSDI-motivated
* Newton machinery (per-node Δv limiter in NIiter) so purely native
* circuits keep stock iteration behaviour. */
ckt->CKTosdiPresent = 1;
/* setup a temporary buffer */
uint32_t *node_ids = TMALLOC(uint32_t, descr->num_nodes);
@ -338,12 +343,12 @@ int OSDIsetup(SMPmatrix *matrix, GENmodel *inModel, CKTcircuit *ckt,
* ranges and would otherwise reject the `0` placeholders that
* osdi_defer_preprocess_line writes onto deferred-param slots.
*
* Per-model default L: BSIM-CMG/Samsung 14LPU expressions look like
* Per-model default L: BSIM-CMG/foundry_b 14LPU expressions look like
* vsat1 = '((l==0.014e-6)*X + (l==0.016e-6)*Y) * ...'
* For these to evaluate to a NON-ZERO valid value at pre-eval time
* (so setup_model accepts), the chosen L must satisfy at least one
* of the `(l==...)` checks. Read the model's lmin/lmax (already
* populated by the .model parse) and pick the midpoint. Samsung's
* populated by the .model parse) and pick the midpoint. foundry_b's
* 14LPU nfet.0 has lmin=10nm, lmax=18nm midpoint=14nm, exactly
* the L the expression looks for. For models without lmin/lmax,
* fall back to 30nm (the original constant default). */
@ -528,7 +533,7 @@ extern int OSDItemp(GENmodel *inModel, CKTcircuit *ckt) {
* setup_instance so the model sees the actual computed values.
* Skipped entirely (no map lookup, no syscalls) when this
* model has no deferred entries the common case for non-
* HSPICE-style OSDI PDKs like TSMC22 BSIM-BULK. */
* HSPICE-style OSDI PDKs like foundry_a BSIM-BULK. */
if (has_deferred) {
defer_apply_ctx ctx = {
.descr = descr,

View File

@ -69,7 +69,7 @@ int OSDItrunc(GENmodel *in_model, CKTcircuit *ckt, double *timestep) {
* gradually.
*
* Was 2.0× originally, tightened to 1.5× for pinb/net_7 oscillation
* (commit 143a0805f) and now to 1.2× for TSMC22 ULP driver_lv_2v5_tb
* (commit 143a0805f) 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

View File

@ -100,6 +100,7 @@ CKTdoJob(CKTcircuit* ckt, int reset, TSKtask* task)
ckt->CKTresidCheckDisabled = task->TSKnoResidCheck;
ckt->CKTosdiStepRejectOff = task->TSKnoOsdiStepReject;
ckt->CKTdtClearOff = task->TSKnoDtClear;
ckt->CKTstateRestoreOff = task->TSKnoStateRestore;
ckt->CKTosdiVlim = task->TSKosdiVlim;
ckt->CKTosdiVlimVds = task->TSKosdiVlimVds;
ckt->CKTosdiVlimVgs = task->TSKosdiVlimVgs;

View File

@ -50,6 +50,9 @@ CKTsetOpt(CKTcircuit *ckt, JOB *anal, int opt, IFvalue *val)
case OPT_NODTCLEAR:
task->TSKnoDtClear = (val->iValue != 0);
break;
case OPT_NOSTATERESTORE:
task->TSKnoStateRestore = (val->iValue != 0);
break;
case OPT_GMIN:
task->TSKgmin = val->rValue;
break;
@ -300,6 +303,7 @@ static IFparm OPTtbl[] = {
{ "noresidcheck", OPT_NORESIDCHECK, IF_SET|IF_FLAG, "Disable axis-4 residual-vector convergence check; SPICE3-only behaviour" },
{ "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)" },
{ "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"},

View File

@ -695,7 +695,7 @@ resume:
* non-uniform x-spacings produces a nonsensical slope at
* the extrapolation end of the fit window, and Newton then
* has to undo a 0.5-1 V starting offset on the affected
* nodes within itl4 iterations. Observed on TSMC22 ULP
* nodes within itl4 iterations. Observed on foundry_a ULP
* driver_lv_2v5_tb: VSN (an L1-coupled supply driving
* 500-finger BSIM-BULK drivers) catches a ~-0.7 V predictor
* over-shoot and Newton can't undo it inside Stage A's
@ -762,6 +762,28 @@ resume:
/* If no convergence in Central solver step */
if(converged != 0) {
/* A nonconverged attempt returns with CKTstate0 still holding
* the last failed Newton iterate's device states (limiter
* voltage history, charge states, OSDI LimitState slots)
* NIiter does not restore them. The retry then evaluates
* devices against that garbage reference, lands further from
* the solution, and fails worse; the contamination compounds
* geometrically across retries at a single timepoint
* (observed: v6#branch 32 A 58 310 3e10 A over ~25
* retries) until the state explodes and dt collapses to
* delmin. Re-prime the working state from the last ACCEPTED
* point so every retry starts from physical values, exactly
* like a first attempt does.
*
* Opt-out via `.option nostaterestore` (suspected of
* regressing non-OSDI example decks on raw-transient-start
* paths uic / optran / mid-run retries where CKTstate1
* may not hold a meaningfully accepted point). */
if (!ckt->CKTstateRestoreOff &&
ckt->CKTstate0 && ckt->CKTstate1)
memcpy(ckt->CKTstate0, ckt->CKTstate1,
(size_t) ckt->CKTnumStates * sizeof(double));
#ifndef SHARED_MODULE
ckt->CKTtime = ckt->CKTtime -ckt->CKTdelta;
ckt->CKTstat->STATrejected ++;

View File

@ -672,7 +672,7 @@ DCtrCurv(CKTcircuit *ckt, int restart)
* On the LAST accepted iteration (the one whose NEXT
* would trigger the stop check), snap cur_val to stop
* exactly so the saved row's X-value is bit-exact.
* GF55 bcd55 isoednfet's `.measure when v(n2)=3.3`
* foundry_c bcd55 isoednfet's `.measure when v(n2)=3.3`
* relies on this. */
job->TRCVstepCount[i]++;
double cur_val = job->TRCVvStart[i] +

View File

@ -651,7 +651,7 @@ int HSMHV2load(
/* NaN-recovery (sanity damping).
*
* Symptom: on big PDKs (Samsung 14LPU 3.3 V LDMOS as `ld3nfet`)
* Symptom: on big PDKs (foundry_b 14LPU 3.3 V LDMOS as `ld3nfet`)
* the initial DC operating-point Newton iteration occasionally
* lands on bias values that make the HiSIM_HV physics produce
* NaN currents/conductances. Those NaN entries are written

View File

@ -202,7 +202,7 @@ INPdevParse(char **line, CKTcircuit *ckt, int dev, GENinstance *fast,
goto quit;
}
/* OSDI models may receive extra instance parameters from PDK
* subcircuits (e.g. 'total' from TSMC nch_mac) that the model
* subcircuits (e.g. 'total' from foundry_a nch_mac) that the model
* does not define. Skip the parameter and its value rather than
* aborting; this matches HSPICE/Spectre behaviour. */
if (device->registry_entry != NULL) {

View File

@ -30,6 +30,10 @@ INPevaluate(char **line, int *error, int gobble)
char *tmpline;
/* setup */
/* Clear the bare-.param side channel on every entry, as its
* consumers (INPdevParse, com_meas) assume a non-NULL value must
* only ever mean THIS call resolved a bare identifier. */
inpeval_last_param_name = NULL;
tmpline = *line;
if (gobble) {

View File

@ -305,7 +305,7 @@ INPgetModBin(CKTcircuit *ckt, char *name, INPmodel **model, INPtables *tab, char
*model = NULL;
/* Read L (required) and W (optional). FinFET PDKs (Samsung 14LPU
/* Read L (required) and W (optional). FinFET PDKs (foundry_b 14LPU
* et al.) have no W on the instance line only `l=` and
* `nfin=` and their `.model` cards bin on `lmin/lmax/nfinmin/
* nfinmax`, not on W. Previously this function bailed out as
@ -348,8 +348,8 @@ INPgetModBin(CKTcircuit *ckt, char *name, INPmodel **model, INPtables *tab, char
/* OSDI bin selection: compare per-finger W (w / nf, already applied
* above) against the bin's wmin/wmax. Do NOT divide by m `m` is a
* parallel-instance multiplier (the whole device is replicated m
* times), so the per-finger geometry is unchanged by it. TSMC and
* GlobalFoundries BSIM-BULK decks author their wmin/wmax tables
* times), so the per-finger geometry is unchanged by it. foundry_a and
* foundry_c BSIM-BULK decks author their wmin/wmax tables
* against the HSPICE/Spectre convention of per-finger W only, so an
* additional /m here would push the effective W below every bin's
* lower bound for any moderately-large multi-instance device. */
@ -399,9 +399,9 @@ INPgetModBin(CKTcircuit *ckt, char *name, INPmodel **model, INPtables *tab, char
}
/* Compare the per-finger W (already w/nf above) and L against
* the bin's wmin/wmax and lmin/lmax. Foundry .lib files (TSMC,
* GlobalFoundries, Samsung) author bin boundaries in POST-SHRINK
* (EFFECTIVE) dimensions: e.g. TSMC 22nm ULP nch.1 wmax=2.5651µm
* the bin's wmin/wmax and lmin/lmax. Foundry .lib files (foundry_a,
* foundry_c, foundry_b) author bin boundaries in POST-SHRINK
* (EFFECTIVE) dimensions: e.g. foundry_a 22nm ULP nch.1 wmax=2.5651µm
* = drawn 3µm × shrink 0.855. The shrink is applied upstream in
* subckt expansion (the subcircuit's `scale` parameter multiplies
* the device W/L) or by the model's own dimension expressions, so
@ -425,7 +425,7 @@ INPgetModBin(CKTcircuit *ckt, char *name, INPmodel **model, INPtables *tab, char
*model = modtmp;
/* Stash the bin's (lmin, lmax) for OSDIsetup's deferred-eval
* pre-pass, which needs a representative L within the bin's
* range to make Samsung-PDK expressions like
* range to make foundry_b-PDK expressions like
* `(l==14n)*X + (l==16n)*Y` evaluate to non-zero. */
if (is_osdi)
osdi_defer_record_bin_range(modtmp->INPmodName, lmin, lmax);