Skip to main content

pedalkernel/compiler/components/
transistors.rs

1//! Transistor component structs: Npn, Pnp, NJfet, PJfet, Nmos, Pmos.
2
3use hashbrown::HashMap;
4
5use crate::compiler::classify::NonlinearKind;
6use crate::compiler::component::{
7    Component, ComponentEdge, EdgeKind, GraphRole, KMethodSpec, ModulationSink, ModulationSinkKind,
8    NeighborReq, NeighborRole, PinConfig, PinDirection, ResolveContext, SignalTerminals,
9    StampResult, K_AXIS_INCIDENT_CONTROL_2D,
10};
11use crate::compiler::dyn_node::DynNode;
12use crate::compiler::graph::NodeId;
13use crate::dsl::MosfetType;
14use crate::elements::{JfetModel, JfetVariableResistor};
15use crate::tree::MnaSystem;
16
17use super::impl_component_dyn;
18
19/// BJT terminal requirements: a Load on AN output terminal (collector-load
20/// common-emitter OR an emitter-follower's emitter load), plus optional
21/// base/emitter bias references and an optional series coupling on the base.
22fn bjt_terminal_requirements() -> Vec<(&'static str, Vec<NeighborReq>)> {
23    vec![
24        ("collector", vec![NeighborReq::required(NeighborRole::Load)]),
25        (
26            "emitter",
27            vec![
28                NeighborReq::required(NeighborRole::Load),
29                NeighborReq::optional(NeighborRole::Ref),
30            ],
31        ),
32        (
33            "base",
34            vec![
35                NeighborReq::optional(NeighborRole::Ref),
36                NeighborReq::optional(NeighborRole::Signal),
37            ],
38        ),
39    ]
40}
41
42/// FET (JFET / MOSFET) terminal requirements: a Load on AN output terminal
43/// (drain-load common-source OR a source-follower's source load), plus optional
44/// gate/source references and an optional series coupling on the gate.
45fn fet_terminal_requirements() -> Vec<(&'static str, Vec<NeighborReq>)> {
46    vec![
47        ("drain", vec![NeighborReq::required(NeighborRole::Load)]),
48        (
49            "source",
50            vec![
51                NeighborReq::required(NeighborRole::Load),
52                NeighborReq::optional(NeighborRole::Ref),
53            ],
54        ),
55        (
56            "gate",
57            vec![
58                NeighborReq::optional(NeighborRole::Ref),
59                NeighborReq::optional(NeighborRole::Signal),
60            ],
61        ),
62    ]
63}
64
65// ═══════════════════════════════════════════════════════════════════════════
66// Npn
67// ═══════════════════════════════════════════════════════════════════════════
68
69#[derive(Debug, Clone, PartialEq)]
70pub struct Npn {
71    pub model: String,
72}
73
74impl Component for Npn {
75    impl_component_dyn!();
76
77    fn type_tag(&self) -> &'static str {
78        "NPN transistor"
79    }
80
81    fn ports(&self) -> Vec<(&'static str, &'static str)> {
82        vec![("base", "emitter"), ("collector", "emitter")]
83    }
84
85    fn signal_terminals(&self) -> SignalTerminals {
86        SignalTerminals::Amplifier {
87            input: "base",
88            output: "collector",
89            control: None,
90        }
91    }
92
93    fn is_passive(&self) -> bool {
94        false
95    }
96
97    fn is_nonlinear(&self) -> bool {
98        true
99    }
100
101    fn pin_config(&self) -> PinConfig {
102        PinConfig {
103            valid_pins: &["base", "collector", "emitter"],
104            aliases: &[],
105        }
106    }
107
108    fn graph_role(&self) -> GraphRole {
109        GraphRole::ActiveEdge {
110            pin_a: "collector",
111            pin_b: "emitter",
112        }
113    }
114
115    fn stamp_mna(
116        &self,
117        _comp_id: &str,
118        _n1: Option<usize>,
119        _n2: Option<usize>,
120        _mna: &mut MnaSystem,
121        _sample_rate: f64,
122    ) -> StampResult {
123        StampResult::Skip
124    }
125
126    fn edges(&self) -> Vec<ComponentEdge> {
127        vec![ComponentEdge {
128            pin_a: "collector",
129            pin_b: "emitter",
130            kind: EdgeKind::Nonlinear,
131            port_group: None,
132        }]
133    }
134
135    fn classify_nonlinear(
136        &self,
137        comp_id: &str,
138        node_a: NodeId,
139        node_b: NodeId,
140        _gnd_node: NodeId,
141        node_names: &HashMap<String, NodeId>,
142    ) -> Option<(NonlinearKind, Vec<NodeId>)> {
143        let base_node = node_names
144            .get(&format!("{}.base", comp_id))
145            .copied()
146            .unwrap_or(node_a);
147        let collector_node = node_names
148            .get(&format!("{}.collector", comp_id))
149            .copied()
150            .unwrap_or(node_a);
151        let emitter_node = node_names
152            .get(&format!("{}.emitter", comp_id))
153            .copied()
154            .unwrap_or(node_b);
155        Some((
156            NonlinearKind::BjtNpn {
157                model_name: self.model.clone(),
158                base_node,
159                collector_node,
160                emitter_node,
161            },
162            vec![node_a, node_b],
163        ))
164    }
165
166    fn footprint_ref(&self) -> (&'static str, &'static str) {
167        ("Device:Q_NPN_BCE", "Q")
168    }
169
170    fn symbol_name(&self) -> &'static str {
171        "npn_bjt"
172    }
173    fn layout_class(&self) -> &'static str {
174        "npn"
175    }
176
177    fn signal_adjacencies(&self) -> Vec<(&'static str, &'static str)> {
178        vec![
179            ("base", "collector"),
180            ("collector", "emitter"),
181            ("base", "emitter"),
182        ]
183    }
184
185    fn pin_direction(&self, pin: &str) -> PinDirection {
186        match pin {
187            "base" => PinDirection::Input,
188            "collector" => PinDirection::Output,
189            "emitter" => PinDirection::Down,
190            _ => PinDirection::Bidirectional,
191        }
192    }
193
194    fn is_bjt(&self) -> bool {
195        true
196    }
197    fn is_gain_device(&self) -> bool {
198        true
199    }
200    fn k_method_spec(&self) -> Option<KMethodSpec> {
201        Some(KMethodSpec {
202            axes: K_AXIS_INCIDENT_CONTROL_2D,
203            reason: "BJT: 2D memoryless I-V (Vbe, Vce)",
204        })
205    }
206    fn model_name(&self) -> Option<&str> {
207        Some(&self.model)
208    }
209    fn port_semantic(
210        &self,
211        _pin_a: &str,
212        _pin_b: &str,
213    ) -> crate::compiler::component::PortSemantic {
214        // All BJT ports (B-E, C-E, B-C) are nonlinear junctions.
215        crate::compiler::component::PortSemantic::Nonlinear
216    }
217    fn terminal_requirements(&self) -> Vec<(&'static str, Vec<NeighborReq>)> {
218        bjt_terminal_requirements()
219    }
220}
221
222// ═══════════════════════════════════════════════════════════════════════════
223// Pnp
224// ═══════════════════════════════════════════════════════════════════════════
225
226#[derive(Debug, Clone, PartialEq)]
227pub struct Pnp {
228    pub model: String,
229}
230
231impl Component for Pnp {
232    impl_component_dyn!();
233
234    fn type_tag(&self) -> &'static str {
235        "PNP transistor"
236    }
237
238    fn ports(&self) -> Vec<(&'static str, &'static str)> {
239        vec![("base", "emitter"), ("collector", "emitter")]
240    }
241
242    fn signal_terminals(&self) -> SignalTerminals {
243        SignalTerminals::Amplifier {
244            input: "base",
245            output: "collector",
246            control: None,
247        }
248    }
249
250    fn is_passive(&self) -> bool {
251        false
252    }
253
254    fn is_nonlinear(&self) -> bool {
255        true
256    }
257
258    fn pin_config(&self) -> PinConfig {
259        PinConfig {
260            valid_pins: &["base", "collector", "emitter"],
261            aliases: &[],
262        }
263    }
264
265    fn graph_role(&self) -> GraphRole {
266        GraphRole::ActiveEdge {
267            pin_a: "collector",
268            pin_b: "emitter",
269        }
270    }
271
272    fn stamp_mna(
273        &self,
274        _comp_id: &str,
275        _n1: Option<usize>,
276        _n2: Option<usize>,
277        _mna: &mut MnaSystem,
278        _sample_rate: f64,
279    ) -> StampResult {
280        StampResult::Skip
281    }
282
283    fn edges(&self) -> Vec<ComponentEdge> {
284        vec![ComponentEdge {
285            pin_a: "collector",
286            pin_b: "emitter",
287            kind: EdgeKind::Nonlinear,
288            port_group: None,
289        }]
290    }
291
292    fn classify_nonlinear(
293        &self,
294        comp_id: &str,
295        node_a: NodeId,
296        node_b: NodeId,
297        _gnd_node: NodeId,
298        node_names: &HashMap<String, NodeId>,
299    ) -> Option<(NonlinearKind, Vec<NodeId>)> {
300        let base_node = node_names
301            .get(&format!("{}.base", comp_id))
302            .copied()
303            .unwrap_or(node_a);
304        let collector_node = node_names
305            .get(&format!("{}.collector", comp_id))
306            .copied()
307            .unwrap_or(node_a);
308        let emitter_node = node_names
309            .get(&format!("{}.emitter", comp_id))
310            .copied()
311            .unwrap_or(node_b);
312        Some((
313            NonlinearKind::BjtPnp {
314                model_name: self.model.clone(),
315                base_node,
316                collector_node,
317                emitter_node,
318            },
319            vec![node_a, node_b],
320        ))
321    }
322
323    fn footprint_ref(&self) -> (&'static str, &'static str) {
324        ("Device:Q_PNP_BCE", "Q")
325    }
326
327    fn symbol_name(&self) -> &'static str {
328        "pnp_bjt"
329    }
330    fn layout_class(&self) -> &'static str {
331        "pnp"
332    }
333
334    fn signal_adjacencies(&self) -> Vec<(&'static str, &'static str)> {
335        vec![
336            ("base", "collector"),
337            ("collector", "emitter"),
338            ("base", "emitter"),
339        ]
340    }
341
342    fn pin_direction(&self, pin: &str) -> PinDirection {
343        match pin {
344            "base" => PinDirection::Input,
345            "collector" => PinDirection::Output,
346            "emitter" => PinDirection::Down,
347            _ => PinDirection::Bidirectional,
348        }
349    }
350
351    fn is_bjt(&self) -> bool {
352        true
353    }
354    fn is_gain_device(&self) -> bool {
355        true
356    }
357    fn k_method_spec(&self) -> Option<KMethodSpec> {
358        Some(KMethodSpec {
359            axes: K_AXIS_INCIDENT_CONTROL_2D,
360            reason: "BJT: 2D memoryless I-V (Vbe, Vce)",
361        })
362    }
363    fn model_name(&self) -> Option<&str> {
364        Some(&self.model)
365    }
366    fn port_semantic(
367        &self,
368        _pin_a: &str,
369        _pin_b: &str,
370    ) -> crate::compiler::component::PortSemantic {
371        crate::compiler::component::PortSemantic::Nonlinear
372    }
373    fn terminal_requirements(&self) -> Vec<(&'static str, Vec<NeighborReq>)> {
374        bjt_terminal_requirements()
375    }
376}
377
378// ═══════════════════════════════════════════════════════════════════════════
379// NJfet
380// ═══════════════════════════════════════════════════════════════════════════
381
382#[derive(Debug, Clone, PartialEq)]
383pub struct NJfet {
384    pub model: String,
385}
386
387impl Component for NJfet {
388    impl_component_dyn!();
389
390    fn type_tag(&self) -> &'static str {
391        "N-channel JFET"
392    }
393
394    fn ports(&self) -> Vec<(&'static str, &'static str)> {
395        vec![("drain", "source")] // gate is voltage-sense
396    }
397
398    fn signal_terminals(&self) -> SignalTerminals {
399        SignalTerminals::Amplifier {
400            input: "gate",
401            output: "drain",
402            control: None,
403        }
404    }
405
406    fn is_passive(&self) -> bool {
407        false
408    }
409
410    fn is_nonlinear(&self) -> bool {
411        true
412    }
413
414    fn pin_config(&self) -> PinConfig {
415        PinConfig {
416            valid_pins: &["gate", "drain", "source", "vgs"],
417            aliases: &[],
418        }
419    }
420
421    fn modulation_pins(&self) -> &'static [&'static str] {
422        &["vgs", "gate"]
423    }
424
425    fn graph_role(&self) -> GraphRole {
426        GraphRole::Edge {
427            pin_a: "drain",
428            pin_b: "source",
429        }
430    }
431
432    fn stamp_mna(
433        &self,
434        _comp_id: &str,
435        _n1: Option<usize>,
436        _n2: Option<usize>,
437        _mna: &mut MnaSystem,
438        _sample_rate: f64,
439    ) -> StampResult {
440        StampResult::Skip
441    }
442
443    fn make_leaf(&self, comp_id: &str, _sample_rate: f64) -> Option<DynNode> {
444        let model = crate::model_lookup::jfet_model_by_name(&self.model);
445        Some(DynNode::JfetVrNode(
446            comp_id.to_string(),
447            JfetVariableResistor::new(model),
448        ))
449    }
450
451    fn is_variable(&self) -> bool {
452        true
453    }
454
455    fn edges(&self) -> Vec<ComponentEdge> {
456        vec![ComponentEdge {
457            pin_a: "drain",
458            pin_b: "source",
459            kind: EdgeKind::Nonlinear,
460            port_group: None,
461        }]
462    }
463
464    fn resolve_edges(&self, ctx: &ResolveContext) -> Option<Vec<ComponentEdge>> {
465        if ctx.control_pin_is_modulated {
466            Some(vec![ComponentEdge {
467                pin_a: "drain",
468                pin_b: "source",
469                kind: EdgeKind::Linear,
470                port_group: None,
471            }])
472        } else {
473            None
474        }
475    }
476
477    fn classify_nonlinear(
478        &self,
479        _comp_id: &str,
480        _node_a: NodeId,
481        node_b: NodeId,
482        gnd_node: NodeId,
483        _node_names: &HashMap<String, NodeId>,
484    ) -> Option<(NonlinearKind, Vec<NodeId>)> {
485        let jn = if node_b == gnd_node { _node_a } else { node_b };
486        Some((
487            NonlinearKind::Jfet {
488                model_name: self.model.clone(),
489                is_n_channel: true,
490            },
491            vec![jn],
492        ))
493    }
494
495    fn modulation_sink(&self, pin: &str) -> Option<ModulationSink> {
496        match pin {
497            "vgs" | "gate" => Some(ModulationSink {
498                target_kind: ModulationSinkKind::JfetVgs,
499                bias: -0.45,
500                range: 0.25,
501            }),
502            _ => None,
503        }
504    }
505
506    fn footprint_ref(&self) -> (&'static str, &'static str) {
507        ("Device:Q_NJFET_DGS", "J")
508    }
509
510    fn symbol_name(&self) -> &'static str {
511        "njfet"
512    }
513    fn layout_class(&self) -> &'static str {
514        "njfet"
515    }
516
517    fn pin_direction(&self, pin: &str) -> PinDirection {
518        match pin {
519            "gate" => PinDirection::Input,
520            "drain" => PinDirection::Output,
521            "source" => PinDirection::Down,
522            _ => PinDirection::Bidirectional,
523        }
524    }
525
526    fn is_jfet(&self) -> bool {
527        true
528    }
529    fn is_gain_device(&self) -> bool {
530        true
531    }
532    fn k_method_spec(&self) -> Option<KMethodSpec> {
533        Some(KMethodSpec {
534            axes: K_AXIS_INCIDENT_CONTROL_2D,
535            reason: "JFET: 2D memoryless I-V (Vgs, Vds)",
536        })
537    }
538    fn model_name(&self) -> Option<&str> {
539        Some(&self.model)
540    }
541    fn port_semantic(&self, pin_a: &str, pin_b: &str) -> crate::compiler::component::PortSemantic {
542        let pins = [pin_a, pin_b];
543        if pins.contains(&"gate") {
544            // Gate junction is nonlinear (diode)
545            crate::compiler::component::PortSemantic::Nonlinear
546        } else {
547            // Drain-source: controlled conductance (Vgs modulates Rds)
548            crate::compiler::component::PortSemantic::ControlledConductance
549        }
550    }
551    fn terminal_requirements(&self) -> Vec<(&'static str, Vec<NeighborReq>)> {
552        fet_terminal_requirements()
553    }
554}
555
556// ═══════════════════════════════════════════════════════════════════════════
557// PJfet
558// ═══════════════════════════════════════════════════════════════════════════
559
560#[derive(Debug, Clone, PartialEq)]
561pub struct PJfet {
562    pub model: String,
563}
564
565impl Component for PJfet {
566    impl_component_dyn!();
567
568    fn type_tag(&self) -> &'static str {
569        "P-channel JFET"
570    }
571
572    fn ports(&self) -> Vec<(&'static str, &'static str)> {
573        vec![("drain", "source")]
574    }
575
576    fn signal_terminals(&self) -> SignalTerminals {
577        SignalTerminals::Amplifier {
578            input: "gate",
579            output: "drain",
580            control: None,
581        }
582    }
583
584    fn is_passive(&self) -> bool {
585        false
586    }
587
588    fn is_nonlinear(&self) -> bool {
589        true
590    }
591
592    fn pin_config(&self) -> PinConfig {
593        PinConfig {
594            valid_pins: &["gate", "drain", "source", "vgs"],
595            aliases: &[],
596        }
597    }
598
599    fn modulation_pins(&self) -> &'static [&'static str] {
600        &["vgs", "gate"]
601    }
602
603    fn graph_role(&self) -> GraphRole {
604        GraphRole::Edge {
605            pin_a: "drain",
606            pin_b: "source",
607        }
608    }
609
610    fn stamp_mna(
611        &self,
612        _comp_id: &str,
613        _n1: Option<usize>,
614        _n2: Option<usize>,
615        _mna: &mut MnaSystem,
616        _sample_rate: f64,
617    ) -> StampResult {
618        StampResult::Skip
619    }
620
621    fn make_leaf(&self, comp_id: &str, _sample_rate: f64) -> Option<DynNode> {
622        let model = crate::model_lookup::jfet_model_by_name(&self.model);
623        Some(DynNode::JfetVrNode(
624            comp_id.to_string(),
625            JfetVariableResistor::new(model),
626        ))
627    }
628
629    fn is_variable(&self) -> bool {
630        true
631    }
632
633    fn edges(&self) -> Vec<ComponentEdge> {
634        vec![ComponentEdge {
635            pin_a: "drain",
636            pin_b: "source",
637            kind: EdgeKind::Nonlinear,
638            port_group: None,
639        }]
640    }
641
642    fn resolve_edges(&self, ctx: &ResolveContext) -> Option<Vec<ComponentEdge>> {
643        if ctx.control_pin_is_modulated {
644            Some(vec![ComponentEdge {
645                pin_a: "drain",
646                pin_b: "source",
647                kind: EdgeKind::Linear,
648                port_group: None,
649            }])
650        } else {
651            None
652        }
653    }
654
655    fn classify_nonlinear(
656        &self,
657        _comp_id: &str,
658        _node_a: NodeId,
659        node_b: NodeId,
660        gnd_node: NodeId,
661        _node_names: &HashMap<String, NodeId>,
662    ) -> Option<(NonlinearKind, Vec<NodeId>)> {
663        let jn = if node_b == gnd_node { _node_a } else { node_b };
664        Some((
665            NonlinearKind::Jfet {
666                model_name: self.model.clone(),
667                is_n_channel: false,
668            },
669            vec![jn],
670        ))
671    }
672
673    fn modulation_sink(&self, pin: &str) -> Option<ModulationSink> {
674        match pin {
675            "vgs" | "gate" => Some(ModulationSink {
676                target_kind: ModulationSinkKind::JfetVgs,
677                bias: -0.45,
678                range: 0.25,
679            }),
680            _ => None,
681        }
682    }
683
684    fn footprint_ref(&self) -> (&'static str, &'static str) {
685        ("Device:Q_PJFET_DGS", "J")
686    }
687
688    fn symbol_name(&self) -> &'static str {
689        "pjfet"
690    }
691    fn layout_class(&self) -> &'static str {
692        "pjfet"
693    }
694
695    fn pin_direction(&self, pin: &str) -> PinDirection {
696        match pin {
697            "gate" => PinDirection::Input,
698            "drain" => PinDirection::Output,
699            "source" => PinDirection::Down,
700            _ => PinDirection::Bidirectional,
701        }
702    }
703
704    fn is_jfet(&self) -> bool {
705        true
706    }
707    fn is_gain_device(&self) -> bool {
708        true
709    }
710    fn k_method_spec(&self) -> Option<KMethodSpec> {
711        Some(KMethodSpec {
712            axes: K_AXIS_INCIDENT_CONTROL_2D,
713            reason: "JFET: 2D memoryless I-V (Vgs, Vds)",
714        })
715    }
716    fn model_name(&self) -> Option<&str> {
717        Some(&self.model)
718    }
719    fn port_semantic(&self, pin_a: &str, pin_b: &str) -> crate::compiler::component::PortSemantic {
720        let pins = [pin_a, pin_b];
721        if pins.contains(&"gate") {
722            crate::compiler::component::PortSemantic::Nonlinear
723        } else {
724            crate::compiler::component::PortSemantic::ControlledConductance
725        }
726    }
727    fn terminal_requirements(&self) -> Vec<(&'static str, Vec<NeighborReq>)> {
728        fet_terminal_requirements()
729    }
730}
731
732// ═══════════════════════════════════════════════════════════════════════════
733// Nmos
734// ═══════════════════════════════════════════════════════════════════════════
735
736#[derive(Debug, Clone, PartialEq)]
737pub struct Nmos {
738    pub mosfet_type: MosfetType,
739}
740
741impl Component for Nmos {
742    impl_component_dyn!();
743
744    fn type_tag(&self) -> &'static str {
745        "N-channel MOSFET"
746    }
747
748    fn ports(&self) -> Vec<(&'static str, &'static str)> {
749        vec![("drain", "source")]
750    }
751
752    fn signal_terminals(&self) -> SignalTerminals {
753        SignalTerminals::Amplifier {
754            input: "gate",
755            output: "drain",
756            control: None,
757        }
758    }
759
760    fn is_passive(&self) -> bool {
761        false
762    }
763
764    fn is_nonlinear(&self) -> bool {
765        true
766    }
767
768    fn pin_config(&self) -> PinConfig {
769        PinConfig {
770            valid_pins: &["gate", "drain", "source", "vgs"],
771            aliases: &[],
772        }
773    }
774
775    fn modulation_pins(&self) -> &'static [&'static str] {
776        &["vgs", "gate"]
777    }
778
779    fn graph_role(&self) -> GraphRole {
780        GraphRole::Edge {
781            pin_a: "drain",
782            pin_b: "source",
783        }
784    }
785
786    fn stamp_mna(
787        &self,
788        _comp_id: &str,
789        _n1: Option<usize>,
790        _n2: Option<usize>,
791        _mna: &mut MnaSystem,
792        _sample_rate: f64,
793    ) -> StampResult {
794        StampResult::Skip
795    }
796
797    fn edges(&self) -> Vec<ComponentEdge> {
798        vec![ComponentEdge {
799            pin_a: "drain",
800            pin_b: "source",
801            kind: EdgeKind::Nonlinear,
802            port_group: None,
803        }]
804    }
805
806    fn classify_nonlinear(
807        &self,
808        _comp_id: &str,
809        _node_a: NodeId,
810        node_b: NodeId,
811        gnd_node: NodeId,
812        _node_names: &HashMap<String, NodeId>,
813    ) -> Option<(NonlinearKind, Vec<NodeId>)> {
814        let jn = if node_b == gnd_node { _node_a } else { node_b };
815        Some((
816            NonlinearKind::Mosfet {
817                mosfet_type: self.mosfet_type,
818                is_n_channel: true,
819            },
820            vec![jn],
821        ))
822    }
823
824    fn modulation_sink(&self, pin: &str) -> Option<ModulationSink> {
825        match pin {
826            "vgs" | "gate" => Some(ModulationSink {
827                target_kind: ModulationSinkKind::MosfetVgs,
828                bias: 3.0,
829                range: 2.0,
830            }),
831            _ => None,
832        }
833    }
834
835    fn footprint_ref(&self) -> (&'static str, &'static str) {
836        ("Device:Q_NMOS_DGS", "Q")
837    }
838
839    fn symbol_name(&self) -> &'static str {
840        "nmos"
841    }
842    fn layout_class(&self) -> &'static str {
843        "nmos"
844    }
845
846    fn pin_direction(&self, pin: &str) -> PinDirection {
847        match pin {
848            "gate" => PinDirection::Input,
849            "drain" => PinDirection::Output,
850            "source" => PinDirection::Down,
851            _ => PinDirection::Bidirectional,
852        }
853    }
854
855    fn is_mosfet(&self) -> bool {
856        true
857    }
858    fn is_gain_device(&self) -> bool {
859        true
860    }
861    fn k_method_spec(&self) -> Option<KMethodSpec> {
862        Some(KMethodSpec {
863            axes: K_AXIS_INCIDENT_CONTROL_2D,
864            reason: "MOSFET: 2D memoryless I-V (Vgs, Vds)",
865        })
866    }
867    fn port_semantic(&self, pin_a: &str, pin_b: &str) -> crate::compiler::component::PortSemantic {
868        let pins = [pin_a, pin_b];
869        if pins.contains(&"gate") {
870            crate::compiler::component::PortSemantic::Nonlinear
871        } else {
872            crate::compiler::component::PortSemantic::ControlledConductance
873        }
874    }
875    fn terminal_requirements(&self) -> Vec<(&'static str, Vec<NeighborReq>)> {
876        fet_terminal_requirements()
877    }
878}
879
880// ═══════════════════════════════════════════════════════════════════════════
881// Pmos
882// ═══════════════════════════════════════════════════════════════════════════
883
884#[derive(Debug, Clone, PartialEq)]
885pub struct Pmos {
886    pub mosfet_type: MosfetType,
887}
888
889impl Component for Pmos {
890    impl_component_dyn!();
891
892    fn type_tag(&self) -> &'static str {
893        "P-channel MOSFET"
894    }
895
896    fn ports(&self) -> Vec<(&'static str, &'static str)> {
897        vec![("drain", "source")]
898    }
899
900    fn signal_terminals(&self) -> SignalTerminals {
901        SignalTerminals::Amplifier {
902            input: "gate",
903            output: "drain",
904            control: None,
905        }
906    }
907
908    fn is_passive(&self) -> bool {
909        false
910    }
911
912    fn is_nonlinear(&self) -> bool {
913        true
914    }
915
916    fn pin_config(&self) -> PinConfig {
917        PinConfig {
918            valid_pins: &["gate", "drain", "source", "vgs"],
919            aliases: &[],
920        }
921    }
922
923    fn modulation_pins(&self) -> &'static [&'static str] {
924        &["vgs", "gate"]
925    }
926
927    fn graph_role(&self) -> GraphRole {
928        GraphRole::Edge {
929            pin_a: "drain",
930            pin_b: "source",
931        }
932    }
933
934    fn stamp_mna(
935        &self,
936        _comp_id: &str,
937        _n1: Option<usize>,
938        _n2: Option<usize>,
939        _mna: &mut MnaSystem,
940        _sample_rate: f64,
941    ) -> StampResult {
942        StampResult::Skip
943    }
944
945    fn edges(&self) -> Vec<ComponentEdge> {
946        vec![ComponentEdge {
947            pin_a: "drain",
948            pin_b: "source",
949            kind: EdgeKind::Nonlinear,
950            port_group: None,
951        }]
952    }
953
954    fn classify_nonlinear(
955        &self,
956        _comp_id: &str,
957        _node_a: NodeId,
958        node_b: NodeId,
959        gnd_node: NodeId,
960        _node_names: &HashMap<String, NodeId>,
961    ) -> Option<(NonlinearKind, Vec<NodeId>)> {
962        let jn = if node_b == gnd_node { _node_a } else { node_b };
963        Some((
964            NonlinearKind::Mosfet {
965                mosfet_type: self.mosfet_type,
966                is_n_channel: false,
967            },
968            vec![jn],
969        ))
970    }
971
972    fn modulation_sink(&self, pin: &str) -> Option<ModulationSink> {
973        match pin {
974            "vgs" | "gate" => Some(ModulationSink {
975                target_kind: ModulationSinkKind::MosfetVgs,
976                bias: 3.0,
977                range: 2.0,
978            }),
979            _ => None,
980        }
981    }
982
983    fn footprint_ref(&self) -> (&'static str, &'static str) {
984        ("Device:Q_PMOS_DGS", "Q")
985    }
986
987    fn symbol_name(&self) -> &'static str {
988        "pmos"
989    }
990    fn layout_class(&self) -> &'static str {
991        "pmos"
992    }
993
994    fn pin_direction(&self, pin: &str) -> PinDirection {
995        match pin {
996            "gate" => PinDirection::Input,
997            "drain" => PinDirection::Output,
998            "source" => PinDirection::Down,
999            _ => PinDirection::Bidirectional,
1000        }
1001    }
1002
1003    fn is_mosfet(&self) -> bool {
1004        true
1005    }
1006    fn is_gain_device(&self) -> bool {
1007        true
1008    }
1009    fn k_method_spec(&self) -> Option<KMethodSpec> {
1010        Some(KMethodSpec {
1011            axes: K_AXIS_INCIDENT_CONTROL_2D,
1012            reason: "MOSFET: 2D memoryless I-V (Vgs, Vds)",
1013        })
1014    }
1015    fn port_semantic(&self, pin_a: &str, pin_b: &str) -> crate::compiler::component::PortSemantic {
1016        let pins = [pin_a, pin_b];
1017        if pins.contains(&"gate") {
1018            crate::compiler::component::PortSemantic::Nonlinear
1019        } else {
1020            crate::compiler::component::PortSemantic::ControlledConductance
1021        }
1022    }
1023    fn terminal_requirements(&self) -> Vec<(&'static str, Vec<NeighborReq>)> {
1024        fet_terminal_requirements()
1025    }
1026}