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

feat(docs): update docs for new simulation APIs #326

Merged
merged 11 commits into from
Nov 29, 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
166 changes: 166 additions & 0 deletions docs/examples/examples/core.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1036,3 +1036,169 @@ mod generate {
}
// end-code-snippet vdivider-generate-add-error-handling
}

mod scir {
use serde::{Deserialize, Serialize};
use substrate::block::Block;
use substrate::io::{SchematicType, TwoTerminalIo};
use substrate::schematic::{
CellBuilder, ExportsNestedData, PrimitiveBinding, Schematic, ScirBinding,
};
use substrate::scir::schema::{Schema, StringSchema};
use substrate::scir::{Cell, Direction, Instance, LibraryBuilder};

// begin-code-snippet scir-schema
pub struct MySchema;

#[derive(Debug, Copy, Clone)]
pub enum MyPrimitive {
Resistor(i64),
Capacitor(i64),
}

impl Schema for MySchema {
type Primitive = MyPrimitive;
}
// end-code-snippet scir-schema

// begin-code-snippet scir-primitive-binding
#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash, Serialize, Deserialize, Block)]
#[substrate(io = "TwoTerminalIo")]
pub struct Resistor(i64);

impl ExportsNestedData for Resistor {
type NestedData = ();
}

impl Schematic<MySchema> for Resistor {
fn schematic(
&self,
io: &<<Self as Block>::Io as SchematicType>::Bundle,
cell: &mut CellBuilder<MySchema>,
) -> substrate::error::Result<Self::NestedData> {
let mut prim = PrimitiveBinding::new(MyPrimitive::Resistor(self.0));

prim.connect("p", io.p);
prim.connect("n", io.n);

cell.set_primitive(prim);
Ok(())
}
}
// end-code-snippet scir-primitive-binding

// begin-code-snippet scir-scir-binding
#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash, Serialize, Deserialize, Block)]
#[substrate(io = "TwoTerminalIo")]
pub struct ParallelResistors(i64, i64);

impl ExportsNestedData for ParallelResistors {
type NestedData = ();
}

impl Schematic<MySchema> for ParallelResistors {
fn schematic(
&self,
io: &<<Self as Block>::Io as SchematicType>::Bundle,
cell: &mut CellBuilder<MySchema>,
) -> substrate::error::Result<Self::NestedData> {
// Creates a SCIR library containing the desired cell.
let mut lib = LibraryBuilder::<MySchema>::new();
let r1 = lib.add_primitive(MyPrimitive::Resistor(self.0));
let r2 = lib.add_primitive(MyPrimitive::Resistor(self.1));
let mut parallel_resistors = Cell::new("parallel_resistors");
let p = parallel_resistors.add_node("p");
let n = parallel_resistors.add_node("n");
parallel_resistors.expose_port(p, Direction::InOut);
parallel_resistors.expose_port(n, Direction::InOut);
let mut r1 = Instance::new("r1", r1);
r1.connect("p", p);
r1.connect("n", n);
parallel_resistors.add_instance(r1);
let mut r2 = Instance::new("r2", r2);
r2.connect("p", p);
r2.connect("n", n);
parallel_resistors.add_instance(r2);
let cell_id = lib.add_cell(parallel_resistors);

// Binds to the desired cell in the SCIR library.
let mut scir = ScirBinding::new(lib.build().unwrap(), cell_id);

scir.connect("p", io.p);
scir.connect("n", io.n);

cell.set_scir(scir);
Ok(())
}
}
// end-code-snippet scir-scir-binding

#[allow(unused_variables)]
fn library() {
// begin-code-snippet scir-library-builder
let mut lib = LibraryBuilder::<StringSchema>::new();
// end-code-snippet scir-library-builder
// begin-code-snippet scir-library-cell
let empty_cell = Cell::new("empty");
let empty_cell_id = lib.add_cell(empty_cell);
// end-code-snippet scir-library-cell
// begin-code-snippet scir-library-primitive
let resistor_id = lib.add_primitive(arcstr::literal!("resistor"));
// end-code-snippet scir-library-primitive
// begin-code-snippet scir-library-signals
let mut vdivider = Cell::new("vdivider");

let vdd = vdivider.add_node("vdd");
let vout = vdivider.add_node("vout");
let vss = vdivider.add_node("vss");

vdivider.expose_port(vdd, Direction::InOut);
vdivider.expose_port(vout, Direction::Output);
vdivider.expose_port(vss, Direction::InOut);
// end-code-snippet scir-library-signals
// begin-code-snippet scir-library-primitive-instances
let mut r1 = Instance::new("r1", resistor_id);

r1.connect("p", vdd);
r1.connect("n", vout);

vdivider.add_instance(r1);

let mut r2 = Instance::new("r2", resistor_id);

r2.connect("p", vout);
r2.connect("n", vss);

vdivider.add_instance(r2);

let vdivider_id = lib.add_cell(vdivider);
// end-code-snippet scir-library-primitive-instances
// begin-code-snippet scir-library-instances
let mut stacked_vdivider = Cell::new("stacked_vdivider");

let vdd = stacked_vdivider.add_node("vdd");
let v1 = stacked_vdivider.add_node("v1");
let v2 = stacked_vdivider.add_node("v2");
let v3 = stacked_vdivider.add_node("v3");
let vss = stacked_vdivider.add_node("vss");

let mut vdiv1 = Instance::new("vdiv1", vdivider_id);

vdiv1.connect("vdd", vdd);
vdiv1.connect("vout", v1);
vdiv1.connect("vss", v2);

stacked_vdivider.add_instance(vdiv1);

let mut vdiv2 = Instance::new("vdiv2", vdivider_id);

vdiv2.connect("vdd", v2);
vdiv2.connect("vout", v3);
vdiv2.connect("vss", vss);

stacked_vdivider.add_instance(vdiv2);

let stacked_vdivider_id = lib.add_cell(stacked_vdivider);
// end-code-snippet scir-library-instances
}
}
13 changes: 6 additions & 7 deletions docs/site/docs/getting-started/inverter.md
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ git submodule update --init libraries/sky130_fd_pr/latest

