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/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/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/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/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 e1a2434a..bd6a6c2c 100644 --- a/libs/spice/src/lib.rs +++ b/libs/spice/src/lib.rs @@ -3,20 +3,19 @@ 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 scir::{Instance, ParamValue}; use std::collections::{HashMap, HashSet}; -use std::io::prelude::*; use std::path::Path; use substrate::block::Block; 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 +197,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/scir/src/netlist.rs b/libs/spice/src/netlist.rs similarity index 53% rename from libs/scir/src/netlist.rs rename to libs/spice/src/netlist.rs index 80eae7fa..c1e3ef87 100644 --- a/libs/scir/src/netlist.rs +++ b/libs/spice/src/netlist.rs @@ -1,11 +1,21 @@ -//! Utilities for writing netlisters for SCIR libraries. +//! Utilities for writing SPICE netlisters for SCIR libraries. -use crate::schema::Schema; -use crate::{Cell, CellId, ChildId, InstanceId, Library, SignalInfo, Slice}; 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, ChildId, Library, NetlistCellConversion, NetlistLibConversion, SignalInfo, Slice, +}; +use substrate::context::Context; +use substrate::schematic::conv::RawLib; +use substrate::schematic::Schematic; /// A netlist include statement. #[derive(Debug, Clone, Eq, PartialEq, Hash, Ord, PartialOrd)] @@ -38,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. @@ -145,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, } } } @@ -193,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)?; } @@ -211,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(); @@ -238,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() @@ -271,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)?; } @@ -304,3 +298,187 @@ impl<'a, S: HasSpiceLikeNetlist, W: Write> NetlisterInstance<'a, S, W> { ))) } } + +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 { + /// 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)) + } + /// 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 b0fc0483..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) } @@ -725,7 +726,7 @@ impl HasSpiceLikeNetlist for Ngspice { Vsource::Pulse(pulse) => { write!( out, - "PULSE({} {} {} {} {} {} {} {})", + " PULSE({} {} {} {} {} {} {} {})", pulse.val0, pulse.val1, pulse.delay.unwrap_or_default(), 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;