spice/
netlist.rs

1//! Utilities for writing SPICE netlisters for SCIR libraries.
2
3use arcstr::ArcStr;
4use itertools::Itertools;
5use scir::netlist::ConvertibleNetlister;
6use std::collections::{HashMap, HashSet};
7
8use std::io::{Result, Write};
9use std::path::PathBuf;
10
11use crate::{BlackboxElement, Primitive, Spice};
12use scir::schema::Schema;
13use scir::{
14    Cell, ChildId, Library, NetlistCellConversion, NetlistLibConversion, SignalInfo, Slice,
15};
16
17/// A netlist include statement.
18#[derive(Debug, Clone, Eq, PartialEq, Hash, Ord, PartialOrd)]
19pub struct Include {
20    /// The path to include.
21    pub path: PathBuf,
22    /// The section of the provided file to include.
23    pub section: Option<ArcStr>,
24}
25
26impl<T: Into<PathBuf>> From<T> for Include {
27    fn from(value: T) -> Self {
28        Self {
29            path: value.into(),
30            section: None,
31        }
32    }
33}
34
35impl Include {
36    /// Creates a new [`Include`].
37    pub fn new(path: impl Into<PathBuf>) -> Self {
38        Self::from(path)
39    }
40
41    /// Returns a new [`Include`] with the given section.
42    pub fn section(mut self, section: impl Into<ArcStr>) -> Self {
43        self.section = Some(section.into());
44        self
45    }
46}
47
48/// A schema with a SPICE-like netlist format.
49pub trait HasSpiceLikeNetlist: Schema {
50    /// Writes a prelude to the beginning of the output stream.
51    ///
52    /// Should include a newline after if needed.
53    #[allow(unused_variables)]
54    fn write_prelude<W: Write>(&self, out: &mut W, lib: &Library<Self>) -> Result<()> {
55        Ok(())
56    }
57    /// Writes an include statement.
58    ///
59    /// A newline will be added afterward.
60    fn write_include<W: Write>(&self, out: &mut W, include: &Include) -> Result<()>;
61    /// Writes a begin subcircuit statement.
62    ///
63    /// A newline will be added afterward.
64    fn write_start_subckt<W: Write>(
65        &self,
66        out: &mut W,
67        name: &ArcStr,
68        ports: &[&SignalInfo],
69    ) -> Result<()>;
70    /// Writes an end subcircuit statement.
71    ///
72    /// A newline will be added afterward.
73    fn write_end_subckt<W: Write>(&self, out: &mut W, name: &ArcStr) -> Result<()>;
74    /// Writes a SCIR instance.
75    ///
76    /// A newline will be added afterward.
77    fn write_instance<W: Write>(
78        &self,
79        out: &mut W,
80        name: &ArcStr,
81        connections: Vec<ArcStr>,
82        child: &ArcStr,
83    ) -> Result<ArcStr>;
84    /// Writes a primitive instantiation.
85    ///
86    /// A newline will be added afterward.
87    fn write_primitive_inst<W: Write>(
88        &self,
89        out: &mut W,
90        name: &ArcStr,
91        connections: HashMap<ArcStr, Vec<ArcStr>>,
92        primitive: &<Self as Schema>::Primitive,
93    ) -> Result<ArcStr>;
94    /// Writes a slice.
95    ///
96    /// Should not include a newline at the end.
97    fn write_slice<W: Write>(&self, out: &mut W, slice: Slice, info: &SignalInfo) -> Result<()> {
98        if let Some(range) = slice.range() {
99            for i in range.indices() {
100                if i > range.start() {
101                    write!(out, " ")?;
102                }
103                write!(out, "{}[{}]", &info.name, i)?;
104            }
105        } else {
106            write!(out, "{}", &info.name)?;
107        }
108        Ok(())
109    }
110    /// Writes a postlude to the end of the output stream.
111    #[allow(unused_variables)]
112    fn write_postlude<W: Write>(&self, out: &mut W, lib: &Library<Self>) -> Result<()> {
113        Ok(())
114    }
115}
116
117/// An enumeration describing whether the ground node of a testbench should be renamed.
118#[derive(Clone, Debug)]
119pub enum RenameGround {
120    /// The ground node should be renamed to the provided [`ArcStr`].
121    Yes(ArcStr),
122    /// The ground node should not be renamed.
123    No,
124}
125
126/// The type of netlist to be exported.
127#[derive(Clone, Debug, Default)]
128#[enumify::enumify(no_as_ref, no_as_mut)]
129pub enum NetlistKind {
130    /// A netlist that is a collection of cells.
131    #[default]
132    Cells,
133    /// A testbench netlist that should have its top cell inlined and its ground renamed to
134    /// the simulator ground node.
135    Testbench(RenameGround),
136}
137
138/// Configuration for SPICE netlists.
139#[derive(Clone, Debug, Default)]
140pub struct NetlistOptions<'a> {
141    kind: NetlistKind,
142    includes: &'a [Include],
143}
144
145impl<'a> NetlistOptions<'a> {
146    /// Creates a new [`NetlistOptions`].
147    pub fn new(kind: NetlistKind, includes: &'a [Include]) -> Self {
148        Self { kind, includes }
149    }
150}
151
152/// An instance of a netlister.
153pub struct NetlisterInstance<'a, S: Schema, W> {
154    schema: &'a S,
155    lib: &'a Library<S>,
156    out: &'a mut W,
157    opts: NetlistOptions<'a>,
158}
159
160impl<'a, S: Schema, W> NetlisterInstance<'a, S, W> {
161    /// Creates a new [`NetlisterInstance`].
162    pub fn new(
163        schema: &'a S,
164        lib: &'a Library<S>,
165        out: &'a mut W,
166        opts: NetlistOptions<'a>,
167    ) -> Self {
168        Self {
169            schema,
170            lib,
171            out,
172            opts,
173        }
174    }
175}
176
177impl<S: HasSpiceLikeNetlist, W: Write> NetlisterInstance<'_, S, W> {
178    /// Exports a SCIR library to the output stream as a SPICE-like netlist.
179    pub fn export(mut self) -> Result<NetlistLibConversion> {
180        let lib = self.export_library()?;
181        self.out.flush()?;
182        Ok(lib)
183    }
184
185    fn export_library(&mut self) -> Result<NetlistLibConversion> {
186        self.schema.write_prelude(self.out, self.lib)?;
187        for include in self.opts.includes {
188            self.schema.write_include(self.out, include)?;
189            writeln!(self.out)?;
190        }
191        writeln!(self.out)?;
192
193        let mut conv = NetlistLibConversion::new();
194
195        for (id, cell) in self.lib.cells() {
196            conv.cells
197                .insert(id, self.export_cell(cell, self.lib.is_top(id))?);
198        }
199
200        self.schema.write_postlude(self.out, self.lib)?;
201        Ok(conv)
202    }
203
204    fn export_cell(&mut self, cell: &Cell, is_top: bool) -> Result<NetlistCellConversion> {
205        let is_testbench_top = is_top && self.opts.kind.is_testbench();
206
207        let indent = if is_testbench_top { "" } else { "  " };
208
209        let ground = match (is_testbench_top, &self.opts.kind) {
210            (true, NetlistKind::Testbench(RenameGround::Yes(replace_with))) => {
211                let msg = "testbench should have one port: ground";
212                let mut ports = cell.ports();
213                let ground = ports.next().expect(msg);
214                assert!(ports.next().is_none(), "{}", msg);
215                let ground = &cell.signal(ground.signal()).name;
216                Some((ground.clone(), replace_with.clone()))
217            }
218            _ => None,
219        };
220
221        if !is_testbench_top {
222            let ports: Vec<&SignalInfo> = cell
223                .ports()
224                .map(|port| cell.signal(port.signal()))
225                .collect();
226            self.schema
227                .write_start_subckt(self.out, cell.name(), &ports)?;
228            writeln!(self.out, "\n")?;
229        }
230
231        let mut conv = NetlistCellConversion::new();
232        for (id, inst) in cell.instances() {
233            write!(self.out, "{}", indent)?;
234            let mut connections: HashMap<_, _> = inst
235                .connections()
236                .iter()
237                .map(|(k, v)| {
238                    Ok((
239                        k.clone(),
240                        v.parts()
241                            .map(|part| self.make_slice(cell, *part, &ground))
242                            .collect::<Result<Vec<_>>>()?,
243                    ))
244                })
245                .collect::<Result<_>>()?;
246            let name = match inst.child() {
247                ChildId::Cell(child_id) => {
248                    let child = self.lib.cell(child_id);
249                    let ports = child
250                        .ports()
251                        .flat_map(|port| {
252                            let port_name = &child.signal(port.signal()).name;
253                            connections.remove(port_name).unwrap()
254                        })
255                        .collect::<Vec<_>>();
256                    self.schema
257                        .write_instance(self.out, inst.name(), ports, child.name())?
258                }
259                ChildId::Primitive(child_id) => {
260                    let child = self.lib.primitive(child_id);
261                    self.schema
262                        .write_primitive_inst(self.out, inst.name(), connections, child)?
263                }
264            };
265            conv.instances.insert(id, name);
266            writeln!(self.out)?;
267        }
268
269        if !is_testbench_top {
270            writeln!(self.out)?;
271            self.schema.write_end_subckt(self.out, cell.name())?;
272            writeln!(self.out, "\n")?;
273        }
274        Ok(conv)
275    }
276
277    fn make_slice(
278        &mut self,
279        cell: &Cell,
280        slice: Slice,
281        rename_ground: &Option<(ArcStr, ArcStr)>,
282    ) -> Result<ArcStr> {
283        let sig_info = cell.signal(slice.signal());
284        if let Some((signal, replace_with)) = rename_ground
285            && signal == &sig_info.name
286            && slice.range().is_none()
287        {
288            // Ground renaming cannot apply to buses.
289            // TODO assert that the ground port has width 1.
290            return Ok(replace_with.clone());
291        }
292        let mut buf = Vec::new();
293        self.schema.write_slice(&mut buf, slice, sig_info)?;
294        Ok(ArcStr::from(std::str::from_utf8(&buf).expect(
295            "slice should only have UTF8-compatible characters",
296        )))
297    }
298}
299
300impl HasSpiceLikeNetlist for Spice {
301    fn write_prelude<W: Write>(&self, out: &mut W, lib: &Library<Self>) -> std::io::Result<()> {
302        writeln!(out, "* Substrate SPICE library")?;
303        writeln!(
304            out,
305            "* This is a generated file. Be careful when editing manually: this file may be overwritten.\n"
306        )?;
307
308        for (_, p) in lib.primitives() {
309            if let Primitive::RawInstanceWithCell {
310                cell, ports, body, ..
311            } = p
312            {
313                write!(out, ".SUBCKT {}", cell)?;
314                for port in ports {
315                    write!(out, " {}", port)?;
316                }
317                writeln!(out)?;
318                writeln!(out, "{}", body)?;
319                self.write_end_subckt(out, cell)?;
320                writeln!(out)?;
321            }
322        }
323
324        let includes = lib
325            .primitives()
326            .filter_map(|p| {
327                if let Primitive::RawInstanceWithInclude { netlist, .. } = p.1 {
328                    Some(netlist.clone())
329                } else {
330                    None
331                }
332            })
333            .collect::<HashSet<_>>();
334        // sort paths before including them to ensure stable output
335        for include in includes.iter().sorted() {
336            writeln!(out, ".INCLUDE {:?}", include)?;
337        }
338
339        Ok(())
340    }
341
342    fn write_include<W: Write>(&self, out: &mut W, include: &Include) -> std::io::Result<()> {
343        if let Some(section) = &include.section {
344            write!(out, ".LIB {:?} {}", include.path, section)?;
345        } else {
346            write!(out, ".INCLUDE {:?}", include.path)?;
347        }
348        Ok(())
349    }
350
351    fn write_start_subckt<W: Write>(
352        &self,
353        out: &mut W,
354        name: &ArcStr,
355        ports: &[&SignalInfo],
356    ) -> std::io::Result<()> {
357        write!(out, ".SUBCKT {}", name)?;
358        for sig in ports {
359            if let Some(width) = sig.width {
360                for i in 0..width {
361                    write!(out, " {}[{}]", sig.name, i)?;
362                }
363            } else {
364                write!(out, " {}", sig.name)?;
365            }
366        }
367        Ok(())
368    }
369
370    fn write_end_subckt<W: Write>(&self, out: &mut W, name: &ArcStr) -> std::io::Result<()> {
371        write!(out, ".ENDS {}", name)
372    }
373
374    fn write_instance<W: Write>(
375        &self,
376        out: &mut W,
377        name: &ArcStr,
378        connections: Vec<ArcStr>,
379        child: &ArcStr,
380    ) -> std::io::Result<ArcStr> {
381        let name = arcstr::format!("X{}", name);
382        write!(out, "{}", name)?;
383
384        for connection in connections {
385            write!(out, " {}", connection)?;
386        }
387
388        write!(out, " {}", child)?;
389
390        Ok(name)
391    }
392
393    fn write_primitive_inst<W: Write>(
394        &self,
395        out: &mut W,
396        name: &ArcStr,
397        mut connections: HashMap<ArcStr, Vec<ArcStr>>,
398        primitive: &<Self as Schema>::Primitive,
399    ) -> std::io::Result<ArcStr> {
400        let name = match &primitive {
401            Primitive::Res2 { value, params } => {
402                let name = arcstr::format!("R{}", name);
403                write!(out, "{}", name)?;
404                for port in ["1", "2"] {
405                    for part in connections
406                        .remove(port)
407                        .unwrap_or_else(|| panic!("res2 instance `{name}` must connect to all ports; missing connection to port `{port}`"))
408                    {
409                        write!(out, " {}", part)?;
410                    }
411                }
412                write!(out, " {value}")?;
413                for (key, value) in params.iter().sorted_by_key(|(key, _)| *key) {
414                    write!(out, " {key}={value}")?;
415                }
416                name
417            }
418            Primitive::Cap2 { value } => {
419                let name = arcstr::format!("C{}", name);
420                write!(out, "{}", name)?;
421                for port in ["1", "2"] {
422                    for part in connections.remove(port).unwrap() {
423                        write!(out, " {}", part)?;
424                    }
425                }
426                write!(out, " {value}")?;
427                name
428            }
429            Primitive::Diode2 {
430                model: mname,
431                params,
432            } => {
433                let name = arcstr::format!("D{}", name);
434                write!(out, "{}", name)?;
435                for port in ["1", "2"] {
436                    for part in connections
437                        .remove(port)
438                        .unwrap_or_else(|| panic!("diode2 instance `{name}` must connect to all ports; missing connection to port `{port}`"))
439                    {
440                        write!(out, " {}", part)?;
441                    }
442                }
443                write!(out, " {}", mname)?;
444                for (key, value) in params.iter().sorted_by_key(|(key, _)| *key) {
445                    write!(out, " {key}={value}")?;
446                }
447                name
448            }
449            Primitive::Bjt {
450                model: mname,
451                params,
452                has_substrate_port,
453            } => {
454                let name = arcstr::format!("Q{}", name);
455                write!(out, "{}", name)?;
456                for &port in if *has_substrate_port {
457                    ["NC", "NB", "NE", "NS"].iter()
458                } else {
459                    ["NC", "NB", "NE"].iter()
460                } {
461                    for part in connections.remove(port).unwrap() {
462                        write!(out, " {}", part)?;
463                    }
464                }
465                write!(out, " {}", mname)?;
466                for (key, value) in params.iter().sorted_by_key(|(key, _)| *key) {
467                    write!(out, " {key}={value}")?;
468                }
469                name
470            }
471            Primitive::Mos {
472                model: mname,
473                params,
474            } => {
475                let name = arcstr::format!("M{}", name);
476                write!(out, "{}", name)?;
477                for port in ["D", "G", "S", "B"] {
478                    for part in connections.remove(port).unwrap() {
479                        write!(out, " {}", part)?;
480                    }
481                }
482                write!(out, " {}", mname)?;
483                for (key, value) in params.iter().sorted_by_key(|(key, _)| *key) {
484                    write!(out, " {key}={value}")?;
485                }
486                name
487            }
488            Primitive::RawInstance { cell, ports, .. }
489            | Primitive::RawInstanceWithCell { cell, ports, .. }
490            | Primitive::RawInstanceWithInclude { cell, ports, .. } => {
491                let default_params = HashMap::new();
492                let params = match &primitive {
493                    Primitive::RawInstance { params, .. }
494                    | Primitive::RawInstanceWithCell { params, .. } => params,
495                    _ => &default_params,
496                };
497                let name = arcstr::format!("X{}", name);
498                write!(out, "{}", name)?;
499                for port in ports {
500                    for part in connections.remove(port).unwrap() {
501                        write!(out, " {}", part)?;
502                    }
503                }
504                write!(out, " {}", cell)?;
505                for (key, value) in params.iter().sorted_by_key(|(key, _)| *key) {
506                    write!(out, " {key}={value}")?;
507                }
508                name
509            }
510            Primitive::BlackboxInstance { contents } => {
511                // TODO: See if there is a way to translate the name based on the
512                // contents, or make documentation explaining that blackbox instances
513                // cannot be addressed by path.
514                for elem in &contents.elems {
515                    match elem {
516                        BlackboxElement::InstanceName => write!(out, "{}", name)?,
517                        BlackboxElement::RawString(s) => write!(out, "{}", s)?,
518                        BlackboxElement::Port(p) => {
519                            for part in connections.get(p).unwrap() {
520                                write!(out, "{}", part)?
521                            }
522                        }
523                    }
524                }
525                name.clone()
526            }
527        };
528        writeln!(out)?;
529        Ok(name)
530    }
531}
532
533impl ConvertibleNetlister<Spice> for Spice {
534    type Error = std::io::Error;
535    type Options<'a> = NetlistOptions<'a>;
536
537    fn write_scir_netlist<W: Write>(
538        &self,
539        lib: &Library<Spice>,
540        out: &mut W,
541        opts: Self::Options<'_>,
542    ) -> std::result::Result<NetlistLibConversion, Self::Error> {
543        NetlisterInstance::new(self, lib, out, opts).export()
544    }
545}
546
547impl substrate::schematic::netlist::ConvertibleNetlister<Spice> for Spice {}