Skip to main content
Version: main

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.

Then, 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 = { git = "https://github.com/ucb-substrate/substrate2.git", branch = "main" }
spice = { git = "https://github.com/ucb-substrate/substrate2.git", branch = "main" }

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::{InOut, Io, Output, Signal, schematic::IoNodeBundle},
};

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.

src/lib.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.