Skip to content

Commit

Permalink
feat(simulation): implement save for nested instances
Browse files Browse the repository at this point in the history
  • Loading branch information
rahulk29 committed Jan 6, 2025
1 parent 9825520 commit a9c42f6
Show file tree
Hide file tree
Showing 7 changed files with 295 additions and 10 deletions.
20 changes: 16 additions & 4 deletions Cargo.lock

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

2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ members = [
"tools/quantus",
"tools/spectre",
"tools/magic-netgen",
"examples2/colbuf", "examples2/via",
"examples2/colbuf", "examples2/via", "examples2/vdivider",
]

exclude = ["tests", "pdks/sky130pdk", "libs/atoll", "examples"]
Expand Down
12 changes: 12 additions & 0 deletions examples2/vdivider/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
[package]
name = "vdivider"
version = "0.1.0"
edition = "2021"

[dependencies]
arcstr = { version = "1", features = ["serde"] }
rust_decimal = "1"
rust_decimal_macros = "1"
serde = { version = "1.0.217", features = ["derive"] }
substrate = { version = "0.8.1", registry = "substrate", path = "../../substrate" }
spectre = { version = "0.9.1", registry = "substrate", path = "../../tools/spectre" }
218 changes: 218 additions & 0 deletions examples2/vdivider/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,218 @@
use arcstr::ArcStr;
use rust_decimal::Decimal;
use rust_decimal_macros::dec;
use serde::{Deserialize, Serialize};
use spectre::blocks::{Iprobe, Resistor, Vsource};
use spectre::Spectre;
use substrate::block::Block;
use substrate::schematic::{CellBuilder, Instance, NestedData, Schematic};
use substrate::types::{Array, InOut, Io, Output, PowerIo, Signal, TestbenchIo};

#[derive(Debug, Default, Clone, Io)]
pub struct VdividerIo {
pub pwr: PowerIo,
pub out: Output<Signal>,
}

#[derive(Debug, Default, Clone, Io)]
pub struct VdividerFlatIo {
pub vdd: InOut<Signal>,
pub vss: InOut<Signal>,
pub out: Output<Signal>,
}

#[derive(Clone, Copy, Hash, PartialEq, Eq, Serialize, Deserialize)]
pub struct Vdivider {
pub r1: Resistor,
pub r2: Resistor,
}

impl Vdivider {
#[inline]
pub fn new(r1: impl Into<Decimal>, r2: impl Into<Decimal>) -> Self {
Self {
r1: Resistor::new(r1),
r2: Resistor::new(r2),
}
}
}

#[derive(Clone, Hash, PartialEq, Eq, Serialize, Deserialize)]
pub struct VdividerArray {
pub vdividers: Vec<Vdivider>,
}

impl Block for Vdivider {
type Io = VdividerIo;

fn name(&self) -> ArcStr {
arcstr::format!("vdivider_{}_{}", self.r1.value(), self.r2.value())
}

fn io(&self) -> Self::Io {
Default::default()
}
}

#[derive(Debug, Clone, Io)]
pub struct VdividerArrayIo {
pub elements: Array<PowerIo>,
}

impl Block for VdividerArray {
type Io = VdividerArrayIo;

fn name(&self) -> ArcStr {
arcstr::format!("vdivider_array_{}", self.vdividers.len())
}

fn io(&self) -> Self::Io {
VdividerArrayIo {
elements: Array::new(self.vdividers.len(), Default::default()),
}
}
}

#[derive(NestedData)]
pub struct VdividerData {
r1: Instance<Resistor>,
r2: Instance<Resistor>,
}

impl Schematic for Vdivider {
type Schema = Spectre;
type NestedData = VdividerData;

fn schematic(
&self,
io: &substrate::types::schematic::IoNodeBundle<Self>,
cell: &mut CellBuilder<<Self as Schematic>::Schema>,
) -> substrate::error::Result<Self::NestedData> {
let r1 = cell.instantiate(self.r1);
let r2 = cell.instantiate(self.r2);

cell.connect(io.pwr.vdd, r1.io().p);
cell.connect(io.out, r1.io().n);
cell.connect(io.out, r2.io().p);
cell.connect(io.pwr.vss, r2.io().n);
Ok(VdividerData { r1, r2 })
}
}

impl Schematic for VdividerArray {
type Schema = Spectre;
type NestedData = Vec<Instance<Vdivider>>;

fn schematic(
&self,
io: &substrate::types::schematic::IoNodeBundle<Self>,
cell: &mut CellBuilder<<Self as Schematic>::Schema>,
) -> substrate::error::Result<Self::NestedData> {
let mut vdividers = Vec::new();

for (i, vdivider) in self.vdividers.iter().enumerate() {
let vdiv = cell.instantiate(*vdivider);

cell.connect(&vdiv.io().pwr, &io.elements[i]);

vdividers.push(vdiv);
}

Ok(vdividers)
}
}

#[derive(Clone, Copy, Debug, Default, Eq, PartialEq, Hash, Serialize, Deserialize, Block)]
#[substrate(io = "TestbenchIo")]
pub struct VdividerTb;

#[derive(NestedData)]
pub struct VdividerTbData {
iprobe: Instance<Iprobe>,
dut: Instance<Vdivider>,
}

