1#![warn(missing_docs)]
3
4use crate::parser::conv::ScirConverter;
5use crate::parser::{Dialect, ParsedSpice, Parser};
6
7use arcstr::ArcStr;
8use itertools::Itertools;
9use rust_decimal::Decimal;
10use scir::schema::{FromSchema, NoSchema, NoSchemaError, Schema};
11use scir::{Instance, Library, NetlistLibConversion, ParamValue, SliceOnePath};
12use std::collections::{HashMap, HashSet};
13use std::fmt::{Display, Formatter};
14use std::path::{Path, PathBuf};
15use substrate::block::Block;
16use substrate::schematic::pex::StringPathSchema;
17use substrate::schematic::{CellBuilder, Schematic};
18use substrate::types::TwoTerminalIo;
19use unicase::UniCase;
20
21pub mod netlist;
22pub mod parser;
23#[cfg(test)]
24mod tests;
25
26pub struct Spice;
28
29impl Spice {
30 pub fn scir_lib_from_parsed(parsed: &ParsedSpice) -> Library<Spice> {
32 let conv = ScirConverter::new(&parsed.ast);
33 conv.convert().unwrap()
34 }
35
36 pub fn scir_lib_from_str(source: &str) -> Library<Spice> {
38 let parsed = Parser::parse(Dialect::Spice, source).unwrap();
39 Spice::scir_lib_from_parsed(&parsed)
40 }
41
42 pub fn scir_lib_from_file(path: impl AsRef<Path>) -> Library<Spice> {
44 let parsed = Parser::parse_file(Dialect::Spice, path).unwrap();
45 Spice::scir_lib_from_parsed(&parsed)
46 }
47
48 pub fn scir_cell_from_parsed(
51 parsed: &ParsedSpice,
52 cell_name: &str,
53 ) -> substrate::schematic::ScirBinding<Spice> {
54 let lib = Spice::scir_lib_from_parsed(parsed);
55 let cell_id = lib.cell_id_named(cell_name);
56 substrate::schematic::ScirBinding::new(lib, cell_id)
57 }
58
59 pub fn scir_cell_from_str(
62 source: &str,
63 cell_name: &str,
64 ) -> substrate::schematic::ScirBinding<Spice> {
65 let parsed = Parser::parse(Dialect::Spice, source).unwrap();
66 Spice::scir_cell_from_parsed(&parsed, cell_name)
67 }
68
69 pub fn scir_cell_from_file(
72 path: impl AsRef<Path>,
73 cell_name: &str,
74 ) -> substrate::schematic::ScirBinding<Spice> {
75 let parsed = Parser::parse_file(Dialect::Spice, path).unwrap();
76 Spice::scir_cell_from_parsed(&parsed, cell_name)
77 }
78
79 pub fn node_voltage_path(
82 lib: &Library<Spice>,
83 conv: &NetlistLibConversion,
84 path: &SliceOnePath,
85 ) -> String {
86 Self::node_path_with_separator(lib, conv, path, ".")
87 }
88
89 pub fn node_path_with_separator(
92 lib: &Library<Spice>,
93 conv: &NetlistLibConversion,
94 path: &SliceOnePath,
95 sep: &str,
96 ) -> String {
97 let path = lib.convert_slice_one_path_with_conv(conv, path.clone(), |name, index| {
98 if let Some(index) = index {
99 arcstr::format!("{}\\[{}\\]", name, index)
100 } else {
101 name.clone()
102 }
103 });
104 path.iter().join(sep)
105 }
106}
107
108impl StringPathSchema for Spice {
109 fn node_path(lib: &Library<Self>, conv: &NetlistLibConversion, path: &SliceOnePath) -> String {
110 Self::node_path_with_separator(lib, conv, path, "/")
111 }
112}
113
114impl Schema for Spice {
115 type Primitive = Primitive;
116}
117
118impl FromSchema<NoSchema> for Spice {
119 type Error = NoSchemaError;
120
121 fn convert_primitive(
122 _primitive: <NoSchema as Schema>::Primitive,
123 ) -> Result<<Self as Schema>::Primitive, Self::Error> {
124 Err(NoSchemaError)
125 }
126
127 fn convert_instance(
128 _instance: &mut Instance,
129 _primitive: &<NoSchema as Schema>::Primitive,
130 ) -> Result<(), Self::Error> {
131 Err(NoSchemaError)
132 }
133}
134
135#[derive(Debug, Clone)]
137pub enum ComponentValue {
138 Fixed(Decimal),
140 Model(ArcStr),
142}
143
144impl Display for ComponentValue {
145 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
146 match self {
147 ComponentValue::Fixed(value) => write!(f, "{value}"),
148 ComponentValue::Model(model) => write!(f, "{model}"),
149 }
150 }
151}
152
153#[derive(Debug, Clone)]
155pub enum Primitive {
156 Res2 {
158 value: ComponentValue,
160 params: HashMap<UniCase<ArcStr>, ParamValue>,
162 },
163 Cap2 {
165 value: Decimal,
167 },
168 Diode2 {
170 model: ArcStr,
172 params: HashMap<UniCase<ArcStr>, ParamValue>,
174 },
175 Bjt {
179 model: ArcStr,
181 params: HashMap<UniCase<ArcStr>, ParamValue>,
183 has_substrate_port: bool,
185 },
186 Mos {
188 model: ArcStr,
190 params: HashMap<UniCase<ArcStr>, ParamValue>,
192 },
193 RawInstance {
195 ports: Vec<ArcStr>,
197 cell: ArcStr,
199 params: HashMap<UniCase<ArcStr>, ParamValue>,
201 },
202 RawInstanceWithCell {
206 ports: Vec<ArcStr>,
208 cell: ArcStr,
210 params: HashMap<UniCase<ArcStr>, ParamValue>,
212 body: ArcStr,
214 },
215 BlackboxInstance {
217 contents: BlackboxContents,
219 },
220 RawInstanceWithInclude {
224 cell: ArcStr,
226 netlist: PathBuf,
228 ports: Vec<ArcStr>,
230 },
231}
232
233#[derive(Debug, Clone)]
235pub struct BlackboxContents {
236 pub elems: Vec<BlackboxElement>,
238}
239
240impl BlackboxContents {
241 pub fn push(&mut self, elem: impl Into<BlackboxElement>) {
243 self.elems.push(elem.into());
244 }
245}
246
247#[derive(Debug, Clone)]
249pub enum BlackboxElement {
250 InstanceName,
252 RawString(ArcStr),
254 Port(ArcStr),
256}
257
258impl FromIterator<BlackboxElement> for BlackboxContents {
259 fn from_iter<T: IntoIterator<Item = BlackboxElement>>(iter: T) -> Self {
260 Self {
261 elems: iter.into_iter().collect(),
262 }
263 }
264}
265
266impl From<BlackboxElement> for BlackboxContents {
267 fn from(value: BlackboxElement) -> Self {
268 Self { elems: vec![value] }
269 }
270}
271
272impl<T: Into<ArcStr>> From<T> for BlackboxContents {
273 fn from(value: T) -> Self {
274 Self {
275 elems: vec![BlackboxElement::RawString(value.into())],
276 }
277 }
278}
279
280impl<T: Into<ArcStr>> From<T> for BlackboxElement {
281 fn from(value: T) -> Self {
282 Self::RawString(value.into())
283 }
284}
285
286impl Primitive {
287 pub fn ports(&self) -> Vec<ArcStr> {
289 match self {
290 Primitive::Res2 { .. } => vec!["1".into(), "2".into()],
291 Primitive::Cap2 { .. } => vec!["1".into(), "2".into()],
292 Primitive::Diode2 { .. } => vec!["1".into(), "2".into()],
293 Primitive::Bjt {
294 has_substrate_port, ..
295 } => {
296 if *has_substrate_port {
297 vec!["NC".into(), "NB".into(), "NE".into(), "NS".into()]
298 } else {
299 vec!["NC".into(), "NB".into(), "NE".into()]
300 }
301 }
302 Primitive::Mos { .. } => vec!["D".into(), "G".into(), "S".into(), "B".into()],
303 Primitive::RawInstance { ports, .. } => ports.clone(),
304 Primitive::RawInstanceWithCell { ports, .. } => ports.clone(),
305 Primitive::RawInstanceWithInclude { ports, .. } => ports.clone(),
306 Primitive::BlackboxInstance { contents } => contents
307 .elems
308 .iter()
309 .filter_map(|x| {
310 if let BlackboxElement::Port(p) = x {
311 Some(p.clone())
312 } else {
313 None
314 }
315 })
316 .collect::<HashSet<_>>()
317 .into_iter()
318 .collect(),
319 }
320 }
321}
322
323#[derive(Clone, Copy, Hash, PartialEq, Eq, Block)]
325#[substrate(io = "TwoTerminalIo")]
326pub struct Resistor {
327 value: Decimal,
329}
330impl Resistor {
331 #[inline]
333 pub fn new(value: impl Into<Decimal>) -> Self {
334 Self {
335 value: value.into(),
336 }
337 }
338
339 #[inline]
341 pub fn value(&self) -> Decimal {
342 self.value
343 }
344}
345
346impl Schematic for Resistor {
347 type Schema = Spice;
348 type NestedData = ();
349
350 fn schematic(
351 &self,
352 io: &substrate::types::schematic::IoNodeBundle<Self>,
353 cell: &mut CellBuilder<<Self as Schematic>::Schema>,
354 ) -> substrate::error::Result<Self::NestedData> {
355 let mut prim = substrate::schematic::PrimitiveBinding::new(Primitive::Res2 {
356 value: ComponentValue::Fixed(self.value()),
357 params: Default::default(),
358 });
359 prim.connect("1", io.p);
360 prim.connect("2", io.n);
361 cell.set_primitive(prim);
362 Ok(())
363 }
364}