Also, ensure that the `SKY130_OPEN_PDK_ROOT` environment variable points to the location of the repo you just cloned.

Similarly, if you would like to use Spectre, you will need to ensure that the `SKY130_COMMERCIAL_PDK_ROOT` environment variable points to an installation of the commercial SKY130 PDK.
If you would like to use Spectre, you will also need to ensure that the `SKY130_COMMERCIAL_PDK_ROOT` environment variable points to an installation of the commercial SKY130 PDK.

## Interface

Expand Down Expand Up @@ -95,7 +95,7 @@ We'll make our inverter generator have three parameters:
We're assuming here that the NMOS and PMOS will have the same length.

In this tutorial, we store all dimensions as integers in layout database units.
In the Sky 130 process, the database unit is a nanometer, so supplying an NMOS width
In the SKY130 process, the database unit is a nanometer, so supplying an NMOS width
of 1,200 will produce a transistor with a width of 1.2 microns.

We'll now define the struct representing our inverter:
Expand Down Expand Up @@ -144,7 +144,7 @@ the device being tested, etc.).

As a result, creating a testbench is the same as creating a regular block except that we don't have to define an IO.
All testbenches must declare their IO to be `TestbenchIo`, which has one port, `vss`, that allows
simulators to identify a global ground (whichthey often assign to node 0).
simulators to identify a global ground (which they often assign to node 0).

Just like regular blocks, testbenches are usually structs containing their parameters.
We'll make our testbench take two parameters:
Expand Down Expand Up @@ -189,9 +189,8 @@ This is how our testbench looks:

<CodeSnippet language="rust" title="src/tb.rs" snippet="testbench">{InverterTb}</CodeSnippet>

We define `NgspiceVout` as a receiver for data saved during simulation.
We then create a general-purpose `Vout` struct. This struct is not necessary if we only want to use ngspice,
but it will come in handy when we add support for other simulators.
We define `Vout` as a receiver for data saved during simulation. We then tell Substrate what data we want to save from
our testbench by implementing the `SaveTb` trait.

## Design

