spice/parser/
conv.rs

1//! Convert SPICE netlists to other formats.
2//!
3//! Currently, we only support converting to SCIR.
4//!
5//! TODO: bus ports, expressions, validation, ArcStr deduplication.
6
7use std::collections::{HashMap, HashSet};
8
9use crate::parser::shorts::ShortPropagator;
10use crate::{ComponentValue, Primitive, Spice};
11use arcstr::ArcStr;
12use lazy_static::lazy_static;
13use num_traits::Pow;
14use regex::Regex;
15use rust_decimal::Decimal;
16use rust_decimal::prelude::One;
17use scir::ParamValue;
18
19use thiserror::Error;
20use unicase::UniCase;
21
22use super::{Ast, Component, DeviceValue, Elem, Node, Subckt, Substr};
23
24/// The type representing subcircuit names.
25pub type SubcktName = Substr;
26
27/// A SPICE netlist conversion result.
28pub type ConvResult<T> = std::result::Result<T, ConvError>;
29
30/// A SPICE netlist conversion error.
31#[derive(Debug, Error)]
32pub enum ConvError {
33    /// An instance of this subcircuit exists, but no definition was provided.
34    #[error("an instance of subcircuit `{0}` exists, but no definition was provided")]
35    MissingSubckt(Substr),
36    /// Incorrect (missing/extra) connections for an instance.
37    #[error(
38        "incorrect (missing/extra) connections for instance {inst} of cell `{child}` (in cell `{parent}`)"
39    )]
40    IncorrectConnections {
41        /// The name of the instance.
42        inst: Substr,
43        /// The name of the cell being instantiated.
44        child: Substr,
45        /// The name of the cell containing the offending instance.
46        parent: Substr,
47    },
48    #[error("invalid literal: `{0}`")]
49    /// The given expression is not a valid literal.
50    InvalidLiteral(Substr),
51    /// Attempted to export a blackboxed subcircuit.
52    #[error("cannot export a blackboxed subcircuit")]
53    ExportBlackbox,
54    /// Netlist conversion produced invalid SCIR.
55    #[error("netlist conversion produced SCIR containing errors: {0}")]
56    InvalidScir(Box<scir::Issues>),
57    /// A non-blackbox cell was instantiated with parameters.
58    ///
59    /// Substrate does not support SPICE-like parameters on non-blackbox cells.
60    #[error(
61        "parameters for instance {inst} of cell `{child}` (in cell `{parent}`) are not allowed because `{child}` was not blackboxed"
62    )]
63    UnsupportedParams {
64        /// The name of the instance.
65        inst: Substr,
66        /// The name of the cell being instantiated.
67        child: Substr,
68        /// The name of the cell containing the offending instance.
69        parent: Substr,
70    },
71}
72
73/// Converts a parsed SPICE netlist to [`scir`].
74///
75/// The converter only converts subcircuits.
76/// Top-level component instantiations are ignored.
77pub struct ScirConverter<'a> {
78    ast: &'a Ast,
79    lib: scir::LibraryBuilder<Spice>,
80    blackbox_cells: HashSet<Substr>,
81    subckts: HashMap<SubcktName, &'a Subckt>,
82    ids: HashMap<SubcktName, scir::CellId>,
83}
84
85impl<'a> ScirConverter<'a> {
86    /// Create a new SCIR converter.
87    pub fn new(ast: &'a Ast) -> Self {
88        Self {
89            ast,
90            lib: scir::LibraryBuilder::new(),
91            blackbox_cells: Default::default(),
92            subckts: Default::default(),
93            ids: Default::default(),
94        }
95    }
96
97    /// Blackboxes the given cell.
98    pub fn blackbox(&mut self, cell_name: impl Into<Substr>) {
99        self.blackbox_cells.insert(cell_name.into());
100    }
101
102    /// Consumes the converter, yielding a SCIR [library](scir::Library).
103    pub fn convert(mut self) -> ConvResult<scir::Library<Spice>> {
104        self.subckts = map_subckts(self.ast);
105        let subckts = self.subckts.values().copied().collect::<Vec<_>>();
106        let mut shorts = ShortPropagator::analyze(self.ast, &self.blackbox_cells);
107        for subckt in subckts {
108            match self.convert_subckt(subckt, &mut shorts) {
109                // Export blackbox errors can be ignored; we just skip
110                // exporting a SCIR cell for blackboxed subcircuits.
111                Ok(_) | Err(ConvError::ExportBlackbox) => (),
112                Err(e) => return Err(e),
113            };
114        }
115        let lib = self
116            .lib
117            .build()
118            .map_err(|issues| ConvError::InvalidScir(Box::new(issues)))?;
119        Ok(lib)
120    }
121
122    fn convert_subckt(
123        &mut self,
124        subckt: &Subckt,
125        shorts: &mut ShortPropagator,
126    ) -> ConvResult<scir::CellId> {
127        if let Some(&id) = self.ids.get(&subckt.name) {
128            return Ok(id);
129        }
130
131        if self.blackbox_cells.contains(&subckt.name) {
132            return Err(ConvError::ExportBlackbox);
133        }
134
135        let parent_name = subckt.name.clone();
136
137        let mut cell = scir::Cell::new(ArcStr::from(subckt.name.as_str()));
138        let mut nodes: HashMap<Substr, scir::SliceOne> = HashMap::new();
139        // TODO: this is an expensive clone
140        let mut local_shorts = shorts.get_cell(&parent_name).clone();
141        let mut node = |name: &Node, cell: &mut scir::Cell| {
142            let name = local_shorts.root(name);
143            if let Some(&node) = nodes.get(&name) {
144                return node;
145            }
146            let id = cell.add_node(name.as_str());
147            nodes.insert(name.clone(), id);
148            id
149        };
150
151        for component in subckt.components.iter() {
152            match component {
153                Component::Mos(mos) => {
154                    let model = ArcStr::from(mos.model.as_str());
155                    let params = mos
156                        .params
157                        .iter()
158                        .map(|(k, v)| {
159                            Ok((
160                                UniCase::new(ArcStr::from(k.as_str())),
161                                match substr_as_numeric_lit(v) {
162                                    Ok(v) => ParamValue::Numeric(v),
163                                    Err(_) => ParamValue::String(v.to_string().into()),
164                                },
165                            ))
166                        })
167                        .collect::<ConvResult<HashMap<_, _>>>()?;
168                    // TODO: Deduplicate primitives, though does not affect functionality
169                    let id = self.lib.add_primitive(Primitive::Mos { model, params });
170                    let mut sinst = scir::Instance::new(&mos.name[1..], id);
171                    sinst.connect("D", node(&mos.d, &mut cell));
172                    sinst.connect("G", node(&mos.g, &mut cell));
173                    sinst.connect("S", node(&mos.s, &mut cell));
174                    sinst.connect("B", node(&mos.b, &mut cell));
175                    cell.add_instance(sinst);
176                }
177                Component::Diode(diode) => {
178                    let model = ArcStr::from(diode.model.as_str());
179                    let params = diode
180                        .params
181                        .iter()
182                        .map(|(k, v)| {
183                            Ok((
184                                UniCase::new(ArcStr::from(k.as_str())),
185                                match substr_as_numeric_lit(v) {
186                                    Ok(v) => ParamValue::Numeric(v),
187                                    Err(_) => ParamValue::String(v.to_string().into()),
188                                },
189                            ))
190                        })
191                        .collect::<ConvResult<HashMap<_, _>>>()?;
192                    // TODO: Deduplicate primitives, though does not affect functionality
193                    let id = self.lib.add_primitive(Primitive::Diode2 { model, params });
194                    let mut sinst = scir::Instance::new(&diode.name[1..], id);
195                    sinst.connect("1", node(&diode.pos, &mut cell));
196                    sinst.connect("2", node(&diode.neg, &mut cell));
197                    cell.add_instance(sinst);
198                }
199                Component::Bjt(bjt) => {
200                    let model = ArcStr::from(bjt.model.as_str());
201                    let params = bjt
202                        .params
203                        .iter()
204                        .map(|(k, v)| {
205                            Ok((
206                                UniCase::new(ArcStr::from(k.as_str())),
207                                match substr_as_numeric_lit(v) {
208                                    Ok(v) => ParamValue::Numeric(v),
209                                    Err(_) => ParamValue::String(v.to_string().into()),
210                                },
211                            ))
212                        })
213                        .collect::<ConvResult<HashMap<_, _>>>()?;
214                    // TODO: Deduplicate primitives, though does not affect functionality
215                    let id = self.lib.add_primitive(Primitive::Bjt {
216                        model,
217                        params,
218                        has_substrate_port: bjt.substrate.is_some(),
219                    });
220                    let mut sinst = scir::Instance::new(&bjt.name[1..], id);
221                    sinst.connect("NC", node(&bjt.collector, &mut cell));
222                    sinst.connect("NB", node(&bjt.base, &mut cell));
223                    sinst.connect("NE", node(&bjt.emitter, &mut cell));
224                    if let Some(substrate) = &bjt.substrate {
225                        sinst.connect("NS", node(substrate, &mut cell));
226                    }
227                    cell.add_instance(sinst);
228                }
229                Component::Res(res) => {
230                    let value = match &res.value {
231                        DeviceValue::Value(value) => {
232                            ComponentValue::Fixed(substr_as_numeric_lit(value)?)
233                        }
234                        DeviceValue::Model(model) => {
235                            ComponentValue::Model(ArcStr::from(model.as_str()))
236                        }
237                    };
238                    let params = res
239                        .params
240                        .iter()
241                        .map(|(k, v)| {
242                            Ok((
243                                UniCase::new(ArcStr::from(k.as_str())),
244                                match substr_as_numeric_lit(v) {
245                                    Ok(v) => ParamValue::Numeric(v),
246                                    Err(_) => ParamValue::String(v.to_string().into()),
247                                },
248                            ))
249                        })
250                        .collect::<ConvResult<HashMap<_, _>>>()?;
251                    let id = self.lib.add_primitive(Primitive::Res2 { value, params });
252                    let mut sinst = scir::Instance::new(&res.name[1..], id);
253                    sinst.connect("1", node(&res.pos, &mut cell));
254                    sinst.connect("2", node(&res.neg, &mut cell));
255                    cell.add_instance(sinst);
256                }
257                Component::Cap(cap) => {
258                    let id = self.lib.add_primitive(Primitive::Cap2 {
259                        value: substr_as_numeric_lit(&cap.value)?,
260                    });
261                    let mut sinst = scir::Instance::new(&cap.name[1..], id);
262                    sinst.connect("1", node(&cap.pos, &mut cell));
263                    sinst.connect("2", node(&cap.neg, &mut cell));
264                    cell.add_instance(sinst);
265                }
266                Component::Instance(inst) => {
267                    let blackbox = self.blackbox_cells.contains(&inst.child);
268                    if let (false, Some(subckt)) = (blackbox, self.subckts.get(&inst.child)) {
269                        // Parameters are not supported for instances of non-blackboxed subcircuit.
270                        if !inst.params.values.is_empty() {
271                            return Err(ConvError::UnsupportedParams {
272                                inst: inst.name.clone(),
273                                child: subckt.name.clone(),
274                                parent: parent_name.clone(),
275                            });
276                        }
277
278                        let id = self.convert_subckt(subckt, shorts)?;
279                        let mut sinst = scir::Instance::new(&inst.name[1..], id);
280                        let subckt = self
281                            .subckts
282                            .get(&inst.child)
283                            .ok_or_else(|| ConvError::MissingSubckt(inst.child.clone()))?;
284
285                        if subckt.ports.len() != inst.ports.len() {
286                            return Err(ConvError::IncorrectConnections {
287                                inst: inst.name.clone(),
288                                child: subckt.name.clone(),
289                                parent: parent_name.clone(),
290                            });
291                        }
292
293                        let cshorts = shorts.get_cell(&inst.child);
294                        for (cport, iport) in subckt.ports.iter().zip(inst.ports.iter()) {
295                            // If child port is not its own root, do not connect to it: it must be shorted to another port
296                            if cshorts.root(cport) == *cport {
297                                sinst.connect(cport.as_str(), node(iport, &mut cell));
298                            }
299                        }
300
301                        if !inst.params.values.is_empty() {
302                            return Err(ConvError::IncorrectConnections {
303                                inst: inst.name.clone(),
304                                child: subckt.name.clone(),
305                                parent: parent_name.clone(),
306                            });
307                        }
308
309                        cell.add_instance(sinst);
310                    } else {
311                        let child = ArcStr::from(inst.child.as_str());
312                        let params = inst
313                            .params
314                            .iter()
315                            .map(|(k, v)| {
316                                Ok((
317                                    UniCase::new(ArcStr::from(k.as_str())),
318                                    match substr_as_numeric_lit(v) {
319                                        Ok(v) => ParamValue::Numeric(v),
320                                        Err(_) => ParamValue::String(v.to_string().into()),
321                                    },
322                                ))
323                            })
324                            .collect::<ConvResult<HashMap<_, _>>>()?;
325                        let ports: Vec<_> = (0..inst.ports.len())
326                            .map(|i| arcstr::format!("{}", i + 1))
327                            .collect();
328                        let id = self.lib.add_primitive(Primitive::RawInstance {
329                            cell: child,
330                            ports: ports.clone(),
331                            params,
332                        });
333                        let mut sinst = scir::Instance::new(&inst.name[1..], id);
334                        for (cport, iport) in ports.iter().zip(inst.ports.iter()) {
335                            sinst.connect(cport, node(iport, &mut cell));
336                        }
337                        cell.add_instance(sinst);
338                    }
339                }
340            };
341        }
342
343        for port in subckt.ports.iter() {
344            let port = node(port, &mut cell);
345            // In the future, we may support parsing port directions from comments in the SPICE file.
346            // For now, we simply expose all ports using the default direction.
347            cell.expose_port(port, Default::default());
348        }
349
350        let id = self.lib.add_cell(cell);
351        self.ids.insert(subckt.name.clone(), id);
352        Ok(id)
353    }
354}
355
356lazy_static! {
357    static ref NUMERIC_LITERAL_REGEX: Regex =
358        Regex::new(r"^(-?[0-9]+\.?[0-9]*)((t|g|x|meg|k|m|u|n|p|f)|(e(-?[0-9]+\.?[0-9]*)))?$")
359            .expect("failed to compile numeric literal regex");
360}
361
362pub(crate) fn convert_str_to_numeric_lit(s: &str) -> Option<Decimal> {
363    let caps = NUMERIC_LITERAL_REGEX.captures(s)?;
364    let num: Decimal = caps.get(1)?.as_str().parse().ok()?;
365    let multiplier = caps
366        .get(3)
367        .and_then(|s| {
368            Decimal::from_scientific(match s.as_str().to_lowercase().as_str() {
369                "t" => "1e12",
370                "g" => "1e9",
371                "x" | "meg" => "1e6",
372                "k" => "1e3",
373                "m" => "1e-3",
374                "u" => "1e-6",
375                "n" => "1e-9",
376                "p" => "1e-12",
377                "f" => "1e-15",
378                _ => "1e0",
379            })
380            .ok()
381        })
382        .or_else(|| {
383            caps.get(5)
384                .and_then(|s| s.as_str().parse().ok())
385                .map(|exp: Decimal| Decimal::TEN.pow(exp))
386        })
387        .unwrap_or_else(Decimal::one);
388
389    Some(num * multiplier)
390}
391
392pub(crate) fn str_as_numeric_lit(s: &str) -> std::result::Result<Decimal, ()> {
393    convert_str_to_numeric_lit(s).ok_or(())
394}
395fn substr_as_numeric_lit(s: &Substr) -> ConvResult<Decimal> {
396    str_as_numeric_lit(s).map_err(|_| ConvError::InvalidLiteral(s.clone()))
397}
398
399pub(crate) fn map_subckts(ast: &Ast) -> HashMap<SubcktName, &Subckt> {
400    let mut subckts = HashMap::new();
401    for elem in ast.elems.iter() {
402        match elem {
403            Elem::Subckt(s) => {
404                if subckts.insert(s.name.clone(), s).is_some() {
405                    tracing::warn!(name=%s.name, "Duplicate subcircuits: found two subcircuits with the same name. The last one found will be used.");
406                }
407            }
408            _ => continue,
409        }
410    }
411    subckts
412}
413
414#[cfg(test)]
415mod tests {
416    use super::*;
417
418    #[test]
419    fn numeric_literal_regex() {
420        assert!(str_as_numeric_lit("2").is_ok());
421        assert!(str_as_numeric_lit("1.23").is_ok());
422        assert!(str_as_numeric_lit("-2").is_ok());
423        assert!(str_as_numeric_lit("-1.23").is_ok());
424        assert!(str_as_numeric_lit("0.0175668f").is_ok());
425        assert!(str_as_numeric_lit("8.88268e-19").is_ok());
426        assert!(str_as_numeric_lit("-0.0175668f").is_ok());
427        assert!(str_as_numeric_lit("-8.88268e-19").is_ok());
428        assert!(str_as_numeric_lit("8.88268e19").is_ok());
429        assert!(str_as_numeric_lit("-8.88268e19").is_ok());
430    }
431}