From e07ca6896acfd8e504a6ecd10b59fe483934a64a Mon Sep 17 00:00:00 2001 From: rohanku Date: Fri, 3 Nov 2023 14:43:52 -0700 Subject: [PATCH 1/3] start fixing spice and adding helper functions for netlisting --- .../design_inverter/pw1200/netlist.spice | 19 + .../tests/design_inverter/pw1200/ngspice.err | 9 + .../tests/design_inverter/pw1200/ngspice.log | 6 + .../tests/netlist_vdivider/vdivider.spice | 11 + libs/scir/src/netlist.rs | 305 ------------ libs/spice/src/lib.rs | 136 +----- libs/spice/src/netlist.rs | 444 ++++++++++++++++++ tools/ngspice/src/lib.rs | 2 +- 8 files changed, 493 insertions(+), 439 deletions(-) create mode 100644 examples/sky130_inverter/tests/design_inverter/pw1200/netlist.spice create mode 100644 examples/sky130_inverter/tests/design_inverter/pw1200/ngspice.err create mode 100644 examples/sky130_inverter/tests/design_inverter/pw1200/ngspice.log create mode 100644 examples/spice_vdivider/tests/netlist_vdivider/vdivider.spice create mode 100644 libs/spice/src/netlist.rs diff --git a/examples/sky130_inverter/tests/design_inverter/pw1200/netlist.spice b/examples/sky130_inverter/tests/design_inverter/pw1200/netlist.spice new file mode 100644 index 00000000..bca8c0ef --- /dev/null +++ b/examples/sky130_inverter/tests/design_inverter/pw1200/netlist.spice @@ -0,0 +1,19 @@ +* Substrate SPICE library +* This is a generated file. Be careful when editing manually: this file may be overwritten. + +.LIB "/Users/rohan/layout/sky130/pdk/skywater-pdk/libraries/sky130_fd_pr/latest/models/sky130.lib.spice" tt + +.SUBCKT inverter vdd vss din dout + + Xinst0 dout din vss vss sky130_fd_pr__nfet_01v8 l=0.150 nf=1 w=1.200 + Xinst1 dout din vdd vdd sky130_fd_pr__pfet_01v8 l=0.150 nf=1 w=1.200 + +.ENDS inverter + +Xinst0 vdd 0 xinst0_din dout inverter +Vinst1 vdd 0 DC 1.8 +Vinst2 xinst0_din 0PULSE(0 1.8 0.0000000001 0.000000000001 0.000000000001 0.000000001 0 1) + +.save v(dout) + +.tran 0.00000000001 0.000000002 diff --git a/examples/sky130_inverter/tests/design_inverter/pw1200/ngspice.err b/examples/sky130_inverter/tests/design_inverter/pw1200/ngspice.err new file mode 100644 index 00000000..729a0b0d --- /dev/null +++ b/examples/sky130_inverter/tests/design_inverter/pw1200/ngspice.err @@ -0,0 +1,9 @@ + File included as: .inc "/Users/rohan/layout/sky130/pdk/skywater-pdk/libraries/sky130_fd_pr/latest/models/sky130.lib.spice" tt + + File included as: .inc tt + +Error: Could not find include file tt +Error: unknown subckt: xinst0.xinst0 dout xinst0_din 0 0 xinst0.sky130_fd_pr__nfet_01v8 xinst0.l=0.150 xinst0.nf=1 w=1.200 + Simulation interrupted due to error! + +Error: there aren't any circuits loaded. diff --git a/examples/sky130_inverter/tests/design_inverter/pw1200/ngspice.log b/examples/sky130_inverter/tests/design_inverter/pw1200/ngspice.log new file mode 100644 index 00000000..dc177c7a --- /dev/null +++ b/examples/sky130_inverter/tests/design_inverter/pw1200/ngspice.log @@ -0,0 +1,6 @@ + +Note: Compatibility modes selected: ps + + +Circuit: * substrate spice library + diff --git a/examples/spice_vdivider/tests/netlist_vdivider/vdivider.spice b/examples/spice_vdivider/tests/netlist_vdivider/vdivider.spice new file mode 100644 index 00000000..8d90b372 --- /dev/null +++ b/examples/spice_vdivider/tests/netlist_vdivider/vdivider.spice @@ -0,0 +1,11 @@ +* Substrate SPICE library +* This is a generated file. Be careful when editing manually: this file may be overwritten. + + +.SUBCKT vdivider vdd vss dout + + Rinst0 vdd dout 100 + Rinst1 dout vss 200 + +.ENDS vdivider + diff --git a/libs/scir/src/netlist.rs b/libs/scir/src/netlist.rs index 80eae7fa..8b137891 100644 --- a/libs/scir/src/netlist.rs +++ b/libs/scir/src/netlist.rs @@ -1,306 +1 @@ -//! Utilities for writing netlisters for SCIR libraries. -use crate::schema::Schema; -use crate::{Cell, CellId, ChildId, InstanceId, Library, SignalInfo, Slice}; -use arcstr::ArcStr; -use std::collections::HashMap; -use std::io::{Result, Write}; -use std::path::PathBuf; - -/// A netlist include statement. -#[derive(Debug, Clone, Eq, PartialEq, Hash, Ord, PartialOrd)] -pub struct Include { - /// The path to include. - pub path: PathBuf, - /// The section of the provided file to include. - pub section: Option, -} - -impl> From for Include { - fn from(value: T) -> Self { - Self { - path: value.into(), - section: None, - } - } -} - -impl Include { - /// Creates a new [`Include`]. - pub fn new(path: impl Into) -> Self { - Self::from(path) - } - - /// Returns a new [`Include`] with the given section. - pub fn section(mut self, section: impl Into) -> Self { - self.section = Some(section.into()); - self - } -} - -/// Metadata associated with the conversion from a SCIR library to a netlist. -#[derive(Debug, Clone, Default)] -pub struct NetlistLibConversion { - /// Conversion metadata for each cell in the SCIR library. - pub cells: HashMap, -} - -impl NetlistLibConversion { - /// Creates a new [`NetlistLibConversion`]. - pub fn new() -> Self { - Self::default() - } -} - -/// Metadata associated with the conversion from a SCIR cell to a netlisted subcircuit. -#[derive(Debug, Clone, Default)] -pub struct NetlistCellConversion { - /// The netlisted names of SCIR instances. - pub instances: HashMap, -} - -impl NetlistCellConversion { - /// Creates a new [`NetlistCellConversion`]. - pub fn new() -> Self { - Self::default() - } -} - -/// A schema with a SPICE-like netlist format. -pub trait HasSpiceLikeNetlist: Schema { - /// Writes a prelude to the beginning of the output stream. - /// - /// Should include a newline after if needed. - #[allow(unused_variables)] - fn write_prelude(&self, out: &mut W, lib: &Library) -> Result<()> { - Ok(()) - } - /// Writes an include statement. - /// - /// A newline will be added afterwards. - fn write_include(&self, out: &mut W, include: &Include) -> Result<()>; - /// Writes a begin subcircuit statement. - /// - /// A newline will be added afterwards. - fn write_start_subckt( - &self, - out: &mut W, - name: &ArcStr, - ports: &[&SignalInfo], - ) -> Result<()>; - /// Writes an end subcircuit statement. - /// - /// A newline will be added afterwards. - fn write_end_subckt(&self, out: &mut W, name: &ArcStr) -> Result<()>; - /// Writes a SCIR instance. - /// - /// A newline will be added afterwards. - fn write_instance( - &self, - out: &mut W, - name: &ArcStr, - connections: Vec, - child: &ArcStr, - ) -> Result; - /// Writes a primitive instantiation,. - /// - /// A newline will be added afterwards. - fn write_primitive_inst( - &self, - out: &mut W, - name: &ArcStr, - connections: HashMap>, - primitive: &::Primitive, - ) -> Result; - /// Writes a slice. - /// - /// Should not include a newline at the end. - fn write_slice(&self, out: &mut W, slice: Slice, info: &SignalInfo) -> Result<()> { - if let Some(range) = slice.range() { - for i in range.indices() { - if i > range.start() { - write!(out, " ")?; - } - write!(out, "{}[{}]", &info.name, i)?; - } - } else { - write!(out, "{}", &info.name)?; - } - Ok(()) - } - /// Writes a postlude to the end of the output stream. - #[allow(unused_variables)] - fn write_postlude(&self, out: &mut W, lib: &Library) -> Result<()> { - Ok(()) - } -} - -/// An enumeration describing whether the ground node of a testbench should be renamed. -#[derive(Clone, Debug)] -pub enum RenameGround { - /// The ground node should be renamed to the provided [`ArcStr`]. - Yes(ArcStr), - /// The ground node should not be renamed. - No, -} - -/// The type of netlist to be exported. -#[derive(Clone, Debug)] -#[enumify::enumify(no_as_ref, no_as_mut)] -pub enum NetlistKind { - /// A testbench netlist that should have its top cell inlined and its ground renamed to - /// the simulator ground node. - Testbench(RenameGround), - /// A netlist that is a collection of cells. - Cells, -} - -/// An instance of a netlister. -pub struct NetlisterInstance<'a, S: Schema, W> { - kind: NetlistKind, - schema: &'a S, - lib: &'a Library, - includes: &'a [Include], - out: &'a mut W, -} - -impl<'a, S: Schema, W> NetlisterInstance<'a, S, W> { - /// Creates a new [`NetlisterInstance`]. - pub fn new( - kind: NetlistKind, - schema: &'a S, - lib: &'a Library, - includes: &'a [Include], - out: &'a mut W, - ) -> Self { - Self { - kind, - schema, - lib, - includes, - out, - } - } -} - -impl<'a, S: HasSpiceLikeNetlist, W: Write> NetlisterInstance<'a, S, W> { - /// Exports a SCIR library to the output stream as a SPICE-like netlist. - pub fn export(mut self) -> Result { - let lib = self.export_library()?; - self.out.flush()?; - Ok(lib) - } - - fn export_library(&mut self) -> Result { - self.schema.write_prelude(self.out, self.lib)?; - for include in self.includes { - self.schema.write_include(self.out, include)?; - writeln!(self.out)?; - } - writeln!(self.out)?; - - let mut conv = NetlistLibConversion::new(); - - for (id, cell) in self.lib.cells() { - conv.cells - .insert(id, self.export_cell(cell, self.lib.is_top(id))?); - } - - self.schema.write_postlude(self.out, self.lib)?; - Ok(conv) - } - - fn export_cell(&mut self, cell: &Cell, is_top: bool) -> Result { - let is_testbench_top = is_top && self.kind.is_testbench(); - - let indent = if is_testbench_top { "" } else { " " }; - - let ground = match (is_testbench_top, &self.kind) { - (true, NetlistKind::Testbench(RenameGround::Yes(replace_with))) => { - let msg = "testbench should have one port: ground"; - let mut ports = cell.ports(); - let ground = ports.next().expect(msg); - assert!(ports.next().is_none(), "{}", msg); - let ground = &cell.signal(ground.signal()).name; - Some((ground.clone(), replace_with.clone())) - } - _ => None, - }; - - if !is_testbench_top { - let ports: Vec<&SignalInfo> = cell - .ports() - .map(|port| cell.signal(port.signal())) - .collect(); - self.schema - .write_start_subckt(self.out, cell.name(), &ports)?; - writeln!(self.out, "\n")?; - } - - let mut conv = NetlistCellConversion::new(); - for (id, inst) in cell.instances.iter() { - write!(self.out, "{}", indent)?; - let mut connections: HashMap<_, _> = inst - .connections() - .iter() - .map(|(k, v)| { - Ok(( - k.clone(), - v.parts() - .map(|part| self.make_slice(cell, *part, &ground)) - .collect::>>()?, - )) - }) - .collect::>()?; - let name = match inst.child() { - ChildId::Cell(child_id) => { - let child = self.lib.cell(child_id); - let ports = child - .ports() - .flat_map(|port| { - let port_name = &child.signal(port.signal()).name; - connections.remove(port_name).unwrap() - }) - .collect::>(); - self.schema - .write_instance(self.out, inst.name(), ports, child.name())? - } - ChildId::Primitive(child_id) => { - let child = self.lib.primitive(child_id); - self.schema - .write_primitive_inst(self.out, inst.name(), connections, child)? - } - }; - conv.instances.insert(*id, name); - writeln!(self.out)?; - } - - if !is_testbench_top { - writeln!(self.out)?; - self.schema.write_end_subckt(self.out, cell.name())?; - writeln!(self.out, "\n")?; - } - Ok(conv) - } - - fn make_slice( - &mut self, - cell: &Cell, - slice: Slice, - rename_ground: &Option<(ArcStr, ArcStr)>, - ) -> Result { - let sig_info = cell.signal(slice.signal()); - if let Some((signal, replace_with)) = rename_ground { - if signal == &sig_info.name && slice.range().is_none() { - // Ground renaming cannot apply to buses. - // TODO assert that the ground port has width 1. - return Ok(replace_with.clone()); - } - } - let mut buf = Vec::new(); - self.schema.write_slice(&mut buf, slice, sig_info)?; - Ok(ArcStr::from(std::str::from_utf8(&buf).expect( - "slice should only have UTF8-compatible characters", - ))) - } -} diff --git a/libs/spice/src/lib.rs b/libs/spice/src/lib.rs index e1a2434a..d4f2b230 100644 --- a/libs/spice/src/lib.rs +++ b/libs/spice/src/lib.rs @@ -1,12 +1,13 @@ //! SPICE netlist exporter. #![warn(missing_docs)] +use crate::netlist::HasSpiceLikeNetlist; use crate::parser::conv::ScirConverter; use crate::parser::{ParsedSpice, Parser}; + use arcstr::ArcStr; use itertools::Itertools; use rust_decimal::Decimal; -use scir::netlist::HasSpiceLikeNetlist; use scir::schema::{FromSchema, NoSchema, NoSchemaError, Schema}; use scir::{Instance, Library, ParamValue, SignalInfo}; use std::collections::{HashMap, HashSet}; @@ -17,6 +18,7 @@ use substrate::io::SchematicType; use substrate::schematic::primitives::Resistor; use substrate::schematic::PrimitiveSchematic; +pub mod netlist; pub mod parser; #[cfg(test)] mod tests; @@ -198,135 +200,3 @@ impl PrimitiveSchematic for Resistor { prim } } - -impl HasSpiceLikeNetlist for Spice { - fn write_prelude(&self, out: &mut W, _lib: &Library) -> std::io::Result<()> { - writeln!(out, "* Substrate SPICE library")?; - writeln!(out, "* This is a generated file. Be careful when editing manually: this file may be overwritten.\n")?; - Ok(()) - } - - fn write_include( - &self, - out: &mut W, - include: &scir::netlist::Include, - ) -> std::io::Result<()> { - if let Some(section) = &include.section { - write!(out, ".LIB {:?} {}", include.path, section)?; - } else { - write!(out, ".INCLUDE {:?}", include.path)?; - } - Ok(()) - } - - fn write_start_subckt( - &self, - out: &mut W, - name: &ArcStr, - ports: &[&SignalInfo], - ) -> std::io::Result<()> { - write!(out, ".SUBCKT {}", name)?; - for sig in ports { - if let Some(width) = sig.width { - for i in 0..width { - write!(out, " {}[{}]", sig.name, i)?; - } - } else { - write!(out, " {}", sig.name)?; - } - } - Ok(()) - } - - fn write_end_subckt(&self, out: &mut W, name: &ArcStr) -> std::io::Result<()> { - write!(out, ".ENDS {}", name) - } - - fn write_instance( - &self, - out: &mut W, - name: &ArcStr, - connections: Vec, - child: &ArcStr, - ) -> std::io::Result { - let name = arcstr::format!("X{}", name); - write!(out, "{}", name)?; - - for connection in connections { - write!(out, " {}", connection)?; - } - - write!(out, " {}", child)?; - - Ok(name) - } - - fn write_primitive_inst( - &self, - out: &mut W, - name: &ArcStr, - mut connections: HashMap>, - primitive: &::Primitive, - ) -> std::io::Result { - let name = match &primitive { - Primitive::Res2 { value } => { - let name = arcstr::format!("R{}", name); - write!(out, "{}", name)?; - for port in ["1", "2"] { - for part in connections.remove(port).unwrap() { - write!(out, " {}", part)?; - } - } - write!(out, " {value}")?; - name - } - Primitive::Mos { mname } => { - let name = arcstr::format!("M{}", name); - write!(out, "{}", name)?; - for port in ["D", "G", "S", "B"] { - for part in connections.remove(port).unwrap() { - write!(out, " {}", part)?; - } - } - write!(out, " {}", mname)?; - name - } - Primitive::RawInstance { - cell, - ports, - params, - } => { - let name = arcstr::format!("X{}", name); - write!(out, "{}", name)?; - for port in ports { - for part in connections.remove(port).unwrap() { - write!(out, " {}", part)?; - } - } - write!(out, " {}", cell)?; - for (key, value) in params.iter().sorted_by_key(|(key, _)| *key) { - write!(out, " {key}={value}")?; - } - name - } - Primitive::BlackboxInstance { contents } => { - // TODO: See if there is a way to translate the name based on the - // contents, or make documentation explaining that blackbox instances - // cannot be addressed by path. - for elem in &contents.elems { - match elem { - BlackboxElement::InstanceName => write!(out, "{}", name)?, - BlackboxElement::RawString(s) => write!(out, "{}", s)?, - BlackboxElement::Port(p) => { - for part in connections.get(p).unwrap() { - write!(out, "{}", part)? - } - } - } - } - name.clone() - } - }; - Ok(name) - } -} diff --git a/libs/spice/src/netlist.rs b/libs/spice/src/netlist.rs new file mode 100644 index 00000000..bce54aa7 --- /dev/null +++ b/libs/spice/src/netlist.rs @@ -0,0 +1,444 @@ +//! Utilities for writing SPICE netlisters for SCIR libraries. + +use arcstr::ArcStr; +use std::collections::HashMap; +use std::io::{Result, Write}; +use std::path::PathBuf; + +use crate::{BlackboxElement, Primitive, Spice}; +use scir::schema::Schema; +use scir::{Cell, CellId, ChildId, InstanceId, Library, SignalInfo, Slice}; +use substrate::context::Context; +use substrate::schematic::Schematic; + +/// A netlist include statement. +#[derive(Debug, Clone, Eq, PartialEq, Hash, Ord, PartialOrd)] +pub struct Include { + /// The path to include. + pub path: PathBuf, + /// The section of the provided file to include. + pub section: Option, +} + +impl> From for Include { + fn from(value: T) -> Self { + Self { + path: value.into(), + section: None, + } + } +} + +impl Include { + /// Creates a new [`Include`]. + pub fn new(path: impl Into) -> Self { + Self::from(path) + } + + /// Returns a new [`Include`] with the given section. + pub fn section(mut self, section: impl Into) -> Self { + self.section = Some(section.into()); + self + } +} + +/// Metadata associated with the conversion from a SCIR library to a netlist. +#[derive(Debug, Clone, Default)] +pub struct NetlistLibConversion { + /// Conversion metadata for each cell in the SCIR library. + pub cells: HashMap, +} + +impl NetlistLibConversion { + /// Creates a new [`NetlistLibConversion`]. + pub fn new() -> Self { + Self::default() + } +} + +/// Metadata associated with the conversion from a SCIR cell to a netlisted subcircuit. +#[derive(Debug, Clone, Default)] +pub struct NetlistCellConversion { + /// The netlisted names of SCIR instances. + pub instances: HashMap, +} + +impl NetlistCellConversion { + /// Creates a new [`NetlistCellConversion`]. + pub fn new() -> Self { + Self::default() + } +} + +/// A schema with a SPICE-like netlist format. +pub trait HasSpiceLikeNetlist: Schema { + /// Writes a prelude to the beginning of the output stream. + /// + /// Should include a newline after if needed. + #[allow(unused_variables)] + fn write_prelude(&self, out: &mut W, lib: &Library) -> Result<()> { + Ok(()) + } + /// Writes an include statement. + /// + /// A newline will be added afterwards. + fn write_include(&self, out: &mut W, include: &Include) -> Result<()>; + /// Writes a begin subcircuit statement. + /// + /// A newline will be added afterwards. + fn write_start_subckt( + &self, + out: &mut W, + name: &ArcStr, + ports: &[&SignalInfo], + ) -> Result<()>; + /// Writes an end subcircuit statement. + /// + /// A newline will be added afterwards. + fn write_end_subckt(&self, out: &mut W, name: &ArcStr) -> Result<()>; + /// Writes a SCIR instance. + /// + /// A newline will be added afterwards. + fn write_instance( + &self, + out: &mut W, + name: &ArcStr, + connections: Vec, + child: &ArcStr, + ) -> Result; + /// Writes a primitive instantiation,. + /// + /// A newline will be added afterwards. + fn write_primitive_inst( + &self, + out: &mut W, + name: &ArcStr, + connections: HashMap>, + primitive: &::Primitive, + ) -> Result; + /// Writes a slice. + /// + /// Should not include a newline at the end. + fn write_slice(&self, out: &mut W, slice: Slice, info: &SignalInfo) -> Result<()> { + if let Some(range) = slice.range() { + for i in range.indices() { + if i > range.start() { + write!(out, " ")?; + } + write!(out, "{}[{}]", &info.name, i)?; + } + } else { + write!(out, "{}", &info.name)?; + } + Ok(()) + } + /// Writes a postlude to the end of the output stream. + #[allow(unused_variables)] + fn write_postlude(&self, out: &mut W, lib: &Library) -> Result<()> { + Ok(()) + } +} + +/// An enumeration describing whether the ground node of a testbench should be renamed. +#[derive(Clone, Debug)] +pub enum RenameGround { + /// The ground node should be renamed to the provided [`ArcStr`]. + Yes(ArcStr), + /// The ground node should not be renamed. + No, +} + +/// The type of netlist to be exported. +#[derive(Clone, Debug)] +#[enumify::enumify(no_as_ref, no_as_mut)] +pub enum NetlistKind { + /// A testbench netlist that should have its top cell inlined and its ground renamed to + /// the simulator ground node. + Testbench(RenameGround), + /// A netlist that is a collection of cells. + Cells, +} + +/// An instance of a netlister. +pub struct NetlisterInstance<'a, S: Schema, W> { + kind: NetlistKind, + schema: &'a S, + lib: &'a Library, + includes: &'a [Include], + out: &'a mut W, +} + +impl<'a, S: Schema, W> NetlisterInstance<'a, S, W> { + /// Creates a new [`NetlisterInstance`]. + pub fn new( + kind: NetlistKind, + schema: &'a S, + lib: &'a Library, + includes: &'a [Include], + out: &'a mut W, + ) -> Self { + Self { + kind, + schema, + lib, + includes, + out, + } + } +} + +impl<'a, S: HasSpiceLikeNetlist, W: Write> NetlisterInstance<'a, S, W> { + /// Exports a SCIR library to the output stream as a SPICE-like netlist. + pub fn export(mut self) -> Result { + let lib = self.export_library()?; + self.out.flush()?; + Ok(lib) + } + + fn export_library(&mut self) -> Result { + self.schema.write_prelude(self.out, self.lib)?; + for include in self.includes { + self.schema.write_include(self.out, include)?; + writeln!(self.out)?; + } + writeln!(self.out)?; + + let mut conv = NetlistLibConversion::new(); + + for (id, cell) in self.lib.cells() { + conv.cells + .insert(id, self.export_cell(cell, self.lib.is_top(id))?); + } + + self.schema.write_postlude(self.out, self.lib)?; + Ok(conv) + } + + fn export_cell(&mut self, cell: &Cell, is_top: bool) -> Result { + let is_testbench_top = is_top && self.kind.is_testbench(); + + let indent = if is_testbench_top { "" } else { " " }; + + let ground = match (is_testbench_top, &self.kind) { + (true, NetlistKind::Testbench(RenameGround::Yes(replace_with))) => { + let msg = "testbench should have one port: ground"; + let mut ports = cell.ports(); + let ground = ports.next().expect(msg); + assert!(ports.next().is_none(), "{}", msg); + let ground = &cell.signal(ground.signal()).name; + Some((ground.clone(), replace_with.clone())) + } + _ => None, + }; + + if !is_testbench_top { + let ports: Vec<&SignalInfo> = cell + .ports() + .map(|port| cell.signal(port.signal())) + .collect(); + self.schema + .write_start_subckt(self.out, cell.name(), &ports)?; + writeln!(self.out, "\n")?; + } + + let mut conv = NetlistCellConversion::new(); + for (id, inst) in cell.instances.iter() { + write!(self.out, "{}", indent)?; + let mut connections: HashMap<_, _> = inst + .connections() + .iter() + .map(|(k, v)| { + Ok(( + k.clone(), + v.parts() + .map(|part| self.make_slice(cell, *part, &ground)) + .collect::>>()?, + )) + }) + .collect::>()?; + let name = match inst.child() { + ChildId::Cell(child_id) => { + let child = self.lib.cell(child_id); + let ports = child + .ports() + .flat_map(|port| { + let port_name = &child.signal(port.signal()).name; + connections.remove(port_name).unwrap() + }) + .collect::>(); + self.schema + .write_instance(self.out, inst.name(), ports, child.name())? + } + ChildId::Primitive(child_id) => { + let child = self.lib.primitive(child_id); + self.schema + .write_primitive_inst(self.out, inst.name(), connections, child)? + } + }; + conv.instances.insert(*id, name); + writeln!(self.out)?; + } + + if !is_testbench_top { + writeln!(self.out)?; + self.schema.write_end_subckt(self.out, cell.name())?; + writeln!(self.out, "\n")?; + } + Ok(conv) + } + + fn make_slice( + &mut self, + cell: &Cell, + slice: Slice, + rename_ground: &Option<(ArcStr, ArcStr)>, + ) -> Result { + let sig_info = cell.signal(slice.signal()); + if let Some((signal, replace_with)) = rename_ground { + if signal == &sig_info.name && slice.range().is_none() { + // Ground renaming cannot apply to buses. + // TODO assert that the ground port has width 1. + return Ok(replace_with.clone()); + } + } + let mut buf = Vec::new(); + self.schema.write_slice(&mut buf, slice, sig_info)?; + Ok(ArcStr::from(std::str::from_utf8(&buf).expect( + "slice should only have UTF8-compatible characters", + ))) + } +} + +impl HasSpiceLikeNetlist for Spice { + fn write_prelude(&self, out: &mut W, _lib: &Library) -> std::io::Result<()> { + writeln!(out, "* Substrate SPICE library")?; + writeln!(out, "* This is a generated file. Be careful when editing manually: this file may be overwritten.\n")?; + Ok(()) + } + + fn write_include(&self, out: &mut W, include: &Include) -> std::io::Result<()> { + if let Some(section) = &include.section { + write!(out, ".LIB {:?} {}", include.path, section)?; + } else { + write!(out, ".INCLUDE {:?}", include.path)?; + } + Ok(()) + } + + fn write_start_subckt( + &self, + out: &mut W, + name: &ArcStr, + ports: &[&SignalInfo], + ) -> std::io::Result<()> { + write!(out, ".SUBCKT {}", name)?; + for sig in ports { + if let Some(width) = sig.width { + for i in 0..width { + write!(out, " {}[{}]", sig.name, i)?; + } + } else { + write!(out, " {}", sig.name)?; + } + } + Ok(()) + } + + fn write_end_subckt(&self, out: &mut W, name: &ArcStr) -> std::io::Result<()> { + write!(out, ".ENDS {}", name) + } + + fn write_instance( + &self, + out: &mut W, + name: &ArcStr, + connections: Vec, + child: &ArcStr, + ) -> std::io::Result { + let name = arcstr::format!("X{}", name); + write!(out, "{}", name)?; + + for connection in connections { + write!(out, " {}", connection)?; + } + + write!(out, " {}", child)?; + + Ok(name) + } + + fn write_primitive_inst( + &self, + out: &mut W, + name: &ArcStr, + mut connections: HashMap>, + primitive: &::Primitive, + ) -> std::io::Result { + let name = match &primitive { + Primitive::Res2 { value } => { + let name = arcstr::format!("R{}", name); + write!(out, "{}", name)?; + for port in ["1", "2"] { + for part in connections.remove(port).unwrap() { + write!(out, " {}", part)?; + } + } + write!(out, " {value}")?; + name + } + Primitive::Mos { mname } => { + let name = arcstr::format!("M{}", name); + write!(out, "{}", name)?; + for port in ["D", "G", "S", "B"] { + for part in connections.remove(port).unwrap() { + write!(out, " {}", part)?; + } + } + write!(out, " {}", mname)?; + name + } + Primitive::RawInstance { + cell, + ports, + params, + } => { + let name = arcstr::format!("X{}", name); + write!(out, "{}", name)?; + for port in ports { + for part in connections.remove(port).unwrap() { + write!(out, " {}", part)?; + } + } + write!(out, " {}", cell)?; + for (key, value) in params.iter().sorted_by_key(|(key, _)| *key) { + write!(out, " {key}={value}")?; + } + name + } + Primitive::BlackboxInstance { contents } => { + // TODO: See if there is a way to translate the name based on the + // contents, or make documentation explaining that blackbox instances + // cannot be addressed by path. + for elem in &contents.elems { + match elem { + BlackboxElement::InstanceName => write!(out, "{}", name)?, + BlackboxElement::RawString(s) => write!(out, "{}", s)?, + BlackboxElement::Port(p) => { + for part in connections.get(p).unwrap() { + write!(out, "{}", part)? + } + } + } + } + name.clone() + } + }; + Ok(name) + } +} + +impl Spice { + pub fn write_tb>(ctx: &Context, block: B) { + let scir = ctx.export_scir() + } +} \ No newline at end of file diff --git a/tools/ngspice/src/lib.rs b/tools/ngspice/src/lib.rs index b0fc0483..b9b9bc85 100644 --- a/tools/ngspice/src/lib.rs +++ b/tools/ngspice/src/lib.rs @@ -725,7 +725,7 @@ impl HasSpiceLikeNetlist for Ngspice { Vsource::Pulse(pulse) => { write!( out, - "PULSE({} {} {} {} {} {} {} {})", + " PULSE({} {} {} {} {} {} {} {})", pulse.val0, pulse.val1, pulse.delay.unwrap_or_default(), From 8107835087f999ba7053923989aa86b38948f886 Mon Sep 17 00:00:00 2001 From: rohanku Date: Fri, 3 Nov 2023 17:36:41 -0700 Subject: [PATCH 2/3] fix errors --- Cargo.lock | 2 +- libs/scir/Cargo.toml | 1 - libs/scir/src/lib.rs | 30 +++++- libs/scir/src/netlist.rs | 1 - libs/scir/src/tests.rs | 209 ------------------------------------ libs/spice/Cargo.toml | 1 + libs/spice/src/lib.rs | 5 +- libs/spice/src/netlist.rs | 134 +++++++++++++++-------- libs/spice/src/tests.rs | 218 +++++++++++++++++++++++++++++++++++++- tests/src/hard_macro.rs | 15 +-- tests/src/netlist.rs | 33 +++--- tools/ngspice/src/lib.rs | 17 +-- tools/ngspice/src/tran.rs | 2 +- tools/spectre/src/lib.rs | 17 +-- tools/spectre/src/tran.rs | 2 +- 15 files changed, 383 insertions(+), 304 deletions(-) delete mode 100644 libs/scir/src/netlist.rs diff --git a/Cargo.lock b/Cargo.lock index 3d8d5c89..982a1df5 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2180,7 +2180,6 @@ dependencies = [ "diagnostics", "enumify", "indexmap 2.0.0", - "itertools 0.11.0", "rust_decimal", "rust_decimal_macros", "serde", @@ -2387,6 +2386,7 @@ name = "spice" version = "0.5.0" dependencies = [ "arcstr", + "enumify", "itertools 0.11.0", "nom", "rust_decimal", diff --git a/libs/scir/Cargo.toml b/libs/scir/Cargo.toml index 6f9c9d66..756bf4d2 100644 --- a/libs/scir/Cargo.toml +++ b/libs/scir/Cargo.toml @@ -11,7 +11,6 @@ tracing = "0.1" serde = "1" indexmap = { version = "2", features = ["serde"] } thiserror = "1" -itertools = "0.11.0" diagnostics = { version = "0.3.0", path = "../diagnostics", registry = "substrate" } uniquify = { version = "0.2.0", path = "../uniquify", registry = "substrate" } diff --git a/libs/scir/src/lib.rs b/libs/scir/src/lib.rs index 21cbb68c..bac0e8e9 100644 --- a/libs/scir/src/lib.rs +++ b/libs/scir/src/lib.rs @@ -37,11 +37,9 @@ use serde::{Deserialize, Serialize}; use tracing::{span, Level}; pub mod merge; -pub mod netlist; pub mod schema; mod slice; -use crate::netlist::NetlistLibConversion; use crate::schema::{FromSchema, NoSchema, NoSchemaError, Schema}; use crate::validation::ValidatorIssue; pub use slice::{Concat, IndexOwned, NamedSlice, NamedSliceOne, Slice, SliceOne, SliceRange}; @@ -799,6 +797,34 @@ pub struct Cell { instance_name_map: HashMap, } +/// Metadata associated with the conversion from a SCIR library to a netlist. +#[derive(Debug, Clone, Default)] +pub struct NetlistLibConversion { + /// Conversion metadata for each cell in the SCIR library. + pub cells: HashMap, +} + +impl NetlistLibConversion { + /// Creates a new [`NetlistLibConversion`]. + pub fn new() -> Self { + Self::default() + } +} + +/// Metadata associated with the conversion from a SCIR cell to a netlisted subcircuit. +#[derive(Debug, Clone, Default)] +pub struct NetlistCellConversion { + /// The netlisted names of SCIR instances. + pub instances: HashMap, +} + +impl NetlistCellConversion { + /// Creates a new [`NetlistCellConversion`]. + pub fn new() -> Self { + Self::default() + } +} + impl LibraryBuilder { /// Creates a new, empty library. pub fn new() -> Self { diff --git a/libs/scir/src/netlist.rs b/libs/scir/src/netlist.rs deleted file mode 100644 index 8b137891..00000000 --- a/libs/scir/src/netlist.rs +++ /dev/null @@ -1 +0,0 @@ - diff --git a/libs/scir/src/tests.rs b/libs/scir/src/tests.rs index 62d12159..82147075 100644 --- a/libs/scir/src/tests.rs +++ b/libs/scir/src/tests.rs @@ -1,8 +1,5 @@ -use itertools::Itertools; -use std::io::Write; use test_log::test; -use crate::netlist::{HasSpiceLikeNetlist, Include, NetlistKind, NetlisterInstance, RenameGround}; use crate::schema::{FromSchema, StringSchema}; use crate::*; @@ -319,209 +316,3 @@ fn name_path_conversion() { ); } } - -#[test] -fn spice_like_netlist() { - pub struct SpiceLikeSchema { - bus_delimiter: (char, char), - } - - impl Schema for SpiceLikeSchema { - type Primitive = ArcStr; - } - - impl HasSpiceLikeNetlist for SpiceLikeSchema { - fn write_include(&self, out: &mut W, include: &Include) -> std::io::Result<()> { - if let Some(section) = &include.section { - write!(out, ".LIB {:?} {}", include.path, section)?; - } else { - write!(out, ".INCLUDE {:?}", include.path)?; - } - Ok(()) - } - - fn write_start_subckt( - &self, - out: &mut W, - name: &ArcStr, - ports: &[&SignalInfo], - ) -> std::io::Result<()> { - let (start, end) = self.bus_delimiter; - write!(out, ".SUBCKT {}", name)?; - for sig in ports { - if let Some(width) = sig.width { - for i in 0..width { - write!(out, " {}{}{}{}", sig.name, start, i, end)?; - } - } else { - write!(out, " {}", sig.name)?; - } - } - Ok(()) - } - - fn write_end_subckt(&self, out: &mut W, name: &ArcStr) -> std::io::Result<()> { - write!(out, ".ENDS {}", name) - } - - fn write_slice( - &self, - out: &mut W, - slice: Slice, - info: &SignalInfo, - ) -> std::io::Result<()> { - let (start, end) = self.bus_delimiter; - if let Some(range) = slice.range() { - for i in range.indices() { - if i > range.start() { - write!(out, " ")?; - } - write!(out, "{}{}{}{}", &info.name, start, i, end)?; - } - } else { - write!(out, "{}", &info.name)?; - } - Ok(()) - } - - fn write_instance( - &self, - out: &mut W, - name: &ArcStr, - connections: Vec, - child: &ArcStr, - ) -> std::io::Result { - write!(out, "{}", name)?; - - for connection in connections { - write!(out, " {}", connection)?; - } - - write!(out, " {}", child)?; - - Ok(name.clone()) - } - - fn write_primitive_inst( - &self, - out: &mut W, - name: &ArcStr, - connections: HashMap>, - primitive: &::Primitive, - ) -> std::io::Result { - write!(out, "{}", name)?; - - let connections = connections - .into_iter() - .sorted_by_key(|(name, _)| name.clone()) - .collect::>(); - - for (_, connection) in connections { - for signal in connection { - write!(out, " {}", signal)?; - } - } - - write!(out, " {}", primitive)?; - - Ok(name.clone()) - } - } - - const N: usize = 3; - - let mut lib = LibraryBuilder::::new(); - - let resistor = lib.add_primitive("resistor".into()); - - let mut dut = Cell::new("dut"); - - let p = dut.add_bus("p", N); - let n = dut.add_bus("n", N); - - for i in 0..N { - let mut resistor = Instance::new(format!("inst_{i}"), resistor); - resistor.connect("p", p.index(i)); - resistor.connect("n", n.index(i)); - dut.add_instance(resistor); - } - - dut.expose_port(p, Direction::InOut); - dut.expose_port(n, Direction::InOut); - - let dut = lib.add_cell(dut); - - let mut tb = Cell::new("tb"); - - let vdd = tb.add_node("vdd"); - let vss = tb.add_node("vss"); - - let mut dut = Instance::new("dut", dut); - dut.connect("p", Concat::new(vec![vdd.into(); 3])); - dut.connect("n", Concat::new(vec![vss.into(); 3])); - tb.add_instance(dut); - - tb.expose_port(vss, Direction::InOut); - let tb = lib.add_cell(tb); - - lib.set_top(tb); - - let lib = lib.build().unwrap(); - - let schema = SpiceLikeSchema { - bus_delimiter: ('<', '|'), - }; - let mut buf = Vec::new(); - let netlister = NetlisterInstance::new( - NetlistKind::Testbench(RenameGround::Yes("0".into())), - &schema, - &lib, - &[], - &mut buf, - ); - - netlister.export().unwrap(); - - let netlist = std::str::from_utf8(&buf).unwrap(); - - println!("{:?}", netlist); - for fragment in [ - r#".SUBCKT dut p<0| p<1| p<2| n<0| n<1| n<2| - - inst_0 n<0| p<0| resistor - inst_1 n<1| p<1| resistor - inst_2 n<2| p<2| resistor - -.ENDS dut"#, - "dut vdd vdd vdd 0 0 0 dut", - ] { - println!("{:?}", fragment); - assert!(netlist.contains(fragment)); - } - - let mut buf = Vec::new(); - let netlister = NetlisterInstance::new(NetlistKind::Cells, &schema, &lib, &[], &mut buf); - - netlister.export().unwrap(); - - let netlist = std::str::from_utf8(&buf).unwrap(); - - println!("{:?}", netlist); - for fragment in [ - r#".SUBCKT dut p<0| p<1| p<2| n<0| n<1| n<2| - - inst_0 n<0| p<0| resistor - inst_1 n<1| p<1| resistor - inst_2 n<2| p<2| resistor - -.ENDS dut"#, - r#".SUBCKT tb vss - - dut vdd vdd vdd vss vss vss dut - -.ENDS tb"#, - ] { - println!("{:?}", fragment); - assert!(netlist.contains(fragment)); - } -} diff --git a/libs/spice/Cargo.toml b/libs/spice/Cargo.toml index 21ba699b..87ea47a2 100644 --- a/libs/spice/Cargo.toml +++ b/libs/spice/Cargo.toml @@ -14,3 +14,4 @@ rust_decimal_macros = "1" scir = { version = "0.6.0", registry = "substrate", path = "../scir" } substrate = { version = "0.7.0", registry = "substrate", path = "../../substrate" } +enumify = { version = "0.1.0", path = "../enumify", registry = "substrate" } diff --git a/libs/spice/src/lib.rs b/libs/spice/src/lib.rs index d4f2b230..bd6a6c2c 100644 --- a/libs/spice/src/lib.rs +++ b/libs/spice/src/lib.rs @@ -1,17 +1,14 @@ //! SPICE netlist exporter. #![warn(missing_docs)] -use crate::netlist::HasSpiceLikeNetlist; use crate::parser::conv::ScirConverter; use crate::parser::{ParsedSpice, Parser}; use arcstr::ArcStr; -use itertools::Itertools; use rust_decimal::Decimal; use scir::schema::{FromSchema, NoSchema, NoSchemaError, Schema}; -use scir::{Instance, Library, ParamValue, SignalInfo}; +use scir::{Instance, ParamValue}; use std::collections::{HashMap, HashSet}; -use std::io::prelude::*; use std::path::Path; use substrate::block::Block; use substrate::io::SchematicType; diff --git a/libs/spice/src/netlist.rs b/libs/spice/src/netlist.rs index bce54aa7..c1e3ef87 100644 --- a/libs/spice/src/netlist.rs +++ b/libs/spice/src/netlist.rs @@ -1,14 +1,20 @@ //! Utilities for writing SPICE netlisters for SCIR libraries. use arcstr::ArcStr; +use itertools::Itertools; use std::collections::HashMap; +use std::fs::File; use std::io::{Result, Write}; -use std::path::PathBuf; +use std::path::{Path, PathBuf}; +use std::sync::Arc; use crate::{BlackboxElement, Primitive, Spice}; use scir::schema::Schema; -use scir::{Cell, CellId, ChildId, InstanceId, Library, SignalInfo, Slice}; +use scir::{ + Cell, ChildId, Library, NetlistCellConversion, NetlistLibConversion, SignalInfo, Slice, +}; use substrate::context::Context; +use substrate::schematic::conv::RawLib; use substrate::schematic::Schematic; /// A netlist include statement. @@ -42,34 +48,6 @@ impl Include { } } -/// Metadata associated with the conversion from a SCIR library to a netlist. -#[derive(Debug, Clone, Default)] -pub struct NetlistLibConversion { - /// Conversion metadata for each cell in the SCIR library. - pub cells: HashMap, -} - -impl NetlistLibConversion { - /// Creates a new [`NetlistLibConversion`]. - pub fn new() -> Self { - Self::default() - } -} - -/// Metadata associated with the conversion from a SCIR cell to a netlisted subcircuit. -#[derive(Debug, Clone, Default)] -pub struct NetlistCellConversion { - /// The netlisted names of SCIR instances. - pub instances: HashMap, -} - -impl NetlistCellConversion { - /// Creates a new [`NetlistCellConversion`]. - pub fn new() -> Self { - Self::default() - } -} - /// A schema with a SPICE-like netlist format. pub trait HasSpiceLikeNetlist: Schema { /// Writes a prelude to the beginning of the output stream. @@ -149,40 +127,52 @@ pub enum RenameGround { } /// The type of netlist to be exported. -#[derive(Clone, Debug)] +#[derive(Clone, Debug, Default)] #[enumify::enumify(no_as_ref, no_as_mut)] pub enum NetlistKind { + /// A netlist that is a collection of cells. + #[default] + Cells, /// A testbench netlist that should have its top cell inlined and its ground renamed to /// the simulator ground node. Testbench(RenameGround), - /// A netlist that is a collection of cells. - Cells, +} + +/// Configuration for SPICE netlists. +#[derive(Clone, Debug, Default)] +pub struct NetlistOptions<'a> { + kind: NetlistKind, + includes: &'a [Include], +} + +impl<'a> NetlistOptions<'a> { + /// Creates a new [`NetlistOptions`]. + pub fn new(kind: NetlistKind, includes: &'a [Include]) -> Self { + Self { kind, includes } + } } /// An instance of a netlister. pub struct NetlisterInstance<'a, S: Schema, W> { - kind: NetlistKind, schema: &'a S, lib: &'a Library, - includes: &'a [Include], out: &'a mut W, + opts: NetlistOptions<'a>, } impl<'a, S: Schema, W> NetlisterInstance<'a, S, W> { /// Creates a new [`NetlisterInstance`]. pub fn new( - kind: NetlistKind, schema: &'a S, lib: &'a Library, - includes: &'a [Include], out: &'a mut W, + opts: NetlistOptions<'a>, ) -> Self { Self { - kind, schema, lib, - includes, out, + opts, } } } @@ -197,7 +187,7 @@ impl<'a, S: HasSpiceLikeNetlist, W: Write> NetlisterInstance<'a, S, W> { fn export_library(&mut self) -> Result { self.schema.write_prelude(self.out, self.lib)?; - for include in self.includes { + for include in self.opts.includes { self.schema.write_include(self.out, include)?; writeln!(self.out)?; } @@ -215,11 +205,11 @@ impl<'a, S: HasSpiceLikeNetlist, W: Write> NetlisterInstance<'a, S, W> { } fn export_cell(&mut self, cell: &Cell, is_top: bool) -> Result { - let is_testbench_top = is_top && self.kind.is_testbench(); + let is_testbench_top = is_top && self.opts.kind.is_testbench(); let indent = if is_testbench_top { "" } else { " " }; - let ground = match (is_testbench_top, &self.kind) { + let ground = match (is_testbench_top, &self.opts.kind) { (true, NetlistKind::Testbench(RenameGround::Yes(replace_with))) => { let msg = "testbench should have one port: ground"; let mut ports = cell.ports(); @@ -242,7 +232,7 @@ impl<'a, S: HasSpiceLikeNetlist, W: Write> NetlisterInstance<'a, S, W> { } let mut conv = NetlistCellConversion::new(); - for (id, inst) in cell.instances.iter() { + for (id, inst) in cell.instances() { write!(self.out, "{}", indent)?; let mut connections: HashMap<_, _> = inst .connections() @@ -275,7 +265,7 @@ impl<'a, S: HasSpiceLikeNetlist, W: Write> NetlisterInstance<'a, S, W> { .write_primitive_inst(self.out, inst.name(), connections, child)? } }; - conv.instances.insert(*id, name); + conv.instances.insert(id, name); writeln!(self.out)?; } @@ -438,7 +428,57 @@ impl HasSpiceLikeNetlist for Spice { } impl Spice { - pub fn write_tb>(ctx: &Context, block: B) { - let scir = ctx.export_scir() + /// Writes a nestlist of a SPICE library to the provided buffer. + pub fn write_scir_netlist( + &self, + lib: &Library, + out: &mut W, + opts: NetlistOptions<'_>, + ) -> Result { + NetlisterInstance::new(self, lib, out, opts).export() + } + /// Writes a netlist of a SPICE library to a file at the given path. + pub fn write_scir_netlist_to_file( + &self, + lib: &Library, + path: impl AsRef, + opts: NetlistOptions<'_>, + ) -> Result { + let path = path.as_ref(); + if let Some(parent) = path.parent() { + std::fs::create_dir_all(parent)?; + } + let mut f = File::create(path)?; + self.write_scir_netlist(lib, &mut f, opts) + } + /// Writes a SPICE netlist of a Substrate block to the given buffer. + pub fn write_block_netlist, W: Write>( + &self, + ctx: &Context, + block: B, + out: &mut W, + opts: NetlistOptions<'_>, + ) -> substrate::error::Result<(RawLib, NetlistLibConversion)> { + let raw_lib = ctx.export_scir::(block)?; + + let conv = self + .write_scir_netlist(&raw_lib.scir, out, opts) + .map_err(Arc::new)?; + Ok((raw_lib, conv)) } -} \ No newline at end of file + /// Writes a SPICE netlist of a Substrate block to a file at the given path. + pub fn write_block_netlist_to_file>( + &self, + ctx: &Context, + block: B, + path: impl AsRef, + opts: NetlistOptions<'_>, + ) -> substrate::error::Result<(RawLib, NetlistLibConversion)> { + let path = path.as_ref(); + if let Some(parent) = path.parent() { + std::fs::create_dir_all(parent).map_err(Arc::new)?; + } + let mut f = File::create(path).map_err(Arc::new)?; + self.write_block_netlist(ctx, block, &mut f, opts) + } +} diff --git a/libs/spice/src/tests.rs b/libs/spice/src/tests.rs index 91eeccbe..25da4fc7 100644 --- a/libs/spice/src/tests.rs +++ b/libs/spice/src/tests.rs @@ -1,6 +1,13 @@ +use crate::netlist::{ + HasSpiceLikeNetlist, Include, NetlistKind, NetlistOptions, NetlisterInstance, RenameGround, +}; use crate::{BlackboxContents, BlackboxElement, Primitive, Spice}; -use scir::netlist::{NetlistKind, NetlisterInstance}; -use scir::{Cell, Direction, Instance, LibraryBuilder}; +use arcstr::ArcStr; +use itertools::Itertools; +use scir::schema::Schema; +use scir::{Cell, Concat, Direction, IndexOwned, Instance, LibraryBuilder, SignalInfo, Slice}; +use std::collections::HashMap; +use std::io::Write; #[test] fn scir_netlists_correctly() { @@ -34,7 +41,7 @@ fn scir_netlists_correctly() { let lib = lib.build().unwrap(); let mut buf: Vec = Vec::new(); - let netlister = NetlisterInstance::new(NetlistKind::Cells, &Spice, &lib, &[], &mut buf); + let netlister = NetlisterInstance::new(&Spice, &lib, &mut buf, Default::default()); netlister.export().unwrap(); let string = String::from_utf8(buf).unwrap(); println!("{}", string); @@ -47,3 +54,208 @@ fn scir_netlists_correctly() { assert_eq!(string.matches("ENDS").count(), 1); assert_eq!(string.matches("Rblackbox vdd vss 3000").count(), 1); } + +#[test] +fn spice_like_netlist() { + pub struct SpiceLikeSchema { + bus_delimiter: (char, char), + } + + impl Schema for SpiceLikeSchema { + type Primitive = ArcStr; + } + + impl HasSpiceLikeNetlist for SpiceLikeSchema { + fn write_include(&self, out: &mut W, include: &Include) -> std::io::Result<()> { + if let Some(section) = &include.section { + write!(out, ".LIB {:?} {}", include.path, section)?; + } else { + write!(out, ".INCLUDE {:?}", include.path)?; + } + Ok(()) + } + + fn write_start_subckt( + &self, + out: &mut W, + name: &ArcStr, + ports: &[&SignalInfo], + ) -> std::io::Result<()> { + let (start, end) = self.bus_delimiter; + write!(out, ".SUBCKT {}", name)?; + for sig in ports { + if let Some(width) = sig.width { + for i in 0..width { + write!(out, " {}{}{}{}", sig.name, start, i, end)?; + } + } else { + write!(out, " {}", sig.name)?; + } + } + Ok(()) + } + + fn write_end_subckt(&self, out: &mut W, name: &ArcStr) -> std::io::Result<()> { + write!(out, ".ENDS {}", name) + } + + fn write_slice( + &self, + out: &mut W, + slice: Slice, + info: &SignalInfo, + ) -> std::io::Result<()> { + let (start, end) = self.bus_delimiter; + if let Some(range) = slice.range() { + for i in range.indices() { + if i > range.start() { + write!(out, " ")?; + } + write!(out, "{}{}{}{}", &info.name, start, i, end)?; + } + } else { + write!(out, "{}", &info.name)?; + } + Ok(()) + } + + fn write_instance( + &self, + out: &mut W, + name: &ArcStr, + connections: Vec, + child: &ArcStr, + ) -> std::io::Result { + write!(out, "{}", name)?; + + for connection in connections { + write!(out, " {}", connection)?; + } + + write!(out, " {}", child)?; + + Ok(name.clone()) + } + + fn write_primitive_inst( + &self, + out: &mut W, + name: &ArcStr, + connections: HashMap>, + primitive: &::Primitive, + ) -> std::io::Result { + write!(out, "{}", name)?; + + let connections = connections + .into_iter() + .sorted_by_key(|(name, _)| name.clone()) + .collect::>(); + + for (_, connection) in connections { + for signal in connection { + write!(out, " {}", signal)?; + } + } + + write!(out, " {}", primitive)?; + + Ok(name.clone()) + } + } + + const N: usize = 3; + + let mut lib = LibraryBuilder::::new(); + + let resistor = lib.add_primitive("resistor".into()); + + let mut dut = Cell::new("dut"); + + let p = dut.add_bus("p", N); + let n = dut.add_bus("n", N); + + for i in 0..N { + let mut resistor = Instance::new(format!("inst_{i}"), resistor); + resistor.connect("p", p.index(i)); + resistor.connect("n", n.index(i)); + dut.add_instance(resistor); + } + + dut.expose_port(p, Direction::InOut); + dut.expose_port(n, Direction::InOut); + + let dut = lib.add_cell(dut); + + let mut tb = Cell::new("tb"); + + let vdd = tb.add_node("vdd"); + let vss = tb.add_node("vss"); + + let mut dut = Instance::new("dut", dut); + dut.connect("p", Concat::new(vec![vdd.into(); 3])); + dut.connect("n", Concat::new(vec![vss.into(); 3])); + tb.add_instance(dut); + + tb.expose_port(vss, Direction::InOut); + let tb = lib.add_cell(tb); + + lib.set_top(tb); + + let lib = lib.build().unwrap(); + + let schema = SpiceLikeSchema { + bus_delimiter: ('<', '|'), + }; + let mut buf = Vec::new(); + let netlister = NetlisterInstance::new( + &schema, + &lib, + &mut buf, + NetlistOptions::new(NetlistKind::Testbench(RenameGround::Yes("0".into())), &[]), + ); + + netlister.export().unwrap(); + + let netlist = std::str::from_utf8(&buf).unwrap(); + + println!("{:?}", netlist); + for fragment in [ + r#".SUBCKT dut p<0| p<1| p<2| n<0| n<1| n<2| + + inst_0 n<0| p<0| resistor + inst_1 n<1| p<1| resistor + inst_2 n<2| p<2| resistor + +.ENDS dut"#, + "dut vdd vdd vdd 0 0 0 dut", + ] { + println!("{:?}", fragment); + assert!(netlist.contains(fragment)); + } + + let mut buf = Vec::new(); + let netlister = NetlisterInstance::new(&schema, &lib, &mut buf, Default::default()); + + netlister.export().unwrap(); + + let netlist = std::str::from_utf8(&buf).unwrap(); + + println!("{:?}", netlist); + for fragment in [ + r#".SUBCKT dut p<0| p<1| p<2| n<0| n<1| n<2| + + inst_0 n<0| p<0| resistor + inst_1 n<1| p<1| resistor + inst_2 n<2| p<2| resistor + +.ENDS dut"#, + r#".SUBCKT tb vss + + dut vdd vdd vdd vss vss vss dut + +.ENDS tb"#, + ] { + println!("{:?}", fragment); + assert!(netlist.contains(fragment)); + } +} diff --git a/tests/src/hard_macro.rs b/tests/src/hard_macro.rs index 2cdcec0e..635e84f2 100644 --- a/tests/src/hard_macro.rs +++ b/tests/src/hard_macro.rs @@ -1,11 +1,11 @@ use crate::paths::test_data; use crate::shared::buffer::BufferIo; -use scir::netlist::{NetlistKind, NetlisterInstance}; use serde::{Deserialize, Serialize}; use sky130pdk::Sky130Pdk; #[cfg(feature = "spectre")] use spectre::Spectre; +use spice::netlist::NetlisterInstance; use spice::Spice; use substrate::block::Block; use substrate::io::SchematicType; @@ -111,8 +111,9 @@ fn export_hard_macro() { let spice_lib = lib.scir.convert_schema::().unwrap().build().unwrap(); let mut buf: Vec = Vec::new(); - let netlister = NetlisterInstance::new(NetlistKind::Cells, &Spice, &spice_lib, &[], &mut buf); - netlister.export().unwrap(); + Spice + .write_scir_netlist(&spice_lib, &mut buf, Default::default()) + .unwrap(); let string = String::from_utf8(buf).unwrap(); println!("Netlist:\n{}", string); } @@ -148,8 +149,7 @@ fn export_hard_macro_to_spectre() { .unwrap(); let mut buf: Vec = Vec::new(); - let netlister = - NetlisterInstance::new(NetlistKind::Cells, &Spectre {}, &spectre_lib, &[], &mut buf); + let netlister = NetlisterInstance::new(&Spectre {}, &spectre_lib, &mut buf, Default::default()); netlister.export().unwrap(); let string = String::from_utf8(buf).unwrap(); println!("Netlist:\n{}", string); @@ -170,8 +170,9 @@ fn export_inline_hard_macro() { let spice_lib = lib.scir.convert_schema::().unwrap().build().unwrap(); let mut buf: Vec = Vec::new(); - let netlister = NetlisterInstance::new(NetlistKind::Cells, &Spice, &spice_lib, &[], &mut buf); - netlister.export().unwrap(); + Spice + .write_scir_netlist(&spice_lib, &mut buf, Default::default()) + .unwrap(); let string = String::from_utf8(buf).unwrap(); println!("Netlist:\n{}", string); } diff --git a/tests/src/netlist.rs b/tests/src/netlist.rs index a1d86d2f..fad3370d 100644 --- a/tests/src/netlist.rs +++ b/tests/src/netlist.rs @@ -1,8 +1,8 @@ use arcstr::ArcStr; use rust_decimal::Decimal; -use scir::netlist::{NetlistKind, NetlisterInstance}; use scir::*; use spectre::Spectre; +use spice::netlist::{NetlistKind, NetlistOptions, NetlisterInstance}; use spice::{BlackboxContents, BlackboxElement, Spice}; use std::collections::HashMap; use substrate::schematic::schema::Schema; @@ -143,8 +143,9 @@ fn vdivider_blackbox_is_valid() { fn netlist_spice_vdivider() { let lib = vdivider::(); let mut buf: Vec = Vec::new(); - let netlister = NetlisterInstance::new(NetlistKind::Cells, &Spice, &lib, &[], &mut buf); - netlister.export().unwrap(); + Spice + .write_scir_netlist(&lib, &mut buf, Default::default()) + .unwrap(); let string = String::from_utf8(buf).unwrap(); println!("{}", string); @@ -164,15 +165,17 @@ fn netlist_spice_vdivider() { fn netlist_spice_vdivider_is_repeatable() { let lib = vdivider::(); let mut buf: Vec = Vec::new(); - let netlister = NetlisterInstance::new(NetlistKind::Cells, &Spice, &lib, &[], &mut buf); - netlister.export().unwrap(); + Spice + .write_scir_netlist(&lib, &mut buf, Default::default()) + .unwrap(); let golden = String::from_utf8(buf).unwrap(); for i in 0..100 { let lib = vdivider::(); let mut buf: Vec = Vec::new(); - let netlister = NetlisterInstance::new(NetlistKind::Cells, &Spice, &lib, &[], &mut buf); - netlister.export().unwrap(); + Spice + .write_scir_netlist(&lib, &mut buf, Default::default()) + .unwrap(); let attempt = String::from_utf8(buf).unwrap(); assert_eq!( attempt, golden, @@ -185,8 +188,9 @@ fn netlist_spice_vdivider_is_repeatable() { fn netlist_spice_vdivider_blackbox() { let lib = vdivider_blackbox(); let mut buf: Vec = Vec::new(); - let netlister = NetlisterInstance::new(NetlistKind::Cells, &Spice, &lib, &[], &mut buf); - netlister.export().unwrap(); + Spice + .write_scir_netlist(&lib, &mut buf, Default::default()) + .unwrap(); let string = String::from_utf8(buf).unwrap(); println!("{}", string); @@ -208,9 +212,14 @@ fn netlist_spectre_vdivider() { let lib = vdivider::(); let mut buf: Vec = Vec::new(); let includes = Vec::new(); - let netlister = - NetlisterInstance::new(NetlistKind::Cells, &Spectre {}, &lib, &includes, &mut buf); - netlister.export().unwrap(); + NetlisterInstance::new( + &Spectre {}, + &lib, + &mut buf, + NetlistOptions::new(NetlistKind::Cells, &includes), + ) + .export() + .unwrap(); let string = String::from_utf8(buf).unwrap(); println!("{}", string); diff --git a/tools/ngspice/src/lib.rs b/tools/ngspice/src/lib.rs index b9b9bc85..90994427 100644 --- a/tools/ngspice/src/lib.rs +++ b/tools/ngspice/src/lib.rs @@ -15,13 +15,12 @@ use cache::error::TryInnerError; use cache::CacheableWithState; use error::*; use nutlex::parser::Data; -use scir::netlist::{ - HasSpiceLikeNetlist, Include, NetlistKind, NetlistLibConversion, NetlisterInstance, - RenameGround, -}; use scir::schema::{FromSchema, NoSchema, NoSchemaError}; -use scir::{ChildId, Library, SignalInfo, SignalPathTail, SliceOnePath}; +use scir::{ChildId, Library, NetlistLibConversion, SignalInfo, SignalPathTail, SliceOnePath}; use serde::{Deserialize, Serialize}; +use spice::netlist::{ + HasSpiceLikeNetlist, Include, NetlistKind, NetlistOptions, NetlisterInstance, RenameGround, +}; use spice::Spice; use substrate::block::Block; use substrate::execute::Executor; @@ -372,11 +371,13 @@ impl Ngspice { saves.sort(); let netlister = NetlisterInstance::new( - NetlistKind::Testbench(RenameGround::Yes(arcstr::literal!("0"))), self, &ctx.lib.scir, - &includes, &mut w, + NetlistOptions::new( + NetlistKind::Testbench(RenameGround::Yes(arcstr::literal!("0"))), + &includes, + ), ); let conv = netlister.export()?; @@ -671,7 +672,7 @@ impl HasSpiceLikeNetlist for Ngspice { fn write_include( &self, out: &mut W, - include: &scir::netlist::Include, + include: &spice::netlist::Include, ) -> std::io::Result<()> { Spice.write_include(out, include) } diff --git a/tools/ngspice/src/tran.rs b/tools/ngspice/src/tran.rs index f3745ad3..5d14c69f 100644 --- a/tools/ngspice/src/tran.rs +++ b/tools/ngspice/src/tran.rs @@ -3,7 +3,7 @@ use crate::{node_voltage_path, Ngspice, ProbeStmt, SaveStmt}; use arcstr::ArcStr; use rust_decimal::Decimal; -use scir::netlist::NetlistLibConversion; +use scir::NetlistLibConversion; use scir::{NamedSliceOne, SliceOnePath}; use serde::{Deserialize, Serialize}; use std::collections::HashMap; diff --git a/tools/spectre/src/lib.rs b/tools/spectre/src/lib.rs index fe83a8da..9c3b0b61 100644 --- a/tools/spectre/src/lib.rs +++ b/tools/spectre/src/lib.rs @@ -16,13 +16,14 @@ use cache::CacheableWithState; use error::*; use itertools::Itertools; use rust_decimal::Decimal; -use scir::netlist::{ - HasSpiceLikeNetlist, Include, NetlistKind, NetlistLibConversion, NetlisterInstance, - RenameGround, -}; use scir::schema::{FromSchema, NoSchema, NoSchemaError}; -use scir::{Library, NamedSliceOne, ParamValue, SignalInfo, Slice, SliceOnePath}; +use scir::{ + Library, NamedSliceOne, NetlistLibConversion, ParamValue, SignalInfo, Slice, SliceOnePath, +}; use serde::{Deserialize, Serialize}; +use spice::netlist::{ + HasSpiceLikeNetlist, Include, NetlistKind, NetlistOptions, NetlisterInstance, RenameGround, +}; use spice::{BlackboxContents, BlackboxElement, Spice}; use substrate::block::Block; use substrate::execute::Executor; @@ -376,11 +377,13 @@ impl Spectre { ics.sort(); let netlister = NetlisterInstance::new( - NetlistKind::Testbench(RenameGround::Yes("0".into())), self, &ctx.lib.scir, - &includes, &mut w, + NetlistOptions::new( + NetlistKind::Testbench(RenameGround::Yes("0".into())), + &includes, + ), ); let conv = netlister.export()?; diff --git a/tools/spectre/src/tran.rs b/tools/spectre/src/tran.rs index edd0393f..c36c4b96 100644 --- a/tools/spectre/src/tran.rs +++ b/tools/spectre/src/tran.rs @@ -3,7 +3,7 @@ use crate::{node_voltage_path, ErrPreset, SimSignal, Spectre}; use arcstr::ArcStr; use rust_decimal::Decimal; -use scir::netlist::NetlistLibConversion; +use scir::NetlistLibConversion; use scir::{NamedSliceOne, SliceOnePath}; use serde::{Deserialize, Serialize}; use std::collections::HashMap; From 7d4d05cc7238f030b803d5d7ac8fd922989a9466 Mon Sep 17 00:00:00 2001 From: rohanku Date: Fri, 3 Nov 2023 17:38:13 -0700 Subject: [PATCH 3/3] delete extra files --- .../tests/design_inverter/pw1200/netlist.scs | 45 ------------------- .../design_inverter/pw1200/netlist.spice | 19 -------- .../tests/design_inverter/pw1200/ngspice.err | 9 ---- .../tests/design_inverter/pw1200/ngspice.log | 6 --- .../tests/design_inverter/pw1200/simulate.sh | 14 ------ .../tests/netlist_vdivider/vdivider.spice | 11 ----- 6 files changed, 104 deletions(-) delete mode 100644 examples/sky130_inverter/tests/design_inverter/pw1200/netlist.scs delete mode 100644 examples/sky130_inverter/tests/design_inverter/pw1200/netlist.spice delete mode 100644 examples/sky130_inverter/tests/design_inverter/pw1200/ngspice.err delete mode 100644 examples/sky130_inverter/tests/design_inverter/pw1200/ngspice.log delete mode 100755 examples/sky130_inverter/tests/design_inverter/pw1200/simulate.sh delete mode 100644 examples/spice_vdivider/tests/netlist_vdivider/vdivider.spice diff --git a/examples/sky130_inverter/tests/design_inverter/pw1200/netlist.scs b/examples/sky130_inverter/tests/design_inverter/pw1200/netlist.scs deleted file mode 100644 index 8fc16083..00000000 --- a/examples/sky130_inverter/tests/design_inverter/pw1200/netlist.scs +++ /dev/null @@ -1,45 +0,0 @@ -// inverter_tb - -simulator lang=spectre - -// This is a generated file. -// Be careful when editing manually: this file may be overwritten. - -include "/Users/rahul/acads/research/sky130/pdk/skywater-pdk/MODELS/SPECTRE/s8phirs_10r/Models/tt.cor" - -subckt nfet_01v8_1200x150x1 ( io_d io_g io_s io_b ) - - rawinst0 ( io_d io_g io_s io_b ) nshort l=0.150 nf=1 w=1.200 - -ends nfet_01v8_1200x150x1 - -subckt pfet_01v8_1200x150x1 ( io_d io_g io_s io_b ) - - rawinst0 ( io_d io_g io_s io_b ) pshort w=1.200 l=0.150 nf=1 - -ends pfet_01v8_1200x150x1 - -subckt inverter ( io_vdd io_vss io_din io_dout ) - - xinst0 ( io_dout io_din io_vss io_vss ) nfet_01v8_1200x150x1 - xinst1 ( io_dout io_din io_vdd io_vdd ) pfet_01v8_1200x150x1 - -ends inverter - -subckt uservsource ( io_p io_n ) - -V0 ( io_p io_n ) vsource type=dc dc=1.8 - -ends uservsource - -subckt uservsource_1 ( io_p io_n ) - -V0 ( io_p io_n ) vsource type=pulse val0=0 val1=1.8 rise=0.000000000001 fall=0.000000000001 width=0.000000001 delay=0.0000000001 - -ends uservsource_1 - -xinst0 ( vdd 0 xinst0_din dout ) inverter -xinst1 ( vdd 0 ) uservsource -xinst2 ( xinst0_din 0 ) uservsource_1 - -analysis0 tran stop=0.000000002 errpreset=conservative diff --git a/examples/sky130_inverter/tests/design_inverter/pw1200/netlist.spice b/examples/sky130_inverter/tests/design_inverter/pw1200/netlist.spice deleted file mode 100644 index bca8c0ef..00000000 --- a/examples/sky130_inverter/tests/design_inverter/pw1200/netlist.spice +++ /dev/null @@ -1,19 +0,0 @@ -* Substrate SPICE library -* This is a generated file. Be careful when editing manually: this file may be overwritten. - -.LIB "/Users/rohan/layout/sky130/pdk/skywater-pdk/libraries/sky130_fd_pr/latest/models/sky130.lib.spice" tt - -.SUBCKT inverter vdd vss din dout - - Xinst0 dout din vss vss sky130_fd_pr__nfet_01v8 l=0.150 nf=1 w=1.200 - Xinst1 dout din vdd vdd sky130_fd_pr__pfet_01v8 l=0.150 nf=1 w=1.200 - -.ENDS inverter - -Xinst0 vdd 0 xinst0_din dout inverter -Vinst1 vdd 0 DC 1.8 -Vinst2 xinst0_din 0PULSE(0 1.8 0.0000000001 0.000000000001 0.000000000001 0.000000001 0 1) - -.save v(dout) - -.tran 0.00000000001 0.000000002 diff --git a/examples/sky130_inverter/tests/design_inverter/pw1200/ngspice.err b/examples/sky130_inverter/tests/design_inverter/pw1200/ngspice.err deleted file mode 100644 index 729a0b0d..00000000 --- a/examples/sky130_inverter/tests/design_inverter/pw1200/ngspice.err +++ /dev/null @@ -1,9 +0,0 @@ - File included as: .inc "/Users/rohan/layout/sky130/pdk/skywater-pdk/libraries/sky130_fd_pr/latest/models/sky130.lib.spice" tt - - File included as: .inc tt - -Error: Could not find include file tt -Error: unknown subckt: xinst0.xinst0 dout xinst0_din 0 0 xinst0.sky130_fd_pr__nfet_01v8 xinst0.l=0.150 xinst0.nf=1 w=1.200 - Simulation interrupted due to error! - -Error: there aren't any circuits loaded. diff --git a/examples/sky130_inverter/tests/design_inverter/pw1200/ngspice.log b/examples/sky130_inverter/tests/design_inverter/pw1200/ngspice.log deleted file mode 100644 index dc177c7a..00000000 --- a/examples/sky130_inverter/tests/design_inverter/pw1200/ngspice.log +++ /dev/null @@ -1,6 +0,0 @@ - -Note: Compatibility modes selected: ps - - -Circuit: * substrate spice library - diff --git a/examples/sky130_inverter/tests/design_inverter/pw1200/simulate.sh b/examples/sky130_inverter/tests/design_inverter/pw1200/simulate.sh deleted file mode 100755 index f7cb98ea..00000000 --- a/examples/sky130_inverter/tests/design_inverter/pw1200/simulate.sh +++ /dev/null @@ -1,14 +0,0 @@ -#!/bin/bash - -set -x - - - -set -e - -spectre \ - -format psfbin \ - -raw /Users/rahul/personal/substrate2/examples/sky130_inverter/tests/design_inverter/pw1200/psf/ \ - =log /Users/rahul/personal/substrate2/examples/sky130_inverter/tests/design_inverter/pw1200/spectre.log \ - \ - /Users/rahul/personal/substrate2/examples/sky130_inverter/tests/design_inverter/pw1200/netlist.scs diff --git a/examples/spice_vdivider/tests/netlist_vdivider/vdivider.spice b/examples/spice_vdivider/tests/netlist_vdivider/vdivider.spice deleted file mode 100644 index 8d90b372..00000000 --- a/examples/spice_vdivider/tests/netlist_vdivider/vdivider.spice +++ /dev/null @@ -1,11 +0,0 @@ -* Substrate SPICE library -* This is a generated file. Be careful when editing manually: this file may be overwritten. - - -.SUBCKT vdivider vdd vss dout - - Rinst0 vdd dout 100 - Rinst1 dout vss 200 - -.ENDS vdivider -