Skip to main content

dune_core/
mos2.rs

1//! MOSFET level 2 — the Grove-Frohman model from the 3f5
2//! (`dev/mos2/mos2load.c`, `mos2temp.c`, `mos2set.c`, `mos2acld.c`,
3//! `mos2trunc.c`). Analytical bulk-charge drain current with
4//! short-channel (`XJ`) and narrow-width (`DELTA`) threshold
5//! corrections, mobility degradation (`UCRIT`/`UEXP`), velocity
6//! saturation by Baum's quartic (`VMAX`/`NEFF`), channel-length
7//! modulation from the depletion approximation, and the `NFS`
8//! subthreshold region. The scaffolding around the current
9//! evaluation (bias ladder, limiting, bulk diodes, depletion and
10//! Meyer charges, companion models, stamps) is byte-equivalent to
11//! MOS1's, which is why this file mirrors `mos1.rs` there.
12
13use crate::consts::{BOLTZ, CHARGE, KOVERQ, REFTEMP, ROOT2};
14use crate::dderivs::Dderivs;
15use crate::diode::pnjlim;
16use crate::disto::{DistoCtx, DistoSetupCtx};
17use crate::matrix::{AcCtx, AnKind, InitMode, LoadCtx, MatrixBuilder, Slot, TRASH, Var};
18use crate::mos1::{MeyerPzCaps, MosGeom, MosPzConductances, fetlim, limvds, meyer_pz_load, qmeyer};
19use crate::mosdisto::{
20    DrainDerivs, MosDistoCoeffs, MosJunctions, bulk_diode, mos_disto_load, setup_tail,
21};
22
23/// Epsilon of silicon (`mos2load.c`: `EPSSIL`).
24const EPSSIL: f64 = 11.7 * 8.854214871e-12;
25
26/// `.model … nmos(level=2 …)/pmos(level=2 …)` parameters, 3f5 names
27/// and defaults (`mos2.c` keyword table; defaults from `mos2set.c`).
28/// `Option` marks parameters whose given-ness changes the
29/// preprocessing, as in [`crate::Mos1Model`].
30#[derive(Clone, Debug)]
31pub struct Mos2Model {
32    /// `NMOS` (+1) or `PMOS` (−1).
33    pub nmos: bool,
34    /// `VTO`/`VT0`, default 0, or computed from `NSUB`/`NSS`/`TPG`.
35    pub vto: Option<f64>,
36    /// `KP`, default `U0·COX`.
37    pub kp: Option<f64>,
38    /// `GAMMA`, default 0, or computed from `NSUB`.
39    pub gamma: Option<f64>,
40    /// `PHI`, default 0.6, or from `NSUB`.
41    pub phi: Option<f64>,
42    /// `LAMBDA`, default 0 (0 enables the depletion-layer
43    /// channel-length modulation when `NSUB` is given).
44    pub lambda: f64,
45    /// `RD`/`RS`, default 0.
46    pub rd: Option<f64>,
47    pub rs: Option<f64>,
48    /// `CBD`/`CBS`, default 0.
49    pub cbd: Option<f64>,
50    pub cbs: Option<f64>,
51    /// `IS`, default 1e-14.
52    pub is: f64,
53    /// `PB`, default 0.8.
54    pub pb: f64,
55    /// `CGSO`/`CGDO`/`CGBO`, default 0.
56    pub cgso: f64,
57    pub cgdo: f64,
58    pub cgbo: f64,
59    /// `RSH`, default 0.
60    pub rsh: Option<f64>,
61    /// `CJ`, per m²; the 3f5 computes a default from `NSUB` but the
62    /// zero-bias capacitances still key off the given flag, so the
63    /// computed default never reaches them.
64    pub cj: Option<f64>,
65    /// `MJ`, default 0.5.
66    pub mj: f64,
67    /// `CJSW`, per meter, default 0.
68    pub cjsw: Option<f64>,
69    /// `MJSW`, default **0.33** (not 0.5 as in MOS1).
70    pub mjsw: f64,
71    /// `JS`, per m², default 0.
72    pub js: f64,
73    /// `TOX`, default 1e-7 m (MOS2 always has an oxide).
74    pub tox: Option<f64>,
75    /// `LD`, default 0.
76    pub ld: f64,
77    /// `U0`/`UO`, cm²/V·s, default 600.
78    pub u0: Option<f64>,
79    /// `FC`, default 0.5.
80    pub fc: f64,
81    /// `NSUB`, 1/cm³.
82    pub nsub: Option<f64>,
83    /// `TPG`, default 1 when `NSUB` is given.
84    pub tpg: Option<f64>,
85    /// `NSS`, 1/cm².
86    pub nss: Option<f64>,
87    /// `DELTA`: width effect on threshold, default 0.
88    pub delta: f64,
89    /// `UEXP`: critical-field exponent for mobility degradation,
90    /// default 0.
91    pub uexp: f64,
92    /// `UCRIT`: critical field, V/cm, default 1e4.
93    pub ucrit: f64,
94    /// `VMAX`: maximum carrier drift velocity, m/s, default 0 (off).
95    pub vmax: f64,
96    /// `XJ`: metallurgical junction depth, default 0.
97    pub xj: f64,
98    /// `NEFF`: total channel charge coefficient, default 1.
99    pub neff: f64,
100    /// `NFS`: fast surface state density, 1/cm², default 0.
101    pub nfs: f64,
102    /// `KF`/`AF` (noise only).
103    pub kf: f64,
104    pub af: f64,
105    /// `NLEV` (ngspice-47 only, default 2): noise equation selector
106    /// — `< 3` channel thermal `4kT·(2/3)|gm|`, `3` the
107    /// `GDSNOI`-scaled form; for the MOSFETs also the flicker form
108    /// (0/1/2–3).
109    pub nlev: i64,
110    /// `GDSNOI` (ngspice-47 only, default 1): channel noise
111    /// coefficient of `NLEV = 3`.
112    pub gdsnoi: f64,
113    /// `TNOM` in kelvin.
114    pub tnom: Option<f64>,
115}
116
117impl Mos2Model {
118    pub fn new(nmos: bool) -> Self {
119        Mos2Model {
120            nmos,
121            vto: None,
122            kp: None,
123            gamma: None,
124            phi: None,
125            lambda: 0.0,
126            rd: None,
127            rs: None,
128            cbd: None,
129            cbs: None,
130            is: 1e-14,
131            pb: 0.8,
132            cgso: 0.0,
133            cgdo: 0.0,
134            cgbo: 0.0,
135            rsh: None,
136            cj: None,
137            mj: 0.5,
138            cjsw: None,
139            mjsw: 0.33,
140            js: 0.0,
141            tox: None,
142            ld: 0.0,
143            u0: None,
144            fc: 0.5,
145            nsub: None,
146            tpg: None,
147            nss: None,
148            delta: 0.0,
149            uexp: 0.0,
150            ucrit: 1e4,
151            vmax: 0.0,
152            xj: 0.0,
153            neff: 1.0,
154            nfs: 0.0,
155            kf: 0.0,
156            af: 1.0,
157            nlev: 2,
158            gdsnoi: 1.0,
159            tnom: None,
160        }
161    }
162
163    /// Same prime-node rule as MOS1 (`mos2set.c`).
164    pub(crate) fn needs_prime(&self, squares: f64) -> bool {
165        self.rd.unwrap_or(0.0) != 0.0 || (self.rsh.unwrap_or(0.0) != 0.0 && squares != 0.0)
166    }
167}
168
169fn egfet(t: f64) -> f64 {
170    1.16 - (7.02e-4 * t * t) / (t + 1108.0)
171}
172
173/// Model- and temperature-derived values (`MOS2temp`).
174#[derive(Debug)]
175pub(crate) struct Mos2Temp {
176    /// The instance temperature, kelvin (`*temp`: the card's
177    /// `TEMP=` or `CKTtemp`) — the thermal noise generators' T
178    /// (ngspice-47 `NevalSrcInstanceTemp`).
179    pub temp: f64,
180    pub type_: f64,
181    pub vt: f64,
182    // Model copies the load needs.
183    pub lat_diff: f64,
184    /// `KF`/`AF`: flicker-noise coefficient and exponent.
185    pub kf: f64,
186    pub af: f64,
187    /// `NLEV`/`GDSNOI` (noise only).
188    pub nlev: i64,
189    pub gdsnoi: f64,
190    pub cgso: f64,
191    pub cgdo: f64,
192    pub cgbo: f64,
193    pub oxide_cap_factor: f64,
194    pub gamma: f64,
195    pub lambda: f64,
196    pub mj: f64,
197    pub mjsw: f64,
198    pub delta: f64,
199    pub xj: f64,
200    pub nfs: f64,
201    pub ucrit: f64,
202    pub uexp: f64,
203    pub vmax: f64,
204    pub neff: f64,
205    /// Raw `U0` after defaulting — the mobility section of the load
206    /// uses the model value, not the temperature-adjusted one
207    /// (3f5 quirk kept).
208    pub u0: f64,
209    /// `NSUB·?` as the load sees it (0 when not given).
210    pub nsub: f64,
211    /// `MOS2xd`: depletion-layer width factor from `NSUB`.
212    pub xd: f64,
213    // Temperature-adjusted values (same formulas as MOS1).
214    pub t_transconductance: f64,
215    pub t_phi: f64,
216    pub t_vbi: f64,
217    pub t_vto: f64,
218    pub t_sat_cur: f64,
219    pub t_sat_cur_dens: f64,
220    pub t_bulk_pot: f64,
221    pub t_dep_cap: f64,
222    pub drain_vcrit: f64,
223    pub source_vcrit: f64,
224    pub cbd: f64,
225    pub cbdsw: f64,
226    pub cbs: f64,
227    pub cbssw: f64,
228    pub f2d: f64,
229    pub f3d: f64,
230    pub f4d: f64,
231    pub f2s: f64,
232    pub f3s: f64,
233    pub f4s: f64,
234    pub drain_conductance: f64,
235    pub source_conductance: f64,
236    pub notes: Vec<String>,
237}
238
239impl Mos2Temp {
240    pub fn new(
241        name: &str,
242        m: &Mos2Model,
243        g: &MosGeom,
244        temp: f64,
245        default_tnom: f64,
246    ) -> Result<Self, String> {
247        let mut notes = Vec::new();
248        let typ = if m.nmos { 1.0 } else { -1.0 };
249        let tnom = m.tnom.unwrap_or(default_tnom);
250
251        let fact1 = tnom / REFTEMP;
252        let vtnom = tnom * KOVERQ;
253        let kt1 = BOLTZ * tnom;
254        let egfet1 = egfet(tnom);
255        let arg1 = -egfet1 / (kt1 + kt1) + 1.1150877 / (BOLTZ * (REFTEMP + REFTEMP));
256        let pbfact1 = -2.0 * vtnom * (1.5 * f64::ln(fact1) + CHARGE * arg1);
257
258        // Model preprocessing. Unlike MOS1, the oxide always exists
259        // (TOX defaults to 1e-7) and KP always falls back to U0·COX.
260        let tox = m.tox.unwrap_or(1e-7);
261        let oxide_cap_factor = 3.9 * 8.854214871e-12 / tox;
262        let u0 = m.u0.unwrap_or(600.0);
263        let mut kp = m.kp;
264        if kp.is_none() {
265            kp = Some(u0 * 1e-4 /* m²/cm² */ * oxide_cap_factor);
266        }
267        let mut phi = m.phi;
268        let mut gamma = m.gamma;
269        let mut vto = m.vto;
270        let mut xd = 0.0;
271        let mut nsub = 0.0;
272        if let Some(nsub_given) = m.nsub {
273            if nsub_given * 1e6 /* cm³/m³ */ > 1.45e16 {
274                nsub = nsub_given;
275                if phi.is_none() {
276                    phi = Some(f64::max(
277                        0.1,
278                        2.0 * vtnom * f64::ln(nsub_given * 1e6 / 1.45e16),
279                    ));
280                }
281                let phi_v = phi.unwrap_or(0.6);
282                let fermis = typ * 0.5 * phi_v;
283                let tpg = m.tpg.unwrap_or(1.0);
284                let mut wkfng = 3.2;
285                if tpg != 0.0 {
286                    let fermig = typ * tpg * 0.5 * egfet1;
287                    wkfng = 3.25 + 0.5 * egfet1 - fermig;
288                }
289                let wkfngs = wkfng - (3.25 + 0.5 * egfet1 + fermis);
290                if gamma.is_none() {
291                    gamma = Some(
292                        f64::sqrt(2.0 * 11.70 * 8.854214871e-12 * CHARGE * nsub_given * 1e6)
293                            / oxide_cap_factor,
294                    );
295                }
296                if vto.is_none() {
297                    let nss = m.nss.unwrap_or(0.0);
298                    let vfb = wkfngs - nss * 1e4 /* cm²/m² */ * CHARGE / oxide_cap_factor;
299                    vto = Some(vfb + typ * (gamma.unwrap_or(0.0) * phi_v.sqrt() + phi_v));
300                }
301                xd = f64::sqrt((EPSSIL + EPSSIL) / (CHARGE * nsub_given * 1e6));
302            } else {
303                return Err(format!("{name}: Nsub < Ni"));
304            }
305        }
306        let kp = kp.unwrap_or(2e-5);
307        let phi = phi.unwrap_or(0.6);
308        let gamma = gamma.unwrap_or(0.0);
309        let vto = vto.unwrap_or(0.0);
310
311        // Per-instance temperature block (identical to MOS1's).
312        let vt = temp * KOVERQ;
313        let ratio = temp / tnom;
314        let fact2 = temp / REFTEMP;
315        let kt = temp * BOLTZ;
316        let egfet_t = egfet(temp);
317        let arg = -egfet_t / (kt + kt) + 1.1150877 / (BOLTZ * (REFTEMP + REFTEMP));
318        let pbfact = -2.0 * vt * (1.5 * f64::ln(fact2) + CHARGE * arg);
319
320        if g.l - 2.0 * m.ld <= 0.0 {
321            notes.push(format!("{name}: effective channel length less than zero"));
322        }
323        let ratio4 = ratio * ratio.sqrt();
324        let t_transconductance = kp / ratio4;
325        let phio = (phi - pbfact1) / fact1;
326        let t_phi = fact2 * phio + pbfact;
327        let t_vbi =
328            vto - typ * (gamma * phi.sqrt()) + 0.5 * (egfet1 - egfet_t) + typ * 0.5 * (t_phi - phi);
329        let t_vto = t_vbi + typ * gamma * t_phi.sqrt();
330        let t_sat_cur = m.is * f64::exp(-egfet_t / vt + egfet1 / vtnom);
331        let t_sat_cur_dens = m.js * f64::exp(-egfet_t / vt + egfet1 / vtnom);
332
333        let pbo = (m.pb - pbfact1) / fact1;
334        let gmaold = (m.pb - pbo) / pbo;
335        let mut capfact = 1.0 / (1.0 + m.mj * (4e-4 * (tnom - REFTEMP) - gmaold));
336        let mut t_cbd = m.cbd.unwrap_or(0.0) * capfact;
337        let mut t_cbs = m.cbs.unwrap_or(0.0) * capfact;
338        let mut t_cj = m.cj.unwrap_or(0.0) * capfact;
339        capfact = 1.0 / (1.0 + m.mjsw * (4e-4 * (tnom - REFTEMP) - gmaold));
340        let mut t_cjsw = m.cjsw.unwrap_or(0.0) * capfact;
341        let t_bulk_pot = fact2 * pbo + pbfact;
342        let gmanew = (t_bulk_pot - pbo) / pbo;
343        capfact = 1.0 + m.mj * (4e-4 * (temp - REFTEMP) - gmanew);
344        t_cbd *= capfact;
345        t_cbs *= capfact;
346        t_cj *= capfact;
347        capfact = 1.0 + m.mjsw * (4e-4 * (temp - REFTEMP) - gmanew);
348        t_cjsw *= capfact;
349        let t_dep_cap = m.fc * t_bulk_pot;
350
351        let (drain_vcrit, source_vcrit) = if t_sat_cur_dens == 0.0 || g.ad == 0.0 || g.as_ == 0.0 {
352            let v = vt * f64::ln(vt / (ROOT2 * t_sat_cur));
353            (v, v)
354        } else {
355            (
356                vt * f64::ln(vt / (ROOT2 * t_sat_cur_dens * g.ad)),
357                vt * f64::ln(vt / (ROOT2 * t_sat_cur_dens * g.as_)),
358            )
359        };
360
361        let czbd = if m.cbd.is_some() {
362            t_cbd
363        } else if m.cj.is_some() {
364            t_cj * g.ad
365        } else {
366            0.0
367        };
368        let czbdsw = if m.cjsw.is_some() { t_cjsw * g.pd } else { 0.0 };
369        let arg = 1.0 - m.fc;
370        let sarg = f64::exp(-m.mj * f64::ln(arg));
371        let sargsw = f64::exp(-m.mjsw * f64::ln(arg));
372        let f2d = czbd * (1.0 - m.fc * (1.0 + m.mj)) * sarg / arg
373            + czbdsw * (1.0 - m.fc * (1.0 + m.mjsw)) * sargsw / arg;
374        let f3d =
375            czbd * m.mj * sarg / arg / t_bulk_pot + czbdsw * m.mjsw * sargsw / arg / t_bulk_pot;
376        let f4d = czbd * t_bulk_pot * (1.0 - arg * sarg) / (1.0 - m.mj)
377            + czbdsw * t_bulk_pot * (1.0 - arg * sargsw) / (1.0 - m.mjsw)
378            - f3d / 2.0 * (t_dep_cap * t_dep_cap)
379            - t_dep_cap * f2d;
380        let czbs = if m.cbs.is_some() {
381            t_cbs
382        } else if m.cj.is_some() {
383            t_cj * g.as_
384        } else {
385            0.0
386        };
387        let czbssw = if m.cjsw.is_some() { t_cjsw * g.ps } else { 0.0 };
388        let f2s = czbs * (1.0 - m.fc * (1.0 + m.mj)) * sarg / arg
389            + czbssw * (1.0 - m.fc * (1.0 + m.mjsw)) * sargsw / arg;
390        let f3s =
391            czbs * m.mj * sarg / arg / t_bulk_pot + czbssw * m.mjsw * sargsw / arg / t_bulk_pot;
392        let f4s = czbs * t_bulk_pot * (1.0 - arg * sarg) / (1.0 - m.mj)
393            + czbssw * t_bulk_pot * (1.0 - arg * sargsw) / (1.0 - m.mjsw)
394            - f3s / 2.0 * (t_dep_cap * t_dep_cap)
395            - t_dep_cap * f2s;
396
397        let series = |r: Option<f64>, squares: f64| match (r, m.rsh) {
398            (Some(r), _) => {
399                if r != 0.0 {
400                    1.0 / r
401                } else {
402                    0.0
403                }
404            }
405            (None, Some(rsh)) => {
406                if rsh != 0.0 {
407                    1.0 / (rsh * squares)
408                } else {
409                    0.0
410                }
411            }
412            (None, None) => 0.0,
413        };
414
415        Ok(Mos2Temp {
416            temp,
417            type_: typ,
418            vt,
419            lat_diff: m.ld,
420            kf: m.kf,
421            af: m.af,
422            nlev: m.nlev,
423            gdsnoi: m.gdsnoi,
424            cgso: m.cgso,
425            cgdo: m.cgdo,
426            cgbo: m.cgbo,
427            oxide_cap_factor,
428            gamma,
429            lambda: m.lambda,
430            mj: m.mj,
431            mjsw: m.mjsw,
432            delta: m.delta,
433            xj: m.xj,
434            nfs: m.nfs,
435            ucrit: m.ucrit,
436            uexp: m.uexp,
437            vmax: m.vmax,
438            neff: m.neff,
439            u0,
440            nsub,
441            xd,
442            t_transconductance,
443            t_phi,
444            t_vbi,
445            t_vto,
446            t_sat_cur,
447            t_sat_cur_dens,
448            t_bulk_pot,
449            t_dep_cap,
450            drain_vcrit,
451            source_vcrit,
452            cbd: czbd,
453            cbdsw: czbdsw,
454            cbs: czbs,
455            cbssw: czbssw,
456            f2d,
457            f3d,
458            f4d,
459            f2s,
460            f3s,
461            f4s,
462            drain_conductance: series(m.rd, g.nrd),
463            source_conductance: series(m.rs, g.nrs),
464            notes,
465        })
466    }
467}
468
469// State offsets (`mos2defs.h` — identical to MOS1's).
470const VBD: usize = 0;
471const VBS: usize = 1;
472const VGS: usize = 2;
473const VDS: usize = 3;
474const CAPGS: usize = 4;
475const QGS: usize = 5;
476const CQGS: usize = 6;
477const CAPGD: usize = 7;
478const QGD: usize = 8;
479const CQGD: usize = 9;
480const CAPGB: usize = 10;
481const QGB: usize = 11;
482const CQGB: usize = 12;
483const QBD: usize = 13;
484const CQBD: usize = 14;
485const QBS: usize = 15;
486const CQBS: usize = 16;
487pub(crate) const MOS2_NUM_STATES: usize = 17;
488
489// Slot order (matches `mos2set.c`'s TSTALLOC list — same as MOS1).
490const D_D: usize = 0;
491const G_G: usize = 1;
492const S_S: usize = 2;
493const B_B: usize = 3;
494const DP_DP: usize = 4;
495const SP_SP: usize = 5;
496const D_DP: usize = 6;
497const G_B: usize = 7;
498const G_DP: usize = 8;
499const G_SP: usize = 9;
500const S_SP: usize = 10;
501const B_DP: usize = 11;
502const B_SP: usize = 12;
503const DP_SP: usize = 13;
504const DP_D: usize = 14;
505const B_G: usize = 15;
506const DP_G: usize = 16;
507const SP_G: usize = 17;
508const SP_S: usize = 18;
509const DP_B: usize = 19;
510const SP_B: usize = 20;
511const SP_DP: usize = 21;
512
513/// Baum-quartic sign tables (`mos2load.c`).
514const SIG1: [f64; 4] = [1.0, -1.0, 1.0, -1.0];
515const SIG2: [f64; 4] = [1.0, 1.0, -1.0, -1.0];
516
517pub(crate) struct Mos2I {
518    drain: Var,
519    gate: Var,
520    source: Var,
521    bulk: Var,
522    d_p: Var,
523    s_p: Var,
524    t: Mos2Temp,
525    geom: MosGeom,
526    off: bool,
527    ic_vds: f64,
528    ic_vgs: f64,
529    ic_vbs: f64,
530    ic_given: crate::devices::IcGiven,
531    st: usize,
532    s: [Slot; 22],
533    // Values that persist between iterations (`MOS2instance`).
534    mode: f64,
535    von: f64,
536    vdsat: f64,
537    gm: f64,
538    gds: f64,
539    gmbs: f64,
540    gbd: f64,
541    gbs: f64,
542    capbd: f64,
543    capbs: f64,
544    cd: f64,
545    cbs: f64,
546    cbd: f64,
547    /// `.disto` Taylor coefficients (`MOS2dSetup`).
548    dist: MosDistoCoeffs,
549}
550
551impl Mos2I {
552    #[allow(clippy::too_many_arguments)]
553    pub fn new(
554        nodes: [Var; 4],  // d g s b
555        primes: [Var; 2], // d' s'
556        t: Mos2Temp,
557        geom: MosGeom,
558        off: bool,
559        ic: (f64, f64, f64), // (vds, vgs, vbs)
560        st: usize,
561    ) -> Self {
562        Mos2I {
563            drain: nodes[0],
564            gate: nodes[1],
565            source: nodes[2],
566            bulk: nodes[3],
567            d_p: primes[0],
568            s_p: primes[1],
569            t,
570            geom,
571            off,
572            ic_vds: ic.0,
573            ic_vgs: ic.1,
574            ic_vbs: ic.2,
575            ic_given: crate::devices::IcGiven::ALL,
576            st,
577            s: [TRASH; 22],
578            mode: 1.0,
579            von: 0.0,
580            vdsat: 0.0,
581            gm: 0.0,
582            gds: 0.0,
583            gmbs: 0.0,
584            gbd: 0.0,
585            gbs: 0.0,
586            capbd: 0.0,
587            capbs: 0.0,
588            cd: 0.0,
589            cbs: 0.0,
590            cbd: 0.0,
591            dist: MosDistoCoeffs::default(),
592        }
593    }
594
595    /// The Grove-Frohman current evaluation (the `moseq2` block of
596    /// `mos2load.c`). Returns `(cdrain, von, vdsat)` in the mode-
597    /// normalized space and updates `gm`/`gds`/`gmbs` exactly where
598    /// the C writes the instance fields — including the paths that
599    /// leave some of them untouched.
600    #[allow(clippy::too_many_arguments, clippy::too_many_lines)]
601    fn eval(
602        &mut self,
603        vbs: f64,
604        vbd: f64,
605        vgs: f64,
606        vgd: f64,
607        vds: f64,
608        beta: f64,
609        oxide_cap: f64,
610        eff_l: f64,
611        vt: f64,
612    ) -> (f64, f64, f64) {
613        let t = &self.t;
614        let typ = t.type_;
615        let mode = self.mode;
616        let mut xlamda = t.lambda;
617        let lvbs = if mode == 1.0 { vbs } else { vbd };
618        let lvds = mode * vds;
619        let lvgs = if mode == 1.0 { vgs } else { vgd };
620        let phi_min_vbs = t.t_phi - lvbs;
621
622        // sqrt(phi − vbs) and derivatives, with the series form for
623        // forward bias.
624        let sphi = t.t_phi.sqrt();
625        let sphi3 = t.t_phi * sphi;
626        let (sarg, dsrgdb, d2sdb2);
627        if lvbs <= 0.0 {
628            sarg = phi_min_vbs.sqrt();
629            dsrgdb = -0.5 / sarg;
630            d2sdb2 = 0.5 * dsrgdb / phi_min_vbs;
631        } else {
632            sarg = sphi / (1.0 + 0.5 * lvbs / t.t_phi);
633            let tmp = sarg / sphi3;
634            dsrgdb = -0.5 * sarg * tmp;
635            d2sdb2 = -dsrgdb * tmp;
636        }
637        let (mut barg, mut dbrgdb, d2bdb2);
638        if lvds - lvbs >= 0.0 {
639            barg = (phi_min_vbs + lvds).sqrt();
640            dbrgdb = -0.5 / barg;
641            d2bdb2 = 0.5 * dbrgdb / (phi_min_vbs + lvds);
642        } else {
643            barg = sphi / (1.0 + 0.5 * (lvbs - lvds) / t.t_phi);
644            let tmp = barg / sphi3;
645            dbrgdb = -0.5 * barg * tmp;
646            d2bdb2 = -dbrgdb * tmp;
647        }
648
649        // Threshold voltage: narrow-channel and short-channel
650        // corrections.
651        let factor = 0.125 * t.delta * 2.0 * std::f64::consts::PI * EPSSIL / oxide_cap * eff_l;
652        let eta = 1.0 + factor;
653        let vbin = t.t_vbi * typ + factor * phi_min_vbs;
654        let (gamasd, dgddvb, dgdvds, dgddb2);
655        if t.gamma > 0.0 || t.nsub > 0.0 {
656            let xwd = t.xd * barg;
657            let xws = t.xd * sarg;
658            let mut argss = 0.0;
659            let mut argsd = 0.0;
660            let mut dbargs = 0.0;
661            let mut dbargd = 0.0;
662            let mut dgdvds_l = 0.0;
663            let mut dgddb2_l = 0.0;
664            let mut args = 0.0;
665            let mut argd = 0.0;
666            let mut argxs = 0.0;
667            let mut argxd = 0.0;
668            if t.xj > 0.0 {
669                let tmp = 2.0 / t.xj;
670                argxs = 1.0 + xws * tmp;
671                argxd = 1.0 + xwd * tmp;
672                args = argxs.sqrt();
673                argd = argxd.sqrt();
674                let tmp = 0.5 * t.xj / eff_l;
675                argss = tmp * (args - 1.0);
676                argsd = tmp * (argd - 1.0);
677            }
678            gamasd = t.gamma * (1.0 - argss - argsd);
679            let dbxwd = t.xd * dbrgdb;
680            let dbxws = t.xd * dsrgdb;
681            if t.xj > 0.0 {
682                let tmp = 0.5 / eff_l;
683                dbargs = tmp * dbxws / args;
684                dbargd = tmp * dbxwd / argd;
685                let dasdb2 =
686                    -t.xd * (d2sdb2 + dsrgdb * dsrgdb * t.xd / (t.xj * argxs)) / (eff_l * args);
687                let daddb2 =
688                    -t.xd * (d2bdb2 + dbrgdb * dbrgdb * t.xd / (t.xj * argxd)) / (eff_l * argd);
689                dgddb2_l = -0.5 * t.gamma * (dasdb2 + daddb2);
690            }
691            dgddvb = -t.gamma * (dbargs + dbargd);
692            if t.xj > 0.0 {
693                let ddxwd = -dbxwd;
694                dgdvds_l = -t.gamma * 0.5 * ddxwd / (eff_l * argd);
695            }
696            dgdvds = dgdvds_l;
697            dgddb2 = dgddb2_l;
698        } else {
699            gamasd = t.gamma;
700            dgddvb = 0.0;
701            dgdvds = 0.0;
702            dgddb2 = 0.0;
703        }
704
705        let cdrain;
706        let mut von = vbin + gamasd * sarg;
707        let vth = von;
708        let mut vdsat = 0.0;
709        // `gm`/`gmbs` keep their previous-iteration values on the
710        // one path the C leaves them untouched; `gds` and `cdrain`
711        // are written on every path.
712        let mut gm = self.gm;
713        let gds;
714        let mut gmbs = self.gmbs;
715
716        'doneval: {
717            let mut xn = 1.0;
718            let mut argg = 0.0;
719            let vgst;
720            if t.nfs != 0.0 && oxide_cap != 0.0 {
721                let cfs = CHARGE * t.nfs * 1e4 /* cm²/m² */;
722                let cdonco = -(gamasd * dsrgdb + dgddvb * sarg) + factor;
723                xn = 1.0 + cfs / oxide_cap * self.geom.w * eff_l + cdonco;
724                let tmp = vt * xn;
725                von += tmp;
726                argg = 1.0 / tmp;
727                vgst = lvgs - von;
728            } else {
729                vgst = lvgs - von;
730                // ngspice (alansfixes) draws this boundary at
731                // `vbin`, which zeroes the current but leaves a
732                // negative gm in (vbin, von]; its Newton only
733                // survives that with ngspice's dynamic gmin
734                // stepping. The currents are the same either way,
735                // so Dune keeps the 3f5 boundary
736                // (`corpus/KNOWN_DIFFS.md`).
737                if lvgs <= von {
738                    // Cutoff region.
739                    gds = 0.0;
740                    gm = 0.0;
741                    gmbs = 0.0;
742                    cdrain = 0.0;
743                    break 'doneval;
744                }
745            }
746
747            // More useful quantities.
748            let sarg3 = sarg * sarg * sarg;
749            let sbiarg = t.t_bulk_pot.sqrt();
750            let mut gammad = gamasd;
751            let mut dgdvbs = dgddvb;
752            let mut body = barg * barg * barg - sarg3;
753            let mut gdbdv = 2.0 * gammad * (barg * barg * dbrgdb - sarg * sarg * dsrgdb);
754            let mut dodvbs = -factor + dgdvbs * sarg + gammad * dsrgdb;
755            let mut dxndvb = 0.0;
756            let mut dxndvd = 0.0;
757            let mut dodvds = 0.0;
758            if t.nfs != 0.0 && oxide_cap != 0.0 {
759                dxndvb = 2.0 * dgdvbs * dsrgdb + gammad * d2sdb2 + dgddb2 * sarg;
760                dodvbs += vt * dxndvb;
761                dxndvd = dgdvds * dsrgdb;
762                dodvds = dgdvds * sarg + vt * dxndvd;
763            }
764
765            // Effective mobility (lines 400/410/500 of the C).
766            let (ufact, ueff, dudvgs, dudvds, dudvbs);
767            let crit = t.ucrit * 100.0 /* cm/m */ * EPSSIL / t.oxide_cap_factor;
768            if oxide_cap > 0.0 && vgst > crit {
769                let udenom = vgst;
770                ufact = f64::exp(t.uexp * f64::ln(crit / udenom));
771                ueff = t.u0 * 1e-4 /* m²/cm² */ * ufact;
772                dudvgs = -ufact * t.uexp / udenom;
773                dudvds = 0.0;
774                dudvbs = t.uexp * ufact * dodvbs / vgst;
775            } else {
776                ufact = 1.0;
777                ueff = t.u0 * 1e-4;
778                dudvgs = 0.0;
779                dudvds = 0.0;
780                dudvbs = 0.0;
781            }
782
783            // Saturation voltage (Grove-Frohman), then Baum's
784            // scattering-velocity theory when VMAX is given.
785            let mut vgsx = lvgs;
786            gammad = gamasd / eta;
787            dgdvbs = dgddvb;
788            if t.nfs != 0.0 && oxide_cap != 0.0 {
789                vgsx = f64::max(lvgs, von);
790            }
791            let mut dsdvgs;
792            let mut dsdvbs;
793            if gammad > 0.0 {
794                let gammd2 = gammad * gammad;
795                let argv = (vgsx - vbin) / eta + phi_min_vbs;
796                if argv <= 0.0 {
797                    vdsat = 0.0;
798                    dsdvgs = 0.0;
799                    dsdvbs = 0.0;
800                } else {
801                    let arg = (1.0 + 4.0 * argv / gammd2).sqrt();
802                    vdsat = (vgsx - vbin) / eta + gammd2 * (1.0 - arg) / 2.0;
803                    vdsat = f64::max(vdsat, 0.0);
804                    dsdvgs = (1.0 - 1.0 / arg) / eta;
805                    dsdvbs = (gammad * (1.0 - arg) + 2.0 * argv / (gammad * arg)) / eta * dgdvbs
806                        + 1.0 / arg
807                        + factor * dsdvgs;
808                }
809            } else {
810                vdsat = f64::max((vgsx - vbin) / eta, 0.0);
811                dsdvgs = 1.0;
812                dsdvbs = 0.0;
813            }
814            if t.vmax > 0.0 {
815                // (the C computes gammd2 here too and never uses it)
816                let v1 = (vgsx - vbin) / eta + phi_min_vbs;
817                let v2 = phi_min_vbs;
818                let xv = t.vmax * eff_l / ueff;
819                let a1 = gammad / 0.75;
820                let b1 = -2.0 * (v1 + xv);
821                let c1 = -2.0 * gammad * xv;
822                let d1 = 2.0 * v1 * (v2 + xv) - v2 * v2 - 4.0 / 3.0 * gammad * sarg3;
823                let a = -b1;
824                let b = a1 * c1 - 4.0 * d1;
825                let c = -d1 * (a1 * a1 - 4.0 * b1) - c1 * c1;
826                let r = -a * a / 3.0 + b;
827                let s = 2.0 * a * a * a / 27.0 - a * b / 3.0 + c;
828                let r3 = r * r * r;
829                let s2 = s * s;
830                let p = s2 / 4.0 + r3 / 27.0;
831                let p0 = p.abs();
832                let p2 = p0.sqrt();
833                let y3 = if p < 0.0 {
834                    let ro = (s2 / 4.0 + p0).sqrt();
835                    let ro = f64::exp(f64::ln(ro) / 3.0);
836                    let fi = f64::atan(-2.0 * p2 / s);
837                    2.0 * ro * f64::cos(fi / 3.0) - a / 3.0
838                } else {
839                    let p3 = f64::exp(f64::ln((-s / 2.0 + p2).abs()) / 3.0);
840                    let p4 = f64::exp(f64::ln((-s / 2.0 - p2).abs()) / 3.0);
841                    p3 + p4 - a / 3.0
842                };
843                let a3 = (a1 * a1 / 4.0 - b1 + y3).sqrt();
844                let b3 = (y3 * y3 / 4.0 - d1).sqrt();
845                let mut x4 = [0.0f64; 8];
846                let mut iknt = 0usize;
847                for i in 0..4 {
848                    let a4 = a1 / 2.0 + SIG1[i] * a3;
849                    let b4 = y3 / 2.0 + SIG2[i] * b3;
850                    let delta4 = a4 * a4 / 4.0 - b4;
851                    if delta4 < 0.0 {
852                        continue;
853                    }
854                    let tmp = delta4.sqrt();
855                    x4[iknt] = -a4 / 2.0 + tmp;
856                    iknt += 1;
857                    x4[iknt] = -a4 / 2.0 - tmp;
858                    iknt += 1;
859                }
860                let mut jknt = 0usize;
861                let mut xvalid = 0.0;
862                for &x in x4.iter().take(iknt) {
863                    if x <= 0.0 {
864                        continue;
865                    }
866                    let mut poly4 = x * x * x * x + a1 * x * x * x;
867                    poly4 += b1 * x * x + c1 * x + d1;
868                    if poly4.abs() > 1.0e-6 {
869                        continue;
870                    }
871                    jknt += 1;
872                    if jknt <= 1 {
873                        xvalid = x;
874                    }
875                    if x > xvalid {
876                        continue;
877                    }
878                    xvalid = x;
879                }
880                if jknt > 0 {
881                    vdsat = xvalid * xvalid - phi_min_vbs;
882                }
883            }
884
885            // Effective channel length and its derivatives.
886            let mut dldvgs = 0.0;
887            let mut dldvds = 0.0;
888            let mut dldvbs = 0.0;
889            let mut bsarg = 0.0;
890            let mut dbsrdb = 0.0;
891            let mut bodys = 0.0;
892            let mut gdbdvs = 0.0;
893            if lvds != 0.0 {
894                gammad = gamasd;
895                if lvbs - vdsat <= 0.0 {
896                    bsarg = (vdsat + phi_min_vbs).sqrt();
897                    dbsrdb = -0.5 / bsarg;
898                } else {
899                    bsarg = sphi / (1.0 + 0.5 * (lvbs - vdsat) / t.t_phi);
900                    dbsrdb = -0.5 * bsarg * bsarg / sphi3;
901                }
902                bodys = bsarg * bsarg * bsarg - sarg3;
903                gdbdvs = 2.0 * gammad * (bsarg * bsarg * dbsrdb - sarg * sarg * dsrgdb);
904                'line610: {
905                    let dldsat;
906                    if t.vmax <= 0.0 {
907                        if t.nsub == 0.0 || xlamda > 0.0 {
908                            break 'line610;
909                        }
910                        let argv = (lvds - vdsat) / 4.0;
911                        let sargv = (1.0 + argv * argv).sqrt();
912                        let arg = (argv + sargv).sqrt();
913                        let xlfact = t.xd / (eff_l * lvds);
914                        xlamda = xlfact * arg;
915                        dldsat = lvds * xlamda / (8.0 * sargv);
916                    } else {
917                        let argv = (vgsx - vbin) / eta - vdsat;
918                        let xdv = t.xd / t.neff.sqrt();
919                        let xlv = t.vmax * xdv / (2.0 * ueff);
920                        let vqchan = argv - gammad * bsarg;
921                        let dqdsat = -1.0 + gammad * dbsrdb;
922                        let vl = t.vmax * eff_l;
923                        let dfunds = vl * dqdsat - ueff * vqchan;
924                        let dfundg = (vl - ueff * vdsat) / eta;
925                        let dfundb = -vl * (1.0 + dqdsat - factor / eta)
926                            + ueff * (gdbdvs - dgdvbs * bodys / 1.5) / eta;
927                        dsdvgs = -dfundg / dfunds;
928                        dsdvbs = -dfundb / dfunds;
929                        if t.nsub == 0.0 || xlamda > 0.0 {
930                            break 'line610;
931                        }
932                        let argv = f64::max(lvds - vdsat, 0.0);
933                        let xls = (xlv * xlv + argv).sqrt();
934                        let xlfact = xdv / (eff_l * lvds);
935                        xlamda = xlfact * (xls - xlv);
936                        dldsat = xdv / (2.0 * xls) / eff_l;
937                    }
938                    dldvgs = dldsat * dsdvgs;
939                    dldvds = -xlamda + dldsat;
940                    dldvbs = dldsat * dsdvbs;
941                }
942            }
943
944            // Punch-through limit on channel shortening.
945            let mut xwb = t.xd * sbiarg;
946            let xld = eff_l - xwb;
947            let mut clfact = 1.0 - xlamda * lvds;
948            dldvds = -xlamda - dldvds;
949            let mut xleff = eff_l * clfact;
950            let deltal = xlamda * lvds * eff_l;
951            if t.nsub == 0.0 {
952                xwb = 0.25e-6;
953            }
954            if xleff < xwb {
955                xleff = xwb / (1.0 + (deltal - xld) / xwb);
956                clfact = xleff / eff_l;
957                let dfact = xleff * xleff / (xwb * xwb);
958                dldvgs *= dfact;
959                dldvds *= dfact;
960                dldvbs *= dfact;
961            }
962
963            // Effective beta.
964            let beta1 = beta * ufact / clfact;
965
966            // Mode of operation.
967            gammad = gamasd;
968            dgdvbs = dgddvb;
969            if lvds <= 1.0e-10 {
970                if lvgs <= von {
971                    if t.nfs == 0.0 || oxide_cap == 0.0 {
972                        gds = 0.0;
973                    } else {
974                        gds = beta1 * (von - vbin - gammad * sarg) * f64::exp(argg * (lvgs - von));
975                    }
976                } else {
977                    gds = beta1 * (lvgs - vbin - gammad * sarg);
978                }
979                gm = 0.0;
980                gmbs = 0.0;
981                cdrain = 0.0;
982                break 'doneval;
983            }
984
985            if lvgs <= von {
986                // Subthreshold region.
987                if vdsat <= 0.0 {
988                    gds = 0.0;
989                    if lvgs > vth {
990                        // The C jumps to `doneval` without setting
991                        // cdrain (reads stale memory); we use 0.
992                        cdrain = 0.0;
993                        break 'doneval;
994                    }
995                    gm = 0.0;
996                    gmbs = 0.0;
997                    cdrain = 0.0;
998                    break 'doneval;
999                }
1000                let vdson = f64::min(vdsat, lvds);
1001                if lvds > vdsat {
1002                    barg = bsarg;
1003                    #[allow(unused_assignments)] // kept from the C
1004                    {
1005                        dbrgdb = dbsrdb;
1006                    }
1007                    body = bodys;
1008                    gdbdv = gdbdvs;
1009                }
1010                let cdson =
1011                    beta1 * ((von - vbin - eta * vdson * 0.5) * vdson - gammad * body / 1.5);
1012                let didvds = beta1 * (von - vbin - eta * vdson - gammad * barg);
1013                let mut gdson = -cdson * dldvds / clfact - beta1 * dgdvds * body / 1.5;
1014                if lvds < vdsat {
1015                    gdson += didvds;
1016                }
1017                let mut gbson = -cdson * dldvbs / clfact
1018                    + beta1 * (dodvbs * vdson + factor * vdson - dgdvbs * body / 1.5 - gdbdv);
1019                if lvds > vdsat {
1020                    gbson += didvds * dsdvbs;
1021                }
1022                let expg = f64::exp(argg * (lvgs - von));
1023                cdrain = cdson * expg;
1024                let gmw = cdrain * argg;
1025                gm = gmw;
1026                if lvds > vdsat {
1027                    gm = gmw + didvds * dsdvgs * expg;
1028                }
1029                let tmp = gmw * (lvgs - von) / xn;
1030                gds = gdson * expg - gm * dodvds - tmp * dxndvd;
1031                gmbs = gbson * expg - gm * dodvbs - tmp * dxndvb;
1032                break 'doneval;
1033            }
1034
1035            if lvds <= vdsat {
1036                // Linear region.
1037                cdrain = beta1 * ((lvgs - vbin - eta * lvds / 2.0) * lvds - gammad * body / 1.5);
1038                let arg = cdrain * (dudvgs / ufact - dldvgs / clfact);
1039                gm = arg + beta1 * lvds;
1040                let arg = cdrain * (dudvds / ufact - dldvds / clfact);
1041                gds =
1042                    arg + beta1 * (lvgs - vbin - eta * lvds - gammad * barg - dgdvds * body / 1.5);
1043                let arg = cdrain * (dudvbs / ufact - dldvbs / clfact);
1044                gmbs = arg - beta1 * (gdbdv + dgdvbs * body / 1.5 - factor * lvds);
1045            } else {
1046                // Saturation region.
1047                cdrain = beta1 * ((lvgs - vbin - eta * vdsat / 2.0) * vdsat - gammad * bodys / 1.5);
1048                let arg = cdrain * (dudvgs / ufact - dldvgs / clfact);
1049                gm = arg
1050                    + beta1 * vdsat
1051                    + beta1 * (lvgs - vbin - eta * vdsat - gammad * bsarg) * dsdvgs;
1052                gds = -cdrain * dldvds / clfact - beta1 * dgdvds * bodys / 1.5;
1053                let arg = cdrain * (dudvbs / ufact - dldvbs / clfact);
1054                gmbs = arg - beta1 * (gdbdvs + dgdvbs * bodys / 1.5 - factor * vdsat)
1055                    + beta1 * (lvgs - vbin - eta * vdsat - gammad * bsarg) * dsdvbs;
1056            }
1057        }
1058
1059        self.gm = gm;
1060        self.gds = gds;
1061        self.gmbs = gmbs;
1062        (cdrain, von, vdsat)
1063    }
1064}
1065
1066impl Mos2I {
1067    /// `MOS2dSetup` (`dev/mos2/mos2dset.c`): the Grove–Frohman drain
1068    /// current re-evaluated with the `Dderivs` algebra (p = vgs,
1069    /// q = vbs, r = vds of the mode-normalized device), then the
1070    /// shared diode/capacitance/type bookkeeping. A literal port,
1071    /// slips included, since ngspice-47 carries the same file: the
1072    /// mobility factor is 0 (not 1) below the critical field, so the
1073    /// drain current contributes nothing there; `argv` doubles
1074    /// `vgsx` instead of subtracting `vbin`; the linear-region gds
1075    /// loses its `lvgs`; the second Baum root adds `tmp`; the
1076    /// channel-length factor's seed has value `lvds` with slope −1.
1077    /// Where the C reads storage it never wrote (UB in both
1078    /// simulators) Dune reads zero, which is what the ngspice-47
1079    /// oracle does — measured: `d_xlamda` (any `LAMBDA > 0`, or no
1080    /// `NSUB`) and `d_dumarg` (`GAMMA > 0`) both reproduce the
1081    /// golden bit for bit as zero; the same rule covers `d_p0` when
1082    /// `p ≥ 0`, `d_dbargs`/`d_dbargd` without `XJ` and `d_cdrain`
1083    /// at the start of every instance (ngspice zeroes it once per
1084    /// call). `sphi` is set before its use in the `vds < vbs` branch
1085    /// (ngspice's HT 050523 fix), and the Baum root arrays are sized
1086    /// for every root (the C's `d_a4[3]`/`d_x4[3]` overrun).
1087    // A literal port keeps the C's dead stores.
1088    #[allow(
1089        unused_assignments,
1090        clippy::too_many_lines,
1091        clippy::cognitive_complexity
1092    )]
1093    fn disto_coeffs(&self, sol: &[f64], gmin: f64) -> MosDistoCoeffs {
1094        let t = &self.t;
1095        let g = &self.geom;
1096        let typ = t.type_;
1097        let vt = t.vt;
1098        let eff_l = g.l - 2.0 * t.lat_diff;
1099        let (drain_sat_cur, source_sat_cur) =
1100            if t.t_sat_cur_dens == 0.0 || g.ad == 0.0 || g.as_ == 0.0 {
1101                (t.t_sat_cur, t.t_sat_cur)
1102            } else {
1103                (t.t_sat_cur_dens * g.ad, t.t_sat_cur_dens * g.as_)
1104            };
1105        let beta = t.t_transconductance * g.w / eff_l;
1106        let oxide_cap = t.oxide_cap_factor * eff_l * g.w;
1107
1108        let vbs = typ * (sol[self.bulk] - sol[self.s_p]);
1109        let vgs = typ * (sol[self.gate] - sol[self.s_p]);
1110        let vds = typ * (sol[self.d_p] - sol[self.s_p]);
1111        let vbd = vbs - vds;
1112        let vgd = vgs - vds;
1113
1114        let gbs = bulk_diode(vbs, source_sat_cur, vt, gmin, typ);
1115        let gbd = bulk_diode(vbd, drain_sat_cur, vt, gmin, typ);
1116        let mode = if vds >= 0.0 { 1.0 } else { -1.0 };
1117
1118        let d_p = Dderivs::var_p();
1119        let d_q = Dderivs::var_q();
1120        let d_r = Dderivs::var_r();
1121        let d_zero = Dderivs::default();
1122        let mut d_cdrain = Dderivs::default();
1123
1124        let lvbs = if mode == 1.0 { vbs } else { vbd };
1125        let lvds = mode * vds;
1126        let lvgs = if mode == 1.0 { vgs } else { vgd };
1127        let phi_min_vbs = t.t_phi - lvbs;
1128        let mut xlamda = t.lambda;
1129        // Never written on most paths (see above).
1130        let mut d_xlamda = d_zero;
1131        let mut vdsat = 0.0;
1132        let mut von;
1133        let vgst;
1134        let mut sphi = 0.0;
1135        let mut sphi3 = 0.0;
1136        let mut doneval = false;
1137
1138        let d_phi_min_vbs = Dderivs {
1139            value: phi_min_vbs,
1140            d1_q: -1.0,
1141            ..d_zero
1142        };
1143
1144        // The C's `goto`s, as a labelled block: `break 'eval` is
1145        // `goto line1050`; falling off the end is `doneval`.
1146        'eval: {
1147            let (sarg, d_sarg, dsrgdb, d_dsrgdb);
1148            if lvbs <= 0.0 {
1149                sarg = f64::sqrt(phi_min_vbs);
1150                d_sarg = d_phi_min_vbs.sqrt();
1151                dsrgdb = -0.5 / sarg;
1152                d_dsrgdb = d_sarg.inv().times(-0.5);
1153            } else {
1154                sphi = f64::sqrt(t.t_phi);
1155                sphi3 = t.t_phi * sphi;
1156                sarg = sphi / (1.0 + 0.5 * lvbs / t.t_phi);
1157                let mut s = Dderivs { value: lvbs, ..d_q }.times(0.5 / t.t_phi);
1158                s.value += 1.0;
1159                d_sarg = s.inv().times(sphi);
1160                dsrgdb = -0.5 * sarg * sarg / sphi3;
1161                d_dsrgdb = d_sarg.mult(&d_sarg).times(-0.5 / sphi3);
1162            }
1163            let (mut barg, mut d_barg, dbrgdb, d_dbrgdb);
1164            if lvds - lvbs >= 0.0 {
1165                barg = f64::sqrt(phi_min_vbs + lvds);
1166                let mut b = d_phi_min_vbs;
1167                b.value += lvds;
1168                b.d1_r += 1.0;
1169                d_barg = b.sqrt();
1170                dbrgdb = -0.5 / barg;
1171                d_dbrgdb = d_barg.inv().times(-0.5);
1172            } else {
1173                sphi = f64::sqrt(t.t_phi);
1174                sphi3 = t.t_phi * sphi;
1175                barg = sphi / (1.0 + 0.5 * (lvbs - lvds) / t.t_phi);
1176                let mut b = Dderivs {
1177                    value: lvbs - lvds,
1178                    ..d_q
1179                };
1180                b.d1_r -= 1.0;
1181                let mut b = b.times(0.5 / t.t_phi);
1182                b.value += 1.0;
1183                d_barg = b.inv().times(sphi);
1184                dbrgdb = -0.5 * barg * barg / sphi3;
1185                d_dbrgdb = d_barg.mult(&d_barg).times(-0.5 / sphi3);
1186            }
1187
1188            // Threshold voltage: narrow-channel effect.
1189            let factor = 0.125 * t.delta * 2.0 * std::f64::consts::PI * EPSSIL / oxide_cap * eff_l;
1190            let eta = 1.0 + factor;
1191            let vbin = t.t_vbi * typ + factor * phi_min_vbs;
1192            let mut d_vbin = d_phi_min_vbs.times(factor);
1193            d_vbin.value += t.t_vbi * typ;
1194
1195            let (gamasd, d_gamasd, dgddvb, d_dgddvb);
1196            if t.gamma > 0.0 || t.nsub > 0.0 {
1197                let xwd = t.xd * barg;
1198                let xws = t.xd * sarg;
1199                let d_xwd = d_barg.times(t.xd);
1200                let d_xws = d_sarg.times(t.xd);
1201                let mut argss = 0.0;
1202                let mut argsd = 0.0;
1203                let mut d_argss = d_zero;
1204                let mut d_argsd = d_zero;
1205                let (mut args, mut argd) = (0.0, 0.0);
1206                let (mut d_args, mut d_argd) = (d_zero, d_zero);
1207                if t.xj > 0.0 {
1208                    let tmp = 2.0 / t.xj;
1209                    let argxs = 1.0 + xws * tmp;
1210                    let mut d_argxs = d_xws.times(tmp);
1211                    d_argxs.value += 1.0;
1212                    let argxd = 1.0 + xwd * tmp;
1213                    let mut d_argxd = d_xwd.times(tmp);
1214                    d_argxd.value += 1.0;
1215                    args = f64::sqrt(argxs);
1216                    d_args = d_argxs.sqrt();
1217                    argd = f64::sqrt(argxd);
1218                    d_argd = d_argxd.sqrt();
1219                    let tmp = 0.5 * t.xj / eff_l;
1220                    argss = tmp * (args - 1.0);
1221                    d_argss = d_args.times(tmp);
1222                    d_argss.value -= tmp;
1223                    argsd = tmp * (argd - 1.0);
1224                    d_argsd = d_argd.times(tmp);
1225                    d_argsd.value -= tmp;
1226                }
1227                gamasd = t.gamma * (1.0 - argss - argsd);
1228                let mut gs = d_argss.plus(&d_argsd);
1229                gs.value -= 1.0;
1230                d_gamasd = gs.times(-t.gamma);
1231                let dbxwd = t.xd * dbrgdb;
1232                let dbxws = t.xd * dsrgdb;
1233                let d_dbxwd = d_dbrgdb.times(t.xd);
1234                let d_dbxws = d_dsrgdb.times(t.xd);
1235                let (mut dbargs, mut dbargd) = (0.0, 0.0);
1236                let (mut d_dbargs, mut d_dbargd) = (d_zero, d_zero);
1237                if t.xj > 0.0 {
1238                    let tmp = 0.5 / eff_l;
1239                    dbargs = tmp * dbxws / args;
1240                    dbargd = tmp * dbxwd / argd;
1241                    d_dbargs = d_dbxws.div(&d_args).times(tmp);
1242                    d_dbargd = d_dbxwd.div(&d_argd).times(tmp);
1243                }
1244                dgddvb = -t.gamma * (dbargs + dbargd);
1245                d_dgddvb = d_dbargs.plus(&d_dbargd).times(-t.gamma);
1246            } else {
1247                gamasd = t.gamma;
1248                d_gamasd = Dderivs::constant(t.gamma);
1249                dgddvb = 0.0;
1250                d_dgddvb = d_zero;
1251            }
1252            von = vbin + gamasd * sarg;
1253            let mut d_von = d_gamasd.mult(&d_sarg).plus(&d_vbin);
1254            let mut d_vdsat = d_zero;
1255            let mut d_argg = d_zero;
1256            let mut d_vgst;
1257            if t.nfs != 0.0 && oxide_cap != 0.0 {
1258                let cfs = CHARGE * t.nfs * 1e4;
1259                let cdonco = -(gamasd * dsrgdb + dgddvb * sarg) + factor;
1260                let d_dummy = d_dgddvb.mult(&d_sarg);
1261                let mut d_cdonco = d_gamasd.mult(&d_dsrgdb).plus(&d_dummy).times(-1.0);
1262                d_cdonco.value += factor;
1263                let xn = 1.0 + cfs / oxide_cap * g.w * eff_l + cdonco;
1264                let d_xn = Dderivs {
1265                    value: xn,
1266                    ..d_cdonco
1267                };
1268                let tmp = vt * xn;
1269                let d_tmp = d_xn.times(vt);
1270                von += tmp;
1271                d_von = d_von.plus(&d_tmp);
1272                d_argg = d_tmp.inv();
1273                vgst = lvgs - von;
1274                d_vgst = d_von.times(-1.0).plus(&d_p);
1275                d_vgst.value += lvgs;
1276            } else {
1277                vgst = lvgs - von;
1278                d_vgst = d_von.times(-1.0).plus(&d_p);
1279                d_vgst.value += lvgs;
1280                if lvgs <= von {
1281                    // Cutoff.
1282                    break 'eval;
1283                }
1284            }
1285
1286            let sarg3 = sarg * sarg * sarg;
1287            let d_sarg3 = d_sarg.cube();
1288            let sbiarg = f64::sqrt(t.t_bulk_pot);
1289            let mut gammad;
1290            let mut d_gammad;
1291            let mut body = barg * barg * barg - sarg3;
1292            let mut d_body = d_sarg3.times(-1.0).plus(&d_barg.cube());
1293            let _ = body;
1294
1295            // Effective mobility (`line400`/`line410`).
1296            let (ufact, d_ufact, ueff, d_ueff);
1297            let tmp = t.ucrit * 100.0 * EPSSIL / t.oxide_cap_factor;
1298            if oxide_cap > 0.0 && vgst > tmp {
1299                let udenom = vgst;
1300                let d_udenom = d_vgst;
1301                ufact = f64::exp(t.uexp * f64::ln(tmp / udenom));
1302                let d_dummy = d_udenom.inv().times(tmp);
1303                d_ufact = d_dummy.pow(t.uexp);
1304                ueff = t.u0 * 1e-4 * ufact;
1305                d_ueff = d_ufact.times(t.u0 * 1e-4);
1306            } else {
1307                ufact = 0.0;
1308                d_ufact = d_zero;
1309                ueff = t.u0 * 1e-4;
1310                d_ueff = Dderivs::constant(ueff);
1311            }
1312            let _ = ufact;
1313
1314            // Saturation voltage (Grove–Frohman), `line500`.
1315            let mut vgsx = lvgs;
1316            let mut d_vgsx = Dderivs { value: lvgs, ..d_p };
1317            gammad = gamasd / eta;
1318            d_gammad = d_gamasd.times(1.0 / eta);
1319            if t.nfs != 0.0 && oxide_cap != 0.0 {
1320                vgsx = f64::max(lvgs, von);
1321                d_vgsx = if lvgs > von {
1322                    Dderivs { value: lvgs, ..d_p }
1323                } else {
1324                    d_von
1325                };
1326            }
1327            if gammad > 0.0 {
1328                let gammd2 = gammad * gammad;
1329                let d_gammd2 = d_gammad.mult(&d_gammad);
1330                let argv = (vgsx - vbin) / eta + phi_min_vbs;
1331                let d_argv = d_vgsx.plus(&d_vgsx).times(1.0 / eta).plus(&d_phi_min_vbs);
1332                if argv <= 0.0 {
1333                    vdsat = 0.0;
1334                    d_vdsat = d_zero;
1335                } else {
1336                    let mut d_arg = d_argv.div(&d_gammd2).times(4.0);
1337                    d_arg.value += 1.0;
1338                    let _ = (d_arg.sqrt(), gammd2);
1339                    // `d_dumarg`: its computation is commented out
1340                    // in the C (read as zero, see above).
1341                    let d_dumarg = d_zero;
1342                    let d_dummy = d_dumarg.times(-1.0).plus(&d_gammd2).times(0.5);
1343                    let v = d_phi_min_vbs.times(-1.0).plus(&d_argv);
1344                    d_vdsat = d_dummy.plus(&v);
1345                    vdsat = f64::max(vdsat, 0.0);
1346                }
1347            } else {
1348                vdsat = (vgsx - vbin) / eta;
1349                d_vdsat = d_vbin.times(-1.0).plus(&d_vgsx).times(1.0 / eta);
1350                vdsat = f64::max(vdsat, 0.0);
1351            }
1352            if t.vmax > 0.0 {
1353                // Baum's scattering-velocity saturation.
1354                let gammd2 = gammad * gammad;
1355                let d_gammd2 = d_gammad.mult(&d_gammad);
1356                let v1 = (vgsx - vbin) / eta + phi_min_vbs;
1357                let d_v1 = d_vbin
1358                    .times(-1.0)
1359                    .plus(&d_vgsx)
1360                    .times(1.0 / eta)
1361                    .plus(&d_phi_min_vbs);
1362                let v2 = phi_min_vbs;
1363                let d_v2 = d_phi_min_vbs;
1364                let xv = t.vmax * eff_l / ueff;
1365                let d_xv = d_ueff.inv().times(t.vmax * eff_l);
1366                let a1 = gammad / 0.75;
1367                let d_a1 = d_gammad.times(4.0 / 3.0);
1368                let b1 = -2.0 * (v1 + xv);
1369                let d_b1 = d_v1.plus(&d_xv).times(-2.0);
1370                let c1 = -2.0 * gammad * xv;
1371                let d_c1 = d_gammad.mult(&d_xv).times(-2.0);
1372                let d1 = 2.0 * v1 * (v2 + xv) - v2 * v2 - 4.0 / 3.0 * gammad * sarg3;
1373                let mut d_d1 = d_gammad.mult(&d_sarg3).times(4.0 / 3.0);
1374                d_d1 = d_d1.plus(&d_v2.mult(&d_v2)).times(-1.0);
1375                d_d1 = d_d1.plus(&d_v2.plus(&d_xv).mult(&d_v1).times(2.0));
1376                let a = -b1;
1377                let d_a = d_b1.times(-1.0);
1378                let b = a1 * c1 - 4.0 * d1;
1379                let d_b = d_d1.times(-4.0).plus(&d_a1.mult(&d_c1));
1380                let c = -d1 * (a1 * a1 - 4.0 * b1) - c1 * c1;
1381                let d_dummy = d_b1.times(-4.0).plus(&d_a1.mult(&d_a1));
1382                let d_c = d_dummy.mult(&d_d1).plus(&d_c1.mult(&d_c1)).times(-1.0);
1383                let r = -a * a / 3.0 + b;
1384                let d_arr = d_a.mult(&d_a).times(-1.0 / 3.0).plus(&d_b);
1385                let s = 2.0 * a * a * a / 27.0 - a * b / 3.0 + c;
1386                let d_s = d_a
1387                    .cube()
1388                    .times(2.0 / 27.0)
1389                    .plus(&d_c)
1390                    .plus(&d_a.mult(&d_b).times(-1.0 / 3.0));
1391                let r3 = r * r * r;
1392                let d_r3 = d_arr.cube();
1393                let s2 = s * s;
1394                let d_s2 = d_s.mult(&d_s);
1395                let p = s2 / 4.0 + r3 / 27.0;
1396                let d_pee = d_s2.times(0.25).plus(&d_r3.times(1.0 / 27.0));
1397                let p0 = p.abs();
1398                let d_p0 = if p < 0.0 { d_pee.times(-1.0) } else { d_zero };
1399                let p2 = f64::sqrt(p0);
1400                let d_p2 = d_p0.sqrt();
1401                let (y3, d_y3);
1402                if p < 0.0 {
1403                    let ro = f64::exp(f64::ln(f64::sqrt(s2 / 4.0 + p0)) / 3.0);
1404                    let d_ro = d_s2.times(0.25).plus(&d_p0).pow(1.0 / 6.0);
1405                    let fi = f64::atan(-2.0 * p2 / s);
1406                    let d_fi = d_p2.div(&d_s).times(-2.0).atan();
1407                    y3 = 2.0 * ro * f64::cos(fi / 3.0) - a / 3.0;
1408                    let d_dummy = d_fi.times(1.0 / 3.0).cos();
1409                    d_y3 = d_ro.mult(&d_dummy).times(2.0).plus(&d_a.times(-1.0 / 3.0));
1410                } else {
1411                    let mut p3 = -s / 2.0 + p2;
1412                    let mut d_p3 = d_s.times(-0.5).plus(&d_p2);
1413                    p3 = f64::exp(f64::ln(p3.abs()) / 3.0);
1414                    if p3 < 0.0 {
1415                        d_p3 = d_p3.times(-1.0);
1416                    }
1417                    d_p3 = d_p3.pow(1.0 / 3.0);
1418                    let mut p4 = -s / 2.0 - p2;
1419                    let mut d_p4 = d_s.times(0.5).plus(&d_p2);
1420                    if p4 < 0.0 {
1421                        d_p4 = d_p4.times(-1.0);
1422                    }
1423                    p4 = f64::exp(f64::ln(p4.abs()) / 3.0);
1424                    d_p4 = d_p4.pow(1.0 / 3.0);
1425                    y3 = p3 + p4 - a / 3.0;
1426                    d_y3 = d_a.times(-1.0 / 3.0).plus(&d_p4).plus(&d_p3);
1427                }
1428                let a3 = f64::sqrt(a1 * a1 / 4.0 - b1 + y3);
1429                let d_a3 = d_a1
1430                    .mult(&d_a1)
1431                    .times(0.25)
1432                    .plus(&d_y3)
1433                    .plus(&d_b1.times(-1.0))
1434                    .sqrt();
1435                let b3 = f64::sqrt(y3 * y3 / 4.0 - d1);
1436                let d_b3 = d_y3.mult(&d_y3).times(0.25).plus(&d_d1.times(-1.0)).sqrt();
1437                let mut x4 = [0.0; 8];
1438                let mut d_x4 = [d_zero; 8];
1439                let mut iknt = 0;
1440                for i in 0..4 {
1441                    let a4 = a1 / 2.0 + SIG1[i] * a3;
1442                    let d_a4 = d_a1.times(0.5).plus(&d_a3.times(SIG1[i]));
1443                    let b4 = y3 / 2.0 + SIG2[i] * b3;
1444                    let d_b4 = d_y3.times(0.5).plus(&d_b3.times(SIG2[i]));
1445                    let delta4 = a4 * a4 / 4.0 - b4;
1446                    let d_delta4 = d_a4.mult(&d_a4).times(0.25).plus(&d_b4.times(-1.0));
1447                    if delta4 < 0.0 {
1448                        continue;
1449                    }
1450                    let tmp = f64::sqrt(delta4);
1451                    let d_tmp = d_delta4.sqrt();
1452                    x4[iknt] = -a4 / 2.0 + tmp;
1453                    d_x4[iknt] = d_a4.times(-0.5).plus(&d_tmp);
1454                    iknt += 1;
1455                    x4[iknt] = -a4 / 2.0 - tmp;
1456                    d_x4[iknt] = d_a4.times(-0.5).plus(&d_tmp);
1457                    iknt += 1;
1458                }
1459                let mut jknt = 0;
1460                let mut xvalid = 0.0;
1461                let mut d_xvalid = d_zero;
1462                for j in 0..iknt {
1463                    if x4[j] <= 0.0 {
1464                        continue;
1465                    }
1466                    let x = x4[j];
1467                    let poly4 = x * x * x * x + a1 * x * x * x + b1 * x * x + c1 * x + d1;
1468                    if poly4.abs() > 1.0e-6 {
1469                        continue;
1470                    }
1471                    jknt += 1;
1472                    if jknt <= 1 {
1473                        xvalid = x;
1474                        d_xvalid = d_x4[j];
1475                    }
1476                    if x > xvalid {
1477                        continue;
1478                    }
1479                    xvalid = x;
1480                    d_xvalid = d_x4[j];
1481                }
1482                if jknt > 0 {
1483                    vdsat = xvalid * xvalid - phi_min_vbs;
1484                    d_vdsat = d_xvalid.mult(&d_xvalid).plus(&d_phi_min_vbs.times(-1.0));
1485                }
1486                let _ = (gammd2, d_gammd2, v2);
1487            }
1488
1489            // Effective channel length.
1490            let (mut bsarg, mut bodys) = (0.0, 0.0);
1491            let (mut d_bsarg, mut d_bodys) = (d_zero, d_zero);
1492            if lvds != 0.0 {
1493                gammad = gamasd;
1494                d_gammad = d_gamasd;
1495                if lvbs - vdsat <= 0.0 {
1496                    bsarg = f64::sqrt(vdsat + phi_min_vbs);
1497                    d_bsarg = d_vdsat.plus(&d_phi_min_vbs).sqrt();
1498                } else {
1499                    bsarg = sphi / (1.0 + 0.5 * (lvbs - vdsat) / t.t_phi);
1500                    let mut b = d_vdsat.times(-1.0);
1501                    b.value += lvbs;
1502                    b.d1_r += 1.0;
1503                    let mut b = b.times(0.5 / t.t_phi);
1504                    b.value += 1.0;
1505                    d_bsarg = b.inv().times(sphi);
1506                }
1507                bodys = bsarg * bsarg * bsarg - sarg3;
1508                d_bodys = d_bsarg.cube().plus(&d_sarg3.times(-1.0));
1509                if t.vmax <= 0.0 {
1510                    if t.nsub != 0.0 && xlamda <= 0.0 {
1511                        let argv = (lvds - vdsat) / 4.0;
1512                        let mut a = d_vdsat.times(-1.0);
1513                        a.value += lvds;
1514                        a.d1_r += 1.0;
1515                        let d_argv = a.times(0.25);
1516                        let sargv = f64::sqrt(1.0 + argv * argv);
1517                        let mut sv = d_argv.mult(&d_argv);
1518                        sv.value += 1.0;
1519                        let d_sargv = sv.sqrt();
1520                        let arg = f64::sqrt(argv + sargv);
1521                        let d_arg = d_sargv.plus(&d_argv).sqrt();
1522                        let xlfact = t.xd / (eff_l * lvds);
1523                        let d_xlfact = Dderivs { value: lvds, ..d_r }.inv().times(t.xd / eff_l);
1524                        xlamda = xlfact * arg;
1525                        d_xlamda = d_xlfact.mult(&d_arg);
1526                    }
1527                } else {
1528                    let xdv = t.xd / f64::sqrt(t.neff);
1529                    let xlv = t.vmax * xdv / (2.0 * ueff);
1530                    let d_xlv = d_ueff.inv().times(t.vmax * xdv * 0.5);
1531                    if t.nsub != 0.0 && xlamda <= 0.0 {
1532                        let mut argv = lvds - vdsat;
1533                        let mut d_argv = d_vdsat.times(-1.0);
1534                        d_argv.value += lvds;
1535                        d_argv.d1_r += 1.0;
1536                        if argv < 0.0 {
1537                            d_argv = d_zero;
1538                        }
1539                        argv = f64::max(argv, 0.0);
1540                        let xls = f64::sqrt(xlv * xlv + argv);
1541                        let d_xls = d_xlv.mult(&d_xlv).plus(&d_argv).sqrt();
1542                        let xlfact = xdv / (eff_l * lvds);
1543                        let mut xf = d_r;
1544                        xf.value += lvds;
1545                        let d_xlfact = xf.inv().times(xdv / eff_l);
1546                        xlamda = xlfact * (xls - xlv);
1547                        d_xlamda = d_xlv.times(-1.0).plus(&d_xls).mult(&d_xlfact);
1548                    }
1549                }
1550            }
1551
1552            // `line610`: limit channel shortening at punch-through.
1553            let mut xwb = t.xd * sbiarg;
1554            let xld = eff_l - xwb;
1555            let mut clfact = 1.0 - xlamda * lvds;
1556            let mut d_clfact = Dderivs {
1557                value: lvds,
1558                d1_r: -1.0,
1559                ..d_r
1560            }
1561            .mult(&d_xlamda);
1562            d_clfact.value += 1.0;
1563            let mut xleff = eff_l * clfact;
1564            let deltal = xlamda * lvds * eff_l;
1565            let d_delta1 = Dderivs {
1566                value: eff_l * lvds,
1567                d1_r: eff_l,
1568                ..d_r
1569            }
1570            .mult(&d_xlamda);
1571            if t.nsub == 0.0 {
1572                xwb = 0.25e-6;
1573            }
1574            if xleff < xwb {
1575                xleff = xwb / (1.0 + (deltal - xld) / xwb);
1576                let mut xl = d_delta1;
1577                xl.value -= xld;
1578                let mut xl = xl.times(1.0 / xwb);
1579                xl.value += 1.0;
1580                let d_xleff = xl.inv().times(xwb);
1581                clfact = xleff / eff_l;
1582                d_clfact = d_xleff.times(1.0 / eff_l);
1583            }
1584            let _ = clfact;
1585
1586            // Effective beta.
1587            let d_beta1 = d_ufact.div(&d_clfact).times(beta);
1588            gammad = gamasd;
1589            d_gammad = d_gamasd;
1590            let _ = gammad;
1591
1592            if lvds <= 1.0e-10 {
1593                if lvgs <= von {
1594                    if t.nfs == 0.0 || oxide_cap == 0.0 {
1595                        d_cdrain.d1_q = 0.0;
1596                        d_cdrain.d2_q2 = 0.0;
1597                        d_cdrain.d3_q3 = 0.0;
1598                        break 'eval;
1599                    }
1600                    let d_dummy = d_gammad
1601                        .mult(&d_sarg)
1602                        .plus(&d_vbin)
1603                        .times(-1.0)
1604                        .plus(&d_von);
1605                    let mut d_mos2gds = d_beta1.mult(&d_dummy);
1606                    let mut d_dummy = d_von.times(-1.0).plus(&d_p);
1607                    d_dummy.value += lvgs;
1608                    let d_dummy = d_dummy.mult(&d_argg).exp();
1609                    d_mos2gds = d_mos2gds.mult(&d_dummy);
1610                    d_cdrain.d1_r = d_mos2gds.value;
1611                    d_cdrain.d2_r2 = d_mos2gds.d1_r;
1612                    d_cdrain.d3_r3 = d_mos2gds.d2_r2;
1613                    break 'eval;
1614                }
1615                let d_mos2gds = d_gammad
1616                    .mult(&d_sarg)
1617                    .plus(&d_vbin)
1618                    .times(-1.0)
1619                    .mult(&d_beta1);
1620                d_cdrain.d1_r = d_mos2gds.value;
1621                d_cdrain.d2_r2 = d_mos2gds.d1_r;
1622                d_cdrain.d3_r3 = d_mos2gds.d2_r2;
1623                break 'eval;
1624            }
1625
1626            if lvgs <= von {
1627                // Subthreshold.
1628                if vdsat <= 0.0 {
1629                    d_cdrain.d1_r = 0.0;
1630                    d_cdrain.d2_r2 = 0.0;
1631                    d_cdrain.d3_r3 = 0.0;
1632                    break 'eval;
1633                }
1634                let d_vdson = if vdsat <= lvds {
1635                    d_vdsat
1636                } else {
1637                    Dderivs { value: lvds, ..d_r }
1638                };
1639                if lvds > vdsat {
1640                    barg = bsarg;
1641                    d_barg = d_bsarg;
1642                    body = bodys;
1643                    d_body = d_bodys;
1644                }
1645                let _ = (barg, d_barg, body);
1646                let d_cdson = d_gammad.mult(&d_body).times(-1.0 / 1.5);
1647                let d_dummy = d_vdson
1648                    .times(0.5 * eta)
1649                    .plus(&d_vbin)
1650                    .times(-1.0)
1651                    .plus(&d_von)
1652                    .mult(&d_vdson)
1653                    .plus(&d_cdson);
1654                let d_cdson = d_dummy.mult(&d_beta1);
1655                let mut d_expg = d_von.times(-1.0);
1656                d_expg.value += lvgs;
1657                d_expg.d1_p += 1.0;
1658                let d_expg = d_expg.mult(&d_argg).exp();
1659                d_cdrain = d_cdson.mult(&d_expg);
1660                break 'eval;
1661            }
1662
1663            // `line900`.
1664            if lvds <= vdsat {
1665                // Linear region.
1666                let mut c = Dderivs {
1667                    value: eta * lvds * 0.5,
1668                    d1_r: 0.5 * eta,
1669                    ..d_r
1670                }
1671                .plus(&d_vbin)
1672                .times(-1.0);
1673                c.value += lvgs;
1674                c.d1_p += 1.0;
1675                let c = c.mult(&Dderivs { value: lvds, ..d_r });
1676                let d_dummy = d_gammad.mult(&d_body).times(-1.0 / 1.5);
1677                d_cdrain = c.plus(&d_dummy).mult(&d_beta1);
1678            } else {
1679                // Saturation region.
1680                let mut c = d_vdsat.times(0.5 * eta).plus(&d_vbin).times(-1.0);
1681                c.value += lvgs;
1682                c.d1_p += 1.0;
1683                let c = c.mult(&d_vdsat);
1684                let d_dummy = d_gammad.mult(&d_bodys).times(-1.0 / 1.5);
1685                d_cdrain = c.plus(&d_dummy).mult(&d_beta1);
1686            }
1687            // `goto doneval`: skip the `line1050` zeroing.
1688            doneval = true;
1689        }
1690        if !doneval {
1691            // `line1050`: everything but the vds terms is zeroed.
1692            d_cdrain = Dderivs {
1693                d1_r: d_cdrain.d1_r,
1694                d2_r2: d_cdrain.d2_r2,
1695                d3_r3: d_cdrain.d3_r3,
1696                ..d_zero
1697            };
1698        }
1699        let _ = von;
1700        let junctions = MosJunctions {
1701            cbs: t.cbs,
1702            cbssw: t.cbssw,
1703            f3s: t.f3s,
1704            cbd: t.cbd,
1705            cbdsw: t.cbdsw,
1706            f3d: t.f3d,
1707            dep_cap: t.t_dep_cap,
1708            bulk_pot: t.t_bulk_pot,
1709            mj: t.mj,
1710            mjsw: t.mjsw,
1711        };
1712        setup_tail(
1713            typ,
1714            mode,
1715            (vbs, vbd, vds),
1716            (vgst, vdsat),
1717            t.t_phi,
1718            oxide_cap,
1719            &junctions,
1720            &DrainDerivs::from_dderivs(&d_cdrain),
1721            gbs,
1722            gbd,
1723        )
1724    }
1725}
1726
1727impl crate::devices::Device for Mos2I {
1728    fn par_load(&self) -> bool {
1729        true
1730    }
1731
1732    /// `MOS2getic` (`mos2ic.c`): VBS, VDS, VGS not given
1733    /// on the card from the external nodes.
1734    fn set_ic(&mut self, rhs: &[f64]) {
1735        use crate::devices::getic;
1736        let g = self.ic_given;
1737        getic(g, 2, &mut self.ic_vbs, rhs, self.bulk, self.source);
1738        getic(g, 0, &mut self.ic_vds, rhs, self.drain, self.source);
1739        getic(g, 1, &mut self.ic_vgs, rhs, self.gate, self.source);
1740    }
1741
1742    fn set_ic_given(&mut self, given: crate::devices::IcGiven) {
1743        self.ic_given = given;
1744    }
1745
1746    /// `Mos2convTest`: predicted vs computed drain and bulk
1747    /// currents, evaluated at the fresh solution (`CKTrhs`).
1748    fn conv_test(&self, ctx: &crate::matrix::ConvCtx) -> bool {
1749        let typ = self.t.type_;
1750        let st = self.st;
1751        let vbs = typ * (ctx.rhs[self.bulk] - ctx.rhs[self.s_p]);
1752        let vgs = typ * (ctx.rhs[self.gate] - ctx.rhs[self.s_p]);
1753        let vds = typ * (ctx.rhs[self.d_p] - ctx.rhs[self.s_p]);
1754        let vbd = vbs - vds;
1755        let vgd = vgs - vds;
1756        let vgdo = ctx.states.get(0, st + VGS) - ctx.states.get(0, st + VDS);
1757        let delvbs = vbs - ctx.states.get(0, st + VBS);
1758        let delvbd = vbd - ctx.states.get(0, st + VBD);
1759        let delvgs = vgs - ctx.states.get(0, st + VGS);
1760        let delvds = vds - ctx.states.get(0, st + VDS);
1761        let delvgd = vgd - vgdo;
1762        let cdhat = if self.mode >= 0.0 {
1763            self.cd - self.gbd * delvbd + self.gmbs * delvbs + self.gm * delvgs + self.gds * delvds
1764        } else {
1765            self.cd - (self.gbd - self.gmbs) * delvbd - self.gm * delvgd + self.gds * delvds
1766        };
1767        let cbhat = self.cbs + self.cbd + self.gbd * delvbd + self.gbs * delvbs;
1768        let tol = ctx.reltol * f64::max(cdhat.abs(), self.cd.abs()) + ctx.abstol;
1769        if (cdhat - self.cd).abs() >= tol {
1770            return false;
1771        }
1772        let cb = self.cbs + self.cbd;
1773        let tol = ctx.reltol * f64::max(cbhat.abs(), cb.abs()) + ctx.abstol;
1774        (cbhat - cb).abs() <= tol
1775    }
1776
1777    fn alloc(&mut self, mb: &mut MatrixBuilder) {
1778        let (d, g, s, b) = (self.drain, self.gate, self.source, self.bulk);
1779        let (dp, sp) = (self.d_p, self.s_p);
1780        self.s = [
1781            mb.slot(d, d),
1782            mb.slot(g, g),
1783            mb.slot(s, s),
1784            mb.slot(b, b),
1785            mb.slot(dp, dp),
1786            mb.slot(sp, sp),
1787            mb.slot(d, dp),
1788            mb.slot(g, b),
1789            mb.slot(g, dp),
1790            mb.slot(g, sp),
1791            mb.slot(s, sp),
1792            mb.slot(b, dp),
1793            mb.slot(b, sp),
1794            mb.slot(dp, sp),
1795            mb.slot(dp, d),
1796            mb.slot(b, g),
1797            mb.slot(dp, g),
1798            mb.slot(sp, g),
1799            mb.slot(sp, s),
1800            mb.slot(dp, b),
1801            mb.slot(sp, b),
1802            mb.slot(sp, dp),
1803        ];
1804    }
1805
1806    #[allow(clippy::too_many_lines)]
1807    fn load(&mut self, ctx: &mut LoadCtx) {
1808        let typ = self.t.type_;
1809        let vt = self.t.vt;
1810        let st = self.st;
1811
1812        let eff_l = self.geom.l - 2.0 * self.t.lat_diff;
1813        let (drain_sat_cur, source_sat_cur) =
1814            if self.t.t_sat_cur_dens == 0.0 || self.geom.ad == 0.0 || self.geom.as_ == 0.0 {
1815                (self.t.t_sat_cur, self.t.t_sat_cur)
1816            } else {
1817                (
1818                    self.t.t_sat_cur_dens * self.geom.ad,
1819                    self.t.t_sat_cur_dens * self.geom.as_,
1820                )
1821            };
1822        let gate_source_overlap_cap = self.t.cgso * self.geom.w;
1823        let gate_drain_overlap_cap = self.t.cgdo * self.geom.w;
1824        let gate_bulk_overlap_cap = self.t.cgbo * eff_l;
1825        let beta = self.t.t_transconductance * self.geom.w / eff_l;
1826        let oxide_cap = self.t.oxide_cap_factor * eff_l * self.geom.w;
1827
1828        // Bias resolution — same ladder as MOS1.
1829        let mut check = true;
1830        let (mut vbs, mut vgs, mut vds);
1831        let predictor = matches!(ctx.mode, InitMode::Pred | InitMode::Tran);
1832        let general = matches!(ctx.mode, InitMode::Float | InitMode::Smsig)
1833            || (ctx.mode == InitMode::Fix && !self.off);
1834        if predictor || general {
1835            if predictor {
1836                let xfact = ctx.pred_xfact;
1837                let mut pred = |o: usize| {
1838                    let s1 = ctx.states.get(1, st + o);
1839                    ctx.states.set(0, st + o, s1);
1840                    (1.0 + xfact) * s1 - xfact * ctx.states.get(2, st + o)
1841                };
1842                vbs = pred(VBS);
1843                vgs = pred(VGS);
1844                vds = pred(VDS);
1845                let vbd0 = ctx.states.get(0, st + VBS) - ctx.states.get(0, st + VDS);
1846                ctx.states.set(0, st + VBD, vbd0);
1847            } else {
1848                vbs = typ * (ctx.sol[self.bulk] - ctx.sol[self.s_p]);
1849                vgs = typ * (ctx.sol[self.gate] - ctx.sol[self.s_p]);
1850                vds = typ * (ctx.sol[self.d_p] - ctx.sol[self.s_p]);
1851            }
1852            let mut vbd = vbs - vds;
1853            let mut vgd = vgs - vds;
1854            let vgdo = ctx.states.get(0, st + VGS) - ctx.states.get(0, st + VDS);
1855
1856            let von = typ * self.von;
1857            if ctx.states.get(0, st + VDS) >= 0.0 {
1858                vgs = fetlim(vgs, ctx.states.get(0, st + VGS), von);
1859                vds = vgs - vgd;
1860                vds = limvds(vds, ctx.states.get(0, st + VDS));
1861            } else {
1862                vgd = fetlim(vgd, vgdo, von);
1863                vds = vgs - vgd;
1864                vds = -limvds(-vds, -ctx.states.get(0, st + VDS));
1865                vgs = vgd + vds;
1866            }
1867            if vds >= 0.0 {
1868                vbs = pnjlim(
1869                    vbs,
1870                    ctx.states.get(0, st + VBS),
1871                    vt,
1872                    self.t.source_vcrit,
1873                    &mut check,
1874                );
1875            } else {
1876                vbd = pnjlim(
1877                    vbd,
1878                    ctx.states.get(0, st + VBD),
1879                    vt,
1880                    self.t.drain_vcrit,
1881                    &mut check,
1882                );
1883                vbs = vbd + vds;
1884            }
1885        } else if ctx.mode == InitMode::Jct && !self.off {
1886            vds = typ * self.ic_vds;
1887            vgs = typ * self.ic_vgs;
1888            vbs = typ * self.ic_vbs;
1889            if vds == 0.0 && vgs == 0.0 && vbs == 0.0 && !(ctx.kind == AnKind::TranOp && ctx.uic) {
1890                vbs = -1.0;
1891                vgs = typ * self.t.t_vto;
1892                vds = 0.0;
1893            }
1894        } else {
1895            vbs = 0.0;
1896            vgs = 0.0;
1897            vds = 0.0;
1898        }
1899
1900        let vbd = vbs - vds;
1901        let vgd = vgs - vds;
1902        let vgb = vgs - vbs;
1903
1904        // Bulk diodes, in the ngspice form: flat leakage below
1905        // −3·vt, clamped exponential above, gmin folded into the
1906        // current (`corpus/KNOWN_DIFFS.md`).
1907        const MAX_EXP_ARG: f64 = 709.0;
1908        let (mut gbs, mut cbs);
1909        if vbs <= -3.0 * vt {
1910            gbs = ctx.gmin;
1911            cbs = gbs * vbs - source_sat_cur;
1912        } else {
1913            let evbs = f64::exp(f64::min(MAX_EXP_ARG, vbs / vt));
1914            gbs = source_sat_cur * evbs / vt + ctx.gmin;
1915            cbs = source_sat_cur * (evbs - 1.0) + ctx.gmin * vbs;
1916        }
1917        let (mut gbd, mut cbd);
1918        if vbd <= -3.0 * vt {
1919            gbd = ctx.gmin;
1920            cbd = gbd * vbd - drain_sat_cur;
1921        } else {
1922            let evbd = f64::exp(f64::min(MAX_EXP_ARG, vbd / vt));
1923            gbd = drain_sat_cur * evbd / vt + ctx.gmin;
1924            cbd = drain_sat_cur * (evbd - 1.0) + ctx.gmin * vbd;
1925        }
1926
1927        self.mode = if vds >= 0.0 { 1.0 } else { -1.0 };
1928        let mode = self.mode;
1929
1930        // Grove-Frohman evaluation.
1931        let (cdrain, von, vdsat) = self.eval(vbs, vbd, vgs, vgd, vds, beta, oxide_cap, eff_l, vt);
1932        self.von = typ * von;
1933        self.vdsat = typ * vdsat;
1934        let (gm, gds, gmbs) = (self.gm, self.gds, self.gmbs);
1935
1936        let mut cd = mode * cdrain - cbd;
1937
1938        let t = &self.t;
1939        let smsig = ctx.mode == InitMode::Smsig;
1940        if ctx.kind == AnKind::Tran || ctx.kind == AnKind::TranOp || smsig {
1941            // Bulk junction depletion charge and capacitance (same
1942            // sqrt fast path and f2/f3/f4 continuation as MOS1).
1943            let dep = |v: f64, cz: f64, czsw: f64, f2: f64, f3: f64, f4: f64| {
1944                if cz != 0.0 || czsw != 0.0 {
1945                    if v < t.t_dep_cap {
1946                        let arg = 1.0 - v / t.t_bulk_pot;
1947                        let (sarg, sargsw);
1948                        if t.mj == t.mjsw {
1949                            if t.mj == 0.5 {
1950                                sarg = 1.0 / arg.sqrt();
1951                                sargsw = sarg;
1952                            } else {
1953                                sarg = f64::exp(-t.mj * f64::ln(arg));
1954                                sargsw = sarg;
1955                            }
1956                        } else {
1957                            sarg = if t.mj == 0.5 {
1958                                1.0 / arg.sqrt()
1959                            } else {
1960                                f64::exp(-t.mj * f64::ln(arg))
1961                            };
1962                            sargsw = if t.mjsw == 0.5 {
1963                                1.0 / arg.sqrt()
1964                            } else {
1965                                f64::exp(-t.mjsw * f64::ln(arg))
1966                            };
1967                        }
1968                        let q = t.t_bulk_pot
1969                            * (cz * (1.0 - arg * sarg) / (1.0 - t.mj)
1970                                + czsw * (1.0 - arg * sargsw) / (1.0 - t.mjsw));
1971                        (q, cz * sarg + czsw * sargsw)
1972                    } else {
1973                        (f4 + v * (f2 + v * f3 / 2.0), f2 + v * f3)
1974                    }
1975                } else {
1976                    (0.0, 0.0)
1977                }
1978            };
1979            let (qbs, capbs) = dep(vbs, t.cbs, t.cbssw, t.f2s, t.f3s, t.f4s);
1980            ctx.states.set(0, st + QBS, qbs);
1981            self.capbs = capbs;
1982            let (qbd, capbd) = dep(vbd, t.cbd, t.cbdsw, t.f2d, t.f3d, t.f4d);
1983            ctx.states.set(0, st + QBD, qbd);
1984            self.capbd = capbd;
1985
1986            if ctx.kind == AnKind::Tran {
1987                let (geq, _) = ctx.integrate(self.capbd, st + QBD);
1988                gbd += geq;
1989                cbd += ctx.states.get(0, st + CQBD);
1990                cd -= ctx.states.get(0, st + CQBD);
1991                let (geq, _) = ctx.integrate(self.capbs, st + QBS);
1992                gbs += geq;
1993                cbs += ctx.states.get(0, st + CQBS);
1994            }
1995        }
1996        self.gbs = gbs;
1997        self.gbd = gbd;
1998
1999        if (!self.off || !matches!(ctx.mode, InitMode::Fix | InitMode::Smsig)) && check {
2000            ctx.noncon = true;
2001        }
2002
2003        ctx.states.set(0, st + VBS, vbs);
2004        ctx.states.set(0, st + VBD, vbd);
2005        ctx.states.set(0, st + VGS, vgs);
2006        ctx.states.set(0, st + VDS, vds);
2007
2008        // Meyer's capacitor model.
2009        let (mut capgs, mut capgd, mut capgb) = (0.0, 0.0, 0.0);
2010        if ctx.kind == AnKind::Tran || ctx.kind == AnKind::TranOp || smsig {
2011            let (half_gs, half_gd, half_gb) = if mode > 0.0 {
2012                qmeyer(vgs, vgd, vgb, von, vdsat, t.t_phi, oxide_cap)
2013            } else {
2014                let (o1, o2, o3) = qmeyer(vgd, vgs, vgb, von, vdsat, t.t_phi, oxide_cap);
2015                (o2, o1, o3)
2016            };
2017            ctx.states.set(0, st + CAPGS, half_gs);
2018            ctx.states.set(0, st + CAPGD, half_gd);
2019            ctx.states.set(0, st + CAPGB, half_gb);
2020            let vgs1 = ctx.states.get(1, st + VGS);
2021            let vgd1 = vgs1 - ctx.states.get(1, st + VDS);
2022            let vgb1 = vgs1 - ctx.states.get(1, st + VBS);
2023            if ctx.kind == AnKind::TranOp || smsig {
2024                capgs = 2.0 * ctx.states.get(0, st + CAPGS) + gate_source_overlap_cap;
2025                capgd = 2.0 * ctx.states.get(0, st + CAPGD) + gate_drain_overlap_cap;
2026                capgb = 2.0 * ctx.states.get(0, st + CAPGB) + gate_bulk_overlap_cap;
2027            } else {
2028                capgs = ctx.states.get(0, st + CAPGS)
2029                    + ctx.states.get(1, st + CAPGS)
2030                    + gate_source_overlap_cap;
2031                capgd = ctx.states.get(0, st + CAPGD)
2032                    + ctx.states.get(1, st + CAPGD)
2033                    + gate_drain_overlap_cap;
2034                capgb = ctx.states.get(0, st + CAPGB)
2035                    + ctx.states.get(1, st + CAPGB)
2036                    + gate_bulk_overlap_cap;
2037            }
2038
2039            if smsig {
2040                return;
2041            }
2042
2043            if predictor {
2044                let xfact = ctx.pred_xfact;
2045                for o in [QGS, QGD, QGB] {
2046                    let v = (1.0 + xfact) * ctx.states.get(1, st + o)
2047                        - xfact * ctx.states.get(2, st + o);
2048                    ctx.states.set(0, st + o, v);
2049                }
2050            } else if ctx.kind == AnKind::Tran {
2051                let q = (vgs - vgs1) * capgs + ctx.states.get(1, st + QGS);
2052                ctx.states.set(0, st + QGS, q);
2053                let q = (vgd - vgd1) * capgd + ctx.states.get(1, st + QGD);
2054                ctx.states.set(0, st + QGD, q);
2055                let q = (vgb - vgb1) * capgb + ctx.states.get(1, st + QGB);
2056                ctx.states.set(0, st + QGB, q);
2057            } else {
2058                ctx.states.set(0, st + QGS, capgs * vgs);
2059                ctx.states.set(0, st + QGD, capgd * vgd);
2060                ctx.states.set(0, st + QGB, capgb * vgb);
2061            }
2062        }
2063
2064        // Meyer companion models.
2065        let (mut gcgs, mut ceqgs, mut gcgd, mut ceqgd, mut gcgb, mut ceqgb) =
2066            (0.0, 0.0, 0.0, 0.0, 0.0, 0.0);
2067        if ctx.mode != InitMode::Tran && ctx.kind == AnKind::Tran {
2068            if capgs == 0.0 {
2069                ctx.states.set(0, st + CQGS, 0.0);
2070            }
2071            if capgd == 0.0 {
2072                ctx.states.set(0, st + CQGD, 0.0);
2073            }
2074            if capgb == 0.0 {
2075                ctx.states.set(0, st + CQGB, 0.0);
2076            }
2077            let (g, ceq) = ctx.integrate(capgs, st + QGS);
2078            gcgs = g;
2079            ceqgs = ceq - gcgs * vgs + ctx.ag[0] * ctx.states.get(0, st + QGS);
2080            let (g, ceq) = ctx.integrate(capgd, st + QGD);
2081            gcgd = g;
2082            ceqgd = ceq - gcgd * vgd + ctx.ag[0] * ctx.states.get(0, st + QGD);
2083            let (g, ceq) = ctx.integrate(capgb, st + QGB);
2084            gcgb = g;
2085            ceqgb = ceq - gcgb * vgb + ctx.ag[0] * ctx.states.get(0, st + QGB);
2086        }
2087
2088        // Load the current vector.
2089        // ngspice folds gmin into the junction current, so the
2090        // Norton companion uses the full conductance (the 3f5
2091        // subtracted gmin here).
2092        let ceqbs = typ * (cbs - gbs * vbs);
2093        let ceqbd = typ * (cbd - gbd * vbd);
2094        let (xnrm, xrev, cdreq);
2095        if mode >= 0.0 {
2096            xnrm = 1.0;
2097            xrev = 0.0;
2098            cdreq = typ * (cdrain - gds * vds - gm * vgs - gmbs * vbs);
2099        } else {
2100            xnrm = 0.0;
2101            xrev = 1.0;
2102            cdreq = -typ * (cdrain - gds * (-vds) - gm * vgd - gmbs * vbd);
2103        }
2104        // Saved for the convergence test (`mos2conv.c`).
2105        self.cd = cd;
2106        self.cbs = cbs;
2107        self.cbd = cbd;
2108        ctx.add_rhs(self.gate, -(typ * (ceqgs + ceqgb + ceqgd)));
2109        ctx.add_rhs(self.bulk, -(ceqbs + ceqbd - typ * ceqgb));
2110        ctx.add_rhs(self.d_p, ceqbd - cdreq + typ * ceqgd);
2111        ctx.add_rhs(self.s_p, cdreq + ceqbs + typ * ceqgs);
2112
2113        // Load the Y matrix.
2114        let gdpr = self.t.drain_conductance;
2115        let gspr = self.t.source_conductance;
2116        ctx.add(self.s[D_D], gdpr);
2117        ctx.add(self.s[G_G], gcgd + gcgs + gcgb);
2118        ctx.add(self.s[S_S], gspr);
2119        ctx.add(self.s[B_B], gbd + gbs + gcgb);
2120        ctx.add(self.s[DP_DP], gdpr + gds + gbd + xrev * (gm + gmbs) + gcgd);
2121        ctx.add(self.s[SP_SP], gspr + gds + gbs + xnrm * (gm + gmbs) + gcgs);
2122        ctx.add(self.s[D_DP], -gdpr);
2123        ctx.add(self.s[G_B], -gcgb);
2124        ctx.add(self.s[G_DP], -gcgd);
2125        ctx.add(self.s[G_SP], -gcgs);
2126        ctx.add(self.s[S_SP], -gspr);
2127        ctx.add(self.s[B_G], -gcgb);
2128        ctx.add(self.s[B_DP], -gbd);
2129        ctx.add(self.s[B_SP], -gbs);
2130        ctx.add(self.s[DP_D], -gdpr);
2131        ctx.add(self.s[DP_G], (xnrm - xrev) * gm - gcgd);
2132        ctx.add(self.s[DP_B], -gbd + (xnrm - xrev) * gmbs);
2133        ctx.add(self.s[DP_SP], -gds - xnrm * (gm + gmbs));
2134        ctx.add(self.s[SP_G], -(xnrm - xrev) * gm - gcgs);
2135        ctx.add(self.s[SP_S], -gspr);
2136        ctx.add(self.s[SP_B], -gbs - (xnrm - xrev) * gmbs);
2137        ctx.add(self.s[SP_DP], -gds - xrev * (gm + gmbs));
2138    }
2139
2140    fn trunc(&self, t: &crate::integ::TerrCtx, time_step: &mut f64) {
2141        crate::integ::terr(self.st + QGS, t, time_step);
2142        crate::integ::terr(self.st + QGD, t, time_step);
2143        crate::integ::terr(self.st + QGB, t, time_step);
2144    }
2145
2146    /// `MOS2noise` (ngspice-47, default `NLEV`): see
2147    /// [`crate::mos1::mos_noise`].
2148    fn noise_suffixes(&self) -> Option<&'static [&'static str]> {
2149        Some(crate::mos1::MOS_NOISE_SUFFIXES)
2150    }
2151
2152    fn noise_dens(&self, ctx: &crate::matrix::NoiseCtx) -> Vec<f64> {
2153        let n = crate::mos1::MosNoise {
2154            drain: self.drain,
2155            source: self.source,
2156            d_p: self.d_p,
2157            s_p: self.s_p,
2158            gdpr: self.t.drain_conductance,
2159            gspr: self.t.source_conductance,
2160            gm: self.gm,
2161            cd: self.cd,
2162            kf: self.t.kf,
2163            af: self.t.af,
2164            nlev: self.t.nlev,
2165            gdsnoi: self.t.gdsnoi,
2166            w: self.geom.w,
2167            eff_l: self.geom.l - 2.0 * self.t.lat_diff,
2168            cox: self.t.oxide_cap_factor,
2169            temp: self.t.temp,
2170            vgs: ctx.states.get(0, self.st + VGS),
2171            vds: ctx.states.get(0, self.st + VDS),
2172            mode: self.mode,
2173            von: self.von,
2174            vdsat: self.vdsat,
2175            typ: self.t.type_,
2176            t_transconductance: self.t.t_transconductance,
2177        };
2178        crate::mos1::mos_noise(ctx, &n)
2179    }
2180
2181    /// `MOS2dSetup`: see [`Mos2I::disto_coeffs`].
2182    fn disto_setup(&mut self, ctx: &DistoSetupCtx) -> Result<(), &'static str> {
2183        self.dist = self.disto_coeffs(ctx.sol, ctx.gmin);
2184        Ok(())
2185    }
2186
2187    /// `MOS2disto` (`dev/mos2/mos2dist.c`, the MOS1 file renamed).
2188    fn disto_load(&self, ctx: &mut DistoCtx) {
2189        mos_disto_load(&self.dist, (self.gate, self.bulk, self.d_p, self.s_p), ctx);
2190    }
2191
2192    /// `MOS2acLoad` (same stamps as MOS1's).
2193    fn load_ac(&mut self, ctx: &mut AcCtx) {
2194        let t = &self.t;
2195        let st = self.st;
2196        let (xnrm, xrev) = if self.mode < 0.0 {
2197            (0.0, 1.0)
2198        } else {
2199            (1.0, 0.0)
2200        };
2201        let eff_l = self.geom.l - 2.0 * t.lat_diff;
2202        let capgs = 2.0 * ctx.states.get(0, st + CAPGS) + t.cgso * self.geom.w;
2203        let capgd = 2.0 * ctx.states.get(0, st + CAPGD) + t.cgdo * self.geom.w;
2204        let capgb = 2.0 * ctx.states.get(0, st + CAPGB) + t.cgbo * eff_l;
2205        let xgs = capgs * ctx.omega;
2206        let xgd = capgd * ctx.omega;
2207        let xgb = capgb * ctx.omega;
2208        let xbd = self.capbd * ctx.omega;
2209        let xbs = self.capbs * ctx.omega;
2210        let (gm, gds, gmbs) = (self.gm, self.gds, self.gmbs);
2211        let (gbd, gbs) = (self.gbd, self.gbs);
2212        let gdpr = t.drain_conductance;
2213        let gspr = t.source_conductance;
2214
2215        ctx.add_b(self.s[G_G], xgd + xgs + xgb);
2216        ctx.add_b(self.s[B_B], xgb + xbd + xbs);
2217        ctx.add_b(self.s[DP_DP], xgd + xbd);
2218        ctx.add_b(self.s[SP_SP], xgs + xbs);
2219        ctx.add_b(self.s[G_B], -xgb);
2220        ctx.add_b(self.s[G_DP], -xgd);
2221        ctx.add_b(self.s[G_SP], -xgs);
2222        ctx.add_b(self.s[B_G], -xgb);
2223        ctx.add_b(self.s[B_DP], -xbd);
2224        ctx.add_b(self.s[B_SP], -xbs);
2225        ctx.add_b(self.s[DP_G], -xgd);
2226        ctx.add_b(self.s[DP_B], -xbd);
2227        ctx.add_b(self.s[SP_G], -xgs);
2228        ctx.add_b(self.s[SP_B], -xbs);
2229        ctx.add_g(self.s[D_D], gdpr);
2230        ctx.add_g(self.s[S_S], gspr);
2231        ctx.add_g(self.s[B_B], gbd + gbs);
2232        ctx.add_g(self.s[DP_DP], gdpr + gds + gbd + xrev * (gm + gmbs));
2233        ctx.add_g(self.s[SP_SP], gspr + gds + gbs + xnrm * (gm + gmbs));
2234        ctx.add_g(self.s[D_DP], -gdpr);
2235        ctx.add_g(self.s[S_SP], -gspr);
2236        ctx.add_g(self.s[B_DP], -gbd);
2237        ctx.add_g(self.s[B_SP], -gbs);
2238        ctx.add_g(self.s[DP_D], -gdpr);
2239        ctx.add_g(self.s[DP_G], (xnrm - xrev) * gm);
2240        ctx.add_g(self.s[DP_B], -gbd + (xnrm - xrev) * gmbs);
2241        ctx.add_g(self.s[DP_SP], -gds - xnrm * (gm + gmbs));
2242        ctx.add_g(self.s[SP_G], -(xnrm - xrev) * gm);
2243        ctx.add_g(self.s[SP_S], -gspr);
2244        ctx.add_g(self.s[SP_B], -gbs - (xnrm - xrev) * gmbs);
2245        ctx.add_g(self.s[SP_DP], -gds - xrev * (gm + gmbs));
2246    }
2247
2248    /// `MOS2pzLoad` (`mos2pzld.c`, the MOS1 file renamed): see
2249    /// [`meyer_pz_load`].
2250    fn load_pz(&mut self, ctx: &mut crate::matrix::PzCtx) -> Result<(), &'static str> {
2251        let t = &self.t;
2252        let st = self.st;
2253        let (xnrm, xrev) = if self.mode < 0.0 {
2254            (0.0, 1.0)
2255        } else {
2256            (1.0, 0.0)
2257        };
2258        let eff_l = self.geom.l - 2.0 * t.lat_diff;
2259        let caps = MeyerPzCaps {
2260            capgs: 2.0 * ctx.states.get(0, st + CAPGS) + t.cgso * self.geom.w,
2261            capgd: 2.0 * ctx.states.get(0, st + CAPGD) + t.cgdo * self.geom.w,
2262            capgb: 2.0 * ctx.states.get(0, st + CAPGB) + t.cgbo * eff_l,
2263            capbd: self.capbd,
2264            capbs: self.capbs,
2265        };
2266        let g = MosPzConductances {
2267            xnrm,
2268            xrev,
2269            gm: self.gm,
2270            gds: self.gds,
2271            gmbs: self.gmbs,
2272            gbd: self.gbd,
2273            gbs: self.gbs,
2274            gdpr: t.drain_conductance,
2275            gspr: t.source_conductance,
2276        };
2277        meyer_pz_load(ctx, &self.s, &caps, &g);
2278        Ok(())
2279    }
2280}
2281
2282#[cfg(test)]
2283mod tests {
2284    use super::*;
2285
2286    /// mosamp2's model card, at TNOM.
2287    fn amp_model() -> Mos2Model {
2288        let mut m = Mos2Model::new(true);
2289        m.nsub = Some(2.2e15);
2290        m.u0 = Some(575.0);
2291        m.ucrit = 49e3;
2292        m.uexp = 0.1;
2293        m.tox = Some(0.11e-6);
2294        m.xj = 2.95e-6;
2295        m.ld = 2.4485e-6;
2296        m.nss = Some(3.2e10);
2297        m.kp = Some(2e-5);
2298        m.phi = Some(0.6);
2299        m.cgso = 1.5e-9;
2300        m.cgdo = 1.5e-9;
2301        m.cbd = Some(4.5e-15);
2302        m.cbs = Some(4.5e-15);
2303        m
2304    }
2305
2306    #[test]
2307    fn temp_derives_gamma_vto_xd_from_nsub() {
2308        let g = MosGeom {
2309            l: 25.4e-6,
2310            w: 88.9e-6,
2311            ad: 0.0,
2312            as_: 0.0,
2313            pd: 0.0,
2314            ps: 0.0,
2315            nrd: 1.0,
2316            nrs: 1.0,
2317        };
2318        let t = Mos2Temp::new("m1", &amp_model(), &g, REFTEMP, REFTEMP).unwrap();
2319        // GAMMA = sqrt(2·eps·q·Nsub)/Cox with Cox = 3.9·e0/tox.
2320        let cox = 3.9 * 8.854214871e-12 / 0.11e-6;
2321        let gamma = f64::sqrt(2.0 * 11.70 * 8.854214871e-12 * CHARGE * 2.2e15 * 1e6) / cox;
2322        assert!((t.gamma - gamma).abs() < 1e-12);
2323        // XD = sqrt(2·epsSil/(q·Nsub)).
2324        let xd = f64::sqrt(2.0 * EPSSIL / (CHARGE * 2.2e15 * 1e6));
2325        assert!((t.xd - xd).abs() < 1e-18);
2326        // KP was given, so tTransconductance is the given value at TNOM.
2327        assert!((t.t_transconductance - 2e-5).abs() < 1e-18);
2328        // PHI given → tPhi = PHI at TNOM.
2329        assert!((t.t_phi - 0.6).abs() < 1e-12);
2330        // VTO computed from NSUB/NSS/TPG must be finite and sensible.
2331        assert!(t.t_vto.is_finite());
2332        assert!(t.t_vto.abs() < 5.0);
2333    }
2334
2335    #[test]
2336    fn nsub_below_ni_is_fatal() {
2337        let mut m = Mos2Model::new(true);
2338        m.nsub = Some(1e9);
2339        let g = MosGeom {
2340            l: 1e-5,
2341            w: 1e-5,
2342            ad: 0.0,
2343            as_: 0.0,
2344            pd: 0.0,
2345            ps: 0.0,
2346            nrd: 1.0,
2347            nrs: 1.0,
2348        };
2349        let e = Mos2Temp::new("m1", &m, &g, REFTEMP, REFTEMP).unwrap_err();
2350        assert!(e.contains("Nsub < Ni"));
2351    }
2352}