1use crate::consts::{BOLTZ, CHARGE, KOVERQ, REFTEMP, ROOT2};
24use crate::dderivs::Dderivs;
25use crate::diode::pnjlim;
26use crate::disto::{DistoCtx, DistoSetupCtx};
27use crate::matrix::{AcCtx, AnKind, InitMode, LoadCtx, MatrixBuilder, Slot, TRASH, Var};
28use crate::mos1::{MeyerPzCaps, MosGeom, MosPzConductances, fetlim, limvds, meyer_pz_load, qmeyer};
29use crate::mosdisto::{
30 DrainDerivs, MosDistoCoeffs, MosJunctions, bulk_diode as disto_bulk_diode, mos_disto_load,
31 setup_tail,
32};
33
34const EPSSIL: f64 = 11.7 * 8.854214871e-12;
36
37#[derive(Clone, Debug)]
42pub struct Mos3Model {
43 pub nmos: bool,
45 pub vto: Option<f64>,
47 pub kp: Option<f64>,
49 pub gamma: Option<f64>,
51 pub phi: Option<f64>,
53 pub rd: Option<f64>,
55 pub rs: Option<f64>,
56 pub cbd: Option<f64>,
58 pub cbs: Option<f64>,
59 pub is: f64,
61 pub pb: f64,
63 pub cgso: f64,
65 pub cgdo: f64,
66 pub cgbo: f64,
67 pub rsh: Option<f64>,
69 pub cj: Option<f64>,
71 pub mj: f64,
73 pub cjsw: Option<f64>,
75 pub mjsw: f64,
77 pub js: f64,
79 pub tox: Option<f64>,
81 pub ld: f64,
83 pub u0: Option<f64>,
85 pub fc: f64,
87 pub nsub: Option<f64>,
89 pub tpg: Option<f64>,
91 pub nss: Option<f64>,
93 pub vmax: f64,
95 pub xj: f64,
97 pub nfs: f64,
99 pub eta: f64,
102 pub delta: f64,
104 pub theta: f64,
107 pub kappa: f64,
109 pub kf: f64,
111 pub af: f64,
112 pub nlev: i64,
117 pub gdsnoi: f64,
120 pub tnom: Option<f64>,
122}
123
124impl Mos3Model {
125 pub fn new(nmos: bool) -> Self {
126 Mos3Model {
127 nmos,
128 vto: None,
129 kp: None,
130 gamma: None,
131 phi: None,
132 rd: None,
133 rs: None,
134 cbd: None,
135 cbs: None,
136 is: 1e-14,
137 pb: 0.8,
138 cgso: 0.0,
139 cgdo: 0.0,
140 cgbo: 0.0,
141 rsh: None,
142 cj: None,
143 mj: 0.5,
144 cjsw: None,
145 mjsw: 0.33,
146 js: 0.0,
147 tox: None,
148 ld: 0.0,
149 u0: None,
150 fc: 0.5,
151 nsub: None,
152 tpg: None,
153 nss: None,
154 vmax: 0.0,
155 xj: 0.0,
156 nfs: 0.0,
157 eta: 0.0,
158 delta: 0.0,
159 theta: 0.0,
160 kappa: 0.2,
161 kf: 0.0,
162 af: 1.0,
163 nlev: 2,
164 gdsnoi: 1.0,
165 tnom: None,
166 }
167 }
168
169 pub(crate) fn needs_prime(&self, squares: f64) -> bool {
171 self.rd.unwrap_or(0.0) != 0.0 || (self.rsh.unwrap_or(0.0) != 0.0 && squares != 0.0)
172 }
173}
174
175fn egfet(t: f64) -> f64 {
176 1.16 - (7.02e-4 * t * t) / (t + 1108.0)
177}
178
179#[derive(Debug)]
181pub(crate) struct Mos3Temp {
182 pub temp: f64,
186 pub type_: f64,
187 pub vt: f64,
188 pub lat_diff: f64,
190 pub kf: f64,
192 pub af: f64,
193 pub nlev: i64,
195 pub gdsnoi: f64,
196 pub cgso: f64,
197 pub cgdo: f64,
198 pub cgbo: f64,
199 pub oxide_cap_factor: f64,
200 pub gamma: f64,
201 pub mj: f64,
202 pub mjsw: f64,
203 pub xj: f64,
204 pub nfs: f64,
205 pub vmax: f64,
206 pub eta: f64,
207 pub theta: f64,
208 pub kappa: f64,
209 pub alpha: f64,
211 pub coeff_dep_lay_width: f64,
213 pub narrow_factor: f64,
215 pub t_transconductance: f64,
217 pub t_surf_mob: f64,
219 pub t_phi: f64,
220 pub t_vbi: f64,
221 pub t_vto: f64,
222 pub t_sat_cur: f64,
223 pub t_sat_cur_dens: f64,
224 pub t_bulk_pot: f64,
225 pub t_dep_cap: f64,
226 pub drain_vcrit: f64,
227 pub source_vcrit: f64,
228 pub cbd: f64,
229 pub cbdsw: f64,
230 pub cbs: f64,
231 pub cbssw: f64,
232 pub f2d: f64,
233 pub f3d: f64,
234 pub f4d: f64,
235 pub f2s: f64,
236 pub f3s: f64,
237 pub f4s: f64,
238 pub drain_conductance: f64,
239 pub source_conductance: f64,
240}
241
242impl Mos3Temp {
243 pub fn new(
244 name: &str,
245 m: &Mos3Model,
246 g: &MosGeom,
247 temp: f64,
248 default_tnom: f64,
249 ) -> Result<Self, String> {
250 let typ = if m.nmos { 1.0 } else { -1.0 };
251 let tnom = m.tnom.unwrap_or(default_tnom);
252
253 let fact1 = tnom / REFTEMP;
254 let vtnom = tnom * KOVERQ;
255 let kt1 = BOLTZ * tnom;
256 let egfet1 = egfet(tnom);
257 let arg1 = -egfet1 / (kt1 + kt1) + 1.1150877 / (BOLTZ * (REFTEMP + REFTEMP));
258 let pbfact1 = -2.0 * vtnom * (1.5 * f64::ln(fact1) + CHARGE * arg1);
259 let mut nifact = (tnom / 300.0) * (tnom / 300.0).sqrt();
264 nifact *= f64::exp(0.5 * egfet1 * ((1.0 / 300.0) - (1.0 / tnom)) / KOVERQ);
265 let ni_temp = 1.45e16 * nifact;
266
267 let tox = m.tox.unwrap_or(1e-7);
270 let oxide_cap_factor = 3.9 * 8.854214871e-12 / tox;
271 let u0 = m.u0.unwrap_or(600.0);
272 let kp = m.kp.unwrap_or(u0 * oxide_cap_factor * 1e-4 );
273 let mut phi = m.phi;
274 let mut gamma = m.gamma;
275 let mut vto = m.vto;
276 let mut alpha = 0.0;
277 let mut coeff_dep_lay_width = 0.0;
278 if let Some(nsub) = m.nsub {
279 if nsub * 1e6 > ni_temp {
280 if phi.is_none() {
281 phi = Some(f64::max(0.1, 2.0 * vtnom * f64::ln(nsub * 1e6 / ni_temp)));
282 }
283 let phi_v = phi.unwrap_or(0.6);
284 let fermis = typ * 0.5 * phi_v;
285 let tpg = m.tpg.unwrap_or(1.0);
286 let mut wkfng = 3.2;
287 if tpg != 0.0 {
288 let fermig = typ * tpg * 0.5 * egfet1;
289 wkfng = 3.25 + 0.5 * egfet1 - fermig;
290 }
291 let wkfngs = wkfng - (3.25 + 0.5 * egfet1 + fermis);
292 if gamma.is_none() {
293 gamma = Some(f64::sqrt(2.0 * EPSSIL * CHARGE * nsub * 1e6) / oxide_cap_factor);
294 }
295 if vto.is_none() {
296 let nss = m.nss.unwrap_or(0.0);
297 let vfb = wkfngs - nss * 1e4 * CHARGE / oxide_cap_factor;
298 vto = Some(vfb + typ * (gamma.unwrap_or(0.0) * phi_v.sqrt() + phi_v));
299 }
300 alpha = (EPSSIL + EPSSIL) / (CHARGE * nsub * 1e6);
301 coeff_dep_lay_width = alpha.sqrt();
302 } else {
303 return Err(format!("{name}: Nsub < Ni"));
304 }
305 }
306 let phi = phi.unwrap_or(0.6);
307 if phi <= 0.0 {
310 return Err(format!("{name}: Phi is not positive."));
311 }
312 let gamma = gamma.unwrap_or(0.0);
313 let vto = vto.unwrap_or(0.0);
314 let narrow_factor = m.delta * 0.5 * std::f64::consts::PI * EPSSIL / oxide_cap_factor;
315
316 let vt = temp * KOVERQ;
318 let ratio = temp / tnom;
319 let fact2 = temp / REFTEMP;
320 let kt = temp * BOLTZ;
321 let egfet_t = egfet(temp);
322 let arg = -egfet_t / (kt + kt) + 1.1150877 / (BOLTZ * (REFTEMP + REFTEMP));
323 let pbfact = -2.0 * vt * (1.5 * f64::ln(fact2) + CHARGE * arg);
324
325 if g.l - 2.0 * m.ld <= 0.0 {
327 return Err(format!("{name}: effective channel length less than zero"));
328 }
329 let ratio4 = ratio * ratio.sqrt();
330 let t_transconductance = kp / ratio4;
331 let t_surf_mob = u0 / ratio4;
332 let phio = (phi - pbfact1) / fact1;
333 let t_phi = fact2 * phio + pbfact;
334 let t_vbi =
335 vto - typ * (gamma * phi.sqrt()) + 0.5 * (egfet1 - egfet_t) + typ * 0.5 * (t_phi - phi);
336 let t_vto = t_vbi + typ * gamma * t_phi.sqrt();
337 let t_sat_cur = m.is * f64::exp(-egfet_t / vt + egfet1 / vtnom);
338 let t_sat_cur_dens = m.js * f64::exp(-egfet_t / vt + egfet1 / vtnom);
339
340 let pbo = (m.pb - pbfact1) / fact1;
341 let gmaold = (m.pb - pbo) / pbo;
342 let mut capfact = 1.0 / (1.0 + m.mj * (4e-4 * (tnom - REFTEMP) - gmaold));
343 let mut t_cbd = m.cbd.unwrap_or(0.0) * capfact;
344 let mut t_cbs = m.cbs.unwrap_or(0.0) * capfact;
345 let mut t_cj = m.cj.unwrap_or(0.0) * capfact;
346 capfact = 1.0 / (1.0 + m.mjsw * (4e-4 * (tnom - REFTEMP) - gmaold));
347 let mut t_cjsw = m.cjsw.unwrap_or(0.0) * capfact;
348 let t_bulk_pot = fact2 * pbo + pbfact;
349 let gmanew = (t_bulk_pot - pbo) / pbo;
350 capfact = 1.0 + m.mj * (4e-4 * (temp - REFTEMP) - gmanew);
351 t_cbd *= capfact;
352 t_cbs *= capfact;
353 t_cj *= capfact;
354 capfact = 1.0 + m.mjsw * (4e-4 * (temp - REFTEMP) - gmanew);
355 t_cjsw *= capfact;
356 let t_dep_cap = m.fc * t_bulk_pot;
357
358 let (drain_vcrit, source_vcrit) = if m.js == 0.0 || g.ad == 0.0 || g.as_ == 0.0 {
362 let v = vt * f64::ln(vt / (ROOT2 * t_sat_cur));
363 (v, v)
364 } else {
365 (
366 vt * f64::ln(vt / (ROOT2 * t_sat_cur_dens * g.ad)),
367 vt * f64::ln(vt / (ROOT2 * t_sat_cur_dens * g.as_)),
368 )
369 };
370
371 let czbd = if m.cbd.is_some() {
372 t_cbd
373 } else if m.cj.is_some() {
374 t_cj * g.ad
375 } else {
376 0.0
377 };
378 let czbdsw = if m.cjsw.is_some() { t_cjsw * g.pd } else { 0.0 };
379 let czbs = if m.cbs.is_some() {
380 t_cbs
381 } else if m.cj.is_some() {
382 t_cj * g.as_
383 } else {
384 0.0
385 };
386 let czbssw = if m.cjsw.is_some() { t_cjsw * g.ps } else { 0.0 };
387 let arg = 1.0 - m.fc;
388 let sarg = f64::exp(-m.mj * f64::ln(arg));
389 let sargsw = f64::exp(-m.mjsw * f64::ln(arg));
390 let (f2d, f3d, f4d) =
391 junction_coeffs(m, czbd, czbdsw, arg, sarg, sargsw, t_bulk_pot, t_dep_cap);
392 let (f2s, f3s, f4s) =
393 junction_coeffs(m, czbs, czbssw, arg, sarg, sargsw, t_bulk_pot, t_dep_cap);
394
395 let series = |r: Option<f64>, squares: f64| match (r, m.rsh) {
396 (Some(r), _) => {
397 if r != 0.0 {
398 1.0 / r
399 } else {
400 0.0
401 }
402 }
403 (None, Some(rsh)) => {
404 if rsh != 0.0 {
405 1.0 / (rsh * squares)
406 } else {
407 0.0
408 }
409 }
410 (None, None) => 0.0,
411 };
412
413 Ok(Mos3Temp {
414 temp,
415 type_: typ,
416 vt,
417 lat_diff: m.ld,
418 kf: m.kf,
419 af: m.af,
420 nlev: m.nlev,
421 gdsnoi: m.gdsnoi,
422 cgso: m.cgso,
423 cgdo: m.cgdo,
424 cgbo: m.cgbo,
425 oxide_cap_factor,
426 gamma,
427 mj: m.mj,
428 mjsw: m.mjsw,
429 xj: m.xj,
430 nfs: m.nfs,
431 vmax: m.vmax,
432 eta: m.eta,
433 theta: m.theta,
434 kappa: m.kappa,
435 alpha,
436 coeff_dep_lay_width,
437 narrow_factor,
438 t_transconductance,
439 t_surf_mob,
440 t_phi,
441 t_vbi,
442 t_vto,
443 t_sat_cur,
444 t_sat_cur_dens,
445 t_bulk_pot,
446 t_dep_cap,
447 drain_vcrit,
448 source_vcrit,
449 cbd: czbd,
450 cbdsw: czbdsw,
451 cbs: czbs,
452 cbssw: czbssw,
453 f2d,
454 f3d,
455 f4d,
456 f2s,
457 f3s,
458 f4s,
459 drain_conductance: series(m.rd, g.nrd),
460 source_conductance: series(m.rs, g.nrs),
461 })
462 }
463}
464
465#[allow(clippy::too_many_arguments)]
472fn junction_coeffs(
473 m: &Mos3Model,
474 cz: f64,
475 czsw: f64,
476 arg: f64,
477 sarg: f64,
478 sargsw: f64,
479 t_bulk_pot: f64,
480 t_dep_cap: f64,
481) -> (f64, f64, f64) {
482 let f2 = cz * (1.0 - m.fc * (1.0 + m.mj)) * sarg / arg
483 + czsw * (1.0 - m.fc * (1.0 + m.mjsw)) * sargsw / arg;
484 let f3 = cz * m.mj * sarg / arg / t_bulk_pot + czsw * m.mjsw * sargsw / arg / t_bulk_pot;
485 let f4 = cz * t_bulk_pot * (1.0 - arg * sarg) / (1.0 - m.mj)
486 + czsw * t_bulk_pot * (1.0 - arg * sargsw) / (1.0 - m.mjsw)
487 - f3 / 2.0 * (t_dep_cap * t_dep_cap)
488 - t_dep_cap * f2;
489 (f2, f3, f4)
490}
491
492const VBD: usize = 0;
494const VBS: usize = 1;
495const VGS: usize = 2;
496const VDS: usize = 3;
497const CAPGS: usize = 4;
498const QGS: usize = 5;
499const CQGS: usize = 6;
500const CAPGD: usize = 7;
501const QGD: usize = 8;
502const CQGD: usize = 9;
503const CAPGB: usize = 10;
504const QGB: usize = 11;
505const CQGB: usize = 12;
506const QBD: usize = 13;
507const CQBD: usize = 14;
508const QBS: usize = 15;
509const CQBS: usize = 16;
510pub(crate) const MOS3_NUM_STATES: usize = 17;
511
512const D_D: usize = 0;
514const G_G: usize = 1;
515const S_S: usize = 2;
516const B_B: usize = 3;
517const DP_DP: usize = 4;
518const SP_SP: usize = 5;
519const D_DP: usize = 6;
520const G_B: usize = 7;
521const G_DP: usize = 8;
522const G_SP: usize = 9;
523const S_SP: usize = 10;
524const B_DP: usize = 11;
525const B_SP: usize = 12;
526const DP_SP: usize = 13;
527const DP_D: usize = 14;
528const B_G: usize = 15;
529const DP_G: usize = 16;
530const SP_G: usize = 17;
531const SP_S: usize = 18;
532const DP_B: usize = 19;
533const SP_B: usize = 20;
534const SP_DP: usize = 21;
535
536fn bulk_diode(v: f64, vt: f64, sat_cur: f64, gmin: f64) -> (f64, f64) {
539 const MAX_EXP_ARG: f64 = 709.0;
540 if v <= -3.0 * vt {
541 let arg = 3.0 * vt / (v * crate::consts::E);
542 let arg = arg * arg * arg;
543 (
544 sat_cur * 3.0 * arg / v + gmin,
545 -sat_cur * (1.0 + arg) + gmin * v,
546 )
547 } else {
548 let ev = f64::exp(f64::min(MAX_EXP_ARG, v / vt));
549 (sat_cur * ev / vt + gmin, sat_cur * (ev - 1.0) + gmin * v)
550 }
551}
552
553const COEFF0: f64 = 0.0631353;
556const COEFF1: f64 = 0.8013292;
557const COEFF2: f64 = -0.01110777;
558
559#[derive(Clone, Copy, Debug, Default, PartialEq)]
562pub(crate) struct Mos3Eval {
563 pub cdrain: f64,
564 pub gm: f64,
565 pub gds: f64,
566 pub gmbs: f64,
567 pub von: f64,
568 pub vdsat: f64,
569}
570
571pub(crate) struct Mos3I {
572 drain: Var,
573 gate: Var,
574 source: Var,
575 bulk: Var,
576 d_p: Var,
577 s_p: Var,
578 t: Mos3Temp,
579 geom: MosGeom,
580 off: bool,
581 ic_vds: f64,
582 ic_vgs: f64,
583 ic_vbs: f64,
584 ic_given: crate::devices::IcGiven,
585 st: usize,
586 s: [Slot; 22],
587 mode: f64,
589 von: f64,
590 vdsat: f64,
591 gm: f64,
592 gds: f64,
593 gmbs: f64,
594 gbd: f64,
595 gbs: f64,
596 capbd: f64,
597 capbs: f64,
598 cd: f64,
599 cbs: f64,
600 cbd: f64,
601 dist: MosDistoCoeffs,
603}
604
605impl Mos3I {
606 #[allow(clippy::too_many_arguments)]
607 pub fn new(
608 nodes: [Var; 4], primes: [Var; 2], t: Mos3Temp,
611 geom: MosGeom,
612 off: bool,
613 ic: (f64, f64, f64), st: usize,
615 ) -> Self {
616 Mos3I {
617 drain: nodes[0],
618 gate: nodes[1],
619 source: nodes[2],
620 bulk: nodes[3],
621 d_p: primes[0],
622 s_p: primes[1],
623 t,
624 geom,
625 off,
626 ic_vds: ic.0,
627 ic_vgs: ic.1,
628 ic_vbs: ic.2,
629 ic_given: crate::devices::IcGiven::ALL,
630 st,
631 s: [TRASH; 22],
632 mode: 1.0,
633 von: 0.0,
634 vdsat: 0.0,
635 gm: 0.0,
636 gds: 0.0,
637 gmbs: 0.0,
638 gbd: 0.0,
639 gbs: 0.0,
640 capbd: 0.0,
641 capbs: 0.0,
642 cd: 0.0,
643 cbs: 0.0,
644 cbd: 0.0,
645 dist: MosDistoCoeffs::default(),
646 }
647 }
648}
649
650#[allow(clippy::too_many_arguments, clippy::too_many_lines)]
667pub(crate) fn moseq3(
668 t: &Mos3Temp,
669 w: f64,
670 lvbs: f64,
671 lvgs: f64,
672 lvds: f64,
673 beta: f64,
674 oxide_cap: f64,
675 eff_l: f64,
676 vt: f64,
677) -> Mos3Eval {
678 let typ = t.type_;
679 let mut beta = beta;
680 let mut vdsat = 0.0;
681 let oneoverxl = 1.0 / eff_l;
682 let eta = t.eta * 8.15e-22 / (t.oxide_cap_factor * eff_l * eff_l * eff_l);
683
684 let (phibs, sqphbs, dsqdvb);
686 if lvbs <= 0.0 {
687 phibs = t.t_phi - lvbs;
688 sqphbs = phibs.sqrt();
689 dsqdvb = -0.5 / sqphbs;
690 } else {
691 let sqphis = t.t_phi.sqrt();
692 let sqphs3 = t.t_phi * sqphis;
693 sqphbs = sqphis / (1.0 + lvbs / (t.t_phi + t.t_phi));
694 phibs = sqphbs * sqphbs;
695 dsqdvb = -phibs / (sqphs3 + sqphs3);
696 }
697
698 let (fshort, dfsdvb);
700 if t.xj != 0.0 && t.coeff_dep_lay_width != 0.0 {
701 let wps = t.coeff_dep_lay_width * sqphbs;
702 let oneoverxj = 1.0 / t.xj;
703 let xjonxl = t.xj * oneoverxl;
704 let djonxj = t.lat_diff * oneoverxj;
705 let wponxj = wps * oneoverxj;
706 let wconxj = COEFF0 + COEFF1 * wponxj + COEFF2 * wponxj * wponxj;
707 let arga = wconxj + djonxj;
708 let argc = wponxj / (1.0 + wponxj);
709 let argb = (1.0 - argc * argc).sqrt();
710 fshort = 1.0 - xjonxl * (arga * argb - djonxj);
711 let dwpdvb = t.coeff_dep_lay_width * dsqdvb;
712 let dadvb = (COEFF1 + COEFF2 * (wponxj + wponxj)) * dwpdvb * oneoverxj;
713 let dbdvb = -argc * argc * (1.0 - argc) * dwpdvb / (argb * wps);
714 dfsdvb = -xjonxl * (dadvb * argb + arga * dbdvb);
715 } else {
716 fshort = 1.0;
717 dfsdvb = 0.0;
718 }
719
720 let gammas = t.gamma * fshort;
722 let fbodys = 0.5 * gammas / (sqphbs + sqphbs);
723 let fbody = fbodys + t.narrow_factor / w;
724 let onfbdy = 1.0 / (1.0 + fbody);
725 let dfbdvb = -fbodys * dsqdvb / sqphbs + fbodys * dfsdvb / fshort;
726 let qbonco = gammas * sqphbs + t.narrow_factor * phibs / w;
727 let dqbdvb = gammas * dsqdvb + t.gamma * dfsdvb * sqphbs - t.narrow_factor / w;
728
729 let vbix = t.t_vbi * typ - eta * lvds;
731
732 let vth = vbix + qbonco;
734 let dvtdvd = -eta;
735 let dvtdvb = dqbdvb;
736
737 let mut von = vth;
739 let (mut xn, mut dxndvb, mut dvodvd, mut dvodvb) = (0.0, 0.0, 0.0, 0.0);
740 if t.nfs != 0.0 {
741 let csonco = CHARGE * t.nfs * 1e4 * eff_l * w / oxide_cap;
742 let cdonco = qbonco / (phibs + phibs);
743 xn = 1.0 + csonco + cdonco;
744 von = vth + vt * xn;
745 dxndvb = dqbdvb / (phibs + phibs) - qbonco * dsqdvb / (phibs * sqphbs);
746 dvodvd = dvtdvd;
747 dvodvb = dvtdvb + vt * dxndvb;
748 } else if lvgs <= von {
749 return Mos3Eval {
751 cdrain: 0.0,
752 gm: 0.0,
753 gds: 0.0,
754 gmbs: 0.0,
755 von,
756 vdsat,
757 };
758 }
759
760 let vgsx = f64::max(lvgs, von);
762
763 let onfg = 1.0 + t.theta * (vgsx - vth);
765 let fgate = 1.0 / onfg;
766 let us = t.t_surf_mob * 1e-4 * fgate;
767 let dfgdvg = -t.theta * fgate * fgate;
768 let dfgdvd = -dfgdvg * dvtdvd;
769 let dfgdvb = -dfgdvg * dvtdvb;
770
771 vdsat = (vgsx - vth) * onfbdy;
773 let (dvsdvg, dvsdvd, dvsdvb);
774 let mut onvdsc = 0.0;
775 if t.vmax <= 0.0 {
776 dvsdvg = onfbdy;
777 dvsdvd = -dvsdvg * dvtdvd;
778 dvsdvb = -dvsdvg * dvtdvb - vdsat * dfbdvb * onfbdy;
779 } else {
780 let vdsc = eff_l * t.vmax / us;
781 onvdsc = 1.0 / vdsc;
782 let arga = (vgsx - vth) * onfbdy;
783 let argb = (arga * arga + vdsc * vdsc).sqrt();
784 vdsat = arga + vdsc - argb;
785 let dvsdga = (1.0 - arga / argb) * onfbdy;
786 dvsdvg = dvsdga - (1.0 - vdsc / argb) * vdsc * dfgdvg * onfg;
787 dvsdvd = -dvsdvg * dvtdvd;
788 dvsdvb = -dvsdvg * dvtdvb - arga * dvsdga * dfbdvb;
789 }
790
791 let vdsx = f64::min(lvds, vdsat);
793 if vdsx == 0.0 {
794 beta *= fgate;
796 let mut gds = beta * (vgsx - vth);
797 if t.nfs != 0.0 && lvgs < von {
798 gds *= f64::exp((lvgs - von) / (vt * xn));
799 }
800 return Mos3Eval {
801 cdrain: 0.0,
802 gm: 0.0,
803 gds,
804 gmbs: 0.0,
805 von,
806 vdsat,
807 };
808 }
809 let cdo = vgsx - vth - 0.5 * (1.0 + fbody) * vdsx;
810 let dcodvb = -dvtdvb - 0.5 * dfbdvb * vdsx;
811
812 let saturated = lvds > vdsat;
815 let cdnorm = cdo * vdsx;
816 let mut gm = vdsx;
817 let mut gds = if saturated {
821 -dvtdvd * vdsx
822 } else {
823 vgsx - vth - (1.0 + fbody + dvtdvd) * vdsx
824 };
825 let mut gmbs = dcodvb * vdsx;
826
827 let cd1 = beta * cdnorm;
829 beta *= fgate;
830 let mut cdrain = beta * cdnorm;
831 gm = beta * gm + dfgdvg * cd1;
832 gds = beta * gds + dfgdvd * cd1;
833 gmbs = beta * gmbs + dfgdvb * cd1;
835
836 let (mut fdrain, mut dfddvg, mut dfddvd, mut dfddvb) = (0.0, 0.0, 0.0, 0.0);
838 if t.vmax > 0.0 {
839 fdrain = 1.0 / (1.0 + vdsx * onvdsc);
840 let fd2 = fdrain * fdrain;
841 let arga = fd2 * vdsx * onvdsc * onfg;
842 dfddvg = -dfgdvg * arga;
843 dfddvd = if saturated {
844 -dfgdvd * arga
845 } else {
846 -dfgdvd * arga - fd2 * onvdsc
847 };
848 dfddvb = -dfgdvb * arga;
849 gm = fdrain * gm + dfddvg * cdrain;
851 gds = fdrain * gds + dfddvd * cdrain;
852 gmbs = fdrain * gmbs + dfddvb * cdrain;
853 cdrain *= fdrain;
854 #[allow(unused_assignments)] {
856 beta *= fdrain;
857 }
858 }
859
860 let mut gds0 = 0.0;
863 'line700: {
864 let (mut delxl, mut dldvd, mut ddldvg, mut ddldvd, mut ddldvb);
865 if !saturated {
866 if t.vmax > 0.0 || t.alpha == 0.0 {
871 break 'line700;
872 }
873 let mut arga = lvds / vdsat;
874 delxl = (t.kappa * t.alpha * vdsat / 8.0).sqrt();
875 dldvd = 4.0 * delxl * arga * arga * arga / vdsat;
876 arga *= arga;
877 arga *= arga;
878 delxl *= arga;
879 ddldvg = 0.0;
880 ddldvd = -dldvd;
881 ddldvb = 0.0;
882 } else if t.vmax <= 0.0 {
883 let arg = lvds - vdsat + vdsat / 8.0;
887 delxl = (t.kappa * t.alpha * arg).sqrt();
888 dldvd = 0.5 * delxl / arg;
889 ddldvg = 0.0;
890 ddldvd = -dldvd;
891 ddldvb = 0.0;
892 } else {
893 if t.alpha == 0.0 {
894 break 'line700;
895 }
896 let cdsat = cdrain;
897 let mut gdsat = cdsat * (1.0 - fdrain) * onvdsc;
898 gdsat = f64::max(1.0e-12, gdsat);
899 let gdoncd = gdsat / cdsat;
900 let gdonfd = gdsat / (1.0 - fdrain);
901 let gdonfg = gdsat * onfg;
902 let dgdvg = gdoncd * gm - gdonfd * dfddvg + gdonfg * dfgdvg;
903 let dgdvd = gdoncd * gds - gdonfd * dfddvd + gdonfg * dfgdvd;
904 let dgdvb = gdoncd * gmbs - gdonfd * dfddvb + gdonfg * dfgdvb;
905
906 let emax = t.kappa * cdsat * oneoverxl / gdsat;
907 let emoncd = emax / cdsat;
908 let emongd = emax / gdsat;
909 let demdvg = emoncd * gm - emongd * dgdvg;
910 let demdvd = emoncd * gds - emongd * dgdvd;
911 let demdvb = emoncd * gmbs - emongd * dgdvb;
912
913 let arga = 0.5 * emax * t.alpha;
914 let argc = t.kappa * t.alpha;
915 let argb = (arga * arga + argc * (lvds - vdsat)).sqrt();
916 delxl = argb - arga;
917 let dldem;
918 if argb != 0.0 {
919 dldvd = argc / (argb + argb);
920 dldem = 0.5 * (arga / argb - 1.0) * t.alpha;
921 } else {
922 dldvd = 0.0;
923 dldem = 0.0;
924 }
925 ddldvg = dldem * demdvg;
926 ddldvd = dldem * demdvd - dldvd;
927 ddldvb = dldem * demdvb;
928 }
929 if delxl > 0.5 * eff_l {
931 delxl = eff_l - (eff_l * eff_l / (4.0 * delxl));
932 let arga = 4.0 * (eff_l - delxl) * (eff_l - delxl) / (eff_l * eff_l);
933 ddldvg *= arga;
934 ddldvd *= arga;
935 ddldvb *= arga;
936 dldvd *= arga;
937 }
938 let dlonxl = delxl * oneoverxl;
940 let xlfact = 1.0 / (1.0 - dlonxl);
941 cdrain *= xlfact;
942 let diddl = cdrain / (eff_l - delxl);
943 gm = gm * xlfact + diddl * ddldvg;
944 gmbs = gmbs * xlfact + diddl * ddldvb;
945 gds0 = diddl * ddldvd;
946 gm += gds0 * dvsdvg;
947 gmbs += gds0 * dvsdvb;
948 gds = gds * xlfact + diddl * dldvd + gds0 * dvsdvd;
949 }
950
951 if lvgs < von {
953 let onxn = 1.0 / xn;
955 let ondvt = onxn / vt;
956 let wfact = f64::exp((lvgs - von) * ondvt);
957 cdrain *= wfact;
958 let gms = gm * wfact;
959 let gmw = cdrain * ondvt;
960 gm = gmw;
961 if saturated {
962 gm += gds0 * dvsdvg * wfact;
963 }
964 gds = gds * wfact + (gms - gmw) * dvodvd;
965 gmbs = gmbs * wfact + (gms - gmw) * dvodvb - gmw * (lvgs - von) * onxn * dxndvb;
966 }
967
968 Mos3Eval {
969 cdrain,
970 gm,
971 gds,
972 gmbs,
973 von,
974 vdsat,
975 }
976}
977
978impl Mos3I {
979 #[allow(unused_assignments, clippy::too_many_lines)]
989 fn disto_coeffs(&self, sol: &[f64], gmin: f64) -> MosDistoCoeffs {
990 let t = &self.t;
991 let g = &self.geom;
992 let typ = t.type_;
993 let vt = t.vt;
994 let eff_l = g.l - 2.0 * t.lat_diff;
995 let (drain_sat_cur, source_sat_cur) =
996 if t.t_sat_cur_dens == 0.0 || g.ad == 0.0 || g.as_ == 0.0 {
997 (t.t_sat_cur, t.t_sat_cur)
998 } else {
999 (t.t_sat_cur_dens * g.ad, t.t_sat_cur_dens * g.as_)
1000 };
1001 let mut beta = t.t_transconductance * g.w / eff_l;
1002 let oxide_cap = t.oxide_cap_factor * eff_l * g.w;
1003
1004 let vbs = typ * (sol[self.bulk] - sol[self.s_p]);
1005 let vgs = typ * (sol[self.gate] - sol[self.s_p]);
1006 let vds = typ * (sol[self.d_p] - sol[self.s_p]);
1007 let vbd = vbs - vds;
1008 let vgd = vgs - vds;
1009
1010 let gbs = disto_bulk_diode(vbs, source_sat_cur, vt, gmin, typ);
1011 let gbd = disto_bulk_diode(vbd, drain_sat_cur, vt, gmin, typ);
1012 let mode = if vds >= 0.0 { 1.0 } else { -1.0 };
1013
1014 let (lvgs, lvds, lvbs) = if mode == 1.0 {
1015 (vgs, vds, vbs)
1016 } else {
1017 (vgd, -vds, vbd)
1018 };
1019
1020 let d_p = Dderivs::var_p();
1021 let d_q = Dderivs::var_q();
1022 let d_r = Dderivs::var_r();
1023 let d_zero = Dderivs::default();
1024 let mut d_cdrain = d_zero;
1025 let mut vdsat = 0.0;
1026 let mut von;
1027
1028 'eval: {
1029 let mut d_vdsat;
1030 let oneoverxl = 1.0 / eff_l;
1031 let eta = t.eta * 8.15e-22 / (t.oxide_cap_factor * eff_l * eff_l * eff_l);
1032
1033 let (phibs, sqphbs, d_phibs, d_sqphbs);
1035 if lvbs <= 0.0 {
1036 phibs = t.t_phi - lvbs;
1037 let mut d = Dderivs { value: lvbs, ..d_q }.times(-1.0);
1038 d.value += t.t_phi;
1039 d_phibs = d;
1040 sqphbs = f64::sqrt(phibs);
1041 d_sqphbs = d_phibs.sqrt();
1042 } else {
1043 let sqphis = f64::sqrt(t.t_phi);
1044 sqphbs = sqphis / (1.0 + lvbs / (t.t_phi + t.t_phi));
1045 let mut d = Dderivs { value: lvbs, ..d_q }.times(1.0 / (t.t_phi + t.t_phi));
1046 d.value += 1.0;
1047 d_sqphbs = d.inv().times(sqphis);
1048 phibs = sqphbs * sqphbs;
1049 d_phibs = d_sqphbs.mult(&d_sqphbs);
1050 }
1051
1052 const COEFF0: f64 = 0.0631353e0;
1054 const COEFF1: f64 = 0.8013292e0;
1055 const COEFF2: f64 = -0.01110777e0;
1056 let (fshort, d_fshort);
1057 if t.xj != 0.0 && t.coeff_dep_lay_width != 0.0 {
1058 let wps = t.coeff_dep_lay_width * sqphbs;
1059 let d_wps = d_sqphbs.times(t.coeff_dep_lay_width);
1060 let oneoverxj = 1.0 / t.xj;
1061 let xjonxl = t.xj * oneoverxl;
1062 let djonxj = t.lat_diff * oneoverxj;
1063 let wponxj = wps * oneoverxj;
1064 let d_wponxj = d_wps.times(oneoverxj);
1065 let wconxj = COEFF0 + COEFF1 * wponxj + COEFF2 * wponxj * wponxj;
1066 let mut d_wconxj = d_wponxj.times(COEFF2);
1067 d_wconxj.value += COEFF1;
1068 let mut d_wconxj = d_wconxj.mult(&d_wponxj);
1069 d_wconxj.value += COEFF0;
1070 let arga = wconxj + djonxj;
1071 let mut d_arga = d_wconxj;
1072 d_arga.value += djonxj;
1073 let argc = wponxj / (1.0 + wponxj);
1074 let mut d_argc = d_wponxj;
1075 d_argc.value += 1.0;
1076 let d_argc = d_argc.inv().mult(&d_wponxj);
1077 let argb = f64::sqrt(1.0 - argc * argc);
1078 let mut d_argb = d_argc.mult(&d_argc).times(-1.0);
1079 d_argb.value += 1.0;
1080 let d_argb = d_argb.sqrt();
1081 fshort = 1.0 - xjonxl * (arga * argb - djonxj);
1082 let mut d = d_arga.mult(&d_argb);
1083 d.value -= djonxj;
1084 let mut d = d.times(-xjonxl);
1085 d.value += 1.0;
1086 d_fshort = d;
1087 } else {
1088 fshort = 1.0;
1089 d_fshort = Dderivs::constant(1.0);
1090 }
1091
1092 let gammas = t.gamma * fshort;
1094 let d_gammas = d_fshort.times(t.gamma);
1095 let fbodys = 0.5 * gammas / (sqphbs + sqphbs);
1096 let d_fbodys = d_gammas.div(&d_sqphbs).times(0.25);
1097 let fbody = fbodys + t.narrow_factor / g.w;
1098 let mut d_fbody = d_fbodys;
1099 d_fbody.value += fbody - fbodys;
1100 let onfbdy = 1.0 / (1.0 + fbody);
1101 let mut d_onfbdy = d_fbody;
1102 d_onfbdy.value += 1.0;
1103 let d_onfbdy = d_onfbdy.inv();
1104 let qbonco = gammas * sqphbs + t.narrow_factor * phibs / g.w;
1105 let d_dummy = d_phibs.times(t.narrow_factor * g.w);
1106 let d_qbonco = d_gammas.mult(&d_sqphbs).plus(&d_dummy);
1107
1108 let vbix = t.t_vbi * typ - eta * lvds;
1110 let d_vbix = Dderivs {
1111 value: vbix,
1112 d1_r: -eta,
1113 ..d_r
1114 };
1115 let vth = vbix + qbonco;
1116 let d_vth = d_vbix.plus(&d_qbonco);
1117 von = vth;
1118 let mut d_von = d_vth;
1119 let mut xn = 0.0;
1120 let mut d_xn = d_zero;
1121 if t.nfs != 0.0 {
1122 let csonco = CHARGE * t.nfs * 1e4 * eff_l * g.w / oxide_cap;
1123 let cdonco = 0.5 * qbonco / phibs;
1124 let d_cdonco = d_qbonco.div(&d_phibs).times(0.5);
1125 xn = 1.0 + csonco + cdonco;
1126 d_xn = d_cdonco;
1127 d_xn.value += 1.0 + csonco;
1128 von = vth + vt * xn;
1129 d_von = d_xn.times(vt).plus(&d_vth);
1130 } else if lvgs <= von {
1131 d_cdrain = d_zero;
1133 break 'eval;
1134 }
1135
1136 let vgsx = f64::max(lvgs, von);
1138 let d_vgsx = if lvgs >= von {
1139 Dderivs { value: lvgs, ..d_p }
1140 } else {
1141 d_von
1142 };
1143 let onfg = 1.0 + t.theta * (vgsx - vth);
1145 let mut d_onfg = d_vth.times(-1.0).plus(&d_vgsx).times(t.theta);
1146 d_onfg.value += 1.0;
1147 let fgate = 1.0 / onfg;
1148 let d_fgate = d_onfg.inv();
1149 let us = t.t_surf_mob * 1e-4 * fgate;
1150 let d_us = d_fgate.times(t.t_surf_mob * 1e-4);
1151 vdsat = (vgsx - vth) * onfbdy;
1153 d_vdsat = d_vth.times(-1.0).plus(&d_vgsx).mult(&d_onfbdy);
1154 let (mut onvdsc, mut d_onvdsc) = (0.0, d_zero);
1155 if t.vmax > 0.0 {
1156 let vdsc = eff_l * t.vmax / us;
1157 let d_vdsc = d_us.inv().times(eff_l * t.vmax);
1158 onvdsc = 1.0 / vdsc;
1159 d_onvdsc = d_vdsc.inv();
1160 let arga = (vgsx - vth) * onfbdy;
1161 let d_arga = d_vdsat;
1162 let argb = f64::sqrt(arga * arga + vdsc * vdsc);
1163 let d_argb = d_vdsc.mult(&d_vdsc).plus(&d_arga.mult(&d_arga)).sqrt();
1164 vdsat = arga + vdsc - argb;
1165 d_vdsat = d_argb.times(-1.0).plus(&d_vdsc).plus(&d_arga);
1166 }
1167
1168 let vdsx = f64::min(lvds, vdsat);
1170 let d_vdsx = if lvds < vdsat {
1171 Dderivs { value: lvds, ..d_r }
1172 } else {
1173 d_vdsat
1174 };
1175 if vdsx == 0.0 {
1176 beta *= fgate;
1180 let d_beta = d_fgate.times(beta);
1181 d_cdrain = d_zero;
1182 let mut d_gds = d_vth.times(-1.0).plus(&d_vgsx).mult(&d_beta);
1183 if t.nfs != 0.0 && lvgs < von {
1184 let mut d_dummy = d_von.times(-1.0);
1185 d_dummy.value += lvgs;
1186 d_dummy.d1_p += 1.0;
1187 let d_dummy = d_dummy.div(&d_xn).times(1.0 / vt).exp();
1188 d_gds = d_gds.mult(&d_dummy);
1189 }
1190 d_cdrain.d1_r = d_gds.value;
1191 d_cdrain.d2_r2 = d_gds.d1_r;
1192 d_cdrain.d3_r3 = d_gds.d2_r2;
1193 break 'eval;
1194 }
1195 let cdo = vgsx - vth - 0.5 * (1.0 + fbody) * vdsx;
1196 let mut d_cdo = d_fbody;
1197 d_cdo.value += 1.0;
1198 let d_cdo = d_cdo
1199 .mult(&d_vdsx)
1200 .times(0.5)
1201 .plus(&d_vth)
1202 .times(-1.0)
1203 .plus(&d_vgsx);
1204 let cdnorm = cdo * vdsx;
1211 let d_cdnorm = d_cdo.mult(&d_vdsx);
1212 beta *= fgate;
1213 let d_beta = d_fgate.times(beta);
1214 let mut cdrain = beta * cdnorm;
1215 d_cdrain = d_beta.mult(&d_cdnorm);
1216 let (mut fdrain, mut d_fdrain) = (0.0, d_zero);
1217 if t.vmax != 0.0 {
1218 fdrain = 1.0 / (1.0 + vdsx * onvdsc);
1219 let mut f = d_vdsx.mult(&d_onvdsc);
1220 f.value += 1.0;
1221 d_fdrain = f.inv();
1222 cdrain *= fdrain;
1223 d_cdrain = d_cdrain.mult(&d_fdrain);
1224 }
1225
1226 if lvds > vdsat && !(t.vmax != 0.0 && t.alpha == 0.0) {
1228 let (mut delxl, mut d_delxl);
1229 if t.vmax == 0.0 {
1230 delxl = f64::sqrt(t.kappa * (lvds - vdsat) * t.alpha);
1231 let mut d = d_vdsat.times(-1.0);
1232 d.value += lvds;
1233 d.d1_r += 1.0;
1234 d_delxl = d.times(t.kappa * t.alpha).sqrt();
1235 } else {
1236 let cdsat = cdrain;
1237 let d_cdsat = d_cdrain;
1238 let mut gdsat = cdsat * (1.0 - fdrain) * onvdsc;
1239 let mut d_dummy = d_fdrain.times(-1.0);
1240 d_dummy.value += 1.0;
1241 let mut d_gdsat = d_cdsat.mult(&d_dummy).mult(&d_onvdsc);
1242 gdsat = f64::max(1.0e-12, gdsat);
1243 if gdsat == 1.0e-12 {
1244 d_gdsat = Dderivs::constant(gdsat);
1245 }
1246 let emax = cdsat * oneoverxl / gdsat;
1247 let d_emax = d_cdsat.div(&d_gdsat).times(oneoverxl);
1248 let arga = 0.5 * emax * t.alpha;
1249 let d_arga = d_emax.times(0.5 * t.alpha);
1250 let argc = t.kappa * t.alpha;
1251 let argb = f64::sqrt(arga * arga + argc * (lvds - vdsat));
1252 let mut d_dummy = d_vdsat.times(-1.0);
1253 d_dummy.value += lvds;
1254 d_dummy.d1_r += 1.0;
1255 let d_argb = d_dummy.times(argc).plus(&d_arga.mult(&d_arga)).sqrt();
1256 delxl = argb - arga;
1257 d_delxl = d_argb.plus(&d_arga.times(-1.0));
1258 }
1259 if delxl > 0.5 * eff_l {
1261 delxl = eff_l - (eff_l * eff_l / delxl * 0.25);
1262 d_delxl = d_delxl.inv().times(-eff_l * eff_l * 0.25);
1263 d_delxl.value += eff_l;
1264 }
1265 let _ = delxl;
1266 let d_dlonxl = d_delxl.times(oneoverxl);
1267 let mut d_xlfact = d_dlonxl.times(-1.0);
1268 d_xlfact.value += 1.0;
1269 d_cdrain = d_cdrain.mult(&d_xlfact.inv());
1270 }
1271
1272 if lvgs < von {
1274 let d_onxn = d_xn.inv();
1275 let d_ondvt = d_onxn.times(1.0 / vt);
1276 let mut d_wfact = d_von.times(-1.0);
1277 d_wfact.value += lvgs;
1278 d_wfact.d1_p += 1.0;
1279 let d_wfact = d_wfact.mult(&d_ondvt).exp();
1280 d_cdrain = d_cdrain.mult(&d_wfact);
1281 }
1282 let _ = (xn, cdrain, us);
1283 }
1284
1285 let junctions = MosJunctions {
1286 cbs: t.cbs,
1287 cbssw: t.cbssw,
1288 f3s: t.f3s,
1289 cbd: t.cbd,
1290 cbdsw: t.cbdsw,
1291 f3d: t.f3d,
1292 dep_cap: t.t_dep_cap,
1293 bulk_pot: t.t_bulk_pot,
1294 mj: t.mj,
1295 mjsw: t.mjsw,
1296 };
1297 setup_tail(
1298 typ,
1299 mode,
1300 (vbs, vbd, vds),
1301 (lvgs - von, vdsat),
1302 t.t_phi,
1303 oxide_cap,
1304 &junctions,
1305 &DrainDerivs::from_dderivs(&d_cdrain),
1306 gbs,
1307 gbd,
1308 )
1309 }
1310}
1311
1312impl crate::devices::Device for Mos3I {
1313 fn par_load(&self) -> bool {
1314 true
1315 }
1316
1317 fn set_ic(&mut self, rhs: &[f64]) {
1320 use crate::devices::getic;
1321 let g = self.ic_given;
1322 getic(g, 2, &mut self.ic_vbs, rhs, self.bulk, self.source);
1323 getic(g, 0, &mut self.ic_vds, rhs, self.drain, self.source);
1324 getic(g, 1, &mut self.ic_vgs, rhs, self.gate, self.source);
1325 }
1326
1327 fn set_ic_given(&mut self, given: crate::devices::IcGiven) {
1328 self.ic_given = given;
1329 }
1330
1331 fn conv_test(&self, ctx: &crate::matrix::ConvCtx) -> bool {
1334 let typ = self.t.type_;
1335 let st = self.st;
1336 let vbs = typ * (ctx.rhs[self.bulk] - ctx.rhs[self.s_p]);
1337 let vgs = typ * (ctx.rhs[self.gate] - ctx.rhs[self.s_p]);
1338 let vds = typ * (ctx.rhs[self.d_p] - ctx.rhs[self.s_p]);
1339 let vbd = vbs - vds;
1340 let vgd = vgs - vds;
1341 let vgdo = ctx.states.get(0, st + VGS) - ctx.states.get(0, st + VDS);
1342 let delvbs = vbs - ctx.states.get(0, st + VBS);
1343 let delvbd = vbd - ctx.states.get(0, st + VBD);
1344 let delvgs = vgs - ctx.states.get(0, st + VGS);
1345 let delvds = vds - ctx.states.get(0, st + VDS);
1346 let delvgd = vgd - vgdo;
1347 let cdhat = if self.mode >= 0.0 {
1348 self.cd - self.gbd * delvbd + self.gmbs * delvbs + self.gm * delvgs + self.gds * delvds
1349 } else {
1350 self.cd - (self.gbd - self.gmbs) * delvbd - self.gm * delvgd + self.gds * delvds
1351 };
1352 let cbhat = self.cbs + self.cbd + self.gbd * delvbd + self.gbs * delvbs;
1353 let tol = ctx.reltol * f64::max(cdhat.abs(), self.cd.abs()) + ctx.abstol;
1354 if (cdhat - self.cd).abs() >= tol {
1355 return false;
1356 }
1357 let cb = self.cbs + self.cbd;
1358 let tol = ctx.reltol * f64::max(cbhat.abs(), cb.abs()) + ctx.abstol;
1359 (cbhat - cb).abs() <= tol
1360 }
1361
1362 fn alloc(&mut self, mb: &mut MatrixBuilder) {
1363 let (d, g, s, b) = (self.drain, self.gate, self.source, self.bulk);
1364 let (dp, sp) = (self.d_p, self.s_p);
1365 self.s = [
1366 mb.slot(d, d),
1367 mb.slot(g, g),
1368 mb.slot(s, s),
1369 mb.slot(b, b),
1370 mb.slot(dp, dp),
1371 mb.slot(sp, sp),
1372 mb.slot(d, dp),
1373 mb.slot(g, b),
1374 mb.slot(g, dp),
1375 mb.slot(g, sp),
1376 mb.slot(s, sp),
1377 mb.slot(b, dp),
1378 mb.slot(b, sp),
1379 mb.slot(dp, sp),
1380 mb.slot(dp, d),
1381 mb.slot(b, g),
1382 mb.slot(dp, g),
1383 mb.slot(sp, g),
1384 mb.slot(sp, s),
1385 mb.slot(dp, b),
1386 mb.slot(sp, b),
1387 mb.slot(sp, dp),
1388 ];
1389 }
1390
1391 #[allow(clippy::too_many_lines)]
1392 fn load(&mut self, ctx: &mut LoadCtx) {
1393 let typ = self.t.type_;
1394 let vt = self.t.vt;
1395 let st = self.st;
1396
1397 let eff_l = self.geom.l - 2.0 * self.t.lat_diff;
1398 let (drain_sat_cur, source_sat_cur) =
1399 if self.t.t_sat_cur_dens == 0.0 || self.geom.ad == 0.0 || self.geom.as_ == 0.0 {
1400 (self.t.t_sat_cur, self.t.t_sat_cur)
1401 } else {
1402 (
1403 self.t.t_sat_cur_dens * self.geom.ad,
1404 self.t.t_sat_cur_dens * self.geom.as_,
1405 )
1406 };
1407 let gate_source_overlap_cap = self.t.cgso * self.geom.w;
1408 let gate_drain_overlap_cap = self.t.cgdo * self.geom.w;
1409 let gate_bulk_overlap_cap = self.t.cgbo * eff_l;
1410 let beta = self.t.t_transconductance * self.geom.w / eff_l;
1411 let oxide_cap = self.t.oxide_cap_factor * eff_l * self.geom.w;
1412
1413 let mut check = true;
1415 let (mut vbs, mut vgs, mut vds);
1416 let predictor = matches!(ctx.mode, InitMode::Pred | InitMode::Tran);
1417 let general = matches!(ctx.mode, InitMode::Float | InitMode::Smsig)
1418 || (ctx.mode == InitMode::Fix && !self.off);
1419 if predictor || general {
1420 if predictor {
1421 let xfact = ctx.pred_xfact;
1422 let mut pred = |o: usize| {
1423 let s1 = ctx.states.get(1, st + o);
1424 ctx.states.set(0, st + o, s1);
1425 (1.0 + xfact) * s1 - xfact * ctx.states.get(2, st + o)
1426 };
1427 vbs = pred(VBS);
1428 vgs = pred(VGS);
1429 vds = pred(VDS);
1430 let vbd0 = ctx.states.get(0, st + VBS) - ctx.states.get(0, st + VDS);
1431 ctx.states.set(0, st + VBD, vbd0);
1432 } else {
1433 vbs = typ * (ctx.sol[self.bulk] - ctx.sol[self.s_p]);
1434 vgs = typ * (ctx.sol[self.gate] - ctx.sol[self.s_p]);
1435 vds = typ * (ctx.sol[self.d_p] - ctx.sol[self.s_p]);
1436 }
1437 let mut vbd = vbs - vds;
1438 let mut vgd = vgs - vds;
1439 let vgdo = ctx.states.get(0, st + VGS) - ctx.states.get(0, st + VDS);
1440
1441 let von = typ * self.von;
1442 if ctx.states.get(0, st + VDS) >= 0.0 {
1443 vgs = fetlim(vgs, ctx.states.get(0, st + VGS), von);
1444 vds = vgs - vgd;
1445 vds = limvds(vds, ctx.states.get(0, st + VDS));
1446 } else {
1447 vgd = fetlim(vgd, vgdo, von);
1448 vds = vgs - vgd;
1449 vds = -limvds(-vds, -ctx.states.get(0, st + VDS));
1450 vgs = vgd + vds;
1451 }
1452 if vds >= 0.0 {
1453 vbs = pnjlim(
1454 vbs,
1455 ctx.states.get(0, st + VBS),
1456 vt,
1457 self.t.source_vcrit,
1458 &mut check,
1459 );
1460 } else {
1461 vbd = pnjlim(
1462 vbd,
1463 ctx.states.get(0, st + VBD),
1464 vt,
1465 self.t.drain_vcrit,
1466 &mut check,
1467 );
1468 vbs = vbd + vds;
1469 }
1470 } else if ctx.mode == InitMode::Jct && !self.off {
1471 vds = typ * self.ic_vds;
1472 vgs = typ * self.ic_vgs;
1473 vbs = typ * self.ic_vbs;
1474 if vds == 0.0 && vgs == 0.0 && vbs == 0.0 && !(ctx.kind == AnKind::TranOp && ctx.uic) {
1475 vbs = -1.0;
1476 vgs = typ * self.t.t_vto;
1477 vds = 0.0;
1478 }
1479 } else {
1480 vbs = 0.0;
1481 vgs = 0.0;
1482 vds = 0.0;
1483 }
1484
1485 let vbd = vbs - vds;
1486 let vgd = vgs - vds;
1487 let vgb = vgs - vbs;
1488
1489 let (mut gbs, mut cbs) = bulk_diode(vbs, vt, source_sat_cur, ctx.gmin);
1495 let (mut gbd, mut cbd) = bulk_diode(vbd, vt, drain_sat_cur, ctx.gmin);
1496
1497 self.mode = if vds >= 0.0 { 1.0 } else { -1.0 };
1498 let mode = self.mode;
1499
1500 let (lvbs, lvgs) = if mode == 1.0 { (vbs, vgs) } else { (vbd, vgd) };
1502 let r = moseq3(
1503 &self.t,
1504 self.geom.w,
1505 lvbs,
1506 lvgs,
1507 mode * vds,
1508 beta,
1509 oxide_cap,
1510 eff_l,
1511 vt,
1512 );
1513 let (cdrain, von, vdsat) = (r.cdrain, r.von, r.vdsat);
1514 let (gm, gds, gmbs) = (r.gm, r.gds, r.gmbs);
1515 self.gm = gm;
1516 self.gds = gds;
1517 self.gmbs = gmbs;
1518 self.von = typ * von;
1519 self.vdsat = typ * vdsat;
1520
1521 let mut cd = mode * cdrain - cbd;
1522
1523 let t = &self.t;
1524 let smsig = ctx.mode == InitMode::Smsig;
1525 if ctx.kind == AnKind::Tran || ctx.kind == AnKind::TranOp || smsig {
1526 let dep = |v: f64, cz: f64, czsw: f64, f2: f64, f3: f64, f4: f64| {
1529 if cz != 0.0 || czsw != 0.0 {
1530 if v < t.t_dep_cap {
1531 let arg = 1.0 - v / t.t_bulk_pot;
1532 let (sarg, sargsw);
1533 if t.mj == t.mjsw {
1534 if t.mj == 0.5 {
1535 sarg = 1.0 / arg.sqrt();
1536 sargsw = sarg;
1537 } else {
1538 sarg = f64::exp(-t.mj * f64::ln(arg));
1539 sargsw = sarg;
1540 }
1541 } else {
1542 sarg = if t.mj == 0.5 {
1543 1.0 / arg.sqrt()
1544 } else {
1545 f64::exp(-t.mj * f64::ln(arg))
1546 };
1547 sargsw = if t.mjsw == 0.5 {
1548 1.0 / arg.sqrt()
1549 } else {
1550 f64::exp(-t.mjsw * f64::ln(arg))
1551 };
1552 }
1553 let q = t.t_bulk_pot
1554 * (cz * (1.0 - arg * sarg) / (1.0 - t.mj)
1555 + czsw * (1.0 - arg * sargsw) / (1.0 - t.mjsw));
1556 (q, cz * sarg + czsw * sargsw)
1557 } else {
1558 (f4 + v * (f2 + v * f3 / 2.0), f2 + v * f3)
1559 }
1560 } else {
1561 (0.0, 0.0)
1562 }
1563 };
1564 let (qbs, capbs) = dep(vbs, t.cbs, t.cbssw, t.f2s, t.f3s, t.f4s);
1565 ctx.states.set(0, st + QBS, qbs);
1566 self.capbs = capbs;
1567 let (qbd, capbd) = dep(vbd, t.cbd, t.cbdsw, t.f2d, t.f3d, t.f4d);
1568 ctx.states.set(0, st + QBD, qbd);
1569 self.capbd = capbd;
1570
1571 if ctx.kind == AnKind::Tran {
1572 let (geq, _) = ctx.integrate(self.capbd, st + QBD);
1573 gbd += geq;
1574 cbd += ctx.states.get(0, st + CQBD);
1575 cd -= ctx.states.get(0, st + CQBD);
1576 let (geq, _) = ctx.integrate(self.capbs, st + QBS);
1577 gbs += geq;
1578 cbs += ctx.states.get(0, st + CQBS);
1579 }
1580 }
1581 self.gbs = gbs;
1582 self.gbd = gbd;
1583
1584 if (!self.off || !matches!(ctx.mode, InitMode::Fix | InitMode::Smsig)) && check {
1585 ctx.noncon = true;
1586 }
1587
1588 ctx.states.set(0, st + VBS, vbs);
1589 ctx.states.set(0, st + VBD, vbd);
1590 ctx.states.set(0, st + VGS, vgs);
1591 ctx.states.set(0, st + VDS, vds);
1592
1593 let (mut capgs, mut capgd, mut capgb) = (0.0, 0.0, 0.0);
1595 if ctx.kind == AnKind::Tran || ctx.kind == AnKind::TranOp || smsig {
1596 let (half_gs, half_gd, half_gb) = if mode > 0.0 {
1597 qmeyer(vgs, vgd, vgb, von, vdsat, t.t_phi, oxide_cap)
1598 } else {
1599 let (o1, o2, o3) = qmeyer(vgd, vgs, vgb, von, vdsat, t.t_phi, oxide_cap);
1600 (o2, o1, o3)
1601 };
1602 ctx.states.set(0, st + CAPGS, half_gs);
1603 ctx.states.set(0, st + CAPGD, half_gd);
1604 ctx.states.set(0, st + CAPGB, half_gb);
1605 let vgs1 = ctx.states.get(1, st + VGS);
1606 let vgd1 = vgs1 - ctx.states.get(1, st + VDS);
1607 let vgb1 = vgs1 - ctx.states.get(1, st + VBS);
1608 if ctx.kind == AnKind::TranOp || smsig {
1609 capgs = 2.0 * ctx.states.get(0, st + CAPGS) + gate_source_overlap_cap;
1610 capgd = 2.0 * ctx.states.get(0, st + CAPGD) + gate_drain_overlap_cap;
1611 capgb = 2.0 * ctx.states.get(0, st + CAPGB) + gate_bulk_overlap_cap;
1612 } else {
1613 capgs = ctx.states.get(0, st + CAPGS)
1614 + ctx.states.get(1, st + CAPGS)
1615 + gate_source_overlap_cap;
1616 capgd = ctx.states.get(0, st + CAPGD)
1617 + ctx.states.get(1, st + CAPGD)
1618 + gate_drain_overlap_cap;
1619 capgb = ctx.states.get(0, st + CAPGB)
1620 + ctx.states.get(1, st + CAPGB)
1621 + gate_bulk_overlap_cap;
1622 }
1623
1624 if smsig {
1625 return;
1626 }
1627
1628 if predictor {
1629 let xfact = ctx.pred_xfact;
1630 for o in [QGS, QGD, QGB] {
1631 let v = (1.0 + xfact) * ctx.states.get(1, st + o)
1632 - xfact * ctx.states.get(2, st + o);
1633 ctx.states.set(0, st + o, v);
1634 }
1635 } else if ctx.kind == AnKind::Tran {
1636 let q = (vgs - vgs1) * capgs + ctx.states.get(1, st + QGS);
1637 ctx.states.set(0, st + QGS, q);
1638 let q = (vgd - vgd1) * capgd + ctx.states.get(1, st + QGD);
1639 ctx.states.set(0, st + QGD, q);
1640 let q = (vgb - vgb1) * capgb + ctx.states.get(1, st + QGB);
1641 ctx.states.set(0, st + QGB, q);
1642 } else {
1643 ctx.states.set(0, st + QGS, capgs * vgs);
1644 ctx.states.set(0, st + QGD, capgd * vgd);
1645 ctx.states.set(0, st + QGB, capgb * vgb);
1646 }
1647 }
1648
1649 let (mut gcgs, mut ceqgs, mut gcgd, mut ceqgd, mut gcgb, mut ceqgb) =
1651 (0.0, 0.0, 0.0, 0.0, 0.0, 0.0);
1652 if ctx.mode != InitMode::Tran && ctx.kind == AnKind::Tran {
1653 if capgs == 0.0 {
1654 ctx.states.set(0, st + CQGS, 0.0);
1655 }
1656 if capgd == 0.0 {
1657 ctx.states.set(0, st + CQGD, 0.0);
1658 }
1659 if capgb == 0.0 {
1660 ctx.states.set(0, st + CQGB, 0.0);
1661 }
1662 let (g, ceq) = ctx.integrate(capgs, st + QGS);
1663 gcgs = g;
1664 ceqgs = ceq - gcgs * vgs + ctx.ag[0] * ctx.states.get(0, st + QGS);
1665 let (g, ceq) = ctx.integrate(capgd, st + QGD);
1666 gcgd = g;
1667 ceqgd = ceq - gcgd * vgd + ctx.ag[0] * ctx.states.get(0, st + QGD);
1668 let (g, ceq) = ctx.integrate(capgb, st + QGB);
1669 gcgb = g;
1670 ceqgb = ceq - gcgb * vgb + ctx.ag[0] * ctx.states.get(0, st + QGB);
1671 }
1672
1673 let ceqbs = typ * (cbs - gbs * vbs);
1678 let ceqbd = typ * (cbd - gbd * vbd);
1679 let (xnrm, xrev, cdreq);
1680 if mode >= 0.0 {
1681 xnrm = 1.0;
1682 xrev = 0.0;
1683 cdreq = typ * (cdrain - gds * vds - gm * vgs - gmbs * vbs);
1684 } else {
1685 xnrm = 0.0;
1686 xrev = 1.0;
1687 cdreq = -typ * (cdrain - gds * (-vds) - gm * vgd - gmbs * vbd);
1688 }
1689 self.cd = cd;
1691 self.cbs = cbs;
1692 self.cbd = cbd;
1693 ctx.add_rhs(self.gate, -(typ * (ceqgs + ceqgb + ceqgd)));
1694 ctx.add_rhs(self.bulk, -(ceqbs + ceqbd - typ * ceqgb));
1695 ctx.add_rhs(self.d_p, ceqbd - cdreq + typ * ceqgd);
1696 ctx.add_rhs(self.s_p, cdreq + ceqbs + typ * ceqgs);
1697
1698 let gdpr = self.t.drain_conductance;
1700 let gspr = self.t.source_conductance;
1701 ctx.add(self.s[D_D], gdpr);
1702 ctx.add(self.s[G_G], gcgd + gcgs + gcgb);
1703 ctx.add(self.s[S_S], gspr);
1704 ctx.add(self.s[B_B], gbd + gbs + gcgb);
1705 ctx.add(self.s[DP_DP], gdpr + gds + gbd + xrev * (gm + gmbs) + gcgd);
1706 ctx.add(self.s[SP_SP], gspr + gds + gbs + xnrm * (gm + gmbs) + gcgs);
1707 ctx.add(self.s[D_DP], -gdpr);
1708 ctx.add(self.s[G_B], -gcgb);
1709 ctx.add(self.s[G_DP], -gcgd);
1710 ctx.add(self.s[G_SP], -gcgs);
1711 ctx.add(self.s[S_SP], -gspr);
1712 ctx.add(self.s[B_G], -gcgb);
1713 ctx.add(self.s[B_DP], -gbd);
1714 ctx.add(self.s[B_SP], -gbs);
1715 ctx.add(self.s[DP_D], -gdpr);
1716 ctx.add(self.s[DP_G], (xnrm - xrev) * gm - gcgd);
1717 ctx.add(self.s[DP_B], -gbd + (xnrm - xrev) * gmbs);
1718 ctx.add(self.s[DP_SP], -gds - xnrm * (gm + gmbs));
1719 ctx.add(self.s[SP_G], -(xnrm - xrev) * gm - gcgs);
1720 ctx.add(self.s[SP_S], -gspr);
1721 ctx.add(self.s[SP_B], -gbs - (xnrm - xrev) * gmbs);
1722 ctx.add(self.s[SP_DP], -gds - xrev * (gm + gmbs));
1723 }
1724
1725 fn trunc(&self, t: &crate::integ::TerrCtx, time_step: &mut f64) {
1726 crate::integ::terr(self.st + QGS, t, time_step);
1727 crate::integ::terr(self.st + QGD, t, time_step);
1728 crate::integ::terr(self.st + QGB, t, time_step);
1729 }
1730
1731 fn noise_suffixes(&self) -> Option<&'static [&'static str]> {
1736 Some(crate::mos1::MOS_NOISE_SUFFIXES)
1737 }
1738
1739 fn noise_dens(&self, ctx: &crate::matrix::NoiseCtx) -> Vec<f64> {
1740 let n = crate::mos1::MosNoise {
1741 drain: self.drain,
1742 source: self.source,
1743 d_p: self.d_p,
1744 s_p: self.s_p,
1745 gdpr: self.t.drain_conductance,
1746 gspr: self.t.source_conductance,
1747 gm: self.gm,
1748 cd: self.cd,
1749 kf: self.t.kf,
1750 af: self.t.af,
1751 nlev: self.t.nlev,
1752 gdsnoi: self.t.gdsnoi,
1753 w: self.geom.w,
1754 eff_l: self.geom.l - 2.0 * self.t.lat_diff,
1755 cox: self.t.oxide_cap_factor,
1756 temp: self.t.temp,
1757 vgs: ctx.states.get(0, self.st + VGS),
1758 vds: ctx.states.get(0, self.st + VDS),
1759 mode: self.mode,
1760 von: self.von,
1761 vdsat: self.vdsat,
1762 typ: self.t.type_,
1763 t_transconductance: self.t.t_transconductance,
1764 };
1765 crate::mos1::mos_noise(ctx, &n)
1766 }
1767
1768 fn disto_setup(&mut self, ctx: &DistoSetupCtx) -> Result<(), &'static str> {
1770 self.dist = self.disto_coeffs(ctx.sol, ctx.gmin);
1771 Ok(())
1772 }
1773
1774 fn disto_load(&self, ctx: &mut DistoCtx) {
1776 mos_disto_load(&self.dist, (self.gate, self.bulk, self.d_p, self.s_p), ctx);
1777 }
1778
1779 fn load_ac(&mut self, ctx: &mut AcCtx) {
1781 let t = &self.t;
1782 let st = self.st;
1783 let (xnrm, xrev) = if self.mode < 0.0 {
1784 (0.0, 1.0)
1785 } else {
1786 (1.0, 0.0)
1787 };
1788 let eff_l = self.geom.l - 2.0 * t.lat_diff;
1789 let capgs = 2.0 * ctx.states.get(0, st + CAPGS) + t.cgso * self.geom.w;
1790 let capgd = 2.0 * ctx.states.get(0, st + CAPGD) + t.cgdo * self.geom.w;
1791 let capgb = 2.0 * ctx.states.get(0, st + CAPGB) + t.cgbo * eff_l;
1792 let xgs = capgs * ctx.omega;
1793 let xgd = capgd * ctx.omega;
1794 let xgb = capgb * ctx.omega;
1795 let xbd = self.capbd * ctx.omega;
1796 let xbs = self.capbs * ctx.omega;
1797 let (gm, gds, gmbs) = (self.gm, self.gds, self.gmbs);
1798 let (gbd, gbs) = (self.gbd, self.gbs);
1799 let gdpr = t.drain_conductance;
1800 let gspr = t.source_conductance;
1801
1802 ctx.add_b(self.s[G_G], xgd + xgs + xgb);
1803 ctx.add_b(self.s[B_B], xgb + xbd + xbs);
1804 ctx.add_b(self.s[DP_DP], xgd + xbd);
1805 ctx.add_b(self.s[SP_SP], xgs + xbs);
1806 ctx.add_b(self.s[G_B], -xgb);
1807 ctx.add_b(self.s[G_DP], -xgd);
1808 ctx.add_b(self.s[G_SP], -xgs);
1809 ctx.add_b(self.s[B_G], -xgb);
1810 ctx.add_b(self.s[B_DP], -xbd);
1811 ctx.add_b(self.s[B_SP], -xbs);
1812 ctx.add_b(self.s[DP_G], -xgd);
1813 ctx.add_b(self.s[DP_B], -xbd);
1814 ctx.add_b(self.s[SP_G], -xgs);
1815 ctx.add_b(self.s[SP_B], -xbs);
1816 ctx.add_g(self.s[D_D], gdpr);
1817 ctx.add_g(self.s[S_S], gspr);
1818 ctx.add_g(self.s[B_B], gbd + gbs);
1819 ctx.add_g(self.s[DP_DP], gdpr + gds + gbd + xrev * (gm + gmbs));
1820 ctx.add_g(self.s[SP_SP], gspr + gds + gbs + xnrm * (gm + gmbs));
1821 ctx.add_g(self.s[D_DP], -gdpr);
1822 ctx.add_g(self.s[S_SP], -gspr);
1823 ctx.add_g(self.s[B_DP], -gbd);
1824 ctx.add_g(self.s[B_SP], -gbs);
1825 ctx.add_g(self.s[DP_D], -gdpr);
1826 ctx.add_g(self.s[DP_G], (xnrm - xrev) * gm);
1827 ctx.add_g(self.s[DP_B], -gbd + (xnrm - xrev) * gmbs);
1828 ctx.add_g(self.s[DP_SP], -gds - xnrm * (gm + gmbs));
1829 ctx.add_g(self.s[SP_G], -(xnrm - xrev) * gm);
1830 ctx.add_g(self.s[SP_S], -gspr);
1831 ctx.add_g(self.s[SP_B], -gbs - (xnrm - xrev) * gmbs);
1832 ctx.add_g(self.s[SP_DP], -gds - xrev * (gm + gmbs));
1833 }
1834
1835 fn load_pz(&mut self, ctx: &mut crate::matrix::PzCtx) -> Result<(), &'static str> {
1845 let t = &self.t;
1846 let st = self.st;
1847 let (xnrm, xrev) = (1.0, 0.0);
1848 let eff_l = self.geom.l - 2.0 * t.lat_diff;
1849 let caps = MeyerPzCaps {
1850 capgs: 2.0 * ctx.states.get(0, st + CAPGS) + t.cgso * self.geom.w,
1851 capgd: 2.0 * ctx.states.get(0, st + CAPGD) + t.cgdo * self.geom.w,
1852 capgb: 2.0 * ctx.states.get(0, st + CAPGB) + t.cgbo * eff_l,
1853 capbd: self.capbd,
1854 capbs: self.capbs,
1855 };
1856 let g = MosPzConductances {
1857 xnrm,
1858 xrev,
1859 gm: self.gm,
1860 gds: self.gds,
1861 gmbs: self.gmbs,
1862 gbd: self.gbd,
1863 gbs: self.gbs,
1864 gdpr: t.drain_conductance,
1865 gspr: t.source_conductance,
1866 };
1867 meyer_pz_load(ctx, &self.s, &caps, &g);
1868 Ok(())
1869 }
1870}
1871
1872#[cfg(test)]
1873mod tests {
1874 use super::*;
1875
1876 fn geom(l: f64, w: f64) -> MosGeom {
1877 MosGeom {
1878 l,
1879 w,
1880 ad: 0.0,
1881 as_: 0.0,
1882 pd: 0.0,
1883 ps: 0.0,
1884 nrd: 1.0,
1885 nrs: 1.0,
1886 }
1887 }
1888
1889 fn n3(vmax: f64) -> Mos3Model {
1892 let mut m = Mos3Model::new(true);
1893 m.vto = Some(0.7);
1894 m.nsub = Some(3e16);
1895 m.tox = Some(2e-8);
1896 m.xj = 0.2e-6;
1897 m.ld = 0.05e-6;
1898 m.u0 = Some(500.0);
1899 m.theta = 0.08;
1900 m.eta = 0.3;
1901 m.delta = 1.2;
1902 m.vmax = vmax;
1903 m.kappa = 0.5;
1904 m.nfs = 8e11;
1905 m
1906 }
1907
1908 struct Dev {
1909 t: Mos3Temp,
1910 w: f64,
1911 eff_l: f64,
1912 beta: f64,
1913 cox: f64,
1914 }
1915
1916 impl Dev {
1917 fn new(m: &Mos3Model, l: f64, w: f64) -> Dev {
1918 let g = geom(l, w);
1919 let t = Mos3Temp::new("m1", m, &g, REFTEMP, REFTEMP).unwrap();
1920 let eff_l = l - 2.0 * t.lat_diff;
1921 let beta = t.t_transconductance * w / eff_l;
1922 let cox = t.oxide_cap_factor * eff_l * w;
1923 Dev {
1924 t,
1925 w,
1926 eff_l,
1927 beta,
1928 cox,
1929 }
1930 }
1931 fn eval(&self, vgs: f64, vds: f64, vbs: f64) -> Mos3Eval {
1932 moseq3(
1933 &self.t, self.w, vbs, vgs, vds, self.beta, self.cox, self.eff_l, self.t.vt,
1934 )
1935 }
1936 fn fd(&self, vgs: f64, vds: f64, vbs: f64) -> (f64, f64, f64) {
1939 let h = 1e-6;
1940 let c = |a: f64, b: f64, d: f64| self.eval(a, b, d).cdrain;
1941 (
1942 (c(vgs + h, vds, vbs) - c(vgs - h, vds, vbs)) / (2.0 * h),
1943 (c(vgs, vds + h, vbs) - c(vgs, vds - h, vbs)) / (2.0 * h),
1944 (c(vgs, vds, vbs + h) - c(vgs, vds, vbs - h)) / (2.0 * h),
1945 )
1946 }
1947 }
1948
1949 fn rel(a: f64, b: f64) -> f64 {
1950 (a - b).abs() / f64::max(a.abs(), b.abs())
1951 }
1952
1953 #[test]
1954 fn temp_derives_from_nsub_with_ngspice_ni() {
1955 let d = Dev::new(&n3(1.5e5), 2e-6, 2e-6);
1956 let t = &d.t;
1957 let cox = 3.9 * 8.854214871e-12 / 2e-8;
1958 assert!(rel(t.oxide_cap_factor, cox) < 1e-15);
1959 let gamma = f64::sqrt(2.0 * EPSSIL * CHARGE * 3e16 * 1e6) / cox;
1961 assert!(rel(t.gamma, gamma) < 1e-14);
1962 let alpha = 2.0 * EPSSIL / (CHARGE * 3e16 * 1e6);
1964 assert!(rel(t.alpha, alpha) < 1e-14);
1965 assert!(rel(t.coeff_dep_lay_width, alpha.sqrt()) < 1e-14);
1966 let nf = 1.2 * 0.5 * std::f64::consts::PI * EPSSIL / cox;
1968 assert!(rel(t.narrow_factor, nf) < 1e-14);
1969 assert!(rel(t.t_transconductance, 500.0 * cox * 1e-4) < 1e-14);
1971 assert!(rel(t.t_surf_mob, 500.0) < 1e-15);
1972 let vtnom = REFTEMP * KOVERQ;
1975 let phi_3f5 = 2.0 * vtnom * f64::ln(3e16 * 1e6 / 1.45e16);
1976 assert!(t.t_phi < phi_3f5 && phi_3f5 - t.t_phi < 1e-3);
1977 assert!((t.t_vto - 0.7).abs() < 1e-12);
1979 }
1980
1981 #[test]
1982 fn temp_rejects_bad_cards() {
1983 let mut m = n3(0.0);
1984 m.nsub = Some(1e9);
1985 let e = Mos3Temp::new("m1", &m, &geom(2e-6, 2e-6), REFTEMP, REFTEMP).unwrap_err();
1986 assert!(e.contains("Nsub < Ni"));
1987 let e = Mos3Temp::new("m1", &n3(0.0), &geom(0.1e-6, 2e-6), REFTEMP, REFTEMP).unwrap_err();
1989 assert!(e.contains("effective channel length"));
1990 let mut m = n3(0.0);
1991 m.nsub = None;
1992 m.phi = Some(-0.1);
1993 let e = Mos3Temp::new("m1", &m, &geom(2e-6, 2e-6), REFTEMP, REFTEMP).unwrap_err();
1994 assert!(e.contains("Phi"));
1995 }
1996
1997 #[test]
1998 fn temp_scales_mobility_and_junctions_with_temperature() {
1999 let mut m = n3(0.0);
2000 m.cj = Some(4e-4);
2001 m.cjsw = Some(3e-10);
2002 let mut g = geom(2e-6, 2e-6);
2003 g.ad = 8e-12;
2004 g.pd = 12e-6;
2005 let hot = REFTEMP + 60.0;
2006 let t = Mos3Temp::new("m1", &m, &g, hot, REFTEMP).unwrap();
2007 let ratio = hot / REFTEMP;
2008 assert!(rel(t.t_surf_mob, 500.0 / (ratio * ratio.sqrt())) < 1e-14);
2009 let v = t.t_dep_cap;
2012 let arg = 1.0 - v / t.t_bulk_pot;
2013 let sarg = f64::exp(-m.mj * f64::ln(arg));
2014 let sargsw = f64::exp(-m.mjsw * f64::ln(arg));
2015 let q = t.t_bulk_pot
2016 * (t.cbd * (1.0 - arg * sarg) / (1.0 - m.mj)
2017 + t.cbdsw * (1.0 - arg * sargsw) / (1.0 - m.mjsw));
2018 let c = t.cbd * sarg + t.cbdsw * sargsw;
2019 assert!(rel(t.f4d + v * (t.f2d + v * t.f3d / 2.0), q) < 1e-12);
2020 assert!(rel(t.f2d + v * t.f3d, c) < 1e-12);
2021 }
2022
2023 #[test]
2024 fn linear_region_with_vmax_has_exact_derivatives() {
2025 let d = Dev::new(&n3(1.5e5), 2e-6, 2e-6);
2028 for (vgs, vds, vbs) in [(2.0, 0.3, 0.0), (3.0, 0.5, -1.0), (1.5, 0.1, -0.5)] {
2029 let r = d.eval(vgs, vds, vbs);
2030 assert!(vds < r.vdsat);
2031 let (gm, gds, gmbs) = d.fd(vgs, vds, vbs);
2032 assert!(rel(r.gm, gm) < 1e-5, "gm {} vs {gm}", r.gm);
2033 assert!(rel(r.gds, gds) < 1e-5, "gds {} vs {gds}", r.gds);
2034 assert!(rel(r.gmbs, gmbs) < 1e-5, "gmbs {} vs {gmbs}", r.gmbs);
2035 }
2036 }
2037
2038 #[test]
2039 fn weak_inversion_derivatives() {
2040 let d = Dev::new(&n3(1.5e5), 2e-6, 2e-6);
2041 let r = d.eval(0.5, 3.0, -1.0);
2042 assert!(0.5 < r.von && r.cdrain > 0.0);
2043 let (gm, gds, gmbs) = d.fd(0.5, 3.0, -1.0);
2044 assert!(rel(r.gm, gm) < 1e-3);
2045 assert!(rel(r.gds, gds) < 1e-3);
2046 assert!(rel(r.gmbs, gmbs) < 1e-3);
2047 }
2048
2049 #[test]
2050 fn saturation_derivatives_track_the_current() {
2051 let mut m = n3(0.0);
2055 m.eta = 0.0;
2056 let d = Dev::new(&m, 3e-6, 6e-6);
2057 for (vgs, vds, vbs) in [(2.0, 3.0, 0.0), (3.0, 2.5, -1.0), (2.0, 0.5, 0.0)] {
2058 let r = d.eval(vgs, vds, vbs);
2059 let (gm, gds, gmbs) = d.fd(vgs, vds, vbs);
2060 assert!(rel(r.gds, gds) < 1e-5, "gds {} vs {gds}", r.gds);
2061 assert!(rel(r.gm, gm) < 2e-2, "gm {} vs {gm}", r.gm);
2062 assert!(rel(r.gmbs, gmbs) < 2e-2, "gmbs {} vs {gmbs}", r.gmbs);
2063 }
2064 let d = Dev::new(&n3(1.5e5), 2e-6, 2e-6);
2065 let r = d.eval(2.5, 3.0, -1.0);
2066 assert!(3.0 > r.vdsat);
2067 let (gm, gds, gmbs) = d.fd(2.5, 3.0, -1.0);
2068 assert!(rel(r.gm, gm) < 3e-2);
2069 assert!(rel(r.gds, gds) < 3e-2);
2070 assert!(rel(r.gmbs, gmbs) < 3e-2);
2071 }
2072
2073 #[test]
2074 fn smoothed_clm_is_continuous_through_vdsat() {
2075 let d = Dev::new(&n3(0.0), 3e-6, 6e-6);
2078 let vdsat = d.eval(2.0, 1.0, 0.0).vdsat;
2079 let lo = d.eval(2.0, vdsat - 1e-9, 0.0);
2080 let hi = d.eval(2.0, vdsat + 1e-9, 0.0);
2081 assert!(rel(lo.cdrain, hi.cdrain) < 1e-8);
2082 assert!(rel(lo.gds, hi.gds) < 1e-4);
2083 let mut m = n3(0.0);
2085 m.kappa = 0.0;
2086 let flat = Dev::new(&m, 3e-6, 6e-6);
2087 assert!(lo.cdrain > flat.eval(2.0, vdsat - 1e-9, 0.0).cdrain);
2088 }
2089
2090 #[test]
2091 fn cutoff_without_nfs_and_vds_zero() {
2092 let mut m = n3(0.0);
2093 m.nfs = 0.0;
2094 let d = Dev::new(&m, 2e-6, 2e-6);
2095 let r = d.eval(0.2, 1.0, 0.0);
2096 assert_eq!((r.cdrain, r.gm, r.gds, r.gmbs), (0.0, 0.0, 0.0, 0.0));
2097 let r = d.eval(2.0, 0.0, 0.0);
2099 assert_eq!((r.cdrain, r.gm, r.gmbs), (0.0, 0.0, 0.0));
2100 let (_, gds, _) = d.fd(2.0, 1e-5, 0.0);
2101 assert!(rel(r.gds, gds) < 1e-3);
2102 }
2103
2104 #[test]
2105 fn bulk_diode_tail_is_c1_at_minus_3vt() {
2106 let vt = REFTEMP * KOVERQ;
2107 let v = -3.0 * vt;
2108 let (g0, i0) = bulk_diode(v, vt, 1e-14, 0.0);
2109 let (g1, i1) = bulk_diode(v + 1e-9, vt, 1e-14, 0.0);
2110 assert!(rel(i0, i1) < 1e-6);
2111 assert!(rel(g0, g1) < 1e-5);
2112 let (g, i) = bulk_diode(-5.0, vt, 1e-14, 1e-12);
2113 assert!(i < -1e-14 && g > 1e-12);
2114 }
2115}