1use crate::consts::{BOLTZ, CHARGE, KOVERQ, REFTEMP, ROOT2};
15use crate::diode::pnjlim;
16use crate::disto::{DfCoeffs, DistoCtx, DistoSetupCtx, Kern, d1_term, df_term, jomega};
17use crate::matrix::{AcCtx, AnKind, InitMode, LoadCtx, MatrixBuilder, Slot, TRASH, Var};
18use crate::mos1::fetlim;
19
20#[derive(Clone, Debug)]
23pub struct JfetModel {
24 pub njf: bool,
26 pub vto: f64,
28 pub beta: f64,
30 pub lambda: f64,
32 pub rd: f64,
34 pub rs: f64,
36 pub cgs: f64,
38 pub cgd: f64,
40 pub pb: f64,
42 pub is: f64,
44 pub fc: f64,
47 pub b: f64,
50 pub kf: f64,
52 pub af: f64,
53 pub nlev: i64,
58 pub gdsnoi: f64,
61 pub tnom: Option<f64>,
63}
64
65impl JfetModel {
66 pub fn new(njf: bool) -> Self {
67 JfetModel {
68 njf,
69 vto: -2.0,
70 beta: 1e-4,
71 lambda: 0.0,
72 rd: 0.0,
73 rs: 0.0,
74 cgs: 0.0,
75 cgd: 0.0,
76 pb: 1.0,
77 is: 1e-14,
78 fc: 0.5,
79 b: 1.0,
80 kf: 0.0,
81 af: 1.0,
82 nlev: 2,
83 gdsnoi: 1.0,
84 tnom: None,
85 }
86 }
87}
88
89impl Default for JfetModel {
90 fn default() -> Self {
91 JfetModel::new(true)
92 }
93}
94
95fn egfet(t: f64) -> f64 {
96 1.16 - (7.02e-4 * t * t) / (t + 1108.0)
97}
98
99#[derive(Clone, Debug)]
102pub(crate) struct JfetTemp {
103 pub temp: f64,
107 pub type_: f64,
108 pub vt: f64,
109 pub vto: f64,
110 pub beta: f64,
111 pub lambda: f64,
112 pub b: f64,
113 pub b_fac: f64,
115 pub t_sat_cur: f64,
116 pub t_cgs: f64,
117 pub t_cgd: f64,
118 pub t_gate_pot: f64,
119 pub cor_dep_cap: f64,
120 pub f1: f64,
121 pub f2: f64,
122 pub f3: f64,
123 pub vcrit: f64,
124 pub drain_conduct: f64,
125 pub source_conduct: f64,
126 pub kf: f64,
128 pub af: f64,
129 pub nlev: i64,
131 pub gdsnoi: f64,
132 pub notes: Vec<String>,
133}
134
135impl JfetTemp {
136 pub fn new(name: &str, m: &JfetModel, temp: f64, default_tnom: f64) -> Self {
137 let mut notes = Vec::new();
138 let tnom = m.tnom.unwrap_or(default_tnom);
139 let vtnom = KOVERQ * tnom;
140 let fact1 = tnom / REFTEMP;
141 let kt1 = BOLTZ * tnom;
142 let egfet1 = egfet(tnom);
143 let arg1 = -egfet1 / (kt1 + kt1) + 1.1150877 / (BOLTZ * (REFTEMP + REFTEMP));
144 let pbfact1 = -2.0 * vtnom * (1.5 * f64::ln(fact1) + CHARGE * arg1);
145 let pbo = (m.pb - pbfact1) / fact1;
146 let gmaold = (m.pb - pbo) / pbo;
147 let cjfact = 1.0 / (1.0 + 0.5 * (4e-4 * (tnom - REFTEMP) - gmaold));
148
149 let conduct = |r: f64| if r != 0.0 { 1.0 / r } else { 0.0 };
150 let mut fc = m.fc;
151 if fc > 0.95 {
152 notes.push(format!(
153 "{name}: Depletion cap. coefficient too large, limited to .95"
154 ));
155 fc = 0.95;
156 }
157 let xfc = f64::ln(1.0 - fc);
158 let f2 = f64::exp((1.0 + 0.5) * xfc);
159 let f3 = 1.0 - fc * (1.0 + 0.5);
160 let b_fac = (1.0 - m.b) / (m.pb - m.vto);
161
162 let vt = temp * KOVERQ;
164 let fact2 = temp / REFTEMP;
165 let ratio1 = temp / tnom - 1.0;
166 let t_sat_cur = m.is * f64::exp(ratio1 * 1.11 / vt);
167 let mut t_cgs = m.cgs * cjfact;
168 let mut t_cgd = m.cgd * cjfact;
169 let kt = BOLTZ * temp;
170 let egfet_t = egfet(temp);
171 let arg = -egfet_t / (kt + kt) + 1.1150877 / (BOLTZ * (REFTEMP + REFTEMP));
172 let pbfact = -2.0 * vt * (1.5 * f64::ln(fact2) + CHARGE * arg);
173 let t_gate_pot = fact2 * pbo + pbfact;
174 let gmanew = (t_gate_pot - pbo) / pbo;
175 let cjfact1 = 1.0 + 0.5 * (4e-4 * (temp - REFTEMP) - gmanew);
176 t_cgs *= cjfact1;
177 t_cgd *= cjfact1;
178 let cor_dep_cap = fc * t_gate_pot;
179 let f1 = t_gate_pot * (1.0 - f64::exp((1.0 - 0.5) * xfc)) / (1.0 - 0.5);
180 let vcrit = vt * f64::ln(vt / (ROOT2 * t_sat_cur));
181
182 JfetTemp {
183 temp,
184 type_: if m.njf { 1.0 } else { -1.0 },
185 vt,
186 vto: m.vto,
187 beta: m.beta,
188 lambda: m.lambda,
189 b: m.b,
190 b_fac,
191 t_sat_cur,
192 t_cgs,
193 t_cgd,
194 t_gate_pot,
195 cor_dep_cap,
196 f1,
197 f2,
198 f3,
199 vcrit,
200 drain_conduct: conduct(m.rd),
201 source_conduct: conduct(m.rs),
202 kf: m.kf,
203 af: m.af,
204 nlev: m.nlev,
205 gdsnoi: m.gdsnoi,
206 notes,
207 }
208 }
209}
210
211#[allow(clippy::too_many_arguments)]
215pub(crate) fn jfet_ids(
216 vgs: f64,
217 vgd: f64,
218 vto: f64,
219 beta: f64,
220 lambda: f64,
221 b: f64,
222 b_fac: f64,
223) -> (f64, f64, f64) {
224 let vds = vgs - vgd;
225 if vds >= 0.0 {
226 let vgst = vgs - vto;
227 if vgst <= 0.0 {
228 return (0.0, 0.0, 0.0);
229 }
230 let betap = beta * (1.0 + lambda * vds);
231 if vgst >= vds {
232 let apart = 2.0 * b + 3.0 * b_fac * (vgst - vds);
234 let cpart = vds * (vds * (b_fac * vds - b) + vgst * apart);
235 let cdrain = betap * cpart;
236 let gm = betap * vds * (apart + 3.0 * b_fac * vgst);
237 let gds = betap * (vgst - vds) * apart + beta * lambda * cpart;
238 (cdrain, gm, gds)
239 } else {
240 let bf = vgst * b_fac;
242 let gm = betap * vgst * (2.0 * b + 3.0 * bf);
243 let cpart = vgst * vgst * (b + bf);
244 (betap * cpart, gm, lambda * beta * cpart)
245 }
246 } else {
247 let vgdt = vgd - vto;
248 if vgdt <= 0.0 {
249 return (0.0, 0.0, 0.0);
250 }
251 let betap = beta * (1.0 - lambda * vds);
252 if vgdt + vds >= 0.0 {
253 let apart = 2.0 * b + 3.0 * b_fac * (vgdt + vds);
255 let cpart = vds * (-vds * (-b_fac * vds - b) + vgdt * apart);
256 let cdrain = betap * cpart;
257 let gm = betap * vds * (apart + 3.0 * b_fac * vgdt);
258 let gds = betap * (vgdt + vds) * apart - beta * lambda * cpart - gm;
259 (cdrain, gm, gds)
260 } else {
261 let bf = vgdt * b_fac;
263 let gm = -betap * vgdt * (2.0 * b + 3.0 * bf);
264 let cpart = vgdt * vgdt * (b + bf);
265 (-betap * cpart, gm, lambda * beta * cpart - gm)
266 }
267 }
268}
269
270pub(crate) const VGS: usize = 0;
272pub(crate) const VGD: usize = 1;
273pub(crate) const CG: usize = 2;
274pub(crate) const CD: usize = 3;
275pub(crate) const CGD: usize = 4;
276pub(crate) const GM: usize = 5;
277pub(crate) const GDS: usize = 6;
278pub(crate) const GGS: usize = 7;
279pub(crate) const GGD: usize = 8;
280pub(crate) const QGS: usize = 9;
281pub(crate) const CQGS: usize = 10;
282pub(crate) const QGD: usize = 11;
283pub(crate) const CQGD: usize = 12;
284pub(crate) const FET3_NUM_STATES: usize = 13;
286
287const D_DP: usize = 0;
289const G_DP: usize = 1;
290const G_SP: usize = 2;
291const S_SP: usize = 3;
292const DP_D: usize = 4;
293const DP_G: usize = 5;
294const DP_SP: usize = 6;
295const SP_G: usize = 7;
296const SP_S: usize = 8;
297const SP_DP: usize = 9;
298const D_D: usize = 10;
299const G_G: usize = 11;
300const S_S: usize = 12;
301const DP_DP: usize = 13;
302const SP_SP: usize = 14;
303
304pub(crate) struct Fet3Nodes {
307 pub drain: Var,
308 pub gate: Var,
309 pub source: Var,
310 pub d_p: Var,
311 pub s_p: Var,
312 s: [Slot; 15],
313}
314
315impl Fet3Nodes {
316 pub fn new(nodes: [Var; 3], primes: [Var; 2]) -> Self {
318 Fet3Nodes {
319 drain: nodes[0],
320 gate: nodes[1],
321 source: nodes[2],
322 d_p: primes[0],
323 s_p: primes[1],
324 s: [TRASH; 15],
325 }
326 }
327
328 pub fn alloc(&mut self, mb: &mut MatrixBuilder) {
329 let (d, g, s, dp, sp) = (self.drain, self.gate, self.source, self.d_p, self.s_p);
330 self.s = [
331 mb.slot(d, dp),
332 mb.slot(g, dp),
333 mb.slot(g, sp),
334 mb.slot(s, sp),
335 mb.slot(dp, d),
336 mb.slot(dp, g),
337 mb.slot(dp, sp),
338 mb.slot(sp, g),
339 mb.slot(sp, s),
340 mb.slot(sp, dp),
341 mb.slot(d, d),
342 mb.slot(g, g),
343 mb.slot(s, s),
344 mb.slot(dp, dp),
345 mb.slot(sp, sp),
346 ];
347 }
348}
349
350pub(crate) struct Fet3Bias {
356 pub vgs: f64,
357 pub vgd: f64,
358 pub check: bool,
359 pub hat: Option<(f64, f64)>,
360}
361
362#[allow(clippy::too_many_arguments)]
366pub(crate) fn fet3_bias(
367 ctx: &mut LoadCtx,
368 n: &Fet3Nodes,
369 st: usize,
370 typ: f64,
371 off: bool,
372 ic: (f64, f64),
373 vt: f64,
374 vcrit: f64,
375 vto: f64,
376) -> Fet3Bias {
377 let s0 = |ctx: &LoadCtx, o: usize| ctx.states.get(0, st + o);
378 let fixed = |vgs, vgd| Fet3Bias {
379 vgs,
380 vgd,
381 check: true,
382 hat: None,
383 };
384 match ctx.mode {
385 InitMode::Smsig => return fixed(s0(ctx, VGS), s0(ctx, VGD)),
386 InitMode::Tran => {
387 return fixed(ctx.states.get(1, st + VGS), ctx.states.get(1, st + VGD));
388 }
389 InitMode::Jct if ctx.kind == AnKind::TranOp && ctx.uic => {
390 let vds = typ * ic.0;
391 let vgs = typ * ic.1;
392 return fixed(vgs, vgs - vds);
393 }
394 InitMode::Jct if !off => return fixed(-1.0, -1.0),
395 InitMode::Jct => return fixed(0.0, 0.0),
396 InitMode::Fix if off => return fixed(0.0, 0.0),
397 _ => {}
398 }
399 let (mut vgs, mut vgd);
400 if ctx.mode == InitMode::Pred {
401 for o in [VGS, VGD, CG, CD, CGD, GM, GDS, GGS, GGD] {
402 let v = ctx.states.get(1, st + o);
403 ctx.states.set(0, st + o, v);
404 }
405 vgs = ctx.pred(st + VGS);
406 vgd = ctx.pred(st + VGD);
407 } else {
408 vgs = typ * (ctx.sol[n.gate] - ctx.sol[n.s_p]);
409 vgd = typ * (ctx.sol[n.gate] - ctx.sol[n.d_p]);
410 }
411 let delvgs = vgs - s0(ctx, VGS);
412 let delvgd = vgd - s0(ctx, VGD);
413 let delvds = delvgs - delvgd;
414 let cghat = s0(ctx, CG) + s0(ctx, GGD) * delvgd + s0(ctx, GGS) * delvgs;
415 let cdhat = s0(ctx, CD) + s0(ctx, GM) * delvgs + s0(ctx, GDS) * delvds - s0(ctx, GGD) * delvgd;
416
417 let mut check = true;
419 let mut ichk1 = true;
420 vgs = pnjlim(vgs, s0(ctx, VGS), vt, vcrit, &mut check);
421 vgd = pnjlim(vgd, s0(ctx, VGD), vt, vcrit, &mut ichk1);
422 if ichk1 {
423 check = true;
424 }
425 vgs = fetlim(vgs, s0(ctx, VGS), vto);
426 vgd = fetlim(vgd, s0(ctx, VGD), vto);
427 Fet3Bias {
428 vgs,
429 vgd,
430 check,
431 hat: Some((cghat, cdhat)),
432 }
433}
434
435pub(crate) fn gate_diode(v: f64, csat: f64, vt: f64, gmin: f64) -> (f64, f64) {
438 if v <= -5.0 * vt {
439 let g = -csat / v + gmin;
440 (g, g * v)
441 } else {
442 let e = f64::exp(v / vt);
443 (csat * e / vt + gmin, csat * (e - 1.0) + gmin * v)
444 }
445}
446
447pub(crate) struct Fet3Op {
449 pub vgs: f64,
450 pub vgd: f64,
451 pub cg: f64,
452 pub cd: f64,
453 pub cgd: f64,
454 pub gm: f64,
455 pub gds: f64,
456 pub ggs: f64,
457 pub ggd: f64,
458}
459
460pub(crate) fn fet3_charge(
465 ctx: &mut LoadCtx,
466 st: usize,
467 capgs: f64,
468 capgd: f64,
469 op: &mut Fet3Op,
470) -> bool {
471 if ctx.kind == AnKind::TranOp && ctx.uic {
472 return false;
473 }
474 if ctx.mode == InitMode::Smsig {
475 ctx.states.set(0, st + QGS, capgs);
476 ctx.states.set(0, st + QGD, capgd);
477 return true;
478 }
479 if ctx.mode == InitMode::Tran {
480 for o in [QGS, QGD] {
481 let v = ctx.states.get(0, st + o);
482 ctx.states.set(1, st + o, v);
483 }
484 }
485 let (geq, _) = ctx.integrate(capgs, st + QGS);
486 op.ggs += geq;
487 op.cg += ctx.states.get(0, st + CQGS);
488 let (geq, _) = ctx.integrate(capgd, st + QGD);
489 op.ggd += geq;
490 let cqgd = ctx.states.get(0, st + CQGD);
491 op.cg += cqgd;
492 op.cd -= cqgd;
493 op.cgd += cqgd;
494 if ctx.mode == InitMode::Tran {
495 for o in [CQGS, CQGD] {
496 let v = ctx.states.get(0, st + o);
497 ctx.states.set(1, st + o, v);
498 }
499 }
500 false
501}
502
503#[allow(clippy::too_many_arguments)]
506pub(crate) fn fet3_finish(
507 ctx: &mut LoadCtx,
508 n: &Fet3Nodes,
509 st: usize,
510 typ: f64,
511 gdpr: f64,
512 gspr: f64,
513 bias: &Fet3Bias,
514 op: &Fet3Op,
515) {
516 if !(ctx.mode == InitMode::Fix && ctx.uic) {
517 let disagree = match bias.hat {
518 Some((cghat, cdhat)) => {
519 (cghat - op.cg).abs()
520 >= ctx.reltol * f64::max(cghat.abs(), op.cg.abs()) + ctx.abstol
521 || (cdhat - op.cd).abs()
522 > ctx.reltol * f64::max(cdhat.abs(), op.cd.abs()) + ctx.abstol
523 }
524 None => true,
525 };
526 if bias.check || disagree {
527 ctx.noncon = true;
528 }
529 }
530 let vals = [
531 (VGS, op.vgs),
532 (VGD, op.vgd),
533 (CG, op.cg),
534 (CD, op.cd),
535 (CGD, op.cgd),
536 (GM, op.gm),
537 (GDS, op.gds),
538 (GGS, op.ggs),
539 (GGD, op.ggd),
540 ];
541 for (o, v) in vals {
542 ctx.states.set(0, st + o, v);
543 }
544
545 let vds = op.vgs - op.vgd;
546 let ceqgd = typ * (op.cgd - op.ggd * op.vgd);
547 let ceqgs = typ * ((op.cg - op.cgd) - op.ggs * op.vgs);
548 let cdreq = typ * ((op.cd + op.cgd) - op.gds * vds - op.gm * op.vgs);
549 ctx.add_rhs(n.gate, -ceqgs - ceqgd);
550 ctx.add_rhs(n.d_p, -cdreq + ceqgd);
551 ctx.add_rhs(n.s_p, cdreq + ceqgs);
552
553 let (gm, gds, ggs, ggd) = (op.gm, op.gds, op.ggs, op.ggd);
554 ctx.add(n.s[D_DP], -gdpr);
555 ctx.add(n.s[G_DP], -ggd);
556 ctx.add(n.s[G_SP], -ggs);
557 ctx.add(n.s[S_SP], -gspr);
558 ctx.add(n.s[DP_D], -gdpr);
559 ctx.add(n.s[DP_G], gm - ggd);
560 ctx.add(n.s[DP_SP], -gds - gm);
561 ctx.add(n.s[SP_G], -ggs - gm);
562 ctx.add(n.s[SP_S], -gspr);
563 ctx.add(n.s[SP_DP], -gds);
564 ctx.add(n.s[D_D], gdpr);
565 ctx.add(n.s[G_G], ggd + ggs);
566 ctx.add(n.s[S_S], gspr);
567 ctx.add(n.s[DP_DP], gdpr + gds + ggd);
568 ctx.add(n.s[SP_SP], gspr + gds + gm + ggs);
569}
570
571pub(crate) fn fet3_ac(ctx: &mut AcCtx, n: &Fet3Nodes, st: usize, gdpr: f64, gspr: f64) {
575 let s0 = |o: usize| ctx.states.get(0, st + o);
576 let gm = s0(GM);
577 let gds = s0(GDS);
578 let ggs = s0(GGS);
579 let xgs = s0(QGS) * ctx.omega;
580 let ggd = s0(GGD);
581 let xgd = s0(QGD) * ctx.omega;
582 ctx.add_g(n.s[D_D], gdpr);
583 ctx.add_g(n.s[G_G], ggd + ggs);
584 ctx.add_b(n.s[G_G], xgd + xgs);
585 ctx.add_g(n.s[S_S], gspr);
586 ctx.add_g(n.s[DP_DP], gdpr + gds + ggd);
587 ctx.add_b(n.s[DP_DP], xgd);
588 ctx.add_g(n.s[SP_SP], gspr + gds + gm + ggs);
589 ctx.add_b(n.s[SP_SP], xgs);
590 ctx.add_g(n.s[D_DP], -gdpr);
591 ctx.add_g(n.s[G_DP], -ggd);
592 ctx.add_b(n.s[G_DP], -xgd);
593 ctx.add_g(n.s[G_SP], -ggs);
594 ctx.add_b(n.s[G_SP], -xgs);
595 ctx.add_g(n.s[S_SP], -gspr);
596 ctx.add_g(n.s[DP_D], -gdpr);
597 ctx.add_g(n.s[DP_G], -ggd + gm);
598 ctx.add_b(n.s[DP_G], -xgd);
599 ctx.add_g(n.s[DP_SP], -gds - gm);
600 ctx.add_g(n.s[SP_G], -ggs - gm);
601 ctx.add_b(n.s[SP_G], -xgs);
602 ctx.add_g(n.s[SP_S], -gspr);
603 ctx.add_g(n.s[SP_DP], -gds);
604}
605
606pub(crate) fn fet3_pz(
613 ctx: &mut crate::matrix::PzCtx,
614 n: &Fet3Nodes,
615 st: usize,
616 gdpr: f64,
617 gspr: f64,
618) {
619 let s0 = |o: usize| ctx.states.get(0, st + o);
620 let gm = s0(GM);
621 let gds = s0(GDS);
622 let ggs = s0(GGS);
623 let xgs = s0(QGS);
624 let ggd = s0(GGD);
625 let xgd = s0(QGD);
626 ctx.add_g(n.s[D_D], gdpr);
627 ctx.add_g(n.s[G_G], ggd + ggs);
628 ctx.add_cap(n.s[G_G], xgd + xgs);
629 ctx.add_g(n.s[S_S], gspr);
630 ctx.add_g(n.s[DP_DP], gdpr + gds + ggd);
631 ctx.add_cap(n.s[DP_DP], xgd);
632 ctx.add_g(n.s[SP_SP], gspr + gds + gm + ggs);
633 ctx.add_cap(n.s[SP_SP], xgs);
634 ctx.add_g(n.s[D_DP], -gdpr);
635 ctx.add_g(n.s[G_DP], -ggd);
636 ctx.add_cap(n.s[G_DP], -xgd);
637 ctx.add_g(n.s[G_SP], -ggs);
638 ctx.add_cap(n.s[G_SP], -xgs);
639 ctx.add_g(n.s[S_SP], -gspr);
640 ctx.add_g(n.s[DP_D], -gdpr);
641 ctx.add_g(n.s[DP_G], -ggd + gm);
642 ctx.add_cap(n.s[DP_G], -xgd);
643 ctx.add_g(n.s[DP_SP], -gds - gm);
644 ctx.add_g(n.s[SP_G], -ggs - gm);
645 ctx.add_cap(n.s[SP_G], -xgs);
646 ctx.add_g(n.s[SP_S], -gspr);
647 ctx.add_g(n.s[SP_DP], -gds);
648}
649
650pub(crate) const FET3_NOISE_SUFFIXES: &[&str] = &["_rd", "_rs", "_id", "_1overf", ""];
653
654pub(crate) enum Fet3Channel {
659 Mes,
661 Jfet {
666 temp: f64,
667 nlev: i64,
668 gdsnoi: f64,
669 vto: f64,
671 beta: f64,
672 },
673}
674
675#[allow(clippy::too_many_arguments)]
680pub(crate) fn fet3_noise(
681 ctx: &crate::matrix::NoiseCtx,
682 n: &Fet3Nodes,
683 st: usize,
684 gdpr: f64,
685 gspr: f64,
686 kf: f64,
687 af: f64,
688 chan: &Fet3Channel,
689) -> Vec<f64> {
690 const N_MINLOG: f64 = 1e-38;
691 let gm = ctx.states.get(0, st + GM);
692 let (rd, rs, id) = match *chan {
693 Fet3Channel::Mes => (
694 ctx.thermal(n.d_p, n.drain, gdpr),
695 ctx.thermal(n.s_p, n.source, gspr),
696 ctx.thermal(n.d_p, n.s_p, 2.0 / 3.0 * gm.abs()),
697 ),
698 Fet3Channel::Jfet {
699 temp,
700 nlev,
701 gdsnoi,
702 vto,
703 beta,
704 } => {
705 let sid = if nlev < 3 {
706 2.0 / 3.0 * gm.abs()
707 } else {
708 let vgs = ctx.states.get(0, st + VGS);
709 let vds = vgs - ctx.states.get(0, st + VGD);
710 let vgst = vgs - vto;
711 let alpha = if vgst >= vds {
712 1.0 - vds / vgst } else {
714 0.0 };
716 2.0 / 3.0 * beta * vgst * (1.0 + alpha + alpha * alpha) / (1.0 + alpha) * gdsnoi
717 };
718 (
719 ctx.thermal_at(n.d_p, n.drain, gdpr, temp),
720 ctx.thermal_at(n.s_p, n.source, gspr, temp),
721 ctx.thermal_at(n.d_p, n.s_p, sid, temp),
722 )
723 }
724 };
725 let cd = ctx.states.get(0, st + CD);
726 let flicker = ctx.flicker(
727 n.d_p,
728 n.s_p,
729 kf * f64::exp(af * f64::ln(f64::max(cd.abs(), N_MINLOG))),
730 1.0,
731 );
732 vec![rd, rs, id, flicker, rd + rs + id + flicker]
733}
734
735#[derive(Clone, Copy, Debug)]
739#[allow(clippy::large_enum_variant)] pub(crate) enum Fet3GateQ {
741 Junction {
744 capgs2: f64,
745 capgs3: f64,
746 capgd2: f64,
747 capgd3: f64,
748 },
749 Statz { qgs: DfCoeffs, qgd: DfCoeffs },
751}
752
753#[derive(Clone, Copy, Debug)]
759pub(crate) struct Fet3DistoCoeffs {
760 pub cdr: DfCoeffs,
761 pub ggs2: f64,
762 pub ggs3: f64,
763 pub ggd2: f64,
764 pub ggd3: f64,
765 pub q: Fet3GateQ,
766}
767
768impl Default for Fet3DistoCoeffs {
769 fn default() -> Self {
770 Fet3DistoCoeffs {
771 cdr: DfCoeffs::default(),
772 ggs2: 0.0,
773 ggs3: 0.0,
774 ggd2: 0.0,
775 ggd3: 0.0,
776 q: Fet3GateQ::Junction {
777 capgs2: 0.0,
778 capgs3: 0.0,
779 capgd2: 0.0,
780 capgd3: 0.0,
781 },
782 }
783 }
784}
785
786#[derive(Clone, Copy, Debug, Default)]
790pub(crate) struct Fet3DrainDerivs {
791 pub gm1: f64,
792 pub gds1: f64,
793 pub gm2: f64,
794 pub gds2: f64,
795 pub gmds: f64,
796 pub gm3: f64,
797 pub gds3: f64,
798 pub gm2ds: f64,
799 pub gmds2: f64,
800}
801
802pub(crate) fn fet3_cdr(typ: f64, mode: f64, g: &Fet3DrainDerivs) -> DfCoeffs {
807 let mut c = DfCoeffs::default();
808 if mode == 1.0 {
809 c.cxx = g.gm2;
810 c.cyy = g.gds2;
811 c.cxy = g.gmds;
812 c.cxxx = g.gm3;
813 c.cyyy = g.gds3;
814 c.cxxy = g.gm2ds;
815 c.cxyy = g.gmds2;
816 } else {
817 c.cxx = -g.gm2;
818 c.cyy = -(g.gm2 + g.gds2 + 2.0 * g.gmds);
819 c.cxy = g.gm2 + g.gmds;
820 c.cxxx = -g.gm3;
821 c.cyyy = g.gm3 + g.gds3 + 3.0 * (g.gm2ds + g.gmds2);
822 c.cxxy = g.gm3 + g.gm2ds;
823 c.cxyy = -(g.gm3 + 2.0 * g.gm2ds + g.gmds2);
824 }
825 c.cxx *= 0.5 * typ;
826 c.cyy *= 0.5 * typ;
827 c.cxy *= typ;
828 c.cxxx /= 6.;
829 c.cyyy /= 6.;
830 c.cxxy *= 0.5;
831 c.cxyy *= 0.5;
832 c
833}
834
835pub(crate) fn fet3_gate_diode_disto(v: f64, csat: f64, vt: f64, gmin: f64) -> (f64, f64, f64) {
840 if v <= -5.0 * vt {
841 (-csat / v + gmin, 0.0, 0.0)
842 } else {
843 let ev = f64::exp(v / vt);
844 let g1 = csat * ev / vt + gmin;
845 let g2 = (g1 - gmin) / (vt * 2.0);
846 let g3 = g2 / (3.0 * vt);
847 (g1, g2, g3)
848 }
849}
850
851pub(crate) fn fet3_disto_load(d: &Fet3DistoCoeffs, n: &Fet3Nodes, ctx: &mut DistoCtx) {
856 let mode = ctx.mode;
857 let omega = ctx.omega;
858 let kx = ctx.h.kern(mode, n.gate, n.s_p);
859 let ky = ctx.h.kern(mode, n.d_p, n.s_p);
860 let kz = Kern::default();
861 let kgd = kx.sub(&ky);
862 let cdrain = df_term(mode, &d.cdr, &kx, &ky, &kz);
863 ctx.stamp(n.d_p, n.s_p, cdrain);
864 let ggs = d1_term(mode, d.ggs2, d.ggs3, &kx);
865 ctx.stamp(n.gate, n.s_p, ggs);
866 let ggd = d1_term(mode, d.ggd2, d.ggd3, &kgd);
867 ctx.stamp(n.gate, n.d_p, ggd);
868 match &d.q {
869 Fet3GateQ::Junction {
870 capgs2,
871 capgs3,
872 capgd2,
873 capgd3,
874 } => {
875 let capgs = jomega(omega, d1_term(mode, *capgs2, *capgs3, &kx));
876 ctx.stamp(n.gate, n.s_p, capgs);
877 let capgd = jomega(omega, d1_term(mode, *capgd2, *capgd3, &kgd));
878 ctx.stamp(n.gate, n.d_p, capgd);
879 }
880 Fet3GateQ::Statz { qgs, qgd } => {
881 let q = jomega(omega, df_term(mode, qgs, &kx, &kgd, &kz));
882 ctx.stamp(n.gate, n.s_p, q);
883 let q = jomega(omega, df_term(mode, qgd, &kx, &kgd, &kz));
884 ctx.stamp(n.gate, n.d_p, q);
885 }
886 }
887}
888
889pub(crate) fn jfet_dsetup(
908 t: &JfetTemp,
909 area: f64,
910 gmin: f64,
911 vgs: f64,
912 vgd: f64,
913) -> Fet3DistoCoeffs {
914 let typ = t.type_;
915 let beta = t.beta * area;
916 let csat = t.t_sat_cur * area;
917 let vt = t.vt;
918 let lambda = t.lambda;
919 let (mut vgs, mut vgd) = (vgs, vgd);
920 let mut vds = vgs - vgd;
921 let mode = if vds < 0.0 {
922 vds = -vds;
923 std::mem::swap(&mut vgs, &mut vgd);
924 -1.0
925 } else {
926 1.0
927 };
928 let (_, lggs2, lggs3) = fet3_gate_diode_disto(vgs, csat, vt, gmin);
929 let (_, lggd2, lggd3) = fet3_gate_diode_disto(vgd, csat, vt, gmin);
930
931 let vgst = vgs - t.vto;
932 let mut g = Fet3DrainDerivs::default();
933 if vgst > 0.0 {
934 let betap = beta * (1.0 + lambda * vds);
935 let twob = betap + betap;
936 if vgst <= vds {
937 g.gm1 = twob * vgst;
939 g.gm2 = twob;
940 g.gds1 = lambda * beta * vgst * vgst;
941 g.gm2ds = 2.0 * lambda * beta;
942 g.gmds = g.gm2ds * vgst;
943 } else {
944 g.gm1 = twob * vds;
946 g.gmds = (beta + beta) * (1.0 + 2.0 * lambda * vds);
947 g.gds2 = 2.0 * beta * (2.0 * lambda * vgst - 1.0 - 3.0 * lambda * vds);
948 g.gds1 =
949 beta * (2.0 * (vgst - vds) + 4.0 * vgst * vds * lambda - 3.0 * lambda * vds * vds);
950 g.gmds2 = 4.0 * beta * lambda;
951 g.gds3 = -6.0 * beta * lambda;
952 }
953 }
954
955 let czgs = t.t_cgs * area;
957 let czgd = t.t_cgd * area;
958 let pot = t.t_gate_pot;
959 let twop = pot + pot;
960 let junction = |v: f64, cz: f64| {
961 if v < t.cor_dep_cap {
962 let sarg = f64::sqrt(1.0 - v / pot);
963 let c1 = cz / sarg;
964 let c2 = c1 / (pot * 4.0 * sarg * sarg);
965 let c3 = c2 / (pot * 2.0 * sarg * sarg);
966 (c2, c3)
967 } else {
968 let czf2 = cz / t.f2;
969 (czf2 / twop * 0.5, 0.0)
970 }
971 };
972 let (lcapgs2, lcapgs3) = junction(vgs, czgs);
973 let (lcapgd2, lcapgd3) = junction(vgd, czgd);
974
975 let cdr = fet3_cdr(typ, mode, &g);
976 let (ggs3, ggd3, capgs3, capgd3) = if mode == 1.0 {
979 (lggs3, lggd3, lcapgs3, lcapgd3)
980 } else {
981 (lggd3, lggs3, lcapgd3, lcapgs3)
982 };
983 Fet3DistoCoeffs {
984 cdr,
985 ggs2: typ * lggs2,
986 ggs3,
987 ggd2: typ * lggd2,
988 ggd3,
989 q: Fet3GateQ::Junction {
990 capgs2: typ * lcapgs2,
991 capgs3,
992 capgd2: typ * lcapgd2,
993 capgd3,
994 },
995 }
996}
997
998pub(crate) struct JfetI {
1000 n: Fet3Nodes,
1001 t: JfetTemp,
1002 area: f64,
1003 off: bool,
1004 ic: (f64, f64),
1006 ic_given: crate::devices::IcGiven,
1007 st: usize,
1008 dist: Fet3DistoCoeffs,
1010}
1011
1012impl JfetI {
1013 pub fn new(
1014 nodes: [Var; 3],
1015 primes: [Var; 2],
1016 t: JfetTemp,
1017 area: f64,
1018 off: bool,
1019 ic: (f64, f64),
1020 st: usize,
1021 ) -> Self {
1022 JfetI {
1023 n: Fet3Nodes::new(nodes, primes),
1024 t,
1025 area,
1026 off,
1027 ic,
1028 ic_given: crate::devices::IcGiven::ALL,
1029 st,
1030 dist: Fet3DistoCoeffs::default(),
1031 }
1032 }
1033}
1034
1035impl crate::devices::Device for JfetI {
1036 fn par_load(&self) -> bool {
1037 true
1038 }
1039
1040 fn set_ic(&mut self, rhs: &[f64]) {
1043 use crate::devices::getic;
1044 let g = self.ic_given;
1045 let n = &self.n;
1046 getic(g, 0, &mut self.ic.0, rhs, n.drain, n.source);
1047 getic(g, 1, &mut self.ic.1, rhs, n.gate, n.source);
1048 }
1049
1050 fn set_ic_given(&mut self, given: crate::devices::IcGiven) {
1051 self.ic_given = given;
1052 }
1053
1054 fn alloc(&mut self, mb: &mut MatrixBuilder) {
1055 self.n.alloc(mb);
1056 }
1057
1058 fn load(&mut self, ctx: &mut LoadCtx) {
1059 let t = &self.t;
1060 let st = self.st;
1061 let typ = t.type_;
1062 let beta = t.beta * self.area;
1063 let gdpr = t.drain_conduct * self.area;
1064 let gspr = t.source_conduct * self.area;
1065 let csat = t.t_sat_cur * self.area;
1066
1067 let bias = fet3_bias(
1068 ctx, &self.n, st, typ, self.off, self.ic, t.vt, t.vcrit, t.vto,
1069 );
1070 let (vgs, vgd) = (bias.vgs, bias.vgd);
1071
1072 let (ggs, cg) = gate_diode(vgs, csat, t.vt, ctx.gmin);
1074 let (ggd, cgd) = gate_diode(vgd, csat, t.vt, ctx.gmin);
1075 let (cdrain, gm, gds) = jfet_ids(vgs, vgd, t.vto, beta, t.lambda, t.b, t.b_fac);
1076 let mut op = Fet3Op {
1077 vgs,
1078 vgd,
1079 cg: cg + cgd,
1080 cd: cdrain - cgd,
1081 cgd,
1082 gm,
1083 gds,
1084 ggs,
1085 ggd,
1086 };
1087
1088 if ctx.kind == AnKind::Tran
1091 || ctx.mode == InitMode::Smsig
1092 || (ctx.kind == AnKind::TranOp && ctx.uic)
1093 {
1094 let czgs = t.t_cgs * self.area;
1095 let czgd = t.t_cgd * self.area;
1096 let twop = t.t_gate_pot + t.t_gate_pot;
1097 let fcpb2 = t.cor_dep_cap * t.cor_dep_cap;
1098 let junction = |v: f64, cz: f64| {
1099 if v < t.cor_dep_cap {
1100 let sarg = f64::sqrt(1.0 - v / t.t_gate_pot);
1101 (twop * cz * (1.0 - sarg), cz / sarg)
1102 } else {
1103 let czf2 = cz / t.f2;
1104 (
1105 cz * t.f1
1106 + czf2 * (t.f3 * (v - t.cor_dep_cap) + (v * v - fcpb2) / (twop + twop)),
1107 czf2 * (t.f3 + v / twop),
1108 )
1109 }
1110 };
1111 let (qgs, capgs) = junction(vgs, czgs);
1112 let (qgd, capgd) = junction(vgd, czgd);
1113 ctx.states.set(0, st + QGS, qgs);
1114 ctx.states.set(0, st + QGD, qgd);
1115 if fet3_charge(ctx, st, capgs, capgd, &mut op) {
1116 return;
1117 }
1118 }
1119
1120 fet3_finish(ctx, &self.n, st, typ, gdpr, gspr, &bias, &op);
1121 }
1122
1123 fn trunc(&self, t: &crate::integ::TerrCtx, time_step: &mut f64) {
1125 crate::integ::terr(self.st + QGS, t, time_step);
1126 crate::integ::terr(self.st + QGD, t, time_step);
1127 }
1128
1129 fn load_ac(&mut self, ctx: &mut AcCtx) {
1131 let gdpr = self.t.drain_conduct * self.area;
1132 let gspr = self.t.source_conduct * self.area;
1133 fet3_ac(ctx, &self.n, self.st, gdpr, gspr);
1134 }
1135
1136 fn load_pz(&mut self, ctx: &mut crate::matrix::PzCtx) -> Result<(), &'static str> {
1138 let gdpr = self.t.drain_conduct * self.area;
1139 let gspr = self.t.source_conduct * self.area;
1140 fet3_pz(ctx, &self.n, self.st, gdpr, gspr);
1141 Ok(())
1142 }
1143
1144 fn noise_suffixes(&self) -> Option<&'static [&'static str]> {
1146 Some(FET3_NOISE_SUFFIXES)
1147 }
1148
1149 fn noise_dens(&self, ctx: &crate::matrix::NoiseCtx) -> Vec<f64> {
1150 let gdpr = self.t.drain_conduct * self.area;
1151 let gspr = self.t.source_conduct * self.area;
1152 let t = &self.t;
1153 let chan = Fet3Channel::Jfet {
1154 temp: t.temp,
1155 nlev: t.nlev,
1156 gdsnoi: t.gdsnoi,
1157 vto: t.vto,
1158 beta: t.beta * self.area,
1159 };
1160 fet3_noise(ctx, &self.n, self.st, gdpr, gspr, t.kf, t.af, &chan)
1161 }
1162
1163 fn disto_setup(&mut self, ctx: &DistoSetupCtx) -> Result<(), &'static str> {
1165 let n = &self.n;
1166 let typ = self.t.type_;
1167 let vgs = typ * (ctx.sol[n.gate] - ctx.sol[n.s_p]);
1168 let vgd = typ * (ctx.sol[n.gate] - ctx.sol[n.d_p]);
1169 self.dist = jfet_dsetup(&self.t, self.area, ctx.gmin, vgs, vgd);
1170 Ok(())
1171 }
1172
1173 fn disto_load(&self, ctx: &mut DistoCtx) {
1175 fet3_disto_load(&self.dist, &self.n, ctx);
1176 }
1177}
1178
1179#[cfg(test)]
1180pub(crate) mod tests {
1181 use super::*;
1182
1183 #[test]
1184 fn temp_is_identity_at_tnom() {
1185 let mut m = JfetModel::new(true);
1186 m.cgs = 2e-12;
1187 m.cgd = 1e-12;
1188 m.pb = 0.8;
1189 m.rd = 50.0;
1190 let t = JfetTemp::new("j1", &m, REFTEMP, REFTEMP);
1191 assert_eq!(t.t_sat_cur, m.is);
1192 assert!((t.t_gate_pot - 0.8).abs() < 1e-12);
1193 assert!((t.t_cgs - 2e-12).abs() < 1e-24);
1194 assert!((t.t_cgd - 1e-12).abs() < 1e-24);
1195 assert_eq!(t.drain_conduct, 0.02);
1196 assert_eq!(t.source_conduct, 0.0);
1197 assert_eq!(t.b_fac, 0.0); let vt = REFTEMP * KOVERQ;
1199 assert!((t.vcrit - vt * (vt / (ROOT2 * 1e-14)).ln()).abs() < 1e-15);
1200 assert!((t.f2 - 0.5f64.powf(1.5)).abs() < 1e-15);
1202 assert!((t.f3 - 0.25).abs() < 1e-15);
1203 assert!((t.f1 - 0.8 * (1.0 - 0.5f64.sqrt()) / 0.5).abs() < 1e-14);
1204 assert!(t.notes.is_empty());
1205 }
1206
1207 #[test]
1208 fn temp_clamps_fc_and_warms_up() {
1209 let mut m = JfetModel::new(false);
1210 m.fc = 0.99;
1211 let t = JfetTemp::new("j1", &m, REFTEMP + 50.0, REFTEMP);
1212 assert_eq!(t.notes.len(), 1);
1213 assert!((t.cor_dep_cap - 0.95 * t.t_gate_pot).abs() < 1e-15);
1214 assert!(t.t_sat_cur > m.is);
1216 assert!(t.t_gate_pot < m.pb);
1217 assert_eq!(t.type_, -1.0);
1218 }
1219
1220 #[test]
1221 fn ids_reduces_to_shichman_hodges_with_b_one() {
1222 let (beta, lambda, vto) = (1e-3, 0.02, -2.0);
1223 let (vgs, vgd) = (-1.0, -6.0);
1225 let (id, gm, gds) = jfet_ids(vgs, vgd, vto, beta, lambda, 1.0, 0.0);
1226 let vgst = vgs - vto;
1227 let vds = vgs - vgd;
1228 assert!((id - beta * (1.0 + lambda * vds) * vgst * vgst).abs() < 1e-18);
1229 assert!((gm - 2.0 * beta * (1.0 + lambda * vds) * vgst).abs() < 1e-18);
1230 assert!((gds - lambda * beta * vgst * vgst).abs() < 1e-18);
1231 let (vgs, vgd) = (0.0, -0.5);
1233 let (id, _, _) = jfet_ids(vgs, vgd, vto, beta, lambda, 1.0, 0.0);
1234 let vds = 0.5;
1235 let vgst = 2.0;
1236 assert!((id - beta * (1.0 + lambda * vds) * vds * (2.0 * vgst - vds)).abs() < 1e-18);
1237 assert_eq!(
1239 jfet_ids(-3.0, -8.0, vto, beta, lambda, 1.0, 0.0),
1240 (0.0, 0.0, 0.0)
1241 );
1242 }
1243
1244 #[test]
1245 fn ids_derivatives_match_finite_differences() {
1246 let (beta, lambda, vto, b, pb) = (1e-3, 0.05, -2.0, 0.8, 1.0);
1249 let b_fac = (1.0 - b) / (pb - vto);
1250 let h = 1e-6;
1251 for &(vgs, vgd) in &[
1252 (-0.5, -1.0), (-0.5, -4.0), (-1.0, -0.6), (-4.0, -0.5), ] {
1257 let f = |a: f64, c: f64| jfet_ids(a, c, vto, beta, lambda, b, b_fac).0;
1258 let (_, gm, gds) = jfet_ids(vgs, vgd, vto, beta, lambda, b, b_fac);
1259 let vds = vgs - vgd;
1262 let dgs = (f(vgs + h, vgs + h - vds) - f(vgs - h, vgs - h - vds)) / (2.0 * h);
1263 let dds = (f(vgs, vgs - vds - h) - f(vgs, vgs - vds + h)) / (2.0 * h);
1264 assert!(
1265 (gm - dgs).abs() < 1e-8 * (1.0 + gm.abs()),
1266 "gm at {vgs},{vgd}"
1267 );
1268 assert!(
1269 (gds - dds).abs() < 1e-8 * (1.0 + gds.abs()),
1270 "gds at {vgs},{vgd}"
1271 );
1272 }
1273 }
1274
1275 #[test]
1276 fn gate_diode_regions() {
1277 let vt = REFTEMP * KOVERQ;
1278 let (g, i) = gate_diode(-1.0, 1e-14, vt, 1e-12);
1279 assert!((i - (-1e-14 - 1e-12)).abs() < 1e-27);
1280 assert!((g - (1e-14 + 1e-12)).abs() < 1e-27);
1281 let (g, i) = gate_diode(0.6, 1e-14, vt, 0.0);
1282 let e = (0.6 / vt).exp();
1283 assert_eq!(i, 1e-14 * (e - 1.0));
1284 assert_eq!(g, 1e-14 * e / vt);
1285 }
1286
1287 pub(crate) fn taylor2_fd(f: &dyn Fn(f64, f64) -> f64, x: f64, y: f64, h: f64) -> [f64; 7] {
1292 let fxx = (f(x + h, y) - 2.0 * f(x, y) + f(x - h, y)) / (h * h);
1293 let fyy = (f(x, y + h) - 2.0 * f(x, y) + f(x, y - h)) / (h * h);
1294 let fxy =
1295 (f(x + h, y + h) - f(x + h, y - h) - f(x - h, y + h) + f(x - h, y - h)) / (4.0 * h * h);
1296 let d3 = |dx: f64, dy: f64| {
1297 (f(x + 2.0 * h * dx, y + 2.0 * h * dy) - 2.0 * f(x + h * dx, y + h * dy)
1299 + 2.0 * f(x - h * dx, y - h * dy)
1300 - f(x - 2.0 * h * dx, y - 2.0 * h * dy))
1301 / (2.0 * h * h * h)
1302 };
1303 let fxxx = d3(1.0, 0.0);
1304 let fyyy = d3(0.0, 1.0);
1305 let dp = d3(1.0, 1.0);
1308 let dm = d3(1.0, -1.0);
1309 let fxxy = (dp - dm - 2.0 * fyyy) / 6.0;
1310 let fxyy = (dp + dm - 2.0 * fxxx) / 6.0;
1311 [
1312 0.5 * fxx,
1313 0.5 * fyy,
1314 fxy,
1315 fxxx / 6.0,
1316 fyyy / 6.0,
1317 0.5 * fxxy,
1318 0.5 * fxyy,
1319 ]
1320 }
1321
1322 pub(crate) fn taylor2_fd_rich(f: &dyn Fn(f64, f64) -> f64, x: f64, y: f64, h: f64) -> [f64; 7] {
1325 let a = taylor2_fd(f, x, y, h);
1326 let b = taylor2_fd(f, x, y, h / 2.0);
1327 std::array::from_fn(|k| (4.0 * b[k] - a[k]) / 3.0)
1328 }
1329
1330 pub(crate) fn df_seven(c: &DfCoeffs) -> [f64; 7] {
1331 [c.cxx, c.cyy, c.cxy, c.cxxx, c.cyyy, c.cxxy, c.cxyy]
1332 }
1333
1334 pub(crate) fn assert_taylor(got: [f64; 7], want: [f64; 7], tol: f64, what: &str) -> f64 {
1338 let s2 = got[..3]
1339 .iter()
1340 .chain(&want[..3])
1341 .fold(0.0f64, |a, v| a.max(v.abs()));
1342 let s3 = got[3..]
1343 .iter()
1344 .chain(&want[3..])
1345 .fold(0.0f64, |a, v| a.max(v.abs()));
1346 let mut worst = 0.0f64;
1347 for k in 0..7 {
1348 let s = if k < 3 { s2 } else { s3 };
1349 if s == 0.0 {
1350 continue;
1351 }
1352 let e = (got[k] - want[k]).abs() / s;
1353 worst = worst.max(e);
1354 assert!(
1355 e <= tol,
1356 "{what}[{k}]: setup {} vs finite difference {} (rel {e:.2e})",
1357 got[k],
1358 want[k]
1359 );
1360 }
1361 worst
1362 }
1363
1364 fn jfet_model() -> JfetModel {
1365 let mut m = JfetModel::new(true);
1366 m.vto = -2.0;
1367 m.beta = 1.3e-3;
1368 m.lambda = 0.03;
1369 m.cgs = 3e-12;
1370 m.cgd = 1.5e-12;
1371 m.pb = 0.8;
1372 m.is = 1e-13;
1373 m
1374 }
1375
1376 #[test]
1385 fn jfet_dsetup_matches_finite_differences() {
1386 let gmin = 1e-12;
1387 let mut worst = 0.0f64;
1388 for njf in [true, false] {
1389 let mut m = jfet_model();
1390 m.njf = njf;
1391 let t = JfetTemp::new("j1", &m, REFTEMP, REFTEMP);
1392 let typ = t.type_;
1393 let area = 1.7;
1394 let beta = t.beta * area;
1395 let csat = t.t_sat_cur * area;
1396 for &(vgs_n, vds_n) in &[
1398 (-0.7, 0.6), (-0.7, 4.0), (-0.7, -0.5), (-1.2, -5.0), (0.3, 3.0), (0.6, 3.0), (-3.0, 2.0), ] {
1406 let (vgs_a, vds_a) = (typ * vgs_n, typ * vds_n);
1407 let d = jfet_dsetup(&t, area, gmin, vgs_n, vgs_n - vds_n);
1408 let id = |x: f64, y: f64| {
1409 typ * jfet_ids(typ * x, typ * (x - y), t.vto, beta, t.lambda, 1.0, 0.0).0
1410 };
1411 let want = taylor2_fd(&id, vgs_a, vds_a, 1e-2);
1412 worst = worst.max(assert_taylor(
1413 df_seven(&d.cdr),
1414 want,
1415 1e-6,
1416 &format!("cdr njf={njf} at ({vgs_n},{vds_n})"),
1417 ));
1418 let diode = |v: f64| typ * gate_diode(typ * v, csat, t.vt, gmin).1;
1420 let pot = t.t_gate_pot;
1421 let charge = |cz: f64| {
1422 move |v: f64| {
1423 let v = typ * v;
1424 let q = if v < t.cor_dep_cap {
1425 2.0 * pot * cz * (1.0 - f64::sqrt(1.0 - v / pot))
1426 } else {
1427 let czf2 = cz / t.f2;
1428 let fcpb2 = t.cor_dep_cap * t.cor_dep_cap;
1429 cz * t.f1
1430 + czf2
1431 * (t.f3 * (v - t.cor_dep_cap) + (v * v - fcpb2) / (4.0 * pot))
1432 };
1433 typ * q
1434 }
1435 };
1436 let one = |f: &dyn Fn(f64) -> f64, v: f64, h: f64| {
1437 let c2 = 0.5 * (f(v + h) - 2.0 * f(v) + f(v - h)) / (h * h);
1438 let c3 = (f(v + 2.0 * h) - 2.0 * f(v + h) + 2.0 * f(v - h) - f(v - 2.0 * h))
1439 / (2.0 * h * h * h)
1440 / 6.0;
1441 (c2, c3)
1442 };
1443 let (vgd_a, inverse) = (vgs_a - vds_a, typ * vds_a < 0.0);
1444 let (gs2, gs3) = one(&diode, vgs_a, 2e-4);
1445 let (gd2, gd3) = one(&diode, vgd_a, 2e-4);
1446 let qgs = charge(t.t_cgs * area);
1447 let qgd = charge(t.t_cgd * area);
1448 let (cs2, cd2, cs3, cd3) = if inverse {
1455 (
1456 one(&qgs, vgd_a, 1e-3).0,
1457 one(&qgd, vgs_a, 1e-3).0,
1458 one(&qgd, vgs_a, 1e-3).1,
1459 one(&qgs, vgd_a, 1e-3).1,
1460 )
1461 } else {
1462 let (cs2, cs3) = one(&qgs, vgs_a, 1e-3);
1463 let (cd2, cd3) = one(&qgd, vgd_a, 1e-3);
1464 (cs2, cd2, cs3, cd3)
1465 };
1466 let (wgs2, wgd2) = if inverse { (gd2, gs2) } else { (gs2, gd2) };
1469 let close = |a: f64, b: f64, floor: f64, what: &str| {
1473 let s = a.abs().max(b.abs());
1474 assert!((a - b).abs() <= 2e-3 * s + floor, "{what}: {a} vs {b}");
1475 };
1476 close(d.ggs2, wgs2, 1e-15, "ggs2");
1477 close(d.ggd2, wgd2, 1e-15, "ggd2");
1478 close(d.ggs3, gs3, 1e-15, "ggs3");
1479 close(d.ggd3, gd3, 1e-15, "ggd3");
1480 let Fet3GateQ::Junction {
1481 capgs2,
1482 capgs3,
1483 capgd2,
1484 capgd3,
1485 } = d.q
1486 else {
1487 panic!("JFET charges are one-variable")
1488 };
1489 close(capgs2, cs2, 1e-17, "capgs2");
1490 close(capgd2, cd2, 1e-17, "capgd2");
1491 close(capgs3, cs3, 1e-17, "capgs3");
1492 close(capgd3, cd3, 1e-17, "capgd3");
1493 }
1494 }
1495 assert!(worst < 1e-6, "worst {worst}");
1496 eprintln!("JFET cdr worst FD relative error {worst:.2e}");
1497 }
1498}