1use 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
19fn 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
42fn 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#[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 crate::compiler::component::PortSemantic::Nonlinear
216 }
217 fn terminal_requirements(&self) -> Vec<(&'static str, Vec<NeighborReq>)> {
218 bjt_terminal_requirements()
219 }
220}
221
222#[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#[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")] }
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 crate::compiler::component::PortSemantic::Nonlinear
546 } else {
547 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#[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#[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#[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}