Skip to content

Commit ef133df

Browse files
authored
feat(docs): update docs for new simulation APIs (#326)
* feat(docs): update docs for new simulation APIs * add scir docs * work on updating examples * fix examples * work on scir documentation * finish documentation updates * fix compilation error * fix errors
1 parent 10c54b3 commit ef133df

16 files changed

Lines changed: 514 additions & 201 deletions

File tree

docs/examples/examples/core.rs

Lines changed: 166 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1036,3 +1036,169 @@ mod generate {
10361036
}
10371037
// end-code-snippet vdivider-generate-add-error-handling
10381038
}
1039+
1040+
mod scir {
1041+
use serde::{Deserialize, Serialize};
1042+
use substrate::block::Block;
1043+
use substrate::io::{SchematicType, TwoTerminalIo};
1044+
use substrate::schematic::{
1045+
CellBuilder, ExportsNestedData, PrimitiveBinding, Schematic, ScirBinding,
1046+
};
1047+
use substrate::scir::schema::{Schema, StringSchema};
1048+
use substrate::scir::{Cell, Direction, Instance, LibraryBuilder};
1049+
1050+
// begin-code-snippet scir-schema
1051+
pub struct MySchema;
1052+
1053+
#[derive(Debug, Copy, Clone)]
1054+
pub enum MyPrimitive {
1055+
Resistor(i64),
1056+
Capacitor(i64),
1057+
}
1058+
1059+
impl Schema for MySchema {
1060+
type Primitive = MyPrimitive;
1061+
}
1062+
// end-code-snippet scir-schema
1063+
1064+
// begin-code-snippet scir-primitive-binding
1065+
#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash, Serialize, Deserialize, Block)]
1066+
#[substrate(io = "TwoTerminalIo")]
1067+
pub struct Resistor(i64);
1068+
1069+
impl ExportsNestedData for Resistor {
1070+
type NestedData = ();
1071+
}
1072+
1073+
impl Schematic<MySchema> for Resistor {
1074+
fn schematic(
1075+
&self,
1076+
io: &<<Self as Block>::Io as SchematicType>::Bundle,
1077+
cell: &mut CellBuilder<MySchema>,
1078+
) -> substrate::error::Result<Self::NestedData> {
1079+
let mut prim = PrimitiveBinding::new(MyPrimitive::Resistor(self.0));
1080+
1081+
prim.connect("p", io.p);
1082+
prim.connect("n", io.n);
1083+
1084+
cell.set_primitive(prim);
1085+
Ok(())
1086+
}
1087+
}
1088+
// end-code-snippet scir-primitive-binding
1089+
1090+
// begin-code-snippet scir-scir-binding
1091+
#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash, Serialize, Deserialize, Block)]
1092+
#[substrate(io = "TwoTerminalIo")]
1093+
pub struct ParallelResistors(i64, i64);
1094+
1095+
impl ExportsNestedData for ParallelResistors {
1096+
type NestedData = ();
1097+
}
1098+
1099+
impl Schematic<MySchema> for ParallelResistors {
1100+
fn schematic(
1101+
&self,
1102+
io: &<<Self as Block>::Io as SchematicType>::Bundle,
1103+
cell: &mut CellBuilder<MySchema>,
1104+
) -> substrate::error::Result<Self::NestedData> {
1105+
// Creates a SCIR library containing the desired cell.
1106+
let mut lib = LibraryBuilder::<MySchema>::new();
1107+
let r1 = lib.add_primitive(MyPrimitive::Resistor(self.0));
1108+
let r2 = lib.add_primitive(MyPrimitive::Resistor(self.1));
1109+
let mut parallel_resistors = Cell::new("parallel_resistors");
1110+
let p = parallel_resistors.add_node("p");
1111+
let n = parallel_resistors.add_node("n");
1112+
parallel_resistors.expose_port(p, Direction::InOut);
1113+
parallel_resistors.expose_port(n, Direction::InOut);
1114+
let mut r1 = Instance::new("r1", r1);
1115+
r1.connect("p", p);
1116+
r1.connect("n", n);
1117+
parallel_resistors.add_instance(r1);
1118+
let mut r2 = Instance::new("r2", r2);
1119+
r2.connect("p", p);
1120+
r2.connect("n", n);
1121+
parallel_resistors.add_instance(r2);
1122+
let cell_id = lib.add_cell(parallel_resistors);
1123+
1124+
// Binds to the desired cell in the SCIR library.
1125+
let mut scir = ScirBinding::new(lib.build().unwrap(), cell_id);
1126+
1127+
scir.connect("p", io.p);
1128+
scir.connect("n", io.n);
1129+
1130+
cell.set_scir(scir);
1131+
Ok(())
1132+
}
1133+
}
1134+
// end-code-snippet scir-scir-binding
1135+
1136+
#[allow(unused_variables)]
1137+
fn library() {
1138+
// begin-code-snippet scir-library-builder
1139+
let mut lib = LibraryBuilder::<StringSchema>::new();
1140+
// end-code-snippet scir-library-builder
1141+
// begin-code-snippet scir-library-cell
1142+
let empty_cell = Cell::new("empty");
1143+
let empty_cell_id = lib.add_cell(empty_cell);
1144+
// end-code-snippet scir-library-cell
1145+
// begin-code-snippet scir-library-primitive
1146+
let resistor_id = lib.add_primitive(arcstr::literal!("resistor"));
1147+
// end-code-snippet scir-library-primitive
1148+
// begin-code-snippet scir-library-signals
1149+
let mut vdivider = Cell::new("vdivider");
1150+
1151+
let vdd = vdivider.add_node("vdd");
1152+
let vout = vdivider.add_node("vout");
1153+
let vss = vdivider.add_node("vss");
1154+
1155+
vdivider.expose_port(vdd, Direction::InOut);
1156+
vdivider.expose_port(vout, Direction::Output);
1157+
vdivider.expose_port(vss, Direction::InOut);
1158+
// end-code-snippet scir-library-signals
1159+
// begin-code-snippet scir-library-primitive-instances
1160+
let mut r1 = Instance::new("r1", resistor_id);
1161+
1162+
r1.connect("p", vdd);
1163+
r1.connect("n", vout);
1164+
1165+
vdivider.add_instance(r1);
1166+
1167+
let mut r2 = Instance::new("r2", resistor_id);
1168+
1169+
r2.connect("p", vout);
1170+
r2.connect("n", vss);
1171+
1172+
vdivider.add_instance(r2);
1173+
1174+
let vdivider_id = lib.add_cell(vdivider);
1175+
// end-code-snippet scir-library-primitive-instances
1176+
// begin-code-snippet scir-library-instances
1177+
let mut stacked_vdivider = Cell::new("stacked_vdivider");
1178+
1179+
let vdd = stacked_vdivider.add_node("vdd");
1180+
let v1 = stacked_vdivider.add_node("v1");
1181+
let v2 = stacked_vdivider.add_node("v2");
1182+
let v3 = stacked_vdivider.add_node("v3");
1183+
let vss = stacked_vdivider.add_node("vss");
1184+
1185+
let mut vdiv1 = Instance::new("vdiv1", vdivider_id);
1186+
1187+
vdiv1.connect("vdd", vdd);
1188+
vdiv1.connect("vout", v1);
1189+
vdiv1.connect("vss", v2);
1190+
1191+
stacked_vdivider.add_instance(vdiv1);
1192+
1193+
let mut vdiv2 = Instance::new("vdiv2", vdivider_id);
1194+
1195+
vdiv2.connect("vdd", v2);
1196+
vdiv2.connect("vout", v3);
1197+
vdiv2.connect("vss", vss);
1198+
1199+
stacked_vdivider.add_instance(vdiv2);
1200+
1201+
let stacked_vdivider_id = lib.add_cell(stacked_vdivider);
1202+
// end-code-snippet scir-library-instances
1203+
}
1204+
}