impl Schematic for VdividerTb {
type Schema = Spectre;
type NestedData = VdividerTbData;

fn schematic(
&self,
io: &substrate::types::schematic::IoNodeBundle<Self>,
cell: &mut CellBuilder<<Self as Schematic>::Schema>,
) -> substrate::error::Result<Self::NestedData> {
let vdd_a = cell.signal("vdd_a", Signal);
let vdd = cell.signal("vdd", Signal);
let out = cell.signal("out", Signal);
let dut = cell.instantiate(Vdivider {
r1: Resistor::new(20),
r2: Resistor::new(20),
});

cell.connect(dut.io().pwr.vdd, vdd);
cell.connect(dut.io().pwr.vss, io.vss);
cell.connect(dut.io().out, out);

let iprobe = cell.instantiate(Iprobe);
cell.connect(iprobe.io().p, vdd_a);
cell.connect(iprobe.io().n, vdd);

let vsource = cell.instantiate(Vsource::dc(dec!(1.8)));
cell.connect(vsource.io().p, vdd_a);
cell.connect(vsource.io().n, io.vss);

Ok(VdividerTbData { iprobe, dut })
}
}

#[cfg(test)]
mod tests {
use rust_decimal_macros::dec;
use spectre::{analysis::tran::Tran, ErrPreset};
use substrate::context::Context;

use super::*;
use std::path::PathBuf;

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

#[inline]
pub fn get_path(test_name: &str, file_name: &str) -> PathBuf {
PathBuf::from(BUILD_DIR).join(test_name).join(file_name)
}

#[inline]
pub fn test_data(file_name: &str) -> PathBuf {
PathBuf::from(TEST_DATA_DIR).join(file_name)
}
#[test]
fn vdivider_tran() {
let test_name = "spectre_vdivider_tran";
let sim_dir = get_path(test_name, "sim/");
let ctx = Context::builder().install(Spectre::default()).build();
let sim = ctx.get_sim_controller(VdividerTb, sim_dir).unwrap();
let output = sim
.simulate(
Default::default(),
Tran {
stop: dec!(1e-6),
errpreset: Some(ErrPreset::Conservative),
..Default::default()
},
)
.unwrap();

for (actual, expected) in [
(&*output.current, 1.8 / 40.),
(&*output.iprobe, 1.8 / 40.),
(&*output.vdd, 1.8),
(&*output.out, 0.9),
] {
assert!(actual
.iter()
.cloned()
.all(|val| relative_eq!(val, expected)));
}
}
}
3 changes: 0 additions & 3 deletions tools/magic-netgen/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -287,6 +287,3 @@ where
<NestedView<T::NestedData, PexContext> as Save<S, A>>::from_saved(output, key)
}
}

#[cfg(test)]
mod tests {}
48 changes: 47 additions & 1 deletion tools/spectre/src/analysis/tran.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,10 @@ use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use std::sync::Arc;
use substrate::schematic::conv::ConvertedNodePath;
use substrate::schematic::{NestedInstance, NestedView, Schematic};
use substrate::simulation::data::{Save, SaveOutput, SaveTime};
use substrate::simulation::{Analysis, SimulationContext, Simulator, SupportedBy};
use substrate::types::schematic::{NestedNode, NestedTerminal, RawNestedNode};
use substrate::types::schematic::{IoTerminalBundle, NestedNode, NestedTerminal, RawNestedNode};

/// A transient analysis.
#[derive(Clone, Default, Debug, Eq, PartialEq, Serialize, Deserialize)]
Expand Down Expand Up @@ -228,6 +229,51 @@ impl Save<Spectre, Tran> for NestedTerminal {
}
}

pub struct NestedInstanceOutput<T>
where
T: Schematic,
NestedView<T::NestedData>: Save<Spectre, Tran>,
NestedView<IoTerminalBundle<T>>: Save<Spectre, Tran>,
{
data: <NestedView<T::NestedData> as Save<Spectre, Tran>>::Saved,
io: <NestedView<IoTerminalBundle<T>> as Save<Spectre, Tran>>::Saved,
}

impl<T> Save<Spectre, Tran> for NestedInstance<T>
where
T: Schematic,
NestedView<T::NestedData>: Save<Spectre, Tran>,
NestedView<IoTerminalBundle<T>>: Save<Spectre, Tran>,
{
type SaveKey = (
<NestedView<T::NestedData> as Save<Spectre, Tran>>::SaveKey,
<NestedView<IoTerminalBundle<T>> as Save<Spectre, Tran>>::SaveKey,
);
type Saved = NestedInstanceOutput<T>;

fn save(
&self,
ctx: &SimulationContext<Spectre>,
opts: &mut <Spectre as Simulator>::Options,
) -> <Self as Save<Spectre, Tran>>::SaveKey {
let data = self.data().save(ctx, opts);
let io = self.io().save(ctx, opts);
(data, io)
}

fn from_saved(
output: &<Tran as Analysis>::Output,
key: &<Self as Save<Spectre, Tran>>::SaveKey,
) -> <Self as Save<Spectre, Tran>>::Saved {
NestedInstanceOutput {
data: <NestedView<T::NestedData> as Save<Spectre, Tran>>::from_saved(output, &key.0),
io: <NestedView<IoTerminalBundle<T>> as Save<Spectre, Tran>>::from_saved(
output, &key.1,
),
}
}
}

impl Analysis for Tran {
type Output = Output;
}
Expand Down
2 changes: 1 addition & 1 deletion tools/spectre/src/blocks.rs
Original file line number Diff line number Diff line change
Expand Up @@ -366,7 +366,7 @@ impl Schematic for Nport {
}

/// An ideal 2-terminal resistor.
#[derive(Clone, Copy, Hash, PartialEq, Eq)]
#[derive(Clone, Copy, Hash, PartialEq, Eq, Serialize, Deserialize)]
pub struct Resistor {
/// The resistor value.
value: Decimal,
Expand Down

0 comments on commit a9c42f6

Please sign in to comment.