Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(simulation): add missing SPICE functionality and update Sky 130 PDK #336

Merged
merged 1 commit into from
Dec 3, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions libs/spice/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ tracing = "0.1"
itertools = "0.11.0"
rust_decimal = "1"
rust_decimal_macros = "1"
unicase = "2"

scir = { version = "0.7.0", registry = "substrate", path = "../scir" }
substrate = { version = "0.8.1", registry = "substrate", path = "../../substrate" }
Expand Down
7 changes: 5 additions & 2 deletions libs/spice/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ use substrate::block::Block;
use substrate::io::SchematicType;
use substrate::schematic::primitives::Resistor;
use substrate::schematic::{CellBuilder, Schematic};
use unicase::UniCase;

pub mod netlist;
pub mod parser;
Expand Down Expand Up @@ -89,7 +90,9 @@ pub enum Primitive {
/// A MOS primitive with ports "D", "G", "S", and "B".
Mos {
/// The name of the MOS model.
mname: ArcStr,
model: ArcStr,
/// Parameters associated with the MOS primitive.
params: HashMap<UniCase<ArcStr>, ParamValue>,
},
/// A raw instance with an associated cell.
RawInstance {
Expand All @@ -98,7 +101,7 @@ pub enum Primitive {
/// The associated cell.
cell: ArcStr,
/// Parameters associated with the raw instance.
params: HashMap<ArcStr, ParamValue>,
params: HashMap<UniCase<ArcStr>, ParamValue>,
},
/// An instance with blackboxed contents.
BlackboxInstance {
Expand Down
8 changes: 7 additions & 1 deletion libs/spice/src/netlist.rs
Original file line number Diff line number Diff line change
Expand Up @@ -376,7 +376,10 @@ impl HasSpiceLikeNetlist for Spice {
write!(out, " {value}")?;
name
}
Primitive::Mos { mname } => {
Primitive::Mos {
model: mname,
params,
} => {
let name = arcstr::format!("M{}", name);
write!(out, "{}", name)?;
for port in ["D", "G", "S", "B"] {
Expand All @@ -385,6 +388,9 @@ impl HasSpiceLikeNetlist for Spice {
}
}
write!(out, " {}", mname)?;
for (key, value) in params.iter().sorted_by_key(|(key, _)| *key) {
write!(out, " {key}={value}")?;
}
name
}
Primitive::RawInstance {
Expand Down
28 changes: 26 additions & 2 deletions libs/spice/src/parser/conv.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ use regex::Regex;
use rust_decimal::Decimal;
use scir::ParamValue;
use thiserror::Error;
use unicase::UniCase;

use super::{Ast, Component, Elem, Subckt, Substr};

Expand Down Expand Up @@ -121,7 +122,30 @@ impl<'a> ScirConverter<'a> {

for component in subckt.components.iter() {
match component {
Component::Mos(_mos) => todo!(),
Component::Mos(mos) => {
let model = ArcStr::from(mos.model.as_str());
let params = mos
.params
.iter()
.map(|(k, v)| {
Ok((
UniCase::new(ArcStr::from(k.as_str())),
match str_as_numeric_lit(v) {
Ok(v) => ParamValue::Numeric(v),
Err(_) => ParamValue::String(v.to_string().into()),
},
))
})
.collect::<ConvResult<HashMap<_, _>>>()?;
// TODO: Deduplicate primitives, though does not affect functionality
let id = self.lib.add_primitive(Primitive::Mos { model, params });
let mut sinst = scir::Instance::new(&mos.name[1..], id);
sinst.connect("D", node(&mos.d, &mut cell));
sinst.connect("G", node(&mos.g, &mut cell));
sinst.connect("S", node(&mos.s, &mut cell));
sinst.connect("B", node(&mos.b, &mut cell));
cell.add_instance(sinst);
}
Component::Res(res) => {
let id = self.lib.add_primitive(Primitive::Res2 {
value: str_as_numeric_lit(&res.value)?,
Expand Down Expand Up @@ -153,7 +177,7 @@ impl<'a> ScirConverter<'a> {
.iter()
.map(|(k, v)| {
Ok((
ArcStr::from(k.as_str()),
UniCase::new(ArcStr::from(k.as_str())),
match str_as_numeric_lit(v) {
Ok(v) => ParamValue::Numeric(v),
Err(_) => ParamValue::String(v.to_string().into()),
Expand Down
20 changes: 20 additions & 0 deletions libs/spice/src/parser/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -189,6 +189,24 @@ impl Parser {
let kind = id.chars().next().unwrap().to_ascii_uppercase();

match kind {
'M' => {
let mut params = Params::default();
for i in (6..self.buffer.len()).step_by(3) {
let k = self.buffer[i].try_ident()?.clone();
assert!(matches!(self.buffer[i + 1], Token::Equals));
let v = self.buffer[i + 2].try_ident()?.clone();
params.insert(k, v);
}
Line::Component(Component::Mos(Mos {
name: self.buffer[0].try_ident()?.clone(),
d: self.buffer[1].try_ident()?.clone(),
g: self.buffer[2].try_ident()?.clone(),
s: self.buffer[3].try_ident()?.clone(),
b: self.buffer[4].try_ident()?.clone(),
model: self.buffer[5].try_ident()?.clone(),
params,
}))
}
'R' => Line::Component(Component::Res(Res {
name: self.buffer[0].try_ident()?.clone(),
pos: self.buffer[1].try_ident()?.clone(),
Expand Down Expand Up @@ -365,6 +383,8 @@ pub struct Mos {
pub s: Node,
/// The body/substrate.
pub b: Node,
/// The name of the associated MOSFET model.
pub model: Substr,
/// Parameters and their values.
pub params: Params,
}
Expand Down
29 changes: 29 additions & 0 deletions libs/spice/src/parser/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,12 @@ use std::path::PathBuf;

pub const TEST_DATA_DIR: &str = concat!(env!("CARGO_MANIFEST_DIR"), "/../../tests/data");

pub const SPICE_MOS: &str = r#"
.subckt my_mos d g s b
M0 d g s b my_mos_model
.ends
"#;

pub const SPICE_RESISTOR: &str = r#"
.subckt my_resistor p n
R1 p n 100
Expand Down Expand Up @@ -93,6 +99,29 @@ fn parse_dff() {
}
}

#[test]
fn convert_mos_to_scir() {
let parsed = Parser::parse(SPICE_MOS).unwrap();
let converter = ScirConverter::new(&parsed.ast);
let lib = converter.convert().unwrap();
let issues = lib.validate();
assert_eq!(issues.num_errors(), 0);
assert_eq!(issues.num_warnings(), 0);
assert_eq!(lib.cells().count(), 1);
let cell = lib.cell_named("my_mos");
assert_eq!(cell.instances().count(), 1);

let (_, inst) = cell.instances().next().unwrap();
let prim = lib.primitive(inst.child().unwrap_primitive());
match prim {
Primitive::Mos { model, params } => {
assert_eq!(model, "my_mos_model");
assert_eq!(params.len(), 0);
}
_ => panic!("incorrect primitive kind"),
}
}

#[test]
fn convert_dff_to_scir() {
let parsed = Parser::parse_file(test_data("spice/dff.spice")).unwrap();
Expand Down
1 change: 1 addition & 0 deletions pdks/sky130pdk/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -16,3 +16,4 @@ indexmap = { version = "2", features = ["serde"] }
serde = { version = "1", features = ["derive"] }
arcstr = { version = "1", features = ["serde"] }
paste = "1"
unicase = "2"
50 changes: 0 additions & 50 deletions pdks/sky130pdk/src/corner.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,56 +23,6 @@ pub enum Sky130Corner {
Ss,
}

/// A struct containing each of the [`Sky130Corner`] variants.
#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash, Serialize, Deserialize)]
pub struct Sky130Corners {
pub(super) tt: Sky130Corner,
pub(super) sf: Sky130Corner,
pub(super) fs: Sky130Corner,
pub(super) ff: Sky130Corner,
pub(super) ss: Sky130Corner,
}

impl Default for Sky130Corners {
fn default() -> Self {
Self::new()
}
}

impl Sky130Corners {
/// Creates a new [`Sky130Corners`].
pub fn new() -> Self {
Self {
tt: Sky130Corner::Tt,
sf: Sky130Corner::Sf,
fs: Sky130Corner::Fs,
ff: Sky130Corner::Ff,
ss: Sky130Corner::Ss,
}
}

/// Returns the typical corner.
pub fn tt(&self) -> Sky130Corner {
self.tt
}
/// Returns the slow-fast corner.
pub fn sf(&self) -> Sky130Corner {
self.sf
}
/// Returns the fast-slow corner.
pub fn fs(&self) -> Sky130Corner {
self.fs
}
/// Returns the fast-fast corner.
pub fn ff(&self) -> Sky130Corner {
self.ff
}
/// Returns the slow-slow corner.
pub fn ss(&self) -> Sky130Corner {
self.ss
}
}

impl Sky130Corner {
/// Returns the name of the corner.
pub fn name(&self) -> arcstr::ArcStr {
Expand Down
33 changes: 25 additions & 8 deletions pdks/sky130pdk/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ use rust_decimal::Decimal;
use rust_decimal_macros::dec;
use spectre::Spectre;
use substrate::pdk::Pdk;
use unicase::UniCase;

use crate::layers::Sky130Layers;
use crate::mos::{MosKind, MosParams};
Expand Down Expand Up @@ -80,23 +81,23 @@ impl FromSchema<Spice> for Sky130Pdk {
params: MosParams {
w: i64::try_from(
*params
.get("w")
.get(&UniCase::new(arcstr::literal!("w")))
.and_then(|expr| expr.get_numeric())
.ok_or(ConvError::MissingParameter)?
* dec!(1000),
)
.map_err(|_| ConvError::InvalidParameter)?,
l: i64::try_from(
*params
.get("l")
.get(&UniCase::new(arcstr::literal!("l")))
.and_then(|expr| expr.get_numeric())
.ok_or(ConvError::MissingParameter)?
* dec!(1000),
)
.map_err(|_| ConvError::InvalidParameter)?,
nf: i64::try_from(
params
.get("nf")
.get(&UniCase::new(arcstr::literal!("nf")))
.and_then(|expr| expr.get_numeric())
.copied()
.unwrap_or(dec!(1)),
Expand All @@ -108,7 +109,11 @@ impl FromSchema<Spice> for Sky130Pdk {
Primitive::RawInstance {
cell: cell.clone(),
ports: ports.clone(),
params: params.clone(),
params: params
.clone()
.into_iter()
.map(|(k, v)| (k.into_inner(), v))
.collect(),
}
}),
_ => Err(ConvError::UnsupportedPrimitive),
Expand Down Expand Up @@ -148,15 +153,27 @@ impl FromSchema<Sky130Pdk> for Spice {
} => spice::Primitive::RawInstance {
cell,
ports,
params,
params: params
.into_iter()
.map(|(k, v)| (UniCase::new(k), v))
.collect(),
},
Primitive::Mos { kind, params } => spice::Primitive::RawInstance {
cell: kind.open_subckt(),
ports: vec!["D".into(), "G".into(), "S".into(), "B".into()],
params: HashMap::from_iter([
(arcstr::literal!("w"), Decimal::new(params.w, 3).into()),
(arcstr::literal!("l"), Decimal::new(params.l, 3).into()),
(arcstr::literal!("nf"), Decimal::from(params.nf).into()),
(
UniCase::new(arcstr::literal!("w")),
Decimal::new(params.w, 3).into(),
),
(
UniCase::new(arcstr::literal!("l")),
Decimal::new(params.l, 3).into(),
),
(
UniCase::new(arcstr::literal!("nf")),
Decimal::from(params.nf).into(),
),
]),
},
})
Expand Down
3 changes: 2 additions & 1 deletion tools/ngspice/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,13 @@ edition = "2021"
rust_decimal = "1.32"
rust_decimal_macros = "1.32"
thiserror = "1"
arcstr = { version = "1", features = [ "serde" ] }
arcstr = { version = "1", features = ["serde"] }
tera = "1"
lazy_static = "1"
serde = { version = "1", features = ["derive"] }
tracing = "0.1"
indexmap = { version = "2", features = ["serde"] }
unicase = "2"

cache = { version = "0.5.0", registry = "substrate", path = "../../libs/cache" }
scir = { version = "0.7.0", registry = "substrate", path = "../../libs/scir" }
Expand Down
Loading
Loading