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.
[registries]
substrate = { index = "https://github.com/substrate-labs/crates-index" }
Next, create a new Rust project:
cargo new --lib my_generator && cd my_generator
In your project's Cargo.toml, add the following dependencies:
[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:
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.
#[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.
#[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.
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.
#[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.