use std::collections::HashMap;
use crate::diode::{DiodeModel, DiodeTemp};
use crate::error::Error;
use crate::matrix::Var;
use crate::options::Options;
use crate::trnoise::NoiseWave;
#[derive(Clone, Debug)]
pub struct Resistor {
pub name: String,
pub nodes: [String; 2],
pub value: f64,
}
#[derive(Clone, Copy, Debug, Default)]
pub(crate) struct ResTemp {
pub tc1: f64,
pub tc2: f64,
pub tnom: Option<f64>,
pub temp: Option<f64>,
pub dtemp: f64,
}
#[derive(Clone, Debug)]
pub struct Capacitor {
pub name: String,
pub nodes: [String; 2],
pub value: f64,
pub ic: Option<f64>,
}
#[derive(Clone, Debug)]
pub struct Inductor {
pub name: String,
pub nodes: [String; 2],
pub value: f64,
pub ic: Option<f64>,
}
#[derive(Clone, Debug)]
pub struct Mutual {
pub name: String,
pub l1: String,
pub l2: String,
pub k: f64,
}
#[derive(Clone, Debug)]
pub struct Vsource {
pub name: String,
pub nodes: [String; 2],
pub dc: Option<f64>,
pub wave: Option<crate::srcfn::Wave>,
pub ac: Option<(f64, f64)>,
pub distof1: Option<(f64, f64)>,
pub distof2: Option<(f64, f64)>,
pub port: Option<RfPort>,
}
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct RfPort {
pub num: u32,
pub z0: f64,
}
impl RfPort {
pub(crate) fn is_port(&self) -> bool {
self.num > 0 && self.z0 > 0.0
}
}
#[derive(Clone, Debug)]
pub struct Isource {
pub name: String,
pub nodes: [String; 2],
pub dc: Option<f64>,
pub wave: Option<crate::srcfn::Wave>,
pub ac: Option<(f64, f64)>,
pub distof1: Option<(f64, f64)>,
pub distof2: Option<(f64, f64)>,
}
#[derive(Clone, Debug)]
pub struct Vcvs {
pub name: String,
pub nodes: [String; 4],
pub gain: f64,
}
#[derive(Clone, Debug)]
pub struct Vccs {
pub name: String,
pub nodes: [String; 4],
pub gain: f64,
}
#[derive(Clone, Debug)]
pub struct Cccs {
pub name: String,
pub nodes: [String; 2],
pub ctrl: String,
pub gain: f64,
}
#[derive(Clone, Debug)]
pub struct Ccvs {
pub name: String,
pub nodes: [String; 2],
pub ctrl: String,
pub gain: f64,
}
#[derive(Clone, Debug)]
pub struct Asrc {
pub name: String,
pub nodes: [String; 2],
pub kind: dune_netlist::ir::SourceKind,
pub expr: dune_netlist::ir::Expr,
}
#[derive(Clone, Debug)]
pub struct Bjt {
pub name: String,
pub nodes: [String; 4],
pub model: crate::bjt::BjtModel,
pub area: f64,
pub off: bool,
pub ic: (f64, f64),
pub temp: Option<f64>,
}
#[derive(Clone, Debug)]
pub struct Mos1 {
pub name: String,
pub nodes: [String; 4],
pub model: crate::mos1::Mos1Model,
pub l: Option<f64>,
pub w: Option<f64>,
pub ad: Option<f64>,
pub as_: Option<f64>,
pub pd: f64,
pub ps: f64,
pub nrd: f64,
pub nrs: f64,
pub off: bool,
pub ic: (f64, f64, f64),
pub temp: Option<f64>,
}
#[derive(Clone, Debug)]
pub struct Mos2 {
pub name: String,
pub nodes: [String; 4],
pub model: crate::mos2::Mos2Model,
pub l: Option<f64>,
pub w: Option<f64>,
pub ad: Option<f64>,
pub as_: Option<f64>,
pub pd: f64,
pub ps: f64,
pub nrd: f64,
pub nrs: f64,
pub off: bool,
pub ic: (f64, f64, f64),
pub temp: Option<f64>,
}
#[derive(Clone, Debug)]
pub struct Mos3 {
pub name: String,
pub nodes: [String; 4],
pub model: crate::mos3::Mos3Model,
pub l: Option<f64>,
pub w: Option<f64>,
pub ad: Option<f64>,
pub as_: Option<f64>,
pub pd: f64,
pub ps: f64,
pub nrd: f64,
pub nrs: f64,
pub off: bool,
pub ic: (f64, f64, f64),
pub temp: Option<f64>,
}
#[derive(Clone, Debug)]
pub struct Mos6 {
pub name: String,
pub nodes: [String; 4],
pub model: crate::mos6::Mos6Model,
pub l: Option<f64>,
pub w: Option<f64>,
pub ad: Option<f64>,
pub as_: Option<f64>,
pub pd: f64,
pub ps: f64,
pub nrd: f64,
pub nrs: f64,
pub off: bool,
pub ic: (f64, f64, f64),
pub temp: Option<f64>,
}
#[derive(Clone, Debug)]
pub struct Bsim1 {
pub name: String,
pub nodes: [String; 4],
pub model: crate::bsim1::Bsim1Model,
pub l: Option<f64>,
pub w: Option<f64>,
pub ad: Option<f64>,
pub as_: Option<f64>,
pub pd: f64,
pub ps: f64,
pub nrd: f64,
pub nrs: f64,
pub off: bool,
pub ic: (f64, f64, f64),
}
#[derive(Clone, Debug)]
pub struct Bsim2 {
pub name: String,
pub nodes: [String; 4],
pub model: crate::bsim2::Bsim2Model,
pub l: Option<f64>,
pub w: Option<f64>,
pub ad: Option<f64>,
pub as_: Option<f64>,
pub pd: f64,
pub ps: f64,
pub nrd: f64,
pub nrs: f64,
pub off: bool,
pub ic: (f64, f64, f64),
}
#[derive(Clone, Debug)]
pub struct Bsim3 {
pub name: String,
pub nodes: [String; 4],
pub model: crate::bsim3::Bsim3Model,
pub l: Option<f64>,
pub w: Option<f64>,
pub ad: f64,
pub as_: f64,
pub pd: f64,
pub ps: f64,
pub nrd: f64,
pub nrs: f64,
pub m: f64,
pub delvto: f64,
pub mulu0: f64,
pub off: bool,
pub ic: (f64, f64, f64),
}
#[derive(Clone, Debug)]
pub struct Diode {
pub name: String,
pub nodes: [String; 2],
pub model: DiodeModel,
pub area: f64,
pub off: bool,
pub temp: Option<f64>,
pub ic: Option<f64>,
}
#[derive(Clone, Debug)]
pub struct Jfet {
pub name: String,
pub nodes: [String; 3],
pub model: crate::jfet::JfetModel,
pub area: f64,
pub off: bool,
pub ic: (f64, f64),
pub temp: Option<f64>,
}
#[derive(Clone, Debug)]
pub struct Mes {
pub name: String,
pub nodes: [String; 3],
pub model: crate::mes::MesModel,
pub area: f64,
pub off: bool,
pub ic: (f64, f64),
}
#[derive(Clone, Debug)]
pub struct Ltra {
pub name: String,
pub nodes: [String; 4],
pub model: crate::ltra::LtraModel,
}
#[derive(Clone, Debug)]
pub struct Tra {
pub name: String,
pub nodes: [String; 4],
pub z0: f64,
pub td: Option<f64>,
pub f: Option<f64>,
pub nl: Option<f64>,
pub ic: [f64; 4],
}
#[derive(Clone, Debug)]
pub struct SwitchV {
pub name: String,
pub nodes: [String; 4],
pub model: crate::switch::SwModel,
pub on: bool,
}
#[derive(Clone, Debug)]
pub struct SwitchI {
pub name: String,
pub nodes: [String; 2],
pub ctrl: String,
pub model: crate::switch::CswModel,
pub on: bool,
}
#[derive(Clone, Debug)]
pub enum Spec {
Bjt(Box<Bjt>),
Mos1(Box<Mos1>),
Mos2(Box<Mos2>),
Mos3(Box<Mos3>),
Mos6(Box<Mos6>),
Bsim1(Box<Bsim1>),
Bsim2(Box<Bsim2>),
Bsim3(Box<Bsim3>),
Resistor(Resistor),
Capacitor(Capacitor),
Inductor(Inductor),
Mutual(Mutual),
Vsource(Vsource),
Isource(Isource),
Vcvs(Vcvs),
Vccs(Vccs),
Cccs(Cccs),
Ccvs(Ccvs),
Diode(Diode),
Jfet(Box<Jfet>),
Mes(Box<Mes>),
Ltra(Box<Ltra>),
Tra(Box<Tra>),
SwitchV(SwitchV),
SwitchI(SwitchI),
Asrc(Box<Asrc>),
}
macro_rules! impl_into_spec {
($($t:ident),*) => {$(
impl From<$t> for Spec {
fn from(s: $t) -> Spec { Spec::$t(s) }
}
)*};
}
impl_into_spec!(
Resistor, Capacitor, Inductor, Mutual, Vsource, Isource, Vcvs, Vccs, Cccs, Ccvs, Diode,
SwitchV, SwitchI
);
impl From<Bjt> for Spec {
fn from(s: Bjt) -> Spec {
Spec::Bjt(Box::new(s))
}
}
impl From<Mos1> for Spec {
fn from(s: Mos1) -> Spec {
Spec::Mos1(Box::new(s))
}
}
impl From<Mos2> for Spec {
fn from(s: Mos2) -> Spec {
Spec::Mos2(Box::new(s))
}
}
impl From<Mos3> for Spec {
fn from(s: Mos3) -> Spec {
Spec::Mos3(Box::new(s))
}
}
impl From<Mos6> for Spec {
fn from(s: Mos6) -> Spec {
Spec::Mos6(Box::new(s))
}
}
impl From<Bsim1> for Spec {
fn from(s: Bsim1) -> Spec {
Spec::Bsim1(Box::new(s))
}
}
impl From<Bsim2> for Spec {
fn from(s: Bsim2) -> Spec {
Spec::Bsim2(Box::new(s))
}
}
impl From<Bsim3> for Spec {
fn from(s: Bsim3) -> Spec {
Spec::Bsim3(Box::new(s))
}
}
impl From<Jfet> for Spec {
fn from(s: Jfet) -> Spec {
Spec::Jfet(Box::new(s))
}
}
impl From<Mes> for Spec {
fn from(s: Mes) -> Spec {
Spec::Mes(Box::new(s))
}
}
impl From<Ltra> for Spec {
fn from(s: Ltra) -> Spec {
Spec::Ltra(Box::new(s))
}
}
impl From<Asrc> for Spec {
fn from(s: Asrc) -> Spec {
Spec::Asrc(Box::new(s))
}
}
impl From<Tra> for Spec {
fn from(s: Tra) -> Spec {
Spec::Tra(Box::new(s))
}
}
impl Spec {
pub(crate) fn name(&self) -> &str {
match self {
Spec::Bjt(s) => &s.name,
Spec::Mos1(s) => &s.name,
Spec::Mos2(s) => &s.name,
Spec::Mos3(s) => &s.name,
Spec::Mos6(s) => &s.name,
Spec::Bsim1(s) => &s.name,
Spec::Bsim2(s) => &s.name,
Spec::Bsim3(s) => &s.name,
Spec::Resistor(s) => &s.name,
Spec::Capacitor(s) => &s.name,
Spec::Inductor(s) => &s.name,
Spec::Mutual(s) => &s.name,
Spec::Vsource(s) => &s.name,
Spec::Isource(s) => &s.name,
Spec::Vcvs(s) => &s.name,
Spec::Vccs(s) => &s.name,
Spec::Cccs(s) => &s.name,
Spec::Ccvs(s) => &s.name,
Spec::Diode(s) => &s.name,
Spec::Jfet(s) => &s.name,
Spec::Mes(s) => &s.name,
Spec::Ltra(s) => &s.name,
Spec::Tra(s) => &s.name,
Spec::SwitchV(s) => &s.name,
Spec::SwitchI(s) => &s.name,
Spec::Asrc(s) => &s.name,
}
}
}
#[derive(Clone, Default)]
pub struct Circuit {
pub title: String,
pub(crate) specs: Vec<Spec>,
pub(crate) nodesets: Vec<(String, f64)>,
pub(crate) ics: Vec<(String, f64)>,
pub(crate) ic_given: HashMap<String, crate::devices::IcGiven>,
pub(crate) res_temp: HashMap<String, ResTemp>,
pub(crate) dtemp: HashMap<String, f64>,
pub(crate) warm: crate::session::WarmSlot,
}
impl Circuit {
pub fn new(title: impl Into<String>) -> Self {
Circuit {
title: title.into(),
..Circuit::default()
}
}
pub fn add(&mut self, spec: impl Into<Spec>) -> Result<(), Error> {
let mut spec = spec.into();
normalize(&mut spec);
let name = spec.name();
if name.is_empty() {
return Err(Error::Build("device name cannot be empty".into()));
}
if self.specs.iter().any(|s| s.name() == name) {
return Err(Error::Build(format!("device `{name}` already defined")));
}
if let Spec::Resistor(r) = &spec
&& (r.value == 0.0 || !r.value.is_finite())
{
return Err(Error::Build(format!(
"resistor `{}` cannot have zero resistance",
r.name
)));
}
self.specs.push(spec);
Ok(())
}
pub fn nodeset(&mut self, node: impl Into<String>, value: f64) {
self.nodesets
.push((node.into().to_ascii_lowercase(), value));
}
pub fn ic(&mut self, node: impl Into<String>, value: f64) {
self.ics.push((node.into().to_ascii_lowercase(), value));
}
pub(crate) fn set_res_temp(&mut self, name: &str, t: ResTemp) {
self.res_temp.insert(name.to_ascii_lowercase(), t);
}
pub(crate) fn set_dtemp(&mut self, name: &str, dtemp: f64) {
self.dtemp.insert(name.to_ascii_lowercase(), dtemp);
}
fn instance_temp(&self, name: &str, temp: Option<f64>, opts: &Options) -> f64 {
temp.unwrap_or_else(|| opts.temp + self.dtemp.get(name).copied().unwrap_or(0.0))
}
pub(crate) fn set_ic_given(&mut self, name: &str, given: crate::devices::IcGiven) {
self.ic_given.insert(name.to_ascii_lowercase(), given);
}
}
fn normalize(spec: &mut Spec) {
#[allow(clippy::ptr_arg)] fn lc(s: &mut String) {
s.make_ascii_lowercase();
}
match spec {
Spec::Resistor(s) => {
lc(&mut s.name);
s.nodes.iter_mut().for_each(lc);
}
Spec::Capacitor(s) => {
lc(&mut s.name);
s.nodes.iter_mut().for_each(lc);
}
Spec::Inductor(s) => {
lc(&mut s.name);
s.nodes.iter_mut().for_each(lc);
}
Spec::Mutual(s) => {
lc(&mut s.name);
lc(&mut s.l1);
lc(&mut s.l2);
}
Spec::Vsource(s) => {
lc(&mut s.name);
s.nodes.iter_mut().for_each(lc);
}
Spec::Isource(s) => {
lc(&mut s.name);
s.nodes.iter_mut().for_each(lc);
}
Spec::Vcvs(s) => {
lc(&mut s.name);
s.nodes.iter_mut().for_each(lc);
}
Spec::Vccs(s) => {
lc(&mut s.name);
s.nodes.iter_mut().for_each(lc);
}
Spec::Cccs(s) => {
lc(&mut s.name);
s.nodes.iter_mut().for_each(lc);
lc(&mut s.ctrl);
}
Spec::Ccvs(s) => {
lc(&mut s.name);
s.nodes.iter_mut().for_each(lc);
lc(&mut s.ctrl);
}
Spec::Diode(s) => {
lc(&mut s.name);
s.nodes.iter_mut().for_each(lc);
}
Spec::Bjt(s) => {
lc(&mut s.name);
s.nodes.iter_mut().for_each(lc);
}
Spec::Mos1(s) => {
lc(&mut s.name);
s.nodes.iter_mut().for_each(lc);
}
Spec::Mos2(s) => {
lc(&mut s.name);
s.nodes.iter_mut().for_each(lc);
}
Spec::Mos3(s) => {
lc(&mut s.name);
s.nodes.iter_mut().for_each(lc);
}
Spec::Mos6(s) => {
lc(&mut s.name);
s.nodes.iter_mut().for_each(lc);
}
Spec::Bsim1(s) => {
lc(&mut s.name);
s.nodes.iter_mut().for_each(lc);
}
Spec::Bsim2(s) => {
lc(&mut s.name);
s.nodes.iter_mut().for_each(lc);
}
Spec::Bsim3(s) => {
lc(&mut s.name);
s.nodes.iter_mut().for_each(lc);
}
Spec::Jfet(s) => {
lc(&mut s.name);
s.nodes.iter_mut().for_each(lc);
}
Spec::Mes(s) => {
lc(&mut s.name);
s.nodes.iter_mut().for_each(lc);
}
Spec::Ltra(s) => {
lc(&mut s.name);
s.nodes.iter_mut().for_each(lc);
}
Spec::Tra(s) => {
lc(&mut s.name);
s.nodes.iter_mut().for_each(lc);
}
Spec::SwitchV(s) => {
lc(&mut s.name);
s.nodes.iter_mut().for_each(lc);
}
Spec::SwitchI(s) => {
lc(&mut s.name);
s.nodes.iter_mut().for_each(lc);
lc(&mut s.ctrl);
}
Spec::Asrc(s) => {
lc(&mut s.name);
s.nodes.iter_mut().for_each(lc);
s.expr.visit_refs_mut(&mut |n| lc(n), &mut |d| lc(d));
}
}
}
pub(crate) struct Vars {
pub names: Vec<String>,
pub kinds: Vec<VarKind>,
}
pub(crate) fn saved_node(name: &str) -> bool {
const HIDDEN: [&str; 6] = [
"#internal",
"#source",
"#drain",
"#collector",
"#emitter",
"#base",
];
!HIDDEN.iter().any(|h| name.contains(h))
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub(crate) enum VarKind {
Ground,
Node,
Branch,
}
impl Vars {
fn new() -> Self {
Vars {
names: vec!["0".into()],
kinds: vec![VarKind::Ground],
}
}
pub fn len(&self) -> usize {
self.names.len()
}
fn push(&mut self, name: String, kind: VarKind) -> Var {
self.names.push(name);
self.kinds.push(kind);
self.names.len() - 1
}
}
#[derive(Clone, Debug)]
pub(crate) struct PortInfo {
pub name: String,
pub num: u32,
pub z0: f64,
pub pos: Var,
pub neg: Var,
pub br: Var,
}
pub(crate) struct Built {
pub vars: Vars,
pub devices: Vec<Box<dyn crate::devices::Device>>,
pub names: Vec<String>,
pub nodesets: Vec<(Var, f64)>,
pub ics: Vec<(Var, f64)>,
pub sources: HashMap<String, (usize, bool)>,
pub ports: Vec<PortInfo>,
pub num_states: usize,
#[cfg_attr(not(feature = "parallel"), allow(dead_code))]
pub state_ranges: Vec<(usize, usize)>,
pub notes: Vec<String>,
pub warm: crate::session::WarmSlot,
}
impl Circuit {
pub(crate) fn build(&self, opts: &Options) -> Result<Built, Error> {
use crate::devices::*;
let mut vars = Vars::new();
let mut node_ids: HashMap<String, Var> = HashMap::new();
node_ids.insert("0".into(), 0);
let node = |vars: &mut Vars, node_ids: &mut HashMap<String, Var>, name: &str| -> Var {
*node_ids
.entry(name.to_string())
.or_insert_with(|| vars.push(name.to_string(), VarKind::Node))
};
let mut branches: HashMap<String, Var> = HashMap::new();
let mut st = crate::state::StateAlloc::new();
let mut state_base: Vec<usize> = Vec::with_capacity(self.specs.len());
let mut state_ranges: Vec<(usize, usize)> = Vec::with_capacity(self.specs.len());
let mut inductors: HashMap<String, (usize, f64)> = HashMap::new(); for spec in &self.specs {
let first = st.total();
state_base.push(match spec {
Spec::Bjt(_) => st.alloc(crate::bjt::BJT_NUM_STATES),
Spec::Mos1(_) => st.alloc(crate::mos1::MOS1_NUM_STATES),
Spec::Mos2(_) => st.alloc(crate::mos2::MOS2_NUM_STATES),
Spec::Mos3(_) => st.alloc(crate::mos3::MOS3_NUM_STATES),
Spec::Mos6(_) => st.alloc(crate::mos6::MOS6_NUM_STATES),
Spec::Bsim1(_) => st.alloc(crate::bsim1::BSIM1_NUM_STATES),
Spec::Bsim2(_) => st.alloc(crate::bsim2::BSIM2_NUM_STATES),
Spec::Bsim3(_) => st.alloc(crate::bsim3::BSIM3_NUM_STATES),
Spec::Capacitor(_) => st.alloc(2),
Spec::Inductor(s) => {
let flux = st.alloc(2);
inductors.insert(s.name.clone(), (flux, s.value));
flux
}
Spec::Diode(_) => st.alloc(5),
Spec::Jfet(_) => st.alloc(crate::jfet::FET3_NUM_STATES),
Spec::Mes(_) => st.alloc(crate::mes::MES_NUM_STATES),
Spec::SwitchV(_) | Spec::SwitchI(_) => st.alloc(crate::switch::SW_NUM_STATES),
_ => 0,
});
state_ranges.push((first, st.total()));
let node_names: &[String] = match spec {
Spec::Bjt(s) => &s.nodes,
Spec::Mos1(s) => &s.nodes,
Spec::Mos2(s) => &s.nodes,
Spec::Mos3(s) => &s.nodes,
Spec::Mos6(s) => &s.nodes,
Spec::Bsim1(s) => &s.nodes,
Spec::Bsim2(s) => &s.nodes,
Spec::Bsim3(s) => &s.nodes,
Spec::Resistor(s) => &s.nodes,
Spec::Capacitor(s) => &s.nodes,
Spec::Inductor(s) => &s.nodes,
Spec::Mutual(_) => &[],
Spec::Vsource(s) => &s.nodes,
Spec::Isource(s) => &s.nodes,
Spec::Vcvs(s) => &s.nodes,
Spec::Vccs(s) => &s.nodes,
Spec::Cccs(s) => &s.nodes,
Spec::Ccvs(s) => &s.nodes,
Spec::Diode(s) => &s.nodes,
Spec::Jfet(s) => &s.nodes,
Spec::Mes(s) => &s.nodes,
Spec::Ltra(s) => &s.nodes,
Spec::Tra(s) => &s.nodes,
Spec::SwitchV(s) => &s.nodes,
Spec::SwitchI(s) => &s.nodes,
Spec::Asrc(s) => &s.nodes,
};
for n in node_names {
node(&mut vars, &mut node_ids, n);
}
if let Spec::Asrc(s) = spec {
let mut refs = Vec::new();
let mut e = s.expr.clone();
e.visit_refs_mut(&mut |n| refs.push(n.clone()), &mut |_| {});
for n in &refs {
node(&mut vars, &mut node_ids, n);
}
if s.kind == dune_netlist::ir::SourceKind::Voltage {
let br = vars.push(s.name.clone(), VarKind::Branch);
branches.insert(s.name.clone(), br);
}
}
match spec {
Spec::Vsource(s) => {
let br = vars.push(s.name.clone(), VarKind::Branch);
branches.insert(s.name.clone(), br);
if s.port.is_some_and(|p| p.is_port()) {
node(&mut vars, &mut node_ids, &format!("{}#res", s.name));
}
}
Spec::Inductor(s) => {
let br = vars.push(s.name.clone(), VarKind::Branch);
branches.insert(s.name.clone(), br);
}
Spec::Vcvs(s) => {
let br = vars.push(s.name.clone(), VarKind::Branch);
branches.insert(s.name.clone(), br);
}
Spec::Ccvs(s) => {
let br = vars.push(s.name.clone(), VarKind::Branch);
branches.insert(s.name.clone(), br);
}
Spec::Ltra(s) => {
node(&mut vars, &mut node_ids, &format!("{}#i1", s.name));
node(&mut vars, &mut node_ids, &format!("{}#i2", s.name));
}
Spec::Tra(s) => {
for suffix in ["i1", "i2", "int1", "int2"] {
node(&mut vars, &mut node_ids, &format!("{}#{suffix}", s.name));
}
}
Spec::Diode(s) if s.model.rs != 0.0 => {
node(&mut vars, &mut node_ids, &format!("{}#internal", s.name));
}
Spec::Jfet(s) => {
if s.model.rs != 0.0 {
node(&mut vars, &mut node_ids, &format!("{}#source", s.name));
}
if s.model.rd != 0.0 {
node(&mut vars, &mut node_ids, &format!("{}#drain", s.name));
}
}
Spec::Mes(s) => {
if s.model.rs != 0.0 {
node(&mut vars, &mut node_ids, &format!("{}#source", s.name));
}
if s.model.rd != 0.0 {
node(&mut vars, &mut node_ids, &format!("{}#drain", s.name));
}
}
Spec::Bjt(s) => {
if s.model.rc != 0.0 {
node(&mut vars, &mut node_ids, &format!("{}#collector", s.name));
}
if s.model.rb != 0.0 {
node(&mut vars, &mut node_ids, &format!("{}#base", s.name));
}
if s.model.re != 0.0 {
node(&mut vars, &mut node_ids, &format!("{}#emitter", s.name));
}
}
Spec::Mos1(s) => {
if s.model.needs_prime(s.nrd) {
node(&mut vars, &mut node_ids, &format!("{}#drain", s.name));
}
if s.model.needs_prime(s.nrs) {
node(&mut vars, &mut node_ids, &format!("{}#source", s.name));
}
}
Spec::Mos2(s) => {
if s.model.needs_prime(s.nrd) {
node(
&mut vars,
&mut node_ids,
&format!("{}#internal#drain", s.name),
);
}
if s.model.needs_prime(s.nrs) {
node(
&mut vars,
&mut node_ids,
&format!("{}#internal#source", s.name),
);
}
}
Spec::Mos3(s) => {
if s.model.needs_prime(s.nrd) {
node(
&mut vars,
&mut node_ids,
&format!("{}#internal#drain", s.name),
);
}
if s.model.needs_prime(s.nrs) {
node(
&mut vars,
&mut node_ids,
&format!("{}#internal#source", s.name),
);
}
}
Spec::Mos6(s) => {
if s.model.needs_prime(s.nrd) {
node(&mut vars, &mut node_ids, &format!("{}#drain", s.name));
}
if s.model.needs_prime(s.nrs) {
node(&mut vars, &mut node_ids, &format!("{}#source", s.name));
}
}
Spec::Bsim1(s) => {
if s.model.needs_prime(s.nrd) {
node(&mut vars, &mut node_ids, &format!("{}#drain", s.name));
}
if s.model.needs_prime(s.nrs) {
node(&mut vars, &mut node_ids, &format!("{}#source", s.name));
}
}
Spec::Bsim2(s) => {
if s.model.needs_prime(s.nrd) {
node(&mut vars, &mut node_ids, &format!("{}#drain", s.name));
}
if s.model.needs_prime(s.nrs) {
node(&mut vars, &mut node_ids, &format!("{}#source", s.name));
}
}
Spec::Bsim3(s) => {
if s.model.needs_prime(s.nrd) {
node(&mut vars, &mut node_ids, &format!("{}#drain", s.name));
}
if s.model.needs_prime(s.nrs) {
node(&mut vars, &mut node_ids, &format!("{}#source", s.name));
}
}
_ => {}
}
}
let num_states = st.total();
let mut devices: Vec<Box<dyn Device>> = Vec::new();
let mut names: Vec<String> = Vec::new();
let mut sources: HashMap<String, (usize, bool)> = HashMap::new();
let mut ports: Vec<PortInfo> = Vec::new();
let mut notes = Vec::new();
let get = |node_ids: &HashMap<String, Var>, n: &str| node_ids[n];
let mut bsim3_models: Vec<crate::bsim3::Bsim3Knots> = Vec::new();
for (spec, &base) in self.specs.iter().zip(&state_base) {
names.push(spec.name().to_string());
match spec {
Spec::Bjt(s) => {
let col = get(&node_ids, &s.nodes[0]);
let bse = get(&node_ids, &s.nodes[1]);
let emi = get(&node_ids, &s.nodes[2]);
let sub = get(&node_ids, &s.nodes[3]);
let prime = |rz: f64, suffix: &str, outer: Var| {
if rz != 0.0 {
node_ids[&format!("{}#{suffix}", s.name)]
} else {
outer
}
};
let primes = [
prime(s.model.rc, "collector", col),
prime(s.model.rb, "base", bse),
prime(s.model.re, "emitter", emi),
];
let temp = self.instance_temp(&s.name, s.temp, opts);
let bt = crate::bjt::BjtTemp::new(&s.name, &s.model, temp, opts.tnom);
notes.extend(bt.notes.iter().cloned());
devices.push(Box::new(crate::bjt::BjtI::new(
[col, bse, emi, sub],
primes,
bt,
s.area,
s.off,
s.ic,
base,
)));
}
Spec::Mos1(s) => {
let d = get(&node_ids, &s.nodes[0]);
let gt = get(&node_ids, &s.nodes[1]);
let so = get(&node_ids, &s.nodes[2]);
let b = get(&node_ids, &s.nodes[3]);
let dp = if s.model.needs_prime(s.nrd) {
node_ids[&format!("{}#drain", s.name)]
} else {
d
};
let sp = if s.model.needs_prime(s.nrs) {
node_ids[&format!("{}#source", s.name)]
} else {
so
};
let geom = crate::mos1::MosGeom {
l: s.l.unwrap_or(opts.defl),
w: s.w.unwrap_or(opts.defw),
ad: s.ad.unwrap_or(opts.defad),
as_: s.as_.unwrap_or(opts.defas),
pd: s.pd,
ps: s.ps,
nrd: s.nrd,
nrs: s.nrs,
};
let temp = self.instance_temp(&s.name, s.temp, opts);
let mt = crate::mos1::Mos1Temp::new(&s.name, &s.model, &geom, temp, opts.tnom)
.map_err(Error::Build)?;
notes.extend(mt.notes.iter().cloned());
devices.push(Box::new(crate::mos1::Mos1I::new(
[d, gt, so, b],
[dp, sp],
mt,
geom,
s.off,
s.ic,
base,
)));
}
Spec::Mos2(s) => {
let d = get(&node_ids, &s.nodes[0]);
let gt = get(&node_ids, &s.nodes[1]);
let so = get(&node_ids, &s.nodes[2]);
let b = get(&node_ids, &s.nodes[3]);
let dp = if s.model.needs_prime(s.nrd) {
node_ids[&format!("{}#internal#drain", s.name)]
} else {
d
};
let sp = if s.model.needs_prime(s.nrs) {
node_ids[&format!("{}#internal#source", s.name)]
} else {
so
};
let geom = crate::mos1::MosGeom {
l: s.l.unwrap_or(opts.defl),
w: s.w.unwrap_or(opts.defw),
ad: s.ad.unwrap_or(opts.defad),
as_: s.as_.unwrap_or(opts.defas),
pd: s.pd,
ps: s.ps,
nrd: s.nrd,
nrs: s.nrs,
};
let temp = self.instance_temp(&s.name, s.temp, opts);
let mt = crate::mos2::Mos2Temp::new(&s.name, &s.model, &geom, temp, opts.tnom)
.map_err(Error::Build)?;
notes.extend(mt.notes.iter().cloned());
devices.push(Box::new(crate::mos2::Mos2I::new(
[d, gt, so, b],
[dp, sp],
mt,
geom,
s.off,
s.ic,
base,
)));
}
Spec::Mos3(s) => {
let d = get(&node_ids, &s.nodes[0]);
let gt = get(&node_ids, &s.nodes[1]);
let so = get(&node_ids, &s.nodes[2]);
let b = get(&node_ids, &s.nodes[3]);
let dp = if s.model.needs_prime(s.nrd) {
node_ids[&format!("{}#internal#drain", s.name)]
} else {
d
};
let sp = if s.model.needs_prime(s.nrs) {
node_ids[&format!("{}#internal#source", s.name)]
} else {
so
};
let geom = crate::mos1::MosGeom {
l: s.l.unwrap_or(opts.defl),
w: s.w.unwrap_or(opts.defw),
ad: s.ad.unwrap_or(opts.defad),
as_: s.as_.unwrap_or(opts.defas),
pd: s.pd,
ps: s.ps,
nrd: s.nrd,
nrs: s.nrs,
};
let temp = self.instance_temp(&s.name, s.temp, opts);
let mt = crate::mos3::Mos3Temp::new(&s.name, &s.model, &geom, temp, opts.tnom)
.map_err(Error::Build)?;
devices.push(Box::new(crate::mos3::Mos3I::new(
[d, gt, so, b],
[dp, sp],
mt,
geom,
s.off,
s.ic,
base,
)));
}
Spec::Mos6(s) => {
let d = get(&node_ids, &s.nodes[0]);
let gt = get(&node_ids, &s.nodes[1]);
let so = get(&node_ids, &s.nodes[2]);
let b = get(&node_ids, &s.nodes[3]);
let dp = if s.model.needs_prime(s.nrd) {
node_ids[&format!("{}#drain", s.name)]
} else {
d
};
let sp = if s.model.needs_prime(s.nrs) {
node_ids[&format!("{}#source", s.name)]
} else {
so
};
let geom = crate::mos1::MosGeom {
l: s.l.unwrap_or(opts.defl),
w: s.w.unwrap_or(opts.defw),
ad: s.ad.unwrap_or(opts.defad),
as_: s.as_.unwrap_or(opts.defas),
pd: s.pd,
ps: s.ps,
nrd: if s.nrd == 0.0 { 1.0 } else { s.nrd },
nrs: if s.nrs == 0.0 { 1.0 } else { s.nrs },
};
let temp = self.instance_temp(&s.name, s.temp, opts);
let mt = crate::mos6::Mos6Temp::new(&s.name, &s.model, &geom, temp, opts.tnom)
.map_err(Error::Build)?;
notes.extend(mt.notes.iter().cloned());
devices.push(Box::new(crate::mos6::Mos6I::new(
[d, gt, so, b],
[dp, sp],
mt,
geom,
s.off,
s.ic,
base,
)));
}
Spec::Bsim1(s) => {
let d = get(&node_ids, &s.nodes[0]);
let gt = get(&node_ids, &s.nodes[1]);
let so = get(&node_ids, &s.nodes[2]);
let b = get(&node_ids, &s.nodes[3]);
let dp = if s.model.needs_prime(s.nrd) {
node_ids[&format!("{}#drain", s.name)]
} else {
d
};
let sp = if s.model.needs_prime(s.nrs) {
node_ids[&format!("{}#source", s.name)]
} else {
so
};
let geom = crate::mos1::MosGeom {
l: s.l.unwrap_or(5e-6),
w: s.w.unwrap_or(5e-6),
ad: s.ad.unwrap_or(0.0),
as_: s.as_.unwrap_or(0.0),
pd: s.pd,
ps: s.ps,
nrd: s.nrd,
nrs: s.nrs,
};
let sz = crate::bsim1::Bsim1Size::new(&s.name, &s.model, &geom)
.map_err(Error::Build)?;
devices.push(Box::new(crate::bsim1::Bsim1I::new(
[d, gt, so, b],
[dp, sp],
sz,
geom,
s.off,
s.ic,
base,
)));
}
Spec::Bsim2(s) => {
let d = get(&node_ids, &s.nodes[0]);
let gt = get(&node_ids, &s.nodes[1]);
let so = get(&node_ids, &s.nodes[2]);
let b = get(&node_ids, &s.nodes[3]);
let dp = if s.model.needs_prime(s.nrd) {
node_ids[&format!("{}#drain", s.name)]
} else {
d
};
let sp = if s.model.needs_prime(s.nrs) {
node_ids[&format!("{}#source", s.name)]
} else {
so
};
let geom = crate::mos1::MosGeom {
l: s.l.unwrap_or(5e-6),
w: s.w.unwrap_or(5e-6),
ad: s.ad.unwrap_or(0.0),
as_: s.as_.unwrap_or(0.0),
pd: s.pd,
ps: s.ps,
nrd: s.nrd,
nrs: s.nrs,
};
let sz = crate::bsim2::Bsim2Size::new(&s.name, &s.model, &geom)
.map_err(Error::Build)?;
devices.push(Box::new(crate::bsim2::Bsim2I::new(
[d, gt, so, b],
[dp, sp],
sz,
geom,
s.off,
s.ic,
base,
)));
}
Spec::Bsim3(s) => {
let d = get(&node_ids, &s.nodes[0]);
let gt = get(&node_ids, &s.nodes[1]);
let so = get(&node_ids, &s.nodes[2]);
let b = get(&node_ids, &s.nodes[3]);
let dp = if s.model.needs_prime(s.nrd) {
node_ids[&format!("{}#drain", s.name)]
} else {
d
};
let sp = if s.model.needs_prime(s.nrs) {
node_ids[&format!("{}#source", s.name)]
} else {
so
};
let geom = crate::bsim3::Bsim3Geom {
l: s.l.unwrap_or(5e-6),
w: s.w.unwrap_or(5e-6),
ad: s.ad,
as_: s.as_,
pd: s.pd,
ps: s.ps,
nrd: s.nrd,
nrs: s.nrs,
m: s.m,
delvto: s.delvto,
mulu0: s.mulu0,
};
let (model, mt, pp) = crate::bsim3::Bsim3Knots::resolve(
&mut bsim3_models,
&s.name,
&s.model,
geom.l,
geom.w,
opts.temp,
opts.tnom,
&mut notes,
)
.map_err(Error::Build)?;
devices.push(Box::new(crate::bsim3::Bsim3I::new(
[d, gt, so, b],
[dp, sp],
model,
mt,
pp,
geom,
s.off,
s.ic,
base,
)));
}
Spec::Resistor(s) => {
let rt = self.res_temp.get(&s.name).copied().unwrap_or_default();
devices.push(Box::new(ResistorI::with_temp(
s.value,
rt,
opts,
get(&node_ids, &s.nodes[0]),
get(&node_ids, &s.nodes[1]),
)));
}
Spec::Capacitor(s) => {
devices.push(Box::new(CapacitorI::new(
s.value,
s.ic,
get(&node_ids, &s.nodes[0]),
get(&node_ids, &s.nodes[1]),
base,
)));
}
Spec::Inductor(s) => {
devices.push(Box::new(InductorI::new(
s.value,
s.ic.unwrap_or(0.0),
get(&node_ids, &s.nodes[0]),
get(&node_ids, &s.nodes[1]),
branches[&s.name],
base,
)));
}
Spec::Mutual(s) => {
for l in [&s.l1, &s.l2] {
if !inductors.contains_key(l) {
return Err(Error::Build(format!(
"`{}`: coupled inductor `{l}` does not exist",
s.name
)));
}
}
let (flux1, l1) = inductors[&s.l1];
let (flux2, l2) = inductors[&s.l2];
devices.push(Box::new(MutualI::new(
s.k,
(l1 * l2).abs().sqrt(),
branches[&s.l1],
branches[&s.l2],
flux1,
flux2,
)));
}
Spec::Vsource(s) => {
sources.insert(s.name.clone(), (devices.len(), true));
let pos = get(&node_ids, &s.nodes[0]);
let neg = get(&node_ids, &s.nodes[1]);
let br = branches[&s.name];
let noise = NoiseWave::from_wave(s.wave.as_ref(), opts, &s.name)?;
let mut dev = VsourceI::new(s.dc, s.wave.clone(), s.ac, pos, neg, br)
.with_disto(s.distof1, s.distof2)
.with_noise(noise);
if let Some(p) = s.port.filter(RfPort::is_port) {
let res = get(&node_ids, &format!("{}#res", s.name));
dev = dev.with_port(res, p.z0);
ports.push(PortInfo {
name: s.name.clone(),
num: p.num,
z0: p.z0,
pos,
neg,
br,
});
}
devices.push(Box::new(dev));
}
Spec::Isource(s) => {
sources.insert(s.name.clone(), (devices.len(), false));
let noise = NoiseWave::from_wave(s.wave.as_ref(), opts, &s.name)?;
devices.push(Box::new(
IsourceI::new(
s.dc,
s.wave.clone(),
s.ac,
get(&node_ids, &s.nodes[0]),
get(&node_ids, &s.nodes[1]),
)
.with_disto(s.distof1, s.distof2)
.with_noise(noise),
));
}
Spec::Vcvs(s) => {
let n = [
get(&node_ids, &s.nodes[0]),
get(&node_ids, &s.nodes[1]),
get(&node_ids, &s.nodes[2]),
get(&node_ids, &s.nodes[3]),
];
devices.push(Box::new(VcvsI::new(s.gain, n, branches[&s.name])));
}
Spec::Vccs(s) => {
let n = [
get(&node_ids, &s.nodes[0]),
get(&node_ids, &s.nodes[1]),
get(&node_ids, &s.nodes[2]),
get(&node_ids, &s.nodes[3]),
];
devices.push(Box::new(VccsI::new(s.gain, n)));
}
Spec::Cccs(s) => {
let ctrl = ctrl_branch(&branches, &s.name, &s.ctrl)?;
devices.push(Box::new(CccsI::new(
s.gain,
get(&node_ids, &s.nodes[0]),
get(&node_ids, &s.nodes[1]),
ctrl,
)));
}
Spec::Asrc(s) => {
let pos = get(&node_ids, &s.nodes[0]);
let neg = get(&node_ids, &s.nodes[1]);
let voltage = s.kind == dune_netlist::ir::SourceKind::Voltage;
if voltage && pos == neg {
return Err(Error::Build(format!(
"`{}`: voltage B source is shorted (both nodes `{}`)",
s.name, s.nodes[0]
)));
}
let tree = crate::asrc::Tree::new(&s.expr);
let mut cols = Vec::with_capacity(tree.vars.len());
for v in &tree.vars {
cols.push(match v {
crate::asrc::VarRef::Node(n) => get(&node_ids, n),
crate::asrc::VarRef::Branch(d) => {
branches.get(d).copied().ok_or_else(|| {
Error::Build(format!(
"`{}`: unknown controlling source `{d}`",
s.name
))
})?
}
});
}
let br = if voltage { branches[&s.name] } else { 0 };
devices.push(Box::new(crate::asrc::AsrcI::new(
s.kind, pos, neg, br, tree, cols,
)));
}
Spec::Ccvs(s) => {
let ctrl = ctrl_branch(&branches, &s.name, &s.ctrl)?;
devices.push(Box::new(CcvsI::new(
s.gain,
get(&node_ids, &s.nodes[0]),
get(&node_ids, &s.nodes[1]),
branches[&s.name],
ctrl,
)));
}
Spec::Diode(s) => {
let pos = get(&node_ids, &s.nodes[0]);
let neg = get(&node_ids, &s.nodes[1]);
let pos_prime = if s.model.rs != 0.0 {
get(&node_ids, &format!("{}#internal", s.name))
} else {
pos
};
let temp = self.instance_temp(&s.name, s.temp, opts);
let t = DiodeTemp::new(
&s.name,
&s.model,
s.area,
1.0,
temp,
opts.tnom,
opts.reltol,
);
notes.extend(t.notes.iter().cloned());
devices.push(Box::new(crate::diode::DiodeI::new(
pos,
neg,
pos_prime,
t,
s.area,
s.off,
s.ic.unwrap_or(0.0),
base,
crate::diode::DiodeSensBase {
name: s.name.clone(),
model: s.model.clone(),
temp,
m: 1.0,
default_tnom: opts.tnom,
reltol: opts.reltol,
},
)));
}
Spec::Jfet(s) => {
let (nodes, primes) =
fet3_vars(&node_ids, &s.name, &s.nodes, s.model.rd, s.model.rs);
let temp = self.instance_temp(&s.name, s.temp, opts);
let t = crate::jfet::JfetTemp::new(&s.name, &s.model, temp, opts.tnom);
notes.extend(t.notes.iter().cloned());
devices.push(Box::new(crate::jfet::JfetI::new(
nodes, primes, t, s.area, s.off, s.ic, base,
)));
}
Spec::Mes(s) => {
let (nodes, primes) =
fet3_vars(&node_ids, &s.name, &s.nodes, s.model.rd, s.model.rs);
let t = crate::mes::MesTemp::new(&s.model);
devices.push(Box::new(crate::mes::MesI::new(
nodes, primes, t, s.area, s.off, s.ic, base,
)));
}
Spec::Ltra(s) => {
let n = [
get(&node_ids, &s.nodes[0]),
get(&node_ids, &s.nodes[1]),
get(&node_ids, &s.nodes[2]),
get(&node_ids, &s.nodes[3]),
];
let br = [
get(&node_ids, &format!("{}#i1", s.name)),
get(&node_ids, &format!("{}#i2", s.name)),
];
let mut p =
crate::ltra::LtraParams::new(&s.name, &s.model, opts.reltol, opts.abstol)
.map_err(Error::Build)?;
if opts.trytocompact {
p.try_to_compact();
}
notes.extend(p.notes.iter().cloned());
devices.push(Box::new(
crate::ltra::LtraI::new(p, n, br).with_fit(opts.ltra_fit),
));
}
Spec::Tra(s) => {
let n = [
get(&node_ids, &s.nodes[0]),
get(&node_ids, &s.nodes[1]),
get(&node_ids, &s.nodes[2]),
get(&node_ids, &s.nodes[3]),
];
let v = |suffix: &str| get(&node_ids, &format!("{}#{suffix}", s.name));
let p = crate::tra::TraParams::new(&s.name, s.z0, s.td, s.f, s.nl, s.ic)
.map_err(Error::Build)?;
devices.push(Box::new(crate::tra::TraI::new(
p,
n,
[v("i1"), v("i2")],
[v("int1"), v("int2")],
)));
}
Spec::SwitchV(s) => {
let m = &s.model;
let p = crate::switch::SwitchParams::new(m.vt, m.vh, m.ron, m.roff, opts.gmin);
let ctrl = crate::switch::Control::Voltage {
pos: get(&node_ids, &s.nodes[2]),
neg: get(&node_ids, &s.nodes[3]),
};
devices.push(Box::new(crate::switch::SwitchInst::new(
p,
get(&node_ids, &s.nodes[0]),
get(&node_ids, &s.nodes[1]),
ctrl,
s.on,
base,
)));
}
Spec::SwitchI(s) => {
let branch = ctrl_branch(&branches, &s.name, &s.ctrl)?;
let m = &s.model;
let p = crate::switch::SwitchParams::new(m.it, m.ih, m.ron, m.roff, opts.gmin);
devices.push(Box::new(crate::switch::SwitchInst::new(
p,
get(&node_ids, &s.nodes[0]),
get(&node_ids, &s.nodes[1]),
crate::switch::Control::Current { branch },
s.on,
base,
)));
}
}
}
for (spec, dev) in self.specs.iter().zip(devices.iter_mut()) {
if let Some(&g) = self.ic_given.get(spec.name()) {
dev.set_ic_given(g);
}
}
let mut nodesets = Vec::new();
for (name, v) in &self.nodesets {
match node_ids.get(name) {
Some(&var) if var != 0 => nodesets.push((var, *v)),
Some(_) => notes.push(".nodeset on ground ignored".to_string()),
None => notes.push(format!(".nodeset names unknown node `{name}`")),
}
}
let mut ics = Vec::new();
for (name, v) in &self.ics {
match node_ids.get(name) {
Some(&var) if var != 0 => ics.push((var, *v)),
Some(_) => notes.push(".ic on ground ignored".to_string()),
None => notes.push(format!(".ic names unknown node `{name}`")),
}
}
let ports = check_ports(ports)?;
Ok(Built {
vars,
devices,
names,
nodesets,
ics,
sources,
ports,
num_states,
state_ranges,
notes,
warm: self.warm.share(),
})
}
}
fn check_ports(mut ports: Vec<PortInfo>) -> Result<Vec<PortInfo>, Error> {
let count = ports.len();
let mut seen = vec![false; count + 1];
for p in ports.iter().rev() {
let n = p.num as usize;
if n > count {
return Err(Error::Build(format!("{}: incorrect port ordering", p.name)));
}
if std::mem::replace(&mut seen[n], true) {
return Err(Error::Build(format!("{}: duplicate port Index", p.name)));
}
}
ports.sort_by_key(|p| p.num);
Ok(ports)
}
fn fet3_vars(
node_ids: &HashMap<String, Var>,
name: &str,
nodes: &[String; 3],
rd: f64,
rs: f64,
) -> ([Var; 3], [Var; 2]) {
let d = node_ids[&nodes[0]];
let g = node_ids[&nodes[1]];
let s = node_ids[&nodes[2]];
let dp = if rd != 0.0 {
node_ids[&format!("{name}#drain")]
} else {
d
};
let sp = if rs != 0.0 {
node_ids[&format!("{name}#source")]
} else {
s
};
([d, g, s], [dp, sp])
}
fn ctrl_branch(branches: &HashMap<String, Var>, name: &str, ctrl: &str) -> Result<Var, Error> {
if !ctrl.starts_with('v') {
return Err(Error::Build(format!(
"`{name}`: controlling source `{ctrl}` must be a voltage source"
)));
}
branches
.get(ctrl)
.copied()
.ok_or_else(|| Error::Build(format!("`{name}`: controlling source `{ctrl}` not found")))
}