1use crate::consts::{ROOT2, VT0};
12use crate::diode::pnjlim;
13use crate::matrix::{AcCtx, AnKind, InitMode, LoadCtx, MatrixBuilder, Slot, TRASH, Var};
14use crate::mos1::{MosGeom, MosPzConductances, fetlim, limvds, mos_pz_conductances};
15
16#[derive(Clone, Debug, Default)]
20pub struct Bsim1Model {
21 pub nmos: bool,
22 pub vfb0: f64,
24 pub vfbl: f64,
25 pub vfbw: f64,
26 pub phi0: f64,
27 pub phil: f64,
28 pub phiw: f64,
29 pub k10: f64,
30 pub k1l: f64,
31 pub k1w: f64,
32 pub k20: f64,
33 pub k2l: f64,
34 pub k2w: f64,
35 pub eta0: f64,
36 pub etal: f64,
37 pub etaw: f64,
38 pub etab0: f64,
39 pub etabl: f64,
40 pub etabw: f64,
41 pub etad0: f64,
42 pub etadl: f64,
43 pub etadw: f64,
44 pub mobzero: f64,
46 pub mobzerob0: f64,
47 pub mobzerobl: f64,
48 pub mobzerobw: f64,
49 pub mobvdd0: f64,
50 pub mobvddl: f64,
51 pub mobvddw: f64,
52 pub mobvddb0: f64,
53 pub mobvddbl: f64,
54 pub mobvddbw: f64,
55 pub mobvddd0: f64,
56 pub mobvdddl: f64,
57 pub mobvdddw: f64,
58 pub ugs0: f64,
59 pub ugsl: f64,
60 pub ugsw: f64,
61 pub ugsb0: f64,
62 pub ugsbl: f64,
63 pub ugsbw: f64,
64 pub uds0: f64,
65 pub udsl: f64,
66 pub udsw: f64,
67 pub udsb0: f64,
68 pub udsbl: f64,
69 pub udsbw: f64,
70 pub udsd0: f64,
71 pub udsdl: f64,
72 pub udsdw: f64,
73 pub n00: f64,
74 pub n0l: f64,
75 pub n0w: f64,
76 pub nb0: f64,
77 pub nbl: f64,
78 pub nbw: f64,
79 pub nd0: f64,
80 pub ndl: f64,
81 pub ndw: f64,
82 pub deltal: f64,
84 pub deltaw: f64,
85 pub tox: f64,
87 pub temp: f64,
89 pub vdd: f64,
91 pub cgso: f64,
93 pub cgdo: f64,
94 pub cgbo: f64,
95 pub xpart: f64,
97 pub rsh: f64,
98 pub js: f64,
99 pub pb: f64,
100 pub mj: f64,
101 pub pbsw: f64,
102 pub mjsw: f64,
103 pub cj: f64,
104 pub cjsw: f64,
105 pub wdf: f64,
107 pub dell: f64,
108 pub kf: f64,
113 pub af: f64,
114}
115
116impl Bsim1Model {
117 pub fn new(nmos: bool) -> Self {
118 Bsim1Model {
119 nmos,
120 af: 1.0,
121 ..Default::default()
122 }
123 }
124
125 pub(crate) fn needs_prime(&self, squares: f64) -> bool {
126 self.rsh != 0.0 && squares != 0.0
127 }
128}
129
130pub(crate) const B12_NOISE_SUFFIXES: &[&str] = &["_rd", "_rs", "_id", "_1overf", ""];
133
134pub(crate) struct B12Noise {
136 pub drain: Var,
137 pub source: Var,
138 pub d_p: Var,
139 pub s_p: Var,
140 pub gdpr: f64,
141 pub gspr: f64,
142 pub gm: f64,
144 pub cd: f64,
145 pub kf: f64,
146 pub af: f64,
147 pub flicker_area: f64,
149 pub cox: f64,
151}
152
153#[cfg(test)]
154thread_local! {
155 pub(crate) static NGSPICE_STATE_OFFSET: std::cell::Cell<Option<f64>> =
159 const { std::cell::Cell::new(None) };
160}
161
162pub(crate) fn b12_noise(ctx: &crate::matrix::NoiseCtx, n: &B12Noise) -> Vec<f64> {
179 const N_MINLOG: f64 = 1e-38;
180 let (gm, cd) = (n.gm, n.cd);
181 #[cfg(test)]
182 let (gm, cd) = match NGSPICE_STATE_OFFSET.get() {
183 Some(state) => (state + 7.0, state + 4.0),
184 None => (gm, cd),
185 };
186 let rd = ctx.thermal(n.d_p, n.drain, n.gdpr);
187 let rs = ctx.thermal(n.s_p, n.source, n.gspr);
188 let id = ctx.thermal(n.d_p, n.s_p, 2.0 / 3.0 * gm.abs());
189 let flicker = ctx.flicker(
190 n.d_p,
191 n.s_p,
192 n.kf * f64::exp(n.af * f64::ln(f64::max(cd.abs(), N_MINLOG)))
193 / (n.flicker_area * n.cox * n.cox),
194 1.0,
195 );
196 vec![rd, rs, id, flicker, rd + rs + id + flicker]
197}
198
199#[derive(Debug)]
201pub(crate) struct Bsim1Size {
202 pub type_: f64,
203 pub vfb: f64,
204 pub phi: f64,
205 pub k1: f64,
206 pub k2: f64,
207 pub eta: f64,
208 pub eta_b: f64,
209 pub eta_d: f64,
210 pub beta_zero: f64,
211 pub beta_zero_b: f64,
212 pub ugs: f64,
213 pub ugs_b: f64,
214 pub uds: f64,
215 pub uds_b: f64,
216 pub uds_d: f64,
217 pub beta_vdd: f64,
218 pub beta_vdd_b: f64,
219 pub beta_vdd_d: f64,
220 pub n0: f64,
221 pub nb: f64,
222 pub nd: f64,
223 pub vt0: f64,
224 pub gd_overlap: f64,
226 pub gs_overlap: f64,
227 pub gb_overlap: f64,
228 pub ld_gd_overlap: f64,
231 pub ld_gs_overlap: f64,
232 pub ld_gb_overlap: f64,
233 pub drain_conductance: f64,
234 pub source_conductance: f64,
235 pub cox: f64,
237 pub vdd: f64,
239 pub delta_l: f64,
240 pub delta_w: f64,
242 pub js: f64,
243 pub pb: f64,
244 pub pbsw: f64,
245 pub mj: f64,
246 pub mjsw: f64,
247 pub cj: f64,
248 pub cjsw: f64,
249 pub xpart: f64,
250 pub kf: f64,
251 pub af: f64,
252}
253
254impl Bsim1Size {
255 pub fn new(name: &str, m: &Bsim1Model, g: &MosGeom) -> Result<Self, String> {
256 let typ = if m.nmos { 1.0 } else { -1.0 };
257 let pb = f64::max(m.pb, 0.1);
258 let pbsw = f64::max(m.pbsw, 0.1);
259 if m.tox <= 0.0 {
260 return Err(format!("{name}: BSIM1 model needs TOX > 0"));
261 }
262 let cox = 3.453e-13 / (m.tox * 1.0e-4); let eff_l = g.l - m.deltal * 1e-6;
265 let eff_w = g.w - m.deltaw * 1e-6;
266 if eff_l <= 0.0 {
267 return Err(format!("{name}: effective channel length <= 0"));
268 }
269 if eff_w <= 0.0 {
270 return Err(format!("{name}: effective channel width <= 0"));
271 }
272 let leff = eff_l * 1e6; let weff = eff_w * 1e6;
274 let cox_w_over_l = cox * weff / leff;
275
276 let p = |p0: f64, pl: f64, pw: f64| p0 + pl / leff + pw / weff;
277
278 let vfb = p(m.vfb0, m.vfbl, m.vfbw);
279 let mut phi = p(m.phi0, m.phil, m.phiw);
280 let mut k1 = p(m.k10, m.k1l, m.k1w);
281 let mut k2 = p(m.k20, m.k2l, m.k2w);
282 if phi < 0.1 {
283 phi = 0.1;
284 }
285 if k1 < 0.0 {
286 k1 = 0.0;
287 }
288 if k2 < 0.0 {
289 k2 = 0.0;
290 }
291 let vt0 = vfb + phi + k1 * phi.sqrt() - k2 * phi;
292
293 let series = |squares: f64| {
294 let r = m.rsh * squares;
295 if r != 0.0 { 1.0 / r } else { 0.0 }
296 };
297
298 Ok(Bsim1Size {
299 type_: typ,
300 vfb,
301 phi,
302 k1,
303 k2,
304 eta: p(m.eta0, m.etal, m.etaw),
305 eta_b: p(m.etab0, m.etabl, m.etabw),
306 eta_d: p(m.etad0, m.etadl, m.etadw),
307 beta_zero: m.mobzero * cox_w_over_l,
308 beta_zero_b: p(m.mobzerob0, m.mobzerobl, m.mobzerobw) * cox_w_over_l,
309 ugs: p(m.ugs0, m.ugsl, m.ugsw),
310 ugs_b: p(m.ugsb0, m.ugsbl, m.ugsbw),
311 uds: p(m.uds0, m.udsl, m.udsw),
312 uds_b: p(m.udsb0, m.udsbl, m.udsbw),
313 uds_d: p(m.udsd0, m.udsdl, m.udsdw),
314 beta_vdd: p(m.mobvdd0, m.mobvddl, m.mobvddw) * cox_w_over_l,
315 beta_vdd_b: p(m.mobvddb0, m.mobvddbl, m.mobvddbw) * cox_w_over_l,
316 beta_vdd_d: f64::max(p(m.mobvddd0, m.mobvdddl, m.mobvdddw) * cox_w_over_l, 0.0),
317 n0: p(m.n00, m.n0l, m.n0w),
318 nb: p(m.nb0, m.nbl, m.nbw),
319 nd: p(m.nd0, m.ndl, m.ndw),
320 vt0,
321 gd_overlap: eff_w * m.cgdo,
322 gs_overlap: eff_w * m.cgso,
323 gb_overlap: g.l * m.cgbo,
324 ld_gd_overlap: g.w * m.cgdo,
325 ld_gs_overlap: g.w * m.cgso,
326 ld_gb_overlap: eff_l * m.cgbo,
327 drain_conductance: series(g.nrd),
328 source_conductance: series(g.nrs),
329 cox,
330 vdd: m.vdd,
331 delta_l: m.deltal,
332 delta_w: m.deltaw,
333 js: m.js,
334 pb,
335 pbsw,
336 mj: m.mj,
337 mjsw: m.mjsw,
338 cj: m.cj,
339 cjsw: m.cjsw,
340 xpart: m.xpart,
341 kf: m.kf,
342 af: m.af,
343 })
344 }
345}
346
347const VBD: usize = 0;
349const VBS: usize = 1;
350const VGS: usize = 2;
351const VDS: usize = 3;
352const CD: usize = 4;
353const CBS: usize = 5;
354const CBD: usize = 6;
355const GM: usize = 7;
356const GDS: usize = 8;
357const GMBS: usize = 9;
358const GBD: usize = 10;
359const GBS: usize = 11;
360const QB: usize = 12;
361const IQB: usize = 13;
362const QG: usize = 14;
363const IQG: usize = 15;
364const QD: usize = 16;
365const IQD: usize = 17;
366const CGGB: usize = 18;
367const CGDB: usize = 19;
368const CGSB: usize = 20;
369const CBGB: usize = 21;
370const CBDB: usize = 22;
371const CBSB: usize = 23;
372const CAPBD: usize = 24;
373const CAPBS: usize = 26;
374const CDGB: usize = 28;
375const CDDB: usize = 29;
376const CDSB: usize = 30;
377const QBS: usize = 33;
378const QBD: usize = 34;
379pub(crate) const BSIM1_NUM_STATES: usize = 35;
380
381const D_D: usize = 0;
383const G_G: usize = 1;
384const S_S: usize = 2;
385const B_B: usize = 3;
386const DP_DP: usize = 4;
387const SP_SP: usize = 5;
388const D_DP: usize = 6;
389const G_B: usize = 7;
390const G_DP: usize = 8;
391const G_SP: usize = 9;
392const S_SP: usize = 10;
393const B_DP: usize = 11;
394const B_SP: usize = 12;
395const DP_SP: usize = 13;
396const DP_D: usize = 14;
397const B_G: usize = 15;
398const DP_G: usize = 16;
399const SP_G: usize = 17;
400const SP_S: usize = 18;
401const DP_B: usize = 19;
402const SP_B: usize = 20;
403const SP_DP: usize = 21;
404
405pub(crate) struct BsimOverlaps {
409 pub gd: f64,
410 pub gs: f64,
411 pub gb: f64,
412}
413
414pub(crate) fn bsim_pz_load(
422 ctx: &mut crate::matrix::PzCtx,
423 s: &[Slot; 22],
424 st: usize,
425 mode: f64,
426 gdpr: f64,
427 gspr: f64,
428 ov: &BsimOverlaps,
429) {
430 let s0 = |o: usize| ctx.states.get(0, st + o);
431 let (xnrm, xrev) = if mode >= 0.0 { (1.0, 0.0) } else { (0.0, 1.0) };
432 let gm = s0(GM);
433 let gds = s0(GDS);
434 let gmbs = s0(GMBS);
435 let gbd = s0(GBD);
436 let gbs = s0(GBS);
437 let capbd = s0(CAPBD);
438 let capbs = s0(CAPBS);
439 let cggb = s0(CGGB);
440 let cgsb = s0(CGSB);
441 let cgdb = s0(CGDB);
442 let cbgb = s0(CBGB);
443 let cbsb = s0(CBSB);
444 let cbdb = s0(CBDB);
445 let cdgb = s0(CDGB);
446 let cdsb = s0(CDSB);
447 let cddb = s0(CDDB);
448 let xcdgb = cdgb - ov.gd;
449 let xcddb = cddb + capbd + ov.gd;
450 let xcdsb = cdsb;
451 let xcsgb = -(cggb + cbgb + cdgb + ov.gs);
452 let xcsdb = -(cgdb + cbdb + cddb);
453 let xcssb = capbs + ov.gs - (cgsb + cbsb + cdsb);
454 let xcggb = cggb + ov.gd + ov.gs + ov.gb;
455 let xcgdb = cgdb - ov.gd;
456 let xcgsb = cgsb - ov.gs;
457 let xcbgb = cbgb - ov.gb;
458 let xcbdb = cbdb - capbd;
459 let xcbsb = cbsb - capbs;
460
461 ctx.add_cap(s[G_G], xcggb);
462 ctx.add_cap(s[B_B], -xcbgb - xcbdb - xcbsb);
463 ctx.add_cap(s[DP_DP], xcddb);
464 ctx.add_cap(s[SP_SP], xcssb);
465 ctx.add_cap(s[G_B], -xcggb - xcgdb - xcgsb);
466 ctx.add_cap(s[G_DP], xcgdb);
467 ctx.add_cap(s[G_SP], xcgsb);
468 ctx.add_cap(s[B_G], xcbgb);
469 ctx.add_cap(s[B_DP], xcbdb);
470 ctx.add_cap(s[B_SP], xcbsb);
471 ctx.add_cap(s[DP_G], xcdgb);
472 ctx.add_cap(s[DP_B], -xcdgb - xcddb - xcdsb);
473 ctx.add_cap(s[DP_SP], xcdsb);
474 ctx.add_cap(s[SP_G], xcsgb);
475 ctx.add_cap(s[SP_B], -xcsgb - xcsdb - xcssb);
476 ctx.add_cap(s[SP_DP], xcsdb);
477 let g = MosPzConductances {
478 xnrm,
479 xrev,
480 gm,
481 gds,
482 gmbs,
483 gbd,
484 gbs,
485 gdpr,
486 gspr,
487 };
488 mos_pz_conductances(ctx, s, &g);
489}
490
491#[derive(Clone, Copy, Default)]
493struct Eval {
494 gm: f64,
495 gds: f64,
496 gmbs: f64,
497 qg: f64,
498 qb: f64,
499 qd: f64,
500 cggb: f64,
501 cgdb: f64,
502 cgsb: f64,
503 cbgb: f64,
504 cbdb: f64,
505 cbsb: f64,
506 cdgb: f64,
507 cddb: f64,
508 cdsb: f64,
509 cdrain: f64,
510 von: f64,
511 vdsat: f64,
512}
513
514pub(crate) struct Bsim1I {
515 drain: Var,
516 gate: Var,
517 source: Var,
518 bulk: Var,
519 d_p: Var,
520 s_p: Var,
521 t: Bsim1Size,
522 geom: MosGeom,
523 off: bool,
524 ic_vds: f64,
525 ic_vgs: f64,
526 ic_vbs: f64,
527 ic_given: crate::devices::IcGiven,
528 st: usize,
529 s: [Slot; 22],
530 mode: f64,
531 von: f64,
532 vdsat: f64,
533 dist: crate::b1disto::B1DistoCoeffs,
535}
536
537impl Bsim1I {
538 #[allow(clippy::too_many_arguments)]
539 pub fn new(
540 nodes: [Var; 4],
541 primes: [Var; 2],
542 t: Bsim1Size,
543 geom: MosGeom,
544 off: bool,
545 ic: (f64, f64, f64),
546 st: usize,
547 ) -> Self {
548 let von = t.vt0; Bsim1I {
550 drain: nodes[0],
551 gate: nodes[1],
552 source: nodes[2],
553 bulk: nodes[3],
554 d_p: primes[0],
555 s_p: primes[1],
556 t,
557 geom,
558 off,
559 ic_vds: ic.0,
560 ic_vgs: ic.1,
561 ic_vbs: ic.2,
562 ic_given: crate::devices::IcGiven::ALL,
563 st,
564 s: [TRASH; 22],
565 mode: 1.0,
566 von,
567 vdsat: 0.0,
568 dist: crate::b1disto::B1DistoCoeffs::default(),
569 }
570 }
571
572 #[allow(clippy::too_many_lines)]
576 fn evaluate(&self, vds: f64, vbs: f64, vgs: f64, charges: bool) -> Eval {
577 let t = &self.t;
578 let mut e = Eval::default();
579
580 let vfb = t.vfb;
581 let mut phi = t.phi;
582 let k1 = t.k1;
583 let k2 = t.k2;
584 let vdd = t.vdd;
585
586 let (ugs, d_ugs_dvbs);
587 let u = t.ugs + t.ugs_b * vbs;
588 if u <= 0.0 {
589 ugs = 0.0;
590 d_ugs_dvbs = 0.0;
591 } else {
592 ugs = u;
593 d_ugs_dvbs = t.ugs_b;
594 }
595 let (uds, d_uds_dvbs, d_uds_dvds);
596 let u = t.uds + t.uds_b * vbs + t.uds_d * (vds - vdd);
597 if u <= 0.0 {
598 uds = 0.0;
599 d_uds_dvbs = 0.0;
600 d_uds_dvds = 0.0;
601 } else {
602 let leff = self.geom.l * 1e6 - t.delta_l; uds = u / leff;
604 d_uds_dvbs = t.uds_b / leff;
605 d_uds_dvds = t.uds_d / leff;
606 }
607 let mut eta = t.eta + t.eta_b * vbs + t.eta_d * (vds - vdd);
608 let (d_eta_dvds, d_eta_dvbs);
609 if eta <= 0.0 {
610 eta = 0.0;
611 d_eta_dvds = 0.0;
612 d_eta_dvbs = 0.0;
613 } else if eta > 1.0 {
614 eta = 1.0;
615 d_eta_dvds = 0.0;
616 d_eta_dvbs = 0.0;
617 } else {
618 d_eta_dvds = t.eta_d;
619 d_eta_dvbs = t.eta_b;
620 }
621 let vpb = if vbs < 0.0 { phi - vbs } else { phi };
622 let sqrt_vpb = vpb.sqrt();
623 let von = vfb + phi + k1 * sqrt_vpb - k2 * vpb - eta * vds;
624 let vth = von;
625 e.von = von;
626 let d_vth_dvds = -eta - d_eta_dvds * vds;
627 let mut d_vth_dvbs = k2 - 0.5 * k1 / sqrt_vpb - d_eta_dvbs * vds;
628 let mut vgs_vth = vgs - vth;
629
630 let g = 1.0 - 1.0 / (1.744 + 0.8364 * vpb);
631 let mut a = 1.0 + 0.5 * g * k1 / sqrt_vpb;
632 a = f64::max(a, 1.0);
633 let arg = f64::max(1.0 + ugs * vgs_vth, 1.0);
634 let d_g_dvbs = -0.8364 * (1.0 - g) * (1.0 - g);
635 let d_a_dvbs = 0.25 * k1 / sqrt_vpb * (2.0 * d_g_dvbs + g / vpb);
636
637 let mut drain_current = 0.0;
638 let mut gm = 0.0;
639 let mut gds = 0.0;
640 let mut gmbs = 0.0;
641
642 if vgs_vth >= 0.0 {
643 let beta_vds_0 = t.beta_zero + t.beta_zero_b * vbs;
645 let beta_vdd = t.beta_vdd + t.beta_vdd_b * vbs;
646 let d_beta_vdd_dvds = f64::max(t.beta_vdd_d, 0.0);
647 let (beta0, d_beta0_dvds, d_beta0_dvbs);
648 if vds > vdd {
649 beta0 = beta_vdd + d_beta_vdd_dvds * (vds - vdd);
650 d_beta0_dvds = d_beta_vdd_dvds;
651 d_beta0_dvbs = t.beta_vdd_b;
652 } else {
653 let vdd_square = vdd * vdd;
654 let c1 = (-beta_vdd + beta_vds_0 + d_beta_vdd_dvds * vdd) / vdd_square;
655 let c2 = 2.0 * (beta_vdd - beta_vds_0) / vdd - d_beta_vdd_dvds;
656 let d_beta_vds_0_dvbs = t.beta_zero_b;
657 let d_beta_vdd_dvbs = t.beta_vdd_b;
658 let d_c1_dvbs = (d_beta_vds_0_dvbs - d_beta_vdd_dvbs) / vdd_square;
659 let d_c2_dvbs = d_c1_dvbs * (-2.0) * vdd;
660 beta0 = (c1 * vds + c2) * vds + beta_vds_0;
661 d_beta0_dvds = 2.0 * c1 * vds + c2;
662 d_beta0_dvbs = d_c1_dvbs * vds * vds + d_c2_dvbs * vds + d_beta_vds_0_dvbs;
663 }
664
665 let beta = beta0 / arg;
666 let d_beta_dvgs = -beta * ugs / arg;
667 let d_beta_dvds = d_beta0_dvds / arg - d_beta_dvgs * d_vth_dvds;
668 let d_beta_dvbs = d_beta0_dvbs / arg + beta * ugs * d_vth_dvbs / arg
669 - beta * vgs_vth * d_ugs_dvbs / arg;
670
671 let mut vc = uds * vgs_vth / a;
672 if vc < 0.0 {
673 vc = 0.0;
674 }
675 let term1 = (1.0 + 2.0 * vc).sqrt();
676 let k = 0.5 * (1.0 + vc + term1);
677 e.vdsat = f64::max(vgs_vth / (a * k.sqrt()), 0.0);
678
679 if vds < e.vdsat {
680 let argl1 = f64::max(1.0 + uds * vds, 1.0);
682 let argl2 = vgs_vth - 0.5 * a * vds;
683 drain_current = beta * argl2 * vds / argl1;
684 gm = (d_beta_dvgs * argl2 * vds + beta * vds) / argl1;
685 gds = (d_beta_dvds * argl2 * vds + beta * (vgs_vth - vds * d_vth_dvds - a * vds)
686 - drain_current * (vds * d_uds_dvds + uds))
687 / argl1;
688 gmbs = (d_beta_dvbs * argl2 * vds
689 + beta * vds * (-d_vth_dvbs - 0.5 * vds * d_a_dvbs)
690 - drain_current * vds * d_uds_dvbs)
691 / argl1;
692 } else {
693 let args1 = 1.0 + 1.0 / term1;
695 let d_vc_dvgs = uds / a;
696 let d_vc_dvds = vgs_vth * d_uds_dvds / a - d_vc_dvgs * d_vth_dvds;
697 let d_vc_dvbs =
698 (vgs_vth * d_uds_dvbs - uds * (d_vth_dvbs + vgs_vth * d_a_dvbs / a)) / a;
699 let d_k_dvc = 0.5 * args1;
700 let d_k_dvgs = d_k_dvc * d_vc_dvgs;
701 let d_k_dvds = d_k_dvc * d_vc_dvds;
702 let d_k_dvbs = d_k_dvc * d_vc_dvbs;
703 let args2 = vgs_vth / a / k;
704 let args3 = args2 * vgs_vth;
705 drain_current = 0.5 * beta * args3;
706 gm = 0.5 * args3 * d_beta_dvgs + beta * args2 - drain_current * d_k_dvgs / k;
707 gds = 0.5 * args3 * d_beta_dvds
708 - beta * args2 * d_vth_dvds
709 - drain_current * d_k_dvds / k;
710 gmbs = 0.5 * d_beta_dvbs * args3
711 - beta * args2 * d_vth_dvbs
712 - drain_current * (d_a_dvbs / a + d_k_dvbs / k);
713 }
714 }
715
716 let n0 = t.n0;
719 if n0 < 200.0 {
720 let nb = t.nb;
721 let nd = t.nd;
722 let mut n = n0 + nb * vbs + nd * vds;
723 if n < 0.5 {
724 n = 0.5;
725 }
726 let warg1 = f64::exp(-vds / VT0);
727 let wds = 1.0 - warg1;
728 let wgs = f64::exp(vgs_vth / (n * VT0));
729 let vtsquare = VT0 * VT0;
730 let warg2 = 6.04965 * vtsquare * t.beta_zero;
731 let ilimit = 4.5 * vtsquare * t.beta_zero;
732 let iexp = warg2 * wgs * wds;
733 drain_current += ilimit * iexp / (ilimit + iexp);
734 let mut temp1 = ilimit / (ilimit + iexp);
735 temp1 *= temp1;
736 let mut temp3 = ilimit / (ilimit + wgs * warg2);
737 temp3 = temp3 * temp3 * warg2 * wgs;
738 gm += temp1 * iexp / (n * VT0);
739 gds += temp3 * (-wds / n / VT0 * (d_vth_dvds + vgs_vth * nd / n) + warg1 / VT0);
740 gmbs -= temp1 * iexp * (d_vth_dvbs + vgs_vth * nb / n) / (n * VT0);
741 }
742
743 if drain_current < 0.0 {
745 drain_current = 0.0;
746 }
747 if gm < 0.0 {
748 gm = 0.0;
749 }
750 if gds < 0.0 {
751 gds = 0.0;
752 }
753 if gmbs < 0.0 {
754 gmbs = 0.0;
755 }
756
757 let wl_cox =
759 t.cox * (self.geom.l - t.delta_l * 1e-6) * (self.geom.w - t.delta_w * 1e-6) * 1e4; if !charges {
762 e.gm = f64::max(gm, 0.0);
763 e.gds = f64::max(gds, 0.0);
764 e.gmbs = f64::max(gmbs, 0.0);
765 e.cdrain = f64::max(drain_current, 0.0);
766 return e;
767 }
768
769 let g = 1.0 - 1.0 / (1.744 + 0.8364 * vpb);
770 let mut a = 1.0 + 0.5 * g * k1 / sqrt_vpb;
771 a = f64::max(a, 1.0);
772 phi = f64::max(0.1, phi);
773
774 let co4v15 = 4.0 / 15.0;
775 let vth0 = vfb + phi + k1 * sqrt_vpb;
776 vgs_vth = vgs - vth0;
777 let arg1 = a * vds;
778 let arg2 = vgs_vth - 0.5 * arg1;
779 let arg3 = vds - arg1;
780 let arg5 = arg1 * arg1;
781 d_vth_dvbs = -0.5 * k1 / sqrt_vpb;
782 let d_a_dvbs2 = 0.5 * k1 * (0.5 * g / vpb - 0.8364 * (1.0 - g) * (1.0 - g)) / sqrt_vpb;
783 let ent = f64::max(arg2, 1.0e-8);
784 let d_ent_dvds = -0.5 * a;
785 let d_ent_dvbs = -d_vth_dvbs - 0.5 * vds * d_a_dvbs2;
786 let vcom = vgs_vth * vgs_vth / 6.0 - 1.25e-1 * arg1 * vgs_vth + 2.5e-2 * arg5;
787 let vds_pinchoff = f64::max(vgs_vth / a, 0.0);
788 let vgb = vgs - vbs;
789 let vgb_vfb = vgb - vfb;
790
791 let d_a_dvbs = d_a_dvbs2;
793
794 if t.xpart >= 1.0 {
795 if vgb_vfb < 0.0 {
797 e.qg = wl_cox * vgb_vfb;
799 e.qb = -e.qg;
800 e.cggb = wl_cox;
801 e.cbgb = -wl_cox;
802 } else if vgs < vth0 {
803 e.qg = 0.5 * wl_cox * k1 * k1 * (-1.0 + (1.0 + 4.0 * vgb_vfb / (k1 * k1)).sqrt());
805 e.qb = -e.qg;
806 e.cggb = wl_cox / (1.0 + 4.0 * vgb_vfb / (k1 * k1)).sqrt();
807 e.cbgb = -e.cggb;
808 } else if vds < vds_pinchoff {
809 let ent_square = ent * ent;
811 let argl1 = 1.2e1 * ent_square;
812 let argl2 = 1.0 - a;
813 let argl3 = arg1 * vds;
814 let argl5 = if ent > 1.0e-8 { arg1 / ent } else { 2.0 };
815 let argl7 = argl5 / 1.2e1;
816 let argl8 = 6.0 * ent;
817 let argl9 = 0.125 * argl5 * argl5;
818 e.qg = wl_cox * (vgs - vfb - phi - 0.5 * vds + vds * argl7);
819 e.qb = wl_cox * (-vth0 + vfb + phi + 0.5 * arg3 - arg3 * argl7);
820 e.qd = -wl_cox * (0.5 * vgs_vth - 0.75 * arg1 + 0.125 * arg1 * argl5);
821 e.cggb = wl_cox * (1.0 - argl3 / argl1);
822 e.cgdb = wl_cox * (-0.5 + arg1 / argl8 - argl3 * d_ent_dvds / argl1);
823 let cgbb = wl_cox * (vds * vds * d_a_dvbs * ent - argl3 * d_ent_dvbs) / argl1;
824 e.cgsb = -(e.cggb + e.cgdb + cgbb);
825 e.cbgb = wl_cox * argl3 * argl2 / argl1;
826 e.cbdb = wl_cox * argl2 * (0.5 - arg1 / argl8 + argl3 * d_ent_dvds / argl1);
827 let cbbb = -wl_cox
828 * (d_vth_dvbs
829 + 0.5 * vds * d_a_dvbs
830 + vds * vds * ((1.0 - 2.0 * a) * d_a_dvbs * ent - argl2 * a * d_ent_dvbs)
831 / argl1);
832 e.cbsb = -(e.cbgb + e.cbdb + cbbb);
833 e.cdgb = -wl_cox * (0.5 - argl9);
834 e.cddb = wl_cox * (0.75 * a - 0.25 * a * arg1 / ent + argl9 * d_ent_dvds);
835 let cdbb = wl_cox
836 * (0.5 * d_vth_dvbs
837 + vds * d_a_dvbs * (0.75 - 0.25 * argl5)
838 + argl9 * d_ent_dvbs);
839 e.cdsb = -(e.cdgb + e.cddb + cdbb);
840 } else {
841 let args1 = 1.0 / (3.0 * a);
843 e.qg = wl_cox * (vgs - vfb - phi - vgs_vth * args1);
844 e.qb = wl_cox * (vfb + phi - vth0 + (1.0 - a) * vgs_vth * args1);
845 e.cggb = wl_cox * (1.0 - args1);
846 let cgbb = wl_cox * args1 * (d_vth_dvbs + vgs_vth * d_a_dvbs / a);
847 e.cgsb = -(e.cggb + cgbb);
848 e.cbgb = wl_cox * (args1 - 1.0 / 3.0);
849 let cbbb =
850 -wl_cox * ((2.0 / 3.0 + args1) * d_vth_dvbs + vgs_vth * args1 * d_a_dvbs / a);
851 e.cbsb = -(e.cbgb + cbbb);
852 }
853 } else {
854 if vgb_vfb < 0.0 {
856 e.qg = wl_cox * vgb_vfb;
857 e.qb = -e.qg;
858 e.cggb = wl_cox;
859 e.cbgb = -wl_cox;
860 } else if vgs < vth0 {
861 e.qg = 0.5 * wl_cox * k1 * k1 * (-1.0 + (1.0 + 4.0 * vgb_vfb / (k1 * k1)).sqrt());
862 e.qb = -e.qg;
863 e.cggb = wl_cox / (1.0 + 4.0 * vgb_vfb / (k1 * k1)).sqrt();
864 e.cbgb = -e.cggb;
865 } else if vds < vds_pinchoff {
866 let vgs_vth_square = vgs_vth * vgs_vth;
867 let ent_square = ent * ent;
868 let argl1 = 1.2e1 * ent_square;
869 let argl2 = 1.0 - a;
870 let argl3 = arg1 * vds;
871 let argl4 = vcom / ent / ent_square;
872 let (argl5, argl6) = if ent > 1.0e-8 {
873 (arg1 / ent, vcom / ent_square)
874 } else {
875 (2.0, 4.0 / 1.5e1)
876 };
877 let argl7 = argl5 / 1.2e1;
878 let argl8 = 6.0 * ent;
879 e.qg = wl_cox * (vgs - vfb - phi - 0.5 * vds + vds * argl7);
880 e.qb = wl_cox * (-vth0 + vfb + phi + 0.5 * arg3 - arg3 * argl7);
881 e.qd = -wl_cox * (0.5 * (vgs_vth - arg1) + arg1 * argl6);
882 e.cggb = wl_cox * (1.0 - argl3 / argl1);
883 e.cgdb = wl_cox * (-0.5 + arg1 / argl8 - argl3 * d_ent_dvds / argl1);
884 let cgbb = wl_cox * (vds * vds * d_a_dvbs * ent - argl3 * d_ent_dvbs) / argl1;
885 e.cgsb = -(e.cggb + e.cgdb + cgbb);
886 e.cbgb = wl_cox * argl3 * argl2 / argl1;
887 e.cbdb = wl_cox * argl2 * (0.5 - arg1 / argl8 + argl3 * d_ent_dvds / argl1);
888 let cbbb = -wl_cox
889 * (d_vth_dvbs
890 + 0.5 * vds * d_a_dvbs
891 + vds * vds * ((1.0 - 2.0 * a) * d_a_dvbs * ent - argl2 * a * d_ent_dvbs)
892 / argl1);
893 e.cbsb = -(e.cbgb + e.cbdb + cbbb);
894 e.cdgb = -wl_cox
895 * (0.5 + arg1 * (4.0 * vgs_vth - 1.5 * arg1) / argl1 - 2.0 * arg1 * argl4);
896 e.cddb = wl_cox
897 * (0.5 * a + 2.0 * arg1 * d_ent_dvds * argl4
898 - a * (2.0 * vgs_vth_square - 3.0 * arg1 * vgs_vth + 0.9 * arg5) / argl1);
899 let cdbb = wl_cox
900 * (0.5 * d_vth_dvbs + 0.5 * vds * d_a_dvbs + 2.0 * arg1 * d_ent_dvbs * argl4
901 - vds
902 * (2.0 * vgs_vth_square * d_a_dvbs
903 - 4.0 * a * vgs_vth * d_vth_dvbs
904 - 3.0 * arg1 * vgs_vth * d_a_dvbs
905 + 1.5 * a * arg1 * d_vth_dvbs
906 + 0.9 * arg5 * d_a_dvbs)
907 / argl1);
908 e.cdsb = -(e.cdgb + e.cddb + cdbb);
909 } else {
910 let args1 = 1.0 / (3.0 * a);
911 e.qg = wl_cox * (vgs - vfb - phi - vgs_vth * args1);
912 e.qb = wl_cox * (vfb + phi - vth0 + (1.0 - a) * vgs_vth * args1);
913 e.qd = -co4v15 * wl_cox * vgs_vth;
914 e.cggb = wl_cox * (1.0 - args1);
915 let cgbb = wl_cox * args1 * (d_vth_dvbs + vgs_vth * d_a_dvbs / a);
916 e.cgsb = -(e.cggb + cgbb);
917 e.cbgb = wl_cox * (args1 - 1.0 / 3.0);
918 let cbbb =
919 -wl_cox * ((2.0 / 3.0 + args1) * d_vth_dvbs + vgs_vth * args1 * d_a_dvbs / a);
920 e.cbsb = -(e.cbgb + cbbb);
921 e.cdgb = -co4v15 * wl_cox;
922 let cdbb = co4v15 * wl_cox * d_vth_dvbs;
923 e.cdsb = -(e.cdgb + cdbb);
924 }
925 }
926
927 e.gm = f64::max(gm, 0.0);
928 e.gds = f64::max(gds, 0.0);
929 e.gmbs = f64::max(gmbs, 0.0);
930 e.cdrain = f64::max(drain_current, 0.0);
931 e
932 }
933}
934
935#[cfg(test)]
938pub(crate) fn evaluate_for_test(
939 t: Bsim1Size,
940 geom: MosGeom,
941 vds: f64,
942 vbs: f64,
943 vgs: f64,
944) -> (f64, f64, f64, f64) {
945 let i = Bsim1I::new([1, 2, 3, 4], [1, 3], t, geom, false, (0.0, 0.0, 0.0), 0);
946 let e = i.evaluate(vds, vbs, vgs, true);
947 (e.cdrain, e.qg, e.qb, e.qd)
948}
949
950impl crate::devices::Device for Bsim1I {
951 fn par_load(&self) -> bool {
952 true
953 }
954
955 fn set_ic(&mut self, rhs: &[f64]) {
958 use crate::devices::getic;
959 let g = self.ic_given;
960 getic(g, 2, &mut self.ic_vbs, rhs, self.bulk, self.source);
961 getic(g, 0, &mut self.ic_vds, rhs, self.drain, self.source);
962 getic(g, 1, &mut self.ic_vgs, rhs, self.gate, self.source);
963 }
964
965 fn set_ic_given(&mut self, given: crate::devices::IcGiven) {
966 self.ic_given = given;
967 }
968
969 fn alloc(&mut self, mb: &mut MatrixBuilder) {
970 let (d, g, s, b) = (self.drain, self.gate, self.source, self.bulk);
971 let (dp, sp) = (self.d_p, self.s_p);
972 self.s = [
973 mb.slot(d, d),
974 mb.slot(g, g),
975 mb.slot(s, s),
976 mb.slot(b, b),
977 mb.slot(dp, dp),
978 mb.slot(sp, sp),
979 mb.slot(d, dp),
980 mb.slot(g, b),
981 mb.slot(g, dp),
982 mb.slot(g, sp),
983 mb.slot(s, sp),
984 mb.slot(b, dp),
985 mb.slot(b, sp),
986 mb.slot(dp, sp),
987 mb.slot(dp, d),
988 mb.slot(b, g),
989 mb.slot(dp, g),
990 mb.slot(sp, g),
991 mb.slot(sp, s),
992 mb.slot(dp, b),
993 mb.slot(sp, b),
994 mb.slot(sp, dp),
995 ];
996 }
997
998 fn conv_test(&self, ctx: &crate::matrix::ConvCtx) -> bool {
1000 let typ = self.t.type_;
1001 let st = self.st;
1002 let s0 = |o: usize| ctx.states.get(0, st + o);
1003 let vbs = typ * (ctx.rhs[self.bulk] - ctx.rhs[self.s_p]);
1004 let vgs = typ * (ctx.rhs[self.gate] - ctx.rhs[self.s_p]);
1005 let vds = typ * (ctx.rhs[self.d_p] - ctx.rhs[self.s_p]);
1006 let vbd = vbs - vds;
1007 let vgd = vgs - vds;
1008 let vgdo = s0(VGS) - s0(VDS);
1009 let delvbs = vbs - s0(VBS);
1010 let delvbd = vbd - s0(VBD);
1011 let delvgs = vgs - s0(VGS);
1012 let delvds = vds - s0(VDS);
1013 let delvgd = vgd - vgdo;
1014 let cdhat = if self.mode >= 0.0 {
1015 s0(CD) - s0(GBD) * delvbd + s0(GMBS) * delvbs + s0(GM) * delvgs + s0(GDS) * delvds
1016 } else {
1017 s0(CD) - (s0(GBD) - s0(GMBS)) * delvbd - s0(GM) * delvgd + s0(GDS) * delvds
1018 };
1019 let cbhat = s0(CBS) + s0(CBD) + s0(GBD) * delvbd + s0(GBS) * delvbs;
1020 let cd = s0(CD);
1021 let cb = s0(CBS) + s0(CBD);
1022 let tol = ctx.reltol * f64::max(cdhat.abs(), cd.abs()) + ctx.abstol;
1023 if (cdhat - cd).abs() >= tol {
1024 return false;
1025 }
1026 let tol = ctx.reltol * f64::max(cbhat.abs(), cb.abs()) + ctx.abstol;
1027 (cbhat - cb).abs() <= tol
1028 }
1029
1030 #[allow(clippy::too_many_lines)]
1031 fn load(&mut self, ctx: &mut LoadCtx) {
1032 let typ = self.t.type_;
1033 let st = self.st;
1034
1035 let drain_sat_cur = f64::max(self.geom.ad * self.t.js, 1.0e-15);
1036 let source_sat_cur = f64::max(self.geom.as_ * self.t.js, 1.0e-15);
1037 let von_inst = typ * self.von;
1038 let vt0_inst = typ * self.t.vt0;
1039
1040 let mut check = true;
1041 let (mut vbs, mut vgs, mut vds);
1042 if ctx.mode == InitMode::Smsig {
1043 vbs = ctx.states.get(0, st + VBS);
1044 vgs = ctx.states.get(0, st + VGS);
1045 vds = ctx.states.get(0, st + VDS);
1046 } else if ctx.mode == InitMode::Tran {
1047 vbs = ctx.states.get(1, st + VBS);
1048 vgs = ctx.states.get(1, st + VGS);
1049 vds = ctx.states.get(1, st + VDS);
1050 } else if ctx.mode == InitMode::Jct && !self.off {
1051 vds = typ * self.ic_vds;
1052 vgs = typ * self.ic_vgs;
1053 vbs = typ * self.ic_vbs;
1054 if vds == 0.0 && vgs == 0.0 && vbs == 0.0 && !(ctx.kind == AnKind::TranOp && ctx.uic) {
1055 vbs = -1.0;
1056 vgs = vt0_inst;
1057 vds = 0.0;
1058 }
1059 } else if matches!(ctx.mode, InitMode::Jct | InitMode::Fix) && self.off {
1060 vbs = 0.0;
1061 vgs = 0.0;
1062 vds = 0.0;
1063 } else {
1064 if ctx.mode == InitMode::Pred {
1065 let xfact = ctx.pred_xfact;
1066 let mut pred = |o: usize| {
1067 let s1 = ctx.states.get(1, st + o);
1068 ctx.states.set(0, st + o, s1);
1069 (1.0 + xfact) * s1 - xfact * ctx.states.get(2, st + o)
1070 };
1071 vbs = pred(VBS);
1072 vgs = pred(VGS);
1073 vds = pred(VDS);
1074 let vbd0 = ctx.states.get(0, st + VBS) - ctx.states.get(0, st + VDS);
1075 ctx.states.set(0, st + VBD, vbd0);
1076 for o in [
1077 CD, CBS, CBD, GM, GDS, GMBS, GBD, GBS, CGGB, CBGB, CBSB, CGDB, CGSB, CBDB,
1078 CDGB, CDDB, CDSB,
1079 ] {
1080 let v = ctx.states.get(1, st + o);
1081 ctx.states.set(0, st + o, v);
1082 }
1083 } else {
1084 vbs = typ * (ctx.sol[self.bulk] - ctx.sol[self.s_p]);
1085 vgs = typ * (ctx.sol[self.gate] - ctx.sol[self.s_p]);
1086 vds = typ * (ctx.sol[self.d_p] - ctx.sol[self.s_p]);
1087 }
1088 let mut vbd = vbs - vds;
1089 let mut vgd = vgs - vds;
1090 let vgdo = ctx.states.get(0, st + VGS) - ctx.states.get(0, st + VDS);
1091
1092 if ctx.states.get(0, st + VDS) >= 0.0 {
1093 vgs = fetlim(vgs, ctx.states.get(0, st + VGS), von_inst);
1094 vds = vgs - vgd;
1095 vds = limvds(vds, ctx.states.get(0, st + VDS));
1096 } else {
1097 vgd = fetlim(vgd, vgdo, von_inst);
1098 vds = vgs - vgd;
1099 vds = -limvds(-vds, -ctx.states.get(0, st + VDS));
1100 vgs = vgd + vds;
1101 }
1102 if vds >= 0.0 {
1103 let vcrit = VT0 * f64::ln(VT0 / (ROOT2 * source_sat_cur));
1104 vbs = pnjlim(vbs, ctx.states.get(0, st + VBS), VT0, vcrit, &mut check);
1105 } else {
1106 let vcrit = VT0 * f64::ln(VT0 / (ROOT2 * drain_sat_cur));
1107 vbd = pnjlim(vbd, ctx.states.get(0, st + VBD), VT0, vcrit, &mut check);
1108 vbs = vbd + vds;
1109 }
1110 }
1111
1112 let vbd = vbs - vds;
1113 let vgd = vgs - vds;
1114 let vgb = vgs - vbs;
1115
1116 let (gbs, cbs);
1118 if vbs <= 0.0 {
1119 gbs = source_sat_cur / VT0 + ctx.gmin;
1120 cbs = gbs * vbs;
1121 } else {
1122 let evbs = f64::exp(vbs / VT0);
1123 gbs = source_sat_cur * evbs / VT0 + ctx.gmin;
1124 cbs = source_sat_cur * (evbs - 1.0) + ctx.gmin * vbs;
1125 }
1126 let (gbd, cbd);
1127 if vbd <= 0.0 {
1128 gbd = drain_sat_cur / VT0 + ctx.gmin;
1129 cbd = gbd * vbd;
1130 } else {
1131 let evbd = f64::exp(vbd / VT0);
1132 gbd = drain_sat_cur * evbd / VT0 + ctx.gmin;
1133 cbd = drain_sat_cur * (evbd - 1.0) + ctx.gmin * vbd;
1134 }
1135
1136 self.mode = if vds >= 0.0 { 1.0 } else { -1.0 };
1137 let mode = self.mode;
1138
1139 let charges = ctx.kind == AnKind::Tran
1140 || ctx.mode == InitMode::Smsig
1141 || (ctx.kind == AnKind::TranOp && ctx.uic);
1142
1143 let ev = if vds >= 0.0 {
1147 self.evaluate(vds, vbs, vgs, charges)
1148 } else {
1149 self.evaluate(-vds, vbd, vgd, charges)
1150 };
1151 let (cggb, cgdb, cgsb, cbgb, cbdb, cbsb);
1152 let (cdgb, cddb, cdsb, csgb, cssb, csdb);
1153 let (qgate, qbulk, qdrn, qsrc);
1154 if vds >= 0.0 {
1155 cggb = ev.cggb;
1156 cgdb = ev.cgdb;
1157 cgsb = ev.cgsb;
1158 cbgb = ev.cbgb;
1159 cbdb = ev.cbdb;
1160 cbsb = ev.cbsb;
1161 cdgb = ev.cdgb;
1162 cddb = ev.cddb;
1163 cdsb = ev.cdsb;
1164 csgb = 0.0;
1165 cssb = 0.0;
1166 csdb = 0.0;
1167 qgate = ev.qg;
1168 qbulk = ev.qb;
1169 qdrn = ev.qd;
1170 qsrc = 0.0;
1171 } else {
1172 cggb = ev.cggb;
1173 cgsb = ev.cgdb;
1174 cgdb = ev.cgsb;
1175 cbgb = ev.cbgb;
1176 cbsb = ev.cbdb;
1177 cbdb = ev.cbsb;
1178 csgb = ev.cdgb;
1179 cssb = ev.cddb;
1180 csdb = ev.cdsb;
1181 cdgb = 0.0;
1182 cddb = 0.0;
1183 cdsb = 0.0;
1184 qgate = ev.qg;
1185 qbulk = ev.qb;
1186 qsrc = ev.qd;
1187 qdrn = 0.0;
1188 }
1189 let (gm, gds, gmbs, cdrain) = (ev.gm, ev.gds, ev.gmbs, ev.cdrain);
1190 self.von = typ * ev.von;
1191 self.vdsat = typ * ev.vdsat;
1192
1193 let cd = mode * cdrain - cbd;
1194
1195 let (mut capbs, mut capbd) = (0.0, 0.0);
1197 if charges {
1198 let t = &self.t;
1199 let czbd = t.cj * self.geom.ad;
1200 let czbs = t.cj * self.geom.as_;
1201 let czbdsw = t.cjsw * self.geom.pd;
1202 let czbssw = t.cjsw * self.geom.ps;
1203 let (phi_b, phi_bsw, mj, mjsw) = (t.pb, t.pbsw, t.mj, t.mjsw);
1204 if vbs < 0.0 {
1205 let arg = 1.0 - vbs / phi_b;
1206 let argsw = 1.0 - vbs / phi_bsw;
1207 let sarg = f64::exp(-mj * arg.ln());
1208 let sargsw = f64::exp(-mjsw * argsw.ln());
1209 ctx.states.set(
1210 0,
1211 st + QBS,
1212 phi_b * czbs * (1.0 - arg * sarg) / (1.0 - mj)
1213 + phi_bsw * czbssw * (1.0 - argsw * sargsw) / (1.0 - mjsw),
1214 );
1215 capbs = czbs * sarg + czbssw * sargsw;
1216 } else {
1217 ctx.states.set(
1218 0,
1219 st + QBS,
1220 vbs * (czbs + czbssw)
1221 + vbs * vbs * (czbs * mj * 0.5 / phi_b + czbssw * mjsw * 0.5 / phi_bsw),
1222 );
1223 capbs = czbs + czbssw + vbs * (czbs * mj / phi_b + czbssw * mjsw / phi_bsw);
1224 }
1225 if vbd < 0.0 {
1226 let arg = 1.0 - vbd / phi_b;
1227 let argsw = 1.0 - vbd / phi_bsw;
1228 let sarg = f64::exp(-mj * arg.ln());
1229 let sargsw = f64::exp(-mjsw * argsw.ln());
1230 ctx.states.set(
1231 0,
1232 st + QBD,
1233 phi_b * czbd * (1.0 - arg * sarg) / (1.0 - mj)
1234 + phi_bsw * czbdsw * (1.0 - argsw * sargsw) / (1.0 - mjsw),
1235 );
1236 capbd = czbd * sarg + czbdsw * sargsw;
1237 } else {
1238 ctx.states.set(
1239 0,
1240 st + QBD,
1241 vbd * (czbd + czbdsw)
1242 + vbd * vbd * (czbd * mj * 0.5 / phi_b + czbdsw * mjsw * 0.5 / phi_bsw),
1243 );
1244 capbd = czbd + czbdsw + vbd * (czbd * mj / phi_b + czbdsw * mjsw / phi_bsw);
1245 }
1246 }
1247
1248 if (!self.off || ctx.mode != InitMode::Fix) && check {
1250 ctx.noncon = true;
1251 }
1252
1253 ctx.states.set(0, st + VBS, vbs);
1254 ctx.states.set(0, st + VBD, vbd);
1255 ctx.states.set(0, st + VGS, vgs);
1256 ctx.states.set(0, st + VDS, vds);
1257 ctx.states.set(0, st + CD, cd);
1258 ctx.states.set(0, st + CBS, cbs);
1259 ctx.states.set(0, st + CBD, cbd);
1260 ctx.states.set(0, st + GM, gm);
1261 ctx.states.set(0, st + GDS, gds);
1262 ctx.states.set(0, st + GMBS, gmbs);
1263 ctx.states.set(0, st + GBD, gbd);
1264 ctx.states.set(0, st + GBS, gbs);
1265 ctx.states.set(0, st + CGGB, cggb);
1266 ctx.states.set(0, st + CGDB, cgdb);
1267 ctx.states.set(0, st + CGSB, cgsb);
1268 ctx.states.set(0, st + CBGB, cbgb);
1269 ctx.states.set(0, st + CBDB, cbdb);
1270 ctx.states.set(0, st + CBSB, cbsb);
1271 ctx.states.set(0, st + CDGB, cdgb);
1272 ctx.states.set(0, st + CDDB, cddb);
1273 ctx.states.set(0, st + CDSB, cdsb);
1274 ctx.states.set(0, st + CAPBS, capbs);
1275 ctx.states.set(0, st + CAPBD, capbd);
1276
1277 let (mut ceqqg, mut ceqqb, mut ceqqd) = (0.0, 0.0, 0.0);
1279 let mut gc = [0.0f64; 12]; let t = &self.t;
1281 if charges {
1282 let ag0 = ctx.ag[0];
1283 let (args, c_b, c_d): ([f64; 8], [f64; 3], [f64; 3]);
1284 if mode > 0.0 {
1285 args = [
1286 t.ld_gd_overlap,
1287 t.ld_gs_overlap,
1288 t.ld_gb_overlap,
1289 capbd,
1290 capbs,
1291 cggb,
1292 cgdb,
1293 cgsb,
1294 ];
1295 c_b = [cbgb, cbdb, cbsb];
1296 c_d = [cdgb, cddb, cdsb];
1297 } else {
1298 args = [
1299 t.ld_gs_overlap,
1300 t.ld_gd_overlap,
1301 t.ld_gb_overlap,
1302 capbs,
1303 capbd,
1304 cggb,
1305 cgsb,
1306 cgdb,
1307 ];
1308 c_b = [cbgb, cbsb, cbdb];
1309 c_d = [csgb, cssb, csdb];
1310 }
1311 let gcdgb_l = (c_d[0] - args[0]) * ag0;
1313 let gcddb_l = (c_d[1] + args[3] + args[0]) * ag0;
1314 let gcdsb_l = c_d[2] * ag0;
1315 let gcsgb_l = -(args[5] + c_b[0] + c_d[0] + args[1]) * ag0;
1316 let gcsdb_l = -(args[6] + c_b[1] + c_d[1]) * ag0;
1317 let gcssb_l = (args[4] + args[1] - (args[7] + c_b[2] + c_d[2])) * ag0;
1318 let gcggb_l = (args[5] + args[0] + args[1] + args[2]) * ag0;
1319 let gcgdb_l = (args[6] - args[0]) * ag0;
1320 let gcgsb_l = (args[7] - args[1]) * ag0;
1321 let gcbgb_l = (c_b[0] - args[2]) * ag0;
1322 let gcbdb_l = (c_b[1] - args[3]) * ag0;
1323 let gcbsb_l = (c_b[2] - args[4]) * ag0;
1324 let (vg1, vg2) = if mode > 0.0 { (vgd, vgs) } else { (vgs, vgd) };
1326 let qgd = args[0] * vg1;
1327 let qgs = args[1] * vg2;
1328 let qgb = args[2] * vgb;
1329 let qgate = qgate + qgd + qgs + qgb;
1330 let qbulk = qbulk - qgb;
1331 let (mut qdrn, mut qsrc) = (qdrn, qsrc);
1332 if mode > 0.0 {
1333 qdrn -= qgd;
1334 qsrc = -(qgate + qbulk + qdrn);
1335 } else {
1336 qsrc -= qgs;
1337 qdrn = -(qgate + qbulk + qsrc);
1338 }
1339 let _ = qsrc;
1340
1341 if mode > 0.0 {
1342 gc = [
1343 gcggb_l, gcgdb_l, gcgsb_l, gcbgb_l, gcbdb_l, gcbsb_l, gcdgb_l, gcddb_l,
1344 gcdsb_l, gcsgb_l, gcsdb_l, gcssb_l,
1345 ];
1346 } else {
1347 gc = [
1349 gcggb_l, gcgsb_l, gcgdb_l, gcbgb_l, gcbsb_l, gcbdb_l, gcsgb_l, gcssb_l,
1350 gcsdb_l, gcdgb_l, gcdsb_l, gcddb_l,
1351 ];
1352 }
1356
1357 ctx.states.set(0, st + QG, qgate);
1358 ctx.states
1359 .set(0, st + QD, qdrn - ctx.states.get(0, st + QBD));
1360 ctx.states.set(
1361 0,
1362 st + QB,
1363 qbulk + ctx.states.get(0, st + QBD) + ctx.states.get(0, st + QBS),
1364 );
1365
1366 if ctx.mode == InitMode::Smsig {
1367 return;
1368 }
1369 if ctx.mode == InitMode::Tran {
1370 for o in [QB, QG, QD] {
1371 let v = ctx.states.get(0, st + o);
1372 ctx.states.set(1, st + o, v);
1373 }
1374 }
1375 ctx.integrate(0.0, st + QB);
1376 ctx.integrate(0.0, st + QG);
1377 ctx.integrate(0.0, st + QD);
1378
1379 let cqgate = ctx.states.get(0, st + IQG);
1381 let cqbulk = ctx.states.get(0, st + IQB);
1382 let cqdrn = ctx.states.get(0, st + IQD);
1383 ceqqg = cqgate - gc[0] * vgb + gc[1] * vbd + gc[2] * vbs;
1384 ceqqb = cqbulk - gc[3] * vgb + gc[4] * vbd + gc[5] * vbs;
1385 ceqqd = cqdrn - gc[6] * vgb + gc[7] * vbd + gc[8] * vbs;
1386 if ctx.mode == InitMode::Tran {
1387 for o in [IQB, IQG, IQD] {
1388 let v = ctx.states.get(0, st + o);
1389 ctx.states.set(1, st + o, v);
1390 }
1391 }
1392 }
1393
1394 let ceqbs = typ * (cbs - (gbs - ctx.gmin) * vbs);
1396 let ceqbd = typ * (cbd - (gbd - ctx.gmin) * vbd);
1397 let ceqqg = typ * ceqqg;
1398 let ceqqb = typ * ceqqb;
1399 let ceqqd = typ * ceqqd;
1400 let (xnrm, xrev, cdreq);
1401 if mode >= 0.0 {
1402 xnrm = 1.0;
1403 xrev = 0.0;
1404 cdreq = typ * (cdrain - gds * vds - gm * vgs - gmbs * vbs);
1405 } else {
1406 xnrm = 0.0;
1407 xrev = 1.0;
1408 cdreq = -typ * (cdrain + gds * vds - gm * vgd - gmbs * vbd);
1409 }
1410 ctx.add_rhs(self.gate, -ceqqg);
1411 ctx.add_rhs(self.bulk, -(ceqbs + ceqbd + ceqqb));
1412 ctx.add_rhs(self.d_p, ceqbd - cdreq - ceqqd);
1413 ctx.add_rhs(self.s_p, cdreq + ceqbs + ceqqg + ceqqb + ceqqd);
1414
1415 let gdpr = t.drain_conductance;
1417 let gspr = t.source_conductance;
1418 ctx.add(self.s[D_D], gdpr);
1419 ctx.add(self.s[G_G], gc[0]);
1420 ctx.add(self.s[S_S], gspr);
1421 ctx.add(self.s[B_B], gbd + gbs - gc[3] - gc[4] - gc[5]);
1422 ctx.add(self.s[DP_DP], gdpr + gds + gbd + xrev * (gm + gmbs) + gc[7]);
1423 ctx.add(
1424 self.s[SP_SP],
1425 gspr + gds + gbs + xnrm * (gm + gmbs) + gc[11],
1426 );
1427 ctx.add(self.s[D_DP], -gdpr);
1428 ctx.add(self.s[G_B], -gc[0] - gc[1] - gc[2]);
1429 ctx.add(self.s[G_DP], gc[1]);
1430 ctx.add(self.s[G_SP], gc[2]);
1431 ctx.add(self.s[S_SP], -gspr);
1432 ctx.add(self.s[B_G], gc[3]);
1433 ctx.add(self.s[B_DP], -gbd + gc[4]);
1434 ctx.add(self.s[B_SP], -gbs + gc[5]);
1435 ctx.add(self.s[DP_D], -gdpr);
1436 ctx.add(self.s[DP_G], (xnrm - xrev) * gm + gc[6]);
1437 ctx.add(
1438 self.s[DP_B],
1439 -gbd + (xnrm - xrev) * gmbs - gc[6] - gc[7] - gc[8],
1440 );
1441 ctx.add(self.s[DP_SP], -gds - xnrm * (gm + gmbs) + gc[8]);
1442 ctx.add(self.s[SP_G], -(xnrm - xrev) * gm + gc[9]);
1443 ctx.add(self.s[SP_S], -gspr);
1444 ctx.add(
1445 self.s[SP_B],
1446 -gbs - (xnrm - xrev) * gmbs - gc[9] - gc[10] - gc[11],
1447 );
1448 ctx.add(self.s[SP_DP], -gds - xrev * (gm + gmbs) + gc[10]);
1449 }
1450
1451 fn trunc(&self, t: &crate::integ::TerrCtx, time_step: &mut f64) {
1452 crate::integ::terr(self.st + QB, t, time_step);
1453 crate::integ::terr(self.st + QG, t, time_step);
1454 crate::integ::terr(self.st + QD, t, time_step);
1455 }
1456
1457 fn noise_suffixes(&self) -> Option<&'static [&'static str]> {
1459 Some(B12_NOISE_SUFFIXES)
1460 }
1461
1462 fn noise_dens(&self, ctx: &crate::matrix::NoiseCtx) -> Vec<f64> {
1463 let t = &self.t;
1464 let n = B12Noise {
1465 drain: self.drain,
1466 source: self.source,
1467 d_p: self.d_p,
1468 s_p: self.s_p,
1469 gdpr: t.drain_conductance,
1470 gspr: t.source_conductance,
1471 gm: ctx.states.get(0, self.st + GM),
1472 cd: ctx.states.get(0, self.st + CD),
1473 kf: t.kf,
1474 af: t.af,
1475 flicker_area: (self.geom.w - t.delta_w * 1e-6) * (self.geom.l - t.delta_l * 1e-6),
1476 cox: t.cox,
1477 };
1478 b12_noise(ctx, &n)
1479 }
1480
1481 fn disto_setup(&mut self, ctx: &crate::disto::DistoSetupCtx) -> Result<(), &'static str> {
1483 self.dist = crate::b1disto::dsetup(
1484 &self.t,
1485 &self.geom,
1486 (self.gate, self.bulk, self.d_p, self.s_p),
1487 ctx.sol,
1488 ctx.gmin,
1489 );
1490 Ok(())
1491 }
1492
1493 fn disto_load(&self, ctx: &mut crate::disto::DistoCtx) {
1495 crate::b1disto::dload(&self.dist, (self.gate, self.bulk, self.d_p, self.s_p), ctx);
1496 }
1497
1498 fn load_ac(&mut self, ctx: &mut AcCtx) {
1500 let st = self.st;
1501 let s0 = |o: usize| ctx.states.get(0, st + o);
1502 let (xnrm, xrev) = if self.mode < 0.0 {
1503 (0.0, 1.0)
1504 } else {
1505 (1.0, 0.0)
1506 };
1507 let gdpr = self.t.drain_conductance;
1508 let gspr = self.t.source_conductance;
1509 let gm = s0(GM);
1510 let gds = s0(GDS);
1511 let gmbs = s0(GMBS);
1512 let gbd = s0(GBD);
1513 let gbs = s0(GBS);
1514 let capbd = s0(CAPBD);
1515 let capbs = s0(CAPBS);
1516 let cggb = s0(CGGB);
1517 let cgsb = s0(CGSB);
1518 let cgdb = s0(CGDB);
1519 let cbgb = s0(CBGB);
1520 let cbsb = s0(CBSB);
1521 let cbdb = s0(CBDB);
1522 let cdgb = s0(CDGB);
1523 let cdsb = s0(CDSB);
1524 let cddb = s0(CDDB);
1525 let o = ctx.omega;
1526 let xcdgb = (cdgb - self.t.gd_overlap) * o;
1527 let xcddb = (cddb + capbd + self.t.gd_overlap) * o;
1528 let xcdsb = cdsb * o;
1529 let xcsgb = -(cggb + cbgb + cdgb + self.t.gs_overlap) * o;
1530 let xcsdb = -(cgdb + cbdb + cddb) * o;
1531 let xcssb = (capbs + self.t.gs_overlap - (cgsb + cbsb + cdsb)) * o;
1532 let xcggb = (cggb + self.t.gd_overlap + self.t.gs_overlap + self.t.gb_overlap) * o;
1533 let xcgdb = (cgdb - self.t.gd_overlap) * o;
1534 let xcgsb = (cgsb - self.t.gs_overlap) * o;
1535 let xcbgb = (cbgb - self.t.gb_overlap) * o;
1536 let xcbdb = (cbdb - capbd) * o;
1537 let xcbsb = (cbsb - capbs) * o;
1538
1539 ctx.add_b(self.s[G_G], xcggb);
1540 ctx.add_b(self.s[B_B], -xcbgb - xcbdb - xcbsb);
1541 ctx.add_b(self.s[DP_DP], xcddb);
1542 ctx.add_b(self.s[SP_SP], xcssb);
1543 ctx.add_b(self.s[G_B], -xcggb - xcgdb - xcgsb);
1544 ctx.add_b(self.s[G_DP], xcgdb);
1545 ctx.add_b(self.s[G_SP], xcgsb);
1546 ctx.add_b(self.s[B_G], xcbgb);
1547 ctx.add_b(self.s[B_DP], xcbdb);
1548 ctx.add_b(self.s[B_SP], xcbsb);
1549 ctx.add_b(self.s[DP_G], xcdgb);
1550 ctx.add_b(self.s[DP_B], -xcdgb - xcddb - xcdsb);
1551 ctx.add_b(self.s[DP_SP], xcdsb);
1552 ctx.add_b(self.s[SP_G], xcsgb);
1553 ctx.add_b(self.s[SP_B], -xcsgb - xcsdb - xcssb);
1554 ctx.add_b(self.s[SP_DP], xcsdb);
1555 ctx.add_g(self.s[D_D], gdpr);
1556 ctx.add_g(self.s[S_S], gspr);
1557 ctx.add_g(self.s[B_B], gbd + gbs);
1558 ctx.add_g(self.s[DP_DP], gdpr + gds + gbd + xrev * (gm + gmbs));
1559 ctx.add_g(self.s[SP_SP], gspr + gds + gbs + xnrm * (gm + gmbs));
1560 ctx.add_g(self.s[D_DP], -gdpr);
1561 ctx.add_g(self.s[S_SP], -gspr);
1562 ctx.add_g(self.s[B_DP], -gbd);
1563 ctx.add_g(self.s[B_SP], -gbs);
1564 ctx.add_g(self.s[DP_D], -gdpr);
1565 ctx.add_g(self.s[DP_G], (xnrm - xrev) * gm);
1566 ctx.add_g(self.s[DP_B], -gbd + (xnrm - xrev) * gmbs);
1567 ctx.add_g(self.s[DP_SP], -gds - xnrm * (gm + gmbs));
1568 ctx.add_g(self.s[SP_G], -(xnrm - xrev) * gm);
1569 ctx.add_g(self.s[SP_S], -gspr);
1570 ctx.add_g(self.s[SP_B], -gbs - (xnrm - xrev) * gmbs);
1571 ctx.add_g(self.s[SP_DP], -gds - xrev * (gm + gmbs));
1572 }
1573
1574 fn load_pz(&mut self, ctx: &mut crate::matrix::PzCtx) -> Result<(), &'static str> {
1576 let ov = BsimOverlaps {
1577 gd: self.t.gd_overlap,
1578 gs: self.t.gs_overlap,
1579 gb: self.t.gb_overlap,
1580 };
1581 let (gdpr, gspr) = (self.t.drain_conductance, self.t.source_conductance);
1582 bsim_pz_load(ctx, &self.s, self.st, self.mode, gdpr, gspr, &ov);
1583 Ok(())
1584 }
1585}
1586
1587#[cfg(test)]
1588mod noise_tests {
1589 use crate::{Circuit, Options};
1590 use std::path::Path;
1591
1592 fn compare(stem: &str, ngspice_offsets: bool, only: &[&str]) -> Result<f64, String> {
1598 let root = Path::new(concat!(env!("CARGO_MANIFEST_DIR"), "/../../corpus"));
1599 let text = std::fs::read_to_string(root.join(format!("regress/{stem}.cir"))).unwrap();
1600 let parsed = dune_netlist::parse_str(stem, &text);
1601 assert!(parsed.is_clean(), "{:?}", parsed.diags);
1602 let (c, _) = Circuit::from_netlist(&parsed.netlist).unwrap();
1603 let mut opts = Options::default();
1604 let mut d = Vec::new();
1605 opts.apply(&parsed.netlist.options, &mut d);
1606 super::NGSPICE_STATE_OFFSET.set(ngspice_offsets.then_some(0.0));
1607 let run = c.run(&parsed.netlist.analyses[0], &opts);
1608 super::NGSPICE_STATE_OFFSET.set(None);
1609 let (sols, _) = run.unwrap();
1610 let golden = dune_raw::Rawfile::read_from(root.join(format!("golden/{stem}.raw"))).unwrap();
1611 assert_eq!(sols.len(), golden.plots.len());
1612 let mut worst = 0.0f64;
1613 for (sol, plot) in sols.iter().zip(&golden.plots) {
1614 for v in &plot.variables {
1615 if !only.is_empty() && !only.iter().any(|o| v.name.contains(o)) {
1616 continue;
1617 }
1618 let want = plot.real_values(&v.name).unwrap();
1619 let got = sol
1620 .column(&v.name)
1621 .ok_or_else(|| format!("{stem}: Dune lacks {}", v.name))?;
1622 for (k, (a, b)) in got.iter().zip(want).enumerate() {
1623 let e = (a - b).abs() / (b.abs() + 1e-30);
1624 if (a - b).abs() > 1e-3 * b.abs() + 1e-25 {
1625 return Err(format!("{stem} {}[{k}]: Dune {a} vs ngspice {b}", v.name));
1626 }
1627 worst = worst.max(e);
1628 }
1629 }
1630 }
1631 Ok(worst)
1632 }
1633
1634 #[test]
1643 fn b12_noise_matches_ngspice_offsets() {
1644 for stem in ["b1noise", "b2noise"] {
1645 let worst = compare(stem, true, &[]).unwrap();
1646 eprintln!("{stem}: worst relative error {worst:.2e} (ngspice offsets emulated)");
1647 assert!(worst < 1e-4, "{stem}: {worst}");
1648 compare(stem, false, &["frequency", "m1_rd", "m1_rs"]).unwrap();
1651 compare(stem, false, &["m1_id"]).unwrap_err();
1652 compare(stem, false, &["m1_1overf"]).unwrap_err();
1653 }
1654 }
1655}