From b0eb706c6629da97223015de1dcc31d31c27cc4e Mon Sep 17 00:00:00 2001 From: rohanku Date: Sat, 25 Nov 2023 11:42:40 -0800 Subject: [PATCH 1/8] feat(docs): update docs for new simulation APIs --- docs/site/docs/getting-started/inverter.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/site/docs/getting-started/inverter.md b/docs/site/docs/getting-started/inverter.md index 0bea69ead..b4fddd593 100644 --- a/docs/site/docs/getting-started/inverter.md +++ b/docs/site/docs/getting-started/inverter.md @@ -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 From 9d4e14e5a8cd828499a6322298c393657e293648 Mon Sep 17 00:00:00 2001 From: rohanku Date: Sat, 25 Nov 2023 16:29:29 -0800 Subject: [PATCH 2/8] add scir docs --- docs/site/docs/getting-started/inverter.md | 4 +- docs/site/docs/schematics/blocks.md | 1 - docs/site/docs/schematics/scir.md | 87 ++++++++++++++++++++++ 3 files changed, 89 insertions(+), 3 deletions(-) create mode 100644 docs/site/docs/schematics/scir.md diff --git a/docs/site/docs/getting-started/inverter.md b/docs/site/docs/getting-started/inverter.md index b4fddd593..e5a084df6 100644 --- a/docs/site/docs/getting-started/inverter.md +++ b/docs/site/docs/getting-started/inverter.md @@ -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: @@ -246,7 +246,7 @@ To add Spectre support, we can simply add the following code: {InverterTb} 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 diff --git a/docs/site/docs/schematics/blocks.md b/docs/site/docs/schematics/blocks.md index a0bd8ab24..386ea8f20 100644 --- a/docs/site/docs/schematics/blocks.md +++ b/docs/site/docs/schematics/blocks.md @@ -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. | diff --git a/docs/site/docs/schematics/scir.md b/docs/site/docs/schematics/scir.md new file mode 100644 index 000000000..db15445ac --- /dev/null +++ b/docs/site/docs/schematics/scir.md @@ -0,0 +1,87 @@ +--- +sidebar_position: 4 +--- + +import CodeSnippet from '@site/src/components/CodeSnippet'; +import VdividerMod from '@substrate/examples/spice_vdivider/src/lib.rs?snippet'; +import Core from '@substrate/docs/examples/examples/core.rs?snippet'; + +# Schematic cell intermediate representation (SCIR) + +SCIR is an intermediate representation used by Substrate to allow schematic portability between netlisters and simulators. +This section will cover where SCIR is used in Substrate's API and how it interfaces with plugins for tools like ngspice and Spectre. + +## Overview + +### Basic objects + +The table below provides high-level definitions of basic SCIR objects. + +| Term | Definition | +| --- | --- | +| Instance | An instantiation of another SCIR object. | +| Cell | A collection of interconnected SCIR instances, corresponding to a SUBCKT in SPICE. | +| Signal | A node or bus within a SCIR cell. | +| Slice | A subset of a SCIR signal. | +| Port | A SCIR signal that has been exposed to the instantiators of its parent SCIR cell. | +| Connection | A SCIR signal from a parent cell connected to a port of child SCIR instance. | +| Library | A set of cells, of which one may be designated as the "top" cell. | + +Take the following SPICE circuit as an example: + +```spice +* CMOS buffer + +.subckt buffer din dout vdd vss +X0 din dinb vdd vss inverter +X1 dinb dout vdd vss inverter +.ends + +.subckt inverter din dout vdd vss +X0 dout din vss vss sky130_fd_pr__nfet_01v8 w=2 l=0.15 +X1 dout din vdd vdd sky130_fd_pr__pfet_01v8 w=4 l=0.15 +.ends +``` + +This circuit could conceptually be parsed to a SCIR library containing two cells named `buffer` and `inverter`. The buffer cell would contain 5 signals (`din`, `dinb`, `dout`, `vdd`, and `vss`), 4 of which are exposed as ports, as well as two instances of the `inverter` cell. The `dinb` signal is connected to the `dout` port of the first inverter instance and the `din` port of the second inverter instance. + +### Primitives + +Since SCIR cells are simply collections of SCIR instances, SCIR instances must be able to instantiate more than just cells since we would otherwise only be able to represent an empty hierarchy. As such, SCIR allows users to define a set of arbitrary primitives that can be instantiated within SCIR cells. These primitives are opaque to SCIR and contain any data that the user sees fit. + +In the above buffer example, the `sky130_fd_pr__nfet_01v8` and `sky130_fd_pr__pfet_01v8` are a type of primitive called a "raw instance" that allow a SCIR instance to reference an external SPICE model and provide its parameters. In Rust, the primitive definition looks like this: + +```rs +enum Primitive { + // ... + + /// A raw instance with an associated cell. + RawInstance { + /// The ordered ports of the instance. + ports: Vec, + /// The associated cell. + cell: ArcStr, + /// Parameters associated with the raw instance. + params: HashMap, + } + + // ... +} +``` + +While SCIR cells and instances do not have parameters, parameters can be injected using SCIR primitives as shown above. + +### Schemas + +SCIR schemas are simply sets of primitives that can be used to describe circuits. For example, the SPICE schema consists of MOSFET, resistor, capacitor, raw instance, and other primitives that can describe any circuit that can be netlisted to SPICE. Similarly, SKY130 is also a schema since it has its own set of primitive MOSFETs and resistors that can be fabricated in the SKY130 process. + +SCIR schemas allows portability to be elegantly encoded by defining which schemas a schema can be converted to. Since the SKY130 PDK supports simulations in ngspice and Spectre, we can declare that the SKY130 schema can be converted to both the ngspice and Spectre schemas. The specifics of this procedure will be detailed later on in this section. + +### Relationship to Substrate + +Generators in Substrate produce cells that can be exported to SCIR. Substrate's APIs allow defining schematics in different schemas, which encodes generator compatibility in the Rust type system. For example, a Substrate block with a schematic in the `Sky130Pdk` schema can be included in a Spectre testbench's schematic in the `Spectre` schema, but cannot be included in an HSPICE testbench since the `Sky130Pdk` schema is not compatible with the HSPICE schema. Similarly, you cannot instantiate a SKY130 schematic in a schematic in a different process node (e.g. GF180). + +While the user generally interfaces with Substrate's block API, simulator and netlister plugins interface with SCIR. +This allows backend tools to abstract away Substrate's internal representation of cells. +For simulation and netlisting, Substrate will export the appropriate cell to SCIR and pass the generated SCIR +library to the necessary plugin for processing. From 0ad5068f30a2a274235dde481a21c4db0852bd03 Mon Sep 17 00:00:00 2001 From: rohanku Date: Sun, 26 Nov 2023 09:49:05 -0800 Subject: [PATCH 3/8] work on updating examples --- examples/Cargo.lock | 129 ++++++++++++++-------------- examples/sky130_inverter/Cargo.toml | 8 +- examples/sky130_inverter/src/lib.rs | 2 +- examples/sky130_inverter/src/tb.rs | 128 +++++++++------------------ examples/spice_vdivider/Cargo.toml | 4 +- examples/spice_vdivider/src/lib.rs | 3 +- 6 files changed, 114 insertions(+), 160 deletions(-) diff --git a/examples/Cargo.lock b/examples/Cargo.lock index 87ea7fb33..26d301ef5 100644 --- a/examples/Cargo.lock +++ b/examples/Cargo.lock @@ -169,6 +169,28 @@ version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "96d30a06541fbafbc7f82ed10c06164cfbd2c401138f6addd8404629c4b16711" +[[package]] +name = "async-stream" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd56dd203fef61ac097dd65721a419ddccb106b2d2b70ba60a6b529f03961a51" +dependencies = [ + "async-stream-impl", + "futures-core", + "pin-project-lite", +] + +[[package]] +name = "async-stream-impl" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "16e62a023e7c117e27523144c5d2459f4397fcc3cab0085af8e2224f643a0193" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.39", +] + [[package]] name = "async-trait" version = "0.1.74" @@ -393,9 +415,9 @@ checksum = "a2bd12c1caf447e69cd4528f47f94d203fd2582878ecb9e9465484c4148a8223" [[package]] name = "cache" -version = "0.4.0" +version = "0.5.0" source = "registry+https://github.com/substrate-labs/crates-index" -checksum = "923f343ac9b24a38bca9909ef6ede9f96314c732bda55312386125e03ad57256" +checksum = "1b4fe5e72a78dc1c68e859ed236162d04d0f034a7382a66861239cf908f54631" dependencies = [ "backoff", "clap", @@ -407,7 +429,7 @@ dependencies = [ "lazy_static", "once_cell", "path-absolutize", - "prost 0.11.9", + "prost", "prost-types", "regex", "rusqlite", @@ -519,9 +541,9 @@ checksum = "702fc72eb24e5a1e48ce58027a675bc24edd52096d5397d4aea7c6dd9eca0bd1" [[package]] name = "codegen" -version = "0.7.1" +version = "0.8.0" source = "registry+https://github.com/substrate-labs/crates-index" -checksum = "9c8214b030c5ec1e0f8f0f1e6fc7a5f35fa08845a9549b1f06ab3cb7b9d7f965" +checksum = "0873505bf1399df6b43e9c9a134245cbf525fdb9ad80f0f58e49be2bd52433f3" dependencies = [ "convert_case 0.6.0", "darling 0.20.3", @@ -542,9 +564,9 @@ checksum = "acbf1af155f9b9ef647e42cdc158db4b64a1b61f743629225fde6f3e0be2a7c7" [[package]] name = "config" -version = "0.2.4" +version = "0.2.5" source = "registry+https://github.com/substrate-labs/crates-index" -checksum = "f08355427ddff7fdaa525bb9d7cd1b79b77c6307f0e5c797bc315833d91c5259" +checksum = "c867f0d190ed946e54cf3b28291ad37cbb5aab17937725981a6b7303e7275a4b" dependencies = [ "anyhow", "cache", @@ -829,9 +851,9 @@ dependencies = [ [[package]] name = "examples" -version = "0.4.1" +version = "0.5.0" source = "registry+https://github.com/substrate-labs/crates-index" -checksum = "a07133f0734726da77f3db8cafdfa5ec4f969dd59a7b903ade3347706e211a19" +checksum = "127053821478b3b829b646eebb5c971ad2968abb66a9422aaa3b2ef186f3b994" dependencies = [ "proc-macro-crate 1.3.1", "proc-macro-error", @@ -886,9 +908,9 @@ checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" [[package]] name = "fs4" -version = "0.6.6" +version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2eeb4ed9e12f43b7fa0baae3f9cdda28352770132ef2e09a23760c29cae8bd47" +checksum = "29f9df8a11882c4e3335eb2d18a0137c505d9ca927470b0cac9c6f0ae07d28f7" dependencies = [ "async-trait", "rustix", @@ -1305,15 +1327,6 @@ dependencies = [ "cfg-if", ] -[[package]] -name = "itertools" -version = "0.10.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b0fd2260e829bddf4cb6ea802289de2f86d6a7a690192fbe91b3f46e0f2c8473" -dependencies = [ - "either", -] - [[package]] name = "itertools" version = "0.11.0" @@ -1445,9 +1458,9 @@ checksum = "e5ce46fe64a9d73be07dcbe690a38ce1b293be448fd8ce1e6c1b8062c9f72c6a" [[package]] name = "ngspice" -version = "0.2.0" +version = "0.3.0" source = "registry+https://github.com/substrate-labs/crates-index" -checksum = "492063038a96be1e4a6e65fd7fdd0f314abeb0fa61ee13542ad9259159e2839d" +checksum = "409596c58583879e7d01e554331cb509a858794a38f7722a6ec10420cbb4b1cc" dependencies = [ "arcstr", "cache", @@ -1611,6 +1624,12 @@ dependencies = [ "regex", ] +[[package]] +name = "paste" +version = "1.0.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "de3145af08024dea9fa9914f381a17b8fc6034dfb00f3a84013f7ff43f29ed4c" + [[package]] name = "path-absolutize" version = "3.1.1" @@ -1840,16 +1859,6 @@ dependencies = [ "unicode-ident", ] -[[package]] -name = "prost" -version = "0.11.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b82eaa1d779e9a4bc1c3217db8ffbeabaae1dca241bf70183242128d48681cd" -dependencies = [ - "bytes", - "prost-derive 0.11.9", -] - [[package]] name = "prost" version = "0.12.1" @@ -1857,7 +1866,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f4fdd22f3b9c31b53c060df4a0613a1c7f062d4115a2b984dd15b1858f7e340d" dependencies = [ "bytes", - "prost-derive 0.12.1", + "prost-derive", ] [[package]] @@ -1868,13 +1877,13 @@ checksum = "8bdf592881d821b83d471f8af290226c8d51402259e9bb5be7f9f8bdebbb11ac" dependencies = [ "bytes", "heck", - "itertools 0.11.0", + "itertools", "log", "multimap", "once_cell", "petgraph", "prettyplease", - "prost 0.12.1", + "prost", "prost-types", "regex", "syn 2.0.39", @@ -1882,19 +1891,6 @@ dependencies = [ "which", ] -[[package]] -name = "prost-derive" -version = "0.11.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e5d2d8d10f3c6ded6da8b05b5fb3b8a5082514344d56c9f871412d29b4e075b4" -dependencies = [ - "anyhow", - "itertools 0.10.5", - "proc-macro2", - "quote", - "syn 1.0.109", -] - [[package]] name = "prost-derive" version = "0.12.1" @@ -1902,7 +1898,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "265baba7fabd416cf5078179f7d2cbeca4ce7a9041111900675ea7c4cb8a4c32" dependencies = [ "anyhow", - "itertools 0.11.0", + "itertools", "proc-macro2", "quote", "syn 2.0.39", @@ -1914,7 +1910,7 @@ version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e081b29f63d83a4bc75cfc9f3fe424f9156cf92d8a4f0c9407cce9a1b67327cf" dependencies = [ - "prost 0.12.1", + "prost", ] [[package]] @@ -2300,13 +2296,14 @@ dependencies = [ [[package]] name = "sky130pdk" -version = "0.7.1" +version = "0.8.0" source = "registry+https://github.com/substrate-labs/crates-index" -checksum = "34f972b5f4bb8ab8201c4255313f8fd1cf08e624ce0fb19ddf1e2466f8b86a8d" +checksum = "cb11327b577b5ef5bdea872f0608f2bc17ab73e7cfce33961fb93c2df82205c5" dependencies = [ "arcstr", "indexmap 2.1.0", "ngspice", + "paste", "rust_decimal", "rust_decimal_macros", "scir", @@ -2378,13 +2375,13 @@ dependencies = [ [[package]] name = "spectre" -version = "0.8.0" +version = "0.9.0" source = "registry+https://github.com/substrate-labs/crates-index" -checksum = "2638efee60e4a6471c656bb1b0f680879bbf7f6cbf56708a7846479b65486d29" +checksum = "8e475c509a5ab22cf7fca6efff3bce5206518376c468907df35b45836164f071" dependencies = [ "arcstr", "cache", - "itertools 0.11.0", + "itertools", "lazy_static", "nutlex", "rust_decimal", @@ -2400,14 +2397,15 @@ dependencies = [ [[package]] name = "spice" -version = "0.6.0" +version = "0.7.0" source = "registry+https://github.com/substrate-labs/crates-index" -checksum = "2aa3f84e7bc872f7282c13cd348df118c009ad4202755a519a7632457718070d" +checksum = "af37250730d8ba2ad78cb1d4b7a53a526bf1f724d3a60076cdebfb6ffc8d4f93" dependencies = [ "arcstr", "enumify", - "itertools 0.11.0", + "itertools", "nom", + "regex", "rust_decimal", "rust_decimal_macros", "scir", @@ -2435,9 +2433,9 @@ checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" [[package]] name = "substrate" -version = "0.7.1" +version = "0.8.0" source = "registry+https://github.com/substrate-labs/crates-index" -checksum = "c81e6aefa0dc55be374ad2dd4baa88eff54dd0716a615c9ed8e20867e3b7bf25" +checksum = "9d105f701ce297497f62a377ddc90f39cb8254533f35aea296f8f789def5a843" dependencies = [ "anyhow", "arcstr", @@ -2734,16 +2732,15 @@ dependencies = [ [[package]] name = "tonic" -version = "0.9.2" +version = "0.10.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3082666a3a6433f7f511c7192923fa1fe07c69332d3c6a2e6bb040b569199d5a" +checksum = "d560933a0de61cf715926b9cac824d4c883c2c43142f787595e48280c40a1d0e" dependencies = [ + "async-stream", "async-trait", "axum", "base64", "bytes", - "futures-core", - "futures-util", "h2", "http", "http-body", @@ -2751,7 +2748,7 @@ dependencies = [ "hyper-timeout", "percent-encoding", "pin-project", - "prost 0.11.9", + "prost", "tokio", "tokio-stream", "tower", @@ -2893,7 +2890,7 @@ source = "registry+https://github.com/substrate-labs/crates-index" checksum = "f995bd535cef0cebcb8b3f288a2c41eabb3c0911c2e84832da5cb6e3d34a4501" dependencies = [ "darling 0.20.3", - "itertools 0.11.0", + "itertools", "proc-macro-crate 1.3.1", "proc-macro-error", "proc-macro2", diff --git a/examples/sky130_inverter/Cargo.toml b/examples/sky130_inverter/Cargo.toml index 555a67e30..fa9c8c591 100644 --- a/examples/sky130_inverter/Cargo.toml +++ b/examples/sky130_inverter/Cargo.toml @@ -6,16 +6,16 @@ publish = false # begin-code-snippet dependencies [dependencies] -substrate = { version = "0.7.1", registry = "substrate" } -ngspice = { version = "0.2.0", registry = "substrate" } -sky130pdk = { version = "0.7.1", registry = "substrate" } +substrate = { version = "0.8.0", registry = "substrate" } +ngspice = { version = "0.3.0", registry = "substrate" } +sky130pdk = { version = "0.8.0", registry = "substrate" } serde = { version = "1", features = ["derive"] } rust_decimal = "1.30" rust_decimal_macros = "1.30" # end-code-snippet dependencies -spectre = { version = "0.8.0", registry = "substrate", optional = true } +spectre = { version = "0.9.0", registry = "substrate", optional = true } # This feature flag allows us to control which tests are run. # We don't want to run tests that use Spectre when we are developing locally. diff --git a/examples/sky130_inverter/src/lib.rs b/examples/sky130_inverter/src/lib.rs index 7ce491910..aa7d58398 100644 --- a/examples/sky130_inverter/src/lib.rs +++ b/examples/sky130_inverter/src/lib.rs @@ -22,7 +22,7 @@ pub struct InverterIo { // begin-code-snippet inverter-struct #[derive(Serialize, Deserialize, Block, Debug, Copy, Clone, Hash, PartialEq, Eq)] -#[substrate(io = "InverterIo", kind = "Cell")] +#[substrate(io = "InverterIo")] pub struct Inverter { /// NMOS width. pub nw: i64, diff --git a/examples/sky130_inverter/src/tb.rs b/examples/sky130_inverter/src/tb.rs index a98be6751..0578208dd 100644 --- a/examples/sky130_inverter/src/tb.rs +++ b/examples/sky130_inverter/src/tb.rs @@ -1,6 +1,7 @@ // begin-code-snippet imports use super::Inverter; +use ngspice::tran::Tran; use ngspice::Ngspice; use rust_decimal::prelude::ToPrimitive; use rust_decimal_macros::dec; @@ -13,14 +14,14 @@ use substrate::context::{Context, PdkContext}; use substrate::io::{Node, SchematicType, Signal, TestbenchIo}; use substrate::pdk::corner::Pvt; use substrate::schematic::{Cell, CellBuilder, ExportsNestedData, Schematic}; -use substrate::simulation::data::FromSaved; +use substrate::simulation::data::{tran, FromSaved, SaveTb}; use substrate::simulation::waveform::{EdgeDir, TimeWaveform, WaveformRef}; use substrate::simulation::{SimulationContext, Simulator, Testbench}; // end-code-snippet imports // begin-code-snippet struct-and-impl #[derive(Clone, Copy, Debug, Eq, PartialEq, Hash, Serialize, Deserialize, Block)] -#[substrate(io = "TestbenchIo", kind = "Cell")] +#[substrate(io = "TestbenchIo")] pub struct InverterTb { pvt: Pvt, dut: Inverter, @@ -78,60 +79,38 @@ impl Schematic for InverterTb { // begin-code-snippet testbench #[derive(Debug, Clone, FromSaved)] -pub struct NgspiceVout { - t: ngspice::tran::TranTime, - v: ngspice::tran::TranVoltage, -} - -#[derive(Debug, Clone, Serialize, Deserialize)] pub struct Vout { - t: Vec, - v: Vec, + t: tran::Time, + v: tran::Voltage, } -impl From for Vout { - fn from(value: NgspiceVout) -> Self { - Self { - t: (*value.t).clone(), - v: (*value.v).clone(), - } - } -} - -impl substrate::simulation::data::Save> - for NgspiceVout -{ - fn save( +impl SaveTb for InverterTb { + fn save_tb( ctx: &SimulationContext, - to_save: &Cell, + cell: &Cell, opts: &mut ::Options, - ) -> Self::Key { - Self::Key { - t: ngspice::tran::TranTime::save(ctx, to_save, opts), - v: ngspice::tran::TranVoltage::save(ctx, to_save.data(), opts), + ) -> >::SavedKey { + VoutSavedKey { + t: tran::Time::save(ctx, to_save, opts), + v: tran::Voltage::save(ctx, to_save.data(), opts), } } } -impl Testbench for InverterTb { +impl Testbench for InverterTb { type Output = Vout; - fn run( - &self, - sim: substrate::simulation::SimController, - ) -> Self::Output { - let opts = ngspice::Options::default(); - let out: NgspiceVout = sim - .simulate( - opts, - Some(&self.pvt.corner), - ngspice::tran::Tran { - stop: dec!(2e-9), - step: dec!(1e-11), - ..Default::default() - }, - ) - .expect("failed to run simulation"); - out.into() + fn run(&self, sim: substrate::simulation::SimController) -> Self::Output { + let mut opts = ngspice::Options::default(); + sim.set_option(self.pvt.corner, &mut opts); + sim.simulate( + opts, + ngspice::tran::Tran { + stop: dec!(2e-9), + step: dec!(1e-11), + ..Default::default() + }, + ) + .expect("failed to run simulation") } } // end-code-snippet testbench @@ -157,7 +136,7 @@ impl InverterDesign { work_dir: impl AsRef, ) -> Inverter where - InverterTb: Testbench, + InverterTb: Testbench, { let work_dir = work_dir.as_ref(); let pvt = Pvt::new(Sky130Corner::Tt, dec!(1.8), dec!(25)); @@ -287,56 +266,33 @@ pub mod spectre_support { } } - #[derive(Debug, Clone, FromSaved)] - pub struct SpectreVout { - t: spectre::tran::TranTime, - v: spectre::tran::TranVoltage, - } - - impl From for Vout { - fn from(value: SpectreVout) -> Self { - Self { - t: (*value.t).clone(), - v: (*value.v).clone(), - } - } - } - - impl substrate::simulation::data::Save> - for SpectreVout - { + impl substrate::simulation::data::Save> for Vout { fn save( ctx: &SimulationContext, to_save: &Cell, opts: &mut ::Options, ) -> Self::Key { Self::Key { - t: spectre::tran::TranTime::save(ctx, to_save, opts), - v: spectre::tran::TranVoltage::save(ctx, to_save.data(), opts), + t: tran::Time::save(ctx, to_save, opts), + v: tran::Voltage::save(ctx, to_save.data(), opts), } } } - impl Testbench for InverterTb { + impl Testbench for InverterTb { type Output = Vout; - fn run( - &self, - sim: substrate::simulation::SimController, - ) -> Self::Output { - let opts = spectre::Options::default(); - let out: SpectreVout = sim - .simulate( - opts, - Some(&self.pvt.corner), - spectre::tran::Tran { - stop: dec!(2e-9), - errpreset: Some(spectre::ErrPreset::Conservative), - ..Default::default() - }, - ) - .expect("failed to run simulation"); - - out.into() + fn run(&self, sim: substrate::simulation::SimController) -> Self::Output { + let mut opts = spectre::Options::default(); + sim.set_option(self.pvt.corner, &mut opts); + sim.simulate( + opts, + spectre::tran::Tran { + stop: dec!(2e-9), + errpreset: Some(spectre::ErrPreset::Conservative), + ..Default::default() + }, + ) + .expect("failed to run simulation") } } diff --git a/examples/spice_vdivider/Cargo.toml b/examples/spice_vdivider/Cargo.toml index 7fc4c7ab1..8f9995cb0 100644 --- a/examples/spice_vdivider/Cargo.toml +++ b/examples/spice_vdivider/Cargo.toml @@ -6,8 +6,8 @@ publish = false # begin-code-snippet dependencies [dependencies] -substrate = { version = "0.7.1", registry = "substrate" } -spice = { version = "0.6.0", registry = "substrate" } +substrate = { version = "0.8.0", registry = "substrate" } +spice = { version = "0.7.0", registry = "substrate" } serde = { version = "1", features = ["derive"] } rust_decimal = "1.30" diff --git a/examples/spice_vdivider/src/lib.rs b/examples/spice_vdivider/src/lib.rs index 18b6a30f8..fb9a83f1b 100644 --- a/examples/spice_vdivider/src/lib.rs +++ b/examples/spice_vdivider/src/lib.rs @@ -19,7 +19,7 @@ pub struct VdividerIo { // begin-code-snippet vdivider-struct #[derive(Serialize, Deserialize, Block, Debug, Copy, Clone, Hash, PartialEq, Eq)] -#[substrate(io = "VdividerIo", kind = "Cell")] +#[substrate(io = "VdividerIo")] pub struct Vdivider { /// The top resistance. pub r1: Decimal, @@ -51,6 +51,7 @@ impl Schematic for Vdivider { } } // end-code-snippet vdivider-schematic + // begin-code-snippet tests #[cfg(test)] mod tests { From d9cdfc637cbbbd850ef253f5ef139cd8090a717c Mon Sep 17 00:00:00 2001 From: rohanku Date: Sun, 26 Nov 2023 13:57:02 -0800 Subject: [PATCH 4/8] fix examples --- examples/sky130_inverter/src/tb.rs | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/examples/sky130_inverter/src/tb.rs b/examples/sky130_inverter/src/tb.rs index 0578208dd..6b4757473 100644 --- a/examples/sky130_inverter/src/tb.rs +++ b/examples/sky130_inverter/src/tb.rs @@ -91,8 +91,8 @@ impl SaveTb for InverterTb { opts: &mut ::Options, ) -> >::SavedKey { VoutSavedKey { - t: tran::Time::save(ctx, to_save, opts), - v: tran::Voltage::save(ctx, to_save.data(), opts), + t: tran::Time::save(ctx, (), opts), + v: tran::Voltage::save(ctx, cell.data(), opts), } } } @@ -266,15 +266,15 @@ pub mod spectre_support { } } - impl substrate::simulation::data::Save> for Vout { - fn save( + impl substrate::simulation::data::SaveTb for InverterTb { + fn save_tb( ctx: &SimulationContext, - to_save: &Cell, + cell: &Cell, opts: &mut ::Options, - ) -> Self::Key { - Self::Key { - t: tran::Time::save(ctx, to_save, opts), - v: tran::Voltage::save(ctx, to_save.data(), opts), + ) -> >::SavedKey { + VoutSavedKey { + t: tran::Time::save(ctx, cell, opts), + v: tran::Voltage::save(ctx, cell.data(), opts), } } } From 330e794348185e2bb719828252b5f1af86fda16e Mon Sep 17 00:00:00 2001 From: rohanku Date: Tue, 28 Nov 2023 16:11:47 -0800 Subject: [PATCH 5/8] work on scir documentation --- docs/examples/examples/core.rs | 19 +++++++++++ docs/site/docs/getting-started/inverter.md | 7 ++-- docs/site/docs/schematics/schematics.md | 2 +- docs/site/docs/schematics/scir.md | 37 +++++++++++++++++++++- examples/Justfile | 3 ++ examples/sky130_inverter/src/tb.rs | 10 +++--- 6 files changed, 68 insertions(+), 10 deletions(-) diff --git a/docs/examples/examples/core.rs b/docs/examples/examples/core.rs index df69e7c94..d5d698077 100644 --- a/docs/examples/examples/core.rs +++ b/docs/examples/examples/core.rs @@ -1036,3 +1036,22 @@ mod generate { } // end-code-snippet vdivider-generate-add-error-handling } + +mod scir { + use substrate::scir::schema::{Schema, StringSchema}; + use substrate::scir::{Cell, LibraryBuilder}; + + fn library() { + // begin-code-snippet scir-library-builder + let mut lib = LibraryBuilder::::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 + let composite_cell = Cell::new("composite_cell"); + } +} diff --git a/docs/site/docs/getting-started/inverter.md b/docs/site/docs/getting-started/inverter.md index e5a084df6..09cffec18 100644 --- a/docs/site/docs/getting-started/inverter.md +++ b/docs/site/docs/getting-started/inverter.md @@ -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: @@ -189,9 +189,8 @@ This is how our testbench looks: {InverterTb} -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 diff --git a/docs/site/docs/schematics/schematics.md b/docs/site/docs/schematics/schematics.md index 010f18d79..89420e464 100644 --- a/docs/site/docs/schematics/schematics.md +++ b/docs/site/docs/schematics/schematics.md @@ -56,7 +56,7 @@ Once a block has an associated IO and nested data, you can define its schematic {VdividerMod} Let's look at what each part of the implementation is doing. -- In the first line, we implement `Schematic` 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` 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. diff --git a/docs/site/docs/schematics/scir.md b/docs/site/docs/schematics/scir.md index db15445ac..8ddb38983 100644 --- a/docs/site/docs/schematics/scir.md +++ b/docs/site/docs/schematics/scir.md @@ -3,7 +3,6 @@ sidebar_position: 4 --- import CodeSnippet from '@site/src/components/CodeSnippet'; -import VdividerMod from '@substrate/examples/spice_vdivider/src/lib.rs?snippet'; import Core from '@substrate/docs/examples/examples/core.rs?snippet'; # Schematic cell intermediate representation (SCIR) @@ -85,3 +84,39 @@ While the user generally interfaces with Substrate's block API, simulator and ne This allows backend tools to abstract away Substrate's internal representation of cells. For simulation and netlisting, Substrate will export the appropriate cell to SCIR and pass the generated SCIR library to the necessary plugin for processing. + +## Technical Details + +### Schemas + +Every SCIR library requires an underlying schema that implements the [`Schema`](https://api.substratelabs.io/scir/schema/trait.Schema.html) trait. + +```rs +pub trait Schema { + type Primitive: Primitive; +} +``` + +A SCIR schema has an associated primitive type that describes available primitives for representing objects that cannot be represented directly in SCIR. As an example, the most basic schema, [`NoSchema`](https://api.substratelabs.io/scir/schema/struct.NoSchema.html), has a primitive type of [`NoPrimitive`](https://api.substratelabs.io/scir/schema/struct.NoPrimitive.html#) that cannot be instantiated — as such, any SCIR library with this schema will have no primitives. + +### Libraries + +Once we have a schema, we can start creating a SCIR library by instantiating a [`LibraryBuilder`](https://api.substratelabs.io/scir/struct.LibraryBuilder.html). To create a library with the [`StringSchema`] schema, whose primitives are arbitrary `ArcStr`s, we write the following: + +{Core} + +SCIR libraries are collections of SCIR cells and primitives. We can create a new cell and add it to our library: + +{Core} + +We can also add primitives to the library as follows (since we are using `StringSchema`, the value of the primitive must be an `ArcStr`): + +{Core} + +SCIR cells may contain signals that connect instances and/or serve as ports that interface with parent cells. + +{Core} + +SCIR cells may also contain instances of SCIR primitives and other cells. We can connect ports of each instance to signals in the parent cell. While connections to instances of SCIR cells must connect to ports declared in the underlying cell, connections to primitives are not checked by SCIR as primitives are opaque to SCIR. + +{Core} diff --git a/examples/Justfile b/examples/Justfile index 6b6d3d738..a4759fcc6 100644 --- a/examples/Justfile +++ b/examples/Justfile @@ -5,3 +5,6 @@ _default: test: cargo test --locked +check: + cargo clippy --locked --all-features --all-targets -- -D warnings + diff --git a/examples/sky130_inverter/src/tb.rs b/examples/sky130_inverter/src/tb.rs index 6b4757473..292af85a7 100644 --- a/examples/sky130_inverter/src/tb.rs +++ b/examples/sky130_inverter/src/tb.rs @@ -198,9 +198,10 @@ pub fn sky130_open_ctx() -> PdkContext { let pdk_root = std::env::var("SKY130_OPEN_PDK_ROOT") .expect("the SKY130_OPEN_PDK_ROOT environment variable must be set"); Context::builder() - .with_simulator(Ngspice::default()) + .install(Ngspice::default()) + .install(Sky130Pdk::open(pdk_root)) .build() - .with_pdk(Sky130Pdk::open(pdk_root)) + .with_pdk() } // end-code-snippet sky130-open-ctx @@ -309,9 +310,10 @@ pub mod spectre_support { let pdk_root = std::env::var("SKY130_COMMERCIAL_PDK_ROOT") .expect("the SKY130_COMMERCIAL_PDK_ROOT environment variable must be set"); Context::builder() - .with_simulator(Spectre::default()) + .install(Spectre::default()) + .install(Sky130Pdk::commercial(pdk_root)) .build() - .with_pdk(Sky130Pdk::commercial(pdk_root)) + .with_pdk() } #[cfg(test)] From 8edcc689d492313a1431b74e404fb892ea6483c7 Mon Sep 17 00:00:00 2001 From: rohanku Date: Tue, 28 Nov 2023 23:01:44 -0800 Subject: [PATCH 6/8] finish documentation updates --- docs/examples/examples/core.rs | 151 +++++++++++++++++++++++- docs/site/docs/schematics/scir.md | 52 +++++++- docs/site/docs/schematics/simulation.md | 35 ++++++ examples/Cargo.lock | 28 ++--- examples/sky130_inverter/Cargo.toml | 8 +- examples/sky130_inverter/src/tb.rs | 4 +- examples/spice_vdivider/Cargo.toml | 4 +- tools/ngspice/src/tran.rs | 11 +- tools/spectre/src/tran.rs | 10 -- 9 files changed, 255 insertions(+), 48 deletions(-) create mode 100644 docs/site/docs/schematics/simulation.md diff --git a/docs/examples/examples/core.rs b/docs/examples/examples/core.rs index d5d698077..a3d864516 100644 --- a/docs/examples/examples/core.rs +++ b/docs/examples/examples/core.rs @@ -1038,9 +1038,102 @@ mod generate { } 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, LibraryBuilder}; + 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 for Resistor { + fn schematic( + &self, + io: &<::Io as SchematicType>::Bundle, + cell: &mut CellBuilder, + ) -> substrate::error::Result { + 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 for ParallelResistors { + fn schematic( + &self, + io: &<::Io as SchematicType>::Bundle, + cell: &mut CellBuilder, + ) -> substrate::error::Result { + // Creates a SCIR library containing the desired cell. + let mut lib = LibraryBuilder::::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::::new(); @@ -1052,6 +1145,60 @@ mod scir { // begin-code-snippet scir-library-primitive let resistor_id = lib.add_primitive(arcstr::literal!("resistor")); // end-code-snippet scir-library-primitive - let composite_cell = Cell::new("composite_cell"); + // 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 } } diff --git a/docs/site/docs/schematics/scir.md b/docs/site/docs/schematics/scir.md index 8ddb38983..7edb2a2f5 100644 --- a/docs/site/docs/schematics/scir.md +++ b/docs/site/docs/schematics/scir.md @@ -50,7 +50,7 @@ Since SCIR cells are simply collections of SCIR instances, SCIR instances must b In the above buffer example, the `sky130_fd_pr__nfet_01v8` and `sky130_fd_pr__pfet_01v8` are a type of primitive called a "raw instance" that allow a SCIR instance to reference an external SPICE model and provide its parameters. In Rust, the primitive definition looks like this: -```rs +```rust enum Primitive { // ... @@ -91,17 +91,41 @@ library to the necessary plugin for processing. Every SCIR library requires an underlying schema that implements the [`Schema`](https://api.substratelabs.io/scir/schema/trait.Schema.html) trait. -```rs +```rust pub trait Schema { type Primitive: Primitive; } ``` -A SCIR schema has an associated primitive type that describes available primitives for representing objects that cannot be represented directly in SCIR. As an example, the most basic schema, [`NoSchema`](https://api.substratelabs.io/scir/schema/struct.NoSchema.html), has a primitive type of [`NoPrimitive`](https://api.substratelabs.io/scir/schema/struct.NoPrimitive.html#) that cannot be instantiated — as such, any SCIR library with this schema will have no primitives. +A SCIR schema has an associated primitive type that describes available primitives for representing objects that cannot be represented directly in SCIR. As an example, the most basic schema, [`NoSchema`](https://api.substratelabs.io/scir/schema/struct.NoSchema.html), has a primitive type of [`NoPrimitive`](https://api.substratelabs.io/scir/schema/struct.NoPrimitive.html) that cannot be instantiated — as such, any SCIR library with this schema will have no primitives. + +The relationship between schemas is encoded via the [`FromSchema`](https://api.substratelabs.io/scir/schema/trait.FromSchema.html) trait, which describes how one schema is converted to another. + +```rs +pub trait FromSchema: Schema { + type Error; + + // Required methods + fn convert_primitive( + primitive: ::Primitive + ) -> Result<::Primitive, Self::Error>; + fn convert_instance( + instance: &mut Instance, + primitive: &::Primitive + ) -> Result<(), Self::Error>; +} +``` + +Schemas that are inter-convertible must have a 1-to-1 correspondence between their primitives, as shown by the +signature of `fn convert_primitive(...)`. The instance conversion function, `fn convert_instance(...)`, +allows you to modify the connections of a SCIR instance that is associated with a primitive to correctly +connect to the ports of the primitive in the new schema. + +The `FromSchema` trait is particularly important since it allows for schematics to be made simulator and netlist portable, and potentially even process portable, as we will see later. ### Libraries -Once we have a schema, we can start creating a SCIR library by instantiating a [`LibraryBuilder`](https://api.substratelabs.io/scir/struct.LibraryBuilder.html). To create a library with the [`StringSchema`] schema, whose primitives are arbitrary `ArcStr`s, we write the following: +Once we have a schema, we can start creating a SCIR library by instantiating a [`LibraryBuilder`](https://api.substratelabs.io/scir/struct.LibraryBuilder.html). To create a library with the [`StringSchema`](https://api.substratelabs.io/scir/schema/struct.StringSchema.html) schema, whose primitives are arbitrary `ArcStr`s, we write the following: {Core} @@ -119,4 +143,24 @@ SCIR cells may contain signals that connect instances and/or serve as ports that SCIR cells may also contain instances of SCIR primitives and other cells. We can connect ports of each instance to signals in the parent cell. While connections to instances of SCIR cells must connect to ports declared in the underlying cell, connections to primitives are not checked by SCIR as primitives are opaque to SCIR. +We can first instantiate the resistor primitives we defined earlier and add our voltage divider cell to our SCIR library. + +{Core} + +We can then create a cell that instantiates two of our newly-defined voltage divider cell. + {Core} + +### Bindings + +SCIR primitives and cells can be instantiated in Substrate generators using *bindings*. Suppose we have the following schema that supports instantiating resistor and capacitor primitives: + +{Core} + +We can create a Substrate block whose schematic corresponds to a `MyPrimitive::Resistor` using a [`PrimitiveBinding`](https://api.substratelabs.io/substrate/schematic/struct.PrimitiveBinding.html). It can then be instantiated in other Substrate generators just like any other block. + +{Core} + +Similarly, we can bind to a SCIR cell using a [`ScirBinding`](https://api.substratelabs.io/substrate/schematic/struct.ScirBinding.html): + +{Core} diff --git a/docs/site/docs/schematics/simulation.md b/docs/site/docs/schematics/simulation.md new file mode 100644 index 000000000..dc09011e9 --- /dev/null +++ b/docs/site/docs/schematics/simulation.md @@ -0,0 +1,35 @@ +--- +sidebar_position: 5 +--- + +import CodeSnippet from '@site/src/components/CodeSnippet'; +import Core from '@substrate/docs/examples/examples/core.rs?snippet'; + +# Simulation + +## Simulators + +Substrate aims to make it easy to plug and play different simulators. The way it does this is by providing a +minimal interface that each simulator must implement, defined in the [`Simulator`](https://api.substratelabs.io/substrate/simulation/trait.Simulator.html) trait. + +In essence, a simulator Substrate plugin simply needs to specify a function that takes in a set of inputs and options and returns a set of outputs or an error. The types of the inputs, outputs, options, and errors are entirely user designed, providing flexibility to the plugin writer. A simulator also needs to have an associated schema (see the [SCIR chapter](./scir.md) for more details), which defines the format in which it should be provided SCIR libraries that represent the schematic that needs to be simulated. + +### Analyses + +Once a simulator is defined, a set of supported analyses can be defined using the [`Analysis`](https://api.substratelabs.io/substrate/simulation/trait.SupportedBy.html) and [`SupportedBy`](https://api.substratelabs.io/substrate/simulation/trait.SupportedBy.html) traits, which essentially convert an analysis (e.g. transient, AC, op) to inputs that the `Simulator` trait can understand and reformats the outputs of the simulator plugin into the output expected from the analysis. + +### Options + +Simulators can also provide a set of options that allow users to modify the behavior of the simulator by carefully designing the interface for their options type. An example of this is the Spectre plugin's [`Options`](https://api.substratelabs.io/spectre/struct.Options.html) type, which allows users to specify includes, saved currents/voltages, and more. Additional supported options, especially ones that should be simulator-portable, can be defined using the [`SimOption`](https://api.substratelabs.io/substrate/simulation/options/trait.SimOption.html) trait. + +### Saved data + +Simulators can also specify what data can be saved from a testbench using the +[`FromSaved`](https://api.substratelabs.io/substrate/simulation/data/trait.FromSaved.html) and +[`Save`](https://api.substratelabs.io/substrate/simulation/data/trait.Save.html) traits. The `Save` +trait modifies the simulator options to keep track of what data needs to be saved and returns a key for +accessing that data. The `FromSaved` trait then takes this key and uses it to retrieve the associated data +from the simulation output after the simulation has run. The simulator plugin writer will need to +store keys in the options and propagate them to the simulation output so that data can be retrieved correctly. + +Simulators should generally support saving currents and voltages for nodes and terminals in Substrate and SCIR formats. diff --git a/examples/Cargo.lock b/examples/Cargo.lock index 26d301ef5..ad6a89af2 100644 --- a/examples/Cargo.lock +++ b/examples/Cargo.lock @@ -541,9 +541,9 @@ checksum = "702fc72eb24e5a1e48ce58027a675bc24edd52096d5397d4aea7c6dd9eca0bd1" [[package]] name = "codegen" -version = "0.8.0" +version = "0.8.1" source = "registry+https://github.com/substrate-labs/crates-index" -checksum = "0873505bf1399df6b43e9c9a134245cbf525fdb9ad80f0f58e49be2bd52433f3" +checksum = "8c966d6bfad305e3245f29465799c567b05a31c82d6674a9755112526e8ad9f2" dependencies = [ "convert_case 0.6.0", "darling 0.20.3", @@ -851,9 +851,9 @@ dependencies = [ [[package]] name = "examples" -version = "0.5.0" +version = "0.5.1" source = "registry+https://github.com/substrate-labs/crates-index" -checksum = "127053821478b3b829b646eebb5c971ad2968abb66a9422aaa3b2ef186f3b994" +checksum = "ebd3acd1072c5b64e6426241d07895aa54e2f42442888e7a4290ef6a7f7762a9" dependencies = [ "proc-macro-crate 1.3.1", "proc-macro-error", @@ -1458,9 +1458,9 @@ checksum = "e5ce46fe64a9d73be07dcbe690a38ce1b293be448fd8ce1e6c1b8062c9f72c6a" [[package]] name = "ngspice" -version = "0.3.0" +version = "0.3.1" source = "registry+https://github.com/substrate-labs/crates-index" -checksum = "409596c58583879e7d01e554331cb509a858794a38f7722a6ec10420cbb4b1cc" +checksum = "66bdced3817e3c459c32da28e52ad575c55d498637cfac9fba7305bcac36b72b" dependencies = [ "arcstr", "cache", @@ -2296,9 +2296,9 @@ dependencies = [ [[package]] name = "sky130pdk" -version = "0.8.0" +version = "0.8.1" source = "registry+https://github.com/substrate-labs/crates-index" -checksum = "cb11327b577b5ef5bdea872f0608f2bc17ab73e7cfce33961fb93c2df82205c5" +checksum = "592d3c6962fa3bca5386a2fc04dcbcd39806ae1ceac69e2fc666435195f4af6b" dependencies = [ "arcstr", "indexmap 2.1.0", @@ -2375,9 +2375,9 @@ dependencies = [ [[package]] name = "spectre" -version = "0.9.0" +version = "0.9.1" source = "registry+https://github.com/substrate-labs/crates-index" -checksum = "8e475c509a5ab22cf7fca6efff3bce5206518376c468907df35b45836164f071" +checksum = "6bca07586f0922168674c0b4c2cb561e31cf65e72dc817e28a35ced6052d03e2" dependencies = [ "arcstr", "cache", @@ -2397,9 +2397,9 @@ dependencies = [ [[package]] name = "spice" -version = "0.7.0" +version = "0.7.1" source = "registry+https://github.com/substrate-labs/crates-index" -checksum = "af37250730d8ba2ad78cb1d4b7a53a526bf1f724d3a60076cdebfb6ffc8d4f93" +checksum = "0417466a53adcf981c234645f8cb7b6b4e29995d002d3c48f8667905abd5947e" dependencies = [ "arcstr", "enumify", @@ -2433,9 +2433,9 @@ checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" [[package]] name = "substrate" -version = "0.8.0" +version = "0.8.1" source = "registry+https://github.com/substrate-labs/crates-index" -checksum = "9d105f701ce297497f62a377ddc90f39cb8254533f35aea296f8f789def5a843" +checksum = "b21020c4053c444a444ea77d8c50329492b9eeb0330b2c65df0f190db72ac41f" dependencies = [ "anyhow", "arcstr", diff --git a/examples/sky130_inverter/Cargo.toml b/examples/sky130_inverter/Cargo.toml index fa9c8c591..76651ab49 100644 --- a/examples/sky130_inverter/Cargo.toml +++ b/examples/sky130_inverter/Cargo.toml @@ -6,16 +6,16 @@ publish = false # begin-code-snippet dependencies [dependencies] -substrate = { version = "0.8.0", registry = "substrate" } -ngspice = { version = "0.3.0", registry = "substrate" } -sky130pdk = { version = "0.8.0", registry = "substrate" } +substrate = { version = "0.8.1", registry = "substrate" } +ngspice = { version = "0.3.1", registry = "substrate" } +sky130pdk = { version = "0.8.1", registry = "substrate" } serde = { version = "1", features = ["derive"] } rust_decimal = "1.30" rust_decimal_macros = "1.30" # end-code-snippet dependencies -spectre = { version = "0.9.0", registry = "substrate", optional = true } +spectre = { version = "0.9.1", registry = "substrate", optional = true } # This feature flag allows us to control which tests are run. # We don't want to run tests that use Spectre when we are developing locally. diff --git a/examples/sky130_inverter/src/tb.rs b/examples/sky130_inverter/src/tb.rs index 292af85a7..0f4d1b1f4 100644 --- a/examples/sky130_inverter/src/tb.rs +++ b/examples/sky130_inverter/src/tb.rs @@ -14,7 +14,7 @@ use substrate::context::{Context, PdkContext}; use substrate::io::{Node, SchematicType, Signal, TestbenchIo}; use substrate::pdk::corner::Pvt; use substrate::schematic::{Cell, CellBuilder, ExportsNestedData, Schematic}; -use substrate::simulation::data::{tran, FromSaved, SaveTb}; +use substrate::simulation::data::{tran, FromSaved, Save, SaveTb}; use substrate::simulation::waveform::{EdgeDir, TimeWaveform, WaveformRef}; use substrate::simulation::{SimulationContext, Simulator, Testbench}; // end-code-snippet imports @@ -78,7 +78,7 @@ impl Schematic for InverterTb { // end-code-snippet schematic // begin-code-snippet testbench -#[derive(Debug, Clone, FromSaved)] +#[derive(Debug, Clone, Serialize, Deserialize, FromSaved)] pub struct Vout { t: tran::Time, v: tran::Voltage, diff --git a/examples/spice_vdivider/Cargo.toml b/examples/spice_vdivider/Cargo.toml index 8f9995cb0..d1c75ce17 100644 --- a/examples/spice_vdivider/Cargo.toml +++ b/examples/spice_vdivider/Cargo.toml @@ -6,8 +6,8 @@ publish = false # begin-code-snippet dependencies [dependencies] -substrate = { version = "0.8.0", registry = "substrate" } -spice = { version = "0.7.0", registry = "substrate" } +substrate = { version = "0.8.1", registry = "substrate" } +spice = { version = "0.7.1", registry = "substrate" } serde = { version = "1", features = ["derive"] } rust_decimal = "1.30" diff --git a/tools/ngspice/src/tran.rs b/tools/ngspice/src/tran.rs index eea5e9efd..c459d2c57 100644 --- a/tools/ngspice/src/tran.rs +++ b/tools/ngspice/src/tran.rs @@ -10,7 +10,7 @@ use std::sync::Arc; use substrate::io::{NodePath, TerminalPath}; use substrate::schematic::conv::ConvertedNodePath; use substrate::schematic::primitives::Resistor; -use substrate::schematic::{Cell, ExportsNestedData, NestedInstance}; +use substrate::schematic::NestedInstance; use substrate::simulation::data::{tran, FromSaved, Save}; use substrate::simulation::{Analysis, SimulationContext, Simulator, SupportedBy}; use substrate::type_dispatch::impl_dispatch; @@ -46,15 +46,6 @@ impl FromSaved for Output { } } -impl Save> for Output { - fn save( - _ctx: &SimulationContext, - _to_save: &Cell, - _opts: &mut ::Options, - ) -> Self::SavedKey { - } -} - impl Save for Output { fn save( _ctx: &SimulationContext, diff --git a/tools/spectre/src/tran.rs b/tools/spectre/src/tran.rs index 10b2bee30..5c3ded83a 100644 --- a/tools/spectre/src/tran.rs +++ b/tools/spectre/src/tran.rs @@ -9,7 +9,6 @@ use std::collections::HashMap; use std::sync::Arc; use substrate::io::{NodePath, TerminalPath}; use substrate::schematic::conv::ConvertedNodePath; -use substrate::schematic::{Cell, ExportsNestedData}; use substrate::simulation::data::{tran, FromSaved, Save}; use substrate::simulation::{Analysis, SimulationContext, Simulator, SupportedBy}; use substrate::type_dispatch::impl_dispatch; @@ -47,15 +46,6 @@ impl FromSaved for Output { } } -impl Save> for Output { - fn save( - _ctx: &SimulationContext, - _to_save: &Cell, - _opts: &mut ::Options, - ) -> Self::SavedKey { - } -} - impl Save for Output { fn save( _ctx: &SimulationContext, From 3aeb7b6409f0a0f9a5be10b35b070cf825512234 Mon Sep 17 00:00:00 2001 From: rohanku Date: Tue, 28 Nov 2023 23:25:45 -0800 Subject: [PATCH 7/8] fix compilation error --- examples/sky130_inverter/src/tb.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/sky130_inverter/src/tb.rs b/examples/sky130_inverter/src/tb.rs index 0f4d1b1f4..60993e5fc 100644 --- a/examples/sky130_inverter/src/tb.rs +++ b/examples/sky130_inverter/src/tb.rs @@ -274,7 +274,7 @@ pub mod spectre_support { opts: &mut ::Options, ) -> >::SavedKey { VoutSavedKey { - t: tran::Time::save(ctx, cell, opts), + t: tran::Time::save(ctx, (), opts), v: tran::Voltage::save(ctx, cell.data(), opts), } } From 303a796701dadb3d4da317d70e871ea08dda098e Mon Sep 17 00:00:00 2001 From: rohanku Date: Tue, 28 Nov 2023 23:50:16 -0800 Subject: [PATCH 8/8] fix errors --- docs/site/docs/schematics/blocks.md | 2 +- docs/site/docs/schematics/scir.md | 14 +++++++++++--- docs/site/docusaurus.config.ts | 4 ++-- 3 files changed, 14 insertions(+), 6 deletions(-) diff --git a/docs/site/docs/schematics/blocks.md b/docs/site/docs/schematics/blocks.md index 386ea8f20..d5fb5425f 100644 --- a/docs/site/docs/schematics/blocks.md +++ b/docs/site/docs/schematics/blocks.md @@ -46,7 +46,7 @@ This derived `Eq` implementation is fine, since it checks that both resistors ar {Core} -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. diff --git a/docs/site/docs/schematics/scir.md b/docs/site/docs/schematics/scir.md index 7edb2a2f5..71e792c1e 100644 --- a/docs/site/docs/schematics/scir.md +++ b/docs/site/docs/schematics/scir.md @@ -72,13 +72,21 @@ While SCIR cells and instances do not have parameters, parameters can be injecte ### Schemas -SCIR schemas are simply sets of primitives that can be used to describe circuits. For example, the SPICE schema consists of MOSFET, resistor, capacitor, raw instance, and other primitives that can describe any circuit that can be netlisted to SPICE. Similarly, SKY130 is also a schema since it has its own set of primitive MOSFETs and resistors that can be fabricated in the SKY130 process. +SCIR schemas are simply sets of primitives that can be used to describe circuits. For example, the +SPICE schema consists of MOSFET, resistor, capacitor, raw instance, and other primitives that can +describe any circuit that can be netlisted to SPICE. Similarly, SKY130 is also a schema since it +has its own set of primitive MOSFETs and resistors that can be fabricated in the SKY130 process. -SCIR schemas allows portability to be elegantly encoded by defining which schemas a schema can be converted to. Since the SKY130 PDK supports simulations in ngspice and Spectre, we can declare that the SKY130 schema can be converted to both the ngspice and Spectre schemas. The specifics of this procedure will be detailed later on in this section. +When you write a schema, you can also specify which schemas it can be converted to. This allows you to elegantly +encode portability in the type system. Since the SKY130 PDK supports simulations in ngspice and Spectre, we +can declare that the SKY130 schema can be converted to both the ngspice and Spectre schemas. +The specifics of this procedure will be detailed later on in this section. ### Relationship to Substrate -Generators in Substrate produce cells that can be exported to SCIR. Substrate's APIs allow defining schematics in different schemas, which encodes generator compatibility in the Rust type system. For example, a Substrate block with a schematic in the `Sky130Pdk` schema can be included in a Spectre testbench's schematic in the `Spectre` schema, but cannot be included in an HSPICE testbench since the `Sky130Pdk` schema is not compatible with the HSPICE schema. Similarly, you cannot instantiate a SKY130 schematic in a schematic in a different process node (e.g. GF180). +Generators in Substrate produce cells that can be exported to SCIR. Substrate's APIs allow defining +schematics in different schemas, which encodes generator compatibility in the Rust type system. For example, +a Substrate block with a schematic in the `Sky130Pdk` schema can be included in a Spectre testbench's schematic in the `Spectre` schema, but cannot be included in an HSPICE testbench since the `Sky130Pdk` schema is not compatible with the HSPICE schema. Similarly, you cannot instantiate a SKY130 schematic in a schematic in a different process node (e.g. GF180). While the user generally interfaces with Substrate's block API, simulator and netlister plugins interface with SCIR. This allows backend tools to abstract away Substrate's internal representation of cells. diff --git a/docs/site/docusaurus.config.ts b/docs/site/docusaurus.config.ts index 950a9ddb3..8c4d59d88 100644 --- a/docs/site/docusaurus.config.ts +++ b/docs/site/docusaurus.config.ts @@ -41,14 +41,14 @@ const config: Config = { // Please change this to your repo. // Remove this to remove the "edit this page" links. editUrl: - 'https://github.com/substrate-labs/substrate2/tree/main/docs', + 'https://github.com/substrate-labs/substrate2/tree/main/docs/site', }, blog: { showReadingTime: true, // Please change this to your repo. // Remove this to remove the "edit this page" links. editUrl: - 'https://github.com/substrate-labs/substrate2/tree/main/docs', + 'https://github.com/substrate-labs/substrate2/tree/main/docs/site', }, theme: { customCss: require.resolve('./src/css/custom.css'),