Expand Down Expand Up @@ -246,7 +245,7 @@ To add Spectre support, we can simply add the following code:
<CodeSnippet language="rust" title="src/tb.rs" snippet="spectre-support">{InverterTb}</CodeSnippet>

Before running the new Spectre test, ensure that the `SKY130_COMMERCIAL_PDK_ROOT` environment variable points to your installation of
the Sky 130 commercial PDK.
the SKY130 commercial PDK.
Also ensure that you have correctly set any environment variables needed by Spectre.

To run the test, run
Expand Down
3 changes: 1 addition & 2 deletions docs/site/docs/schematics/blocks.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,6 @@ There are a few things you need to specify when defining a block:
| Member | Description |
|---|---|
| `type Io` | The IO type of the block. See the [IOs section](./io.md) for more details. |
| `type Kind` | The kind of the block, which must implement [`BlockKind`](https://api.substratelabs.io/substrate/block/trait.BlockKind.html). For now, you should only need the [`Cell`](https://api.substratelabs.io/substrate/block/struct.Cell.html) kind, which describes any block that is composed of other Substrate blocks. The other block kinds are used for interfacing with [SCIR](https://api.substratelabs.io/scir/), which is discussed in a [later section](#TODO). |
| `fn id() -> ArcStr` | Returns a unique ID of this block within the crate. While this is not used by Substrate as of November 2023, its intended purpose is to allow generators to be called by name, potentially via a CLI. **No two blocks in the same crate should have the same ID string.** |
| `fn name(&self)` | Returns a name describing a specific instantiation of a block. This is used to create descriptive cell names when netlisting or writing a layout to GDS. |
| `fn io(&self) -> Self::Io` | Returns an instantiation of the block's IO type, describing the properties of the IO for a specific set of parameters. This allows you to vary bus lengths at runtime based on block parameters. |
Expand All @@ -47,7 +46,7 @@ This derived `Eq` implementation is fine, since it checks that both resistors ar

<CodeSnippet language="rust" snippet="vdivider-bad-eq">{Core}</CodeSnippet>

Now, let's say you generate a voltage divider with two 100 ohm resistors. Then, you try to generate a goltage divider with one 100 ohm resistor and one 200 ohm resistor. Since Substrate thinks these are equivalent due to your `Eq` implementation, it will reuse the previously generated voltage divider with two 100 ohm resistors!
Now, let's say you generate a voltage divider with two 100 ohm resistors. Then, you try to generate a voltage divider with one 100 ohm resistor and one 200 ohm resistor. Since Substrate thinks these are equivalent due to your `Eq` implementation, it will reuse the previously generated voltage divider with two 100 ohm resistors!

:::warning
The moral of the story, make sure that your block struct contains any relevant parameters and has a correct `Eq` implementation. Otherwise, Substrate may incorrectly cache generated versions of your block, leading to errors that are extremely difficult to catch.
Expand Down
2 changes: 1 addition & 1 deletion docs/site/docs/schematics/schematics.md
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ Once a block has an associated IO and nested data, you can define its schematic
<CodeSnippet language="rust" snippet="vdivider-schematic">{VdividerMod}</CodeSnippet>

Let's look at what each part of the implementation is doing.
- In the first line, we implement `Schematic<Spice>` for `Vdivider`. `Spice` is a schema, or essentially a specific format in which a block can be defined. Essentially, we are saying that `Vdivider` has a schematic in the `Spice` schema, which allows us to netlist the voltage divider to SPICE and run simulations with it in SPICE simulators. For more details on schemas, see the [SCIR chapter](#TODO).
- In the first line, we implement `Schematic<Spice>` for `Vdivider`. `Spice` is a schema, or essentially a specific format in which a block can be defined. Essentially, we are saying that `Vdivider` has a schematic in the `Spice` schema, which allows us to netlist the voltage divider to SPICE and run simulations with it in SPICE simulators. For more details on schemas, see the [SCIR chapter](./scir.md).
- `fn schematic(...)`, which defines our schematic, takes in three arguments:
- `&self` - the block itself, which should contain parameters to the generator.
- `io` - the bundle corresponding to the cell's IO.
Expand Down
Loading