Skip to main content
Version: release

Quickstart

To get you up to speed with Substrate's basic features, we'll show you how make a simple voltage divider schematic.

In this tutorial, we'll breeze through the basics to give you a sense of what Substrate generators look like. If you're looking for a deeper dive into how analog design and simulation work in Substrate, check out the Designing an inverter tutorial.

Creating a Substrate project

Substrate is fully integrated with the Rust ecosystem, so all you need to get started is a recent installation of Rust! Ensure that you have version 1.80.0 or beyond.

Also ensure that you have the protocol buffer compiler (protoc) installed.

First, add the Substrate registry to your Cargo config:
~/.cargo/config.toml
[registries]
substrate = { index = "https://github.com/substrate-labs/crates-index" }
You only need to do this the first time you set up Substrate.

Next, create a new Rust project:

cargo new --lib my_generator && cd my_generator

In your project's Cargo.toml, add the following dependencies:

Cargo.toml
[dependencies]
substrate = { version = "0.10.2", registry = "substrate" }
spice = { version = "0.9.2", registry = "substrate" }

rust_decimal = "1"
rust_decimal_macros = "1"

Let's now add some imports that we'll use later on. Replace the content of src/lib.rs with the following:

src/lib.rs
use rust_decimal::Decimal;
use spice::{Resistor, Spice};
use substrate::{
block::Block,
error::Result,
schematic::{CellBuilder, Schematic},
types::{schematic::IoNodeBundle, InOut, Io, Output, Signal},
};

Interface

We'll first define the interface (also referred to as IO) exposed by our voltage divider.

src/lib.rs
#[derive(Io, Clone, Default, Debug)]
pub struct VdividerIo {
pub vdd: InOut<Signal>,
pub vss: InOut<Signal>,
pub dout: Output<Signal>,
}

Voltage divider parameters

Now that we've defined an IO, we can define a block. Substrate blocks are analogous to modules or cells in other generator frameworks.

src/lib.rs
#[derive(Block, Debug, Copy, Clone, Hash, PartialEq, Eq)]
#[substrate(io = "VdividerIo")]
pub struct Vdivider {
/// The top resistance.
pub r1: Decimal,
/// The bottom resistance.
pub r2: Decimal,
}

Schematic generator

We now define the schematic of the voltage divider.

src/lib.rs
impl Schematic for Vdivider {
type Schema = Spice;
type NestedData = ();
fn schematic(
&self,
io: &IoNodeBundle<Self>,
cell: &mut CellBuilder<<Self as Schematic>::Schema>,
) -> Result<Self::NestedData> {
let r1 = cell.instantiate(Resistor::new(self.r1));
let r2 = cell.instantiate(Resistor::new(self.r2));

cell.connect(io.vdd, r1.io().p);
cell.connect(io.dout, r1.io().n);
cell.connect(io.dout, r2.io().p);
cell.connect(io.vss, r2.io().n);

Ok(())
}
}

Writing the netlist

We can now write a Rust unit test to write the netlist to a file.

lib/tb.rs
#[cfg(test)]
mod tests {
use super::*;
use rust_decimal_macros::dec;
use spice::netlist::NetlistOptions;
use std::path::PathBuf;
use substrate::context::Context;
use substrate::schematic::netlist::ConvertibleNetlister;

#[test]
pub fn netlist_vdivider() {
let ctx = Context::new();
Spice
.write_netlist_to_file(
&ctx,
Vdivider {
r1: dec!(100),
r2: dec!(200),
},
PathBuf::from(concat!(
env!("CARGO_MANIFEST_DIR"),
"/tests/netlist_vdivider"
))
.join("vdivider.spice"),
NetlistOptions::default(),
)
.expect("failed to netlist vdivider");
}
}

To run the test, run

cargo test netlist_vdivider

Conclusion

If all goes well, the test above should write the voltage divider netlist to tests/netlist_vdivider/vdivider.spice. A full, runnable example for this tutorial is available here.