docs/site/docs/getting-started/inverter.md

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,7 @@ git submodule update --init libraries/sky130_fd_pr/latest
5959

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

62-
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.
62+
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.
6363

6464
## Interface
6565

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

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

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

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

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

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

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

196195
## Design
197196

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

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

252251
To run the test, run

docs/site/docs/schematics/blocks.md

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,6 @@ There are a few things you need to specify when defining a block:
3030
| Member | Description |
3131
|---|---|
3232
| `type Io` | The IO type of the block. See the [IOs section](./io.md) for more details. |
33-
| `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). |
3433
| `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.** |
3534
| `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. |
3635
| `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. |
@@ -47,7 +46,7 @@ This derived `Eq` implementation is fine, since it checks that both resistors ar
4746

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

50-
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!
49+
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!
5150

5251
:::warning
5352
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.

docs/site/docs/schematics/schematics.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@ Once a block has an associated IO and nested data, you can define its schematic
5656
<CodeSnippet language="rust" snippet="vdivider-schematic">{VdividerMod}</CodeSnippet>
5757

5858
Let's look at what each part of the implementation is doing.
59-
- 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).
59+
- 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).
6060
- `fn schematic(...)`, which defines our schematic, takes in three arguments:
6161
- `&self` - the block itself, which should contain parameters to the generator.
6262
- `io` - the bundle corresponding to the cell's IO.

0 commit comments

Comments
 (0)