From 7bf452f1774c5f57b79815b8f359413fed1f6a4a Mon Sep 17 00:00:00 2001 From: ankan roy Date: Fri, 6 Nov 2026 22:44:42 +0530 Subject: [PATCH] Add a Signal Integrity Course and pcb_spice guides; fix tsci simulate usage - guides/spice-simulation/si-course: a 22-lesson course for EEs new to tscircuit, from a voltage divider (ngspice) to DDR3 on a real board. Lessons 1-4 and 7-15 show output from real tsci simulate runs, each checked against a hand calculation. Lessons that don't run yet say why. - Simulating PCB Traces with tsci simulate: the AM3352 DQ0 eye walkthrough. - A Bit's Journey Across the Board: the same walkthrough as a story. - command-line/tsci-simulate: the CLI is `tsci simulate ` (no `analog` subcommand); document --output, --solver-timeout-ms, engine selection and `return-current`. Co-Authored-By: Claude Opus 5.5 --- docs/command-line/tsci-simulate.md | 29 +- .../a-bits-journey-across-the-board.mdx | 397 ++++++++++++++++++ .../si-course/1-simple/01-voltage-divider.mdx | 68 +++ .../si-course/1-simple/02-diode-iv.mdx | 65 +++ .../si-course/1-simple/03-rc-step.mdx | 63 +++ .../si-course/1-simple/04-rc-bode.mdx | 65 +++ .../1-simple/05-parameter-sweeps.mdx | 55 +++ .../si-course/1-simple/06-real-circuits.mdx | 32 ++ .../si-course/1-simple/_category_.json | 1 + .../si-course/2-medium/07-trace-is-an-rlc.mdx | 69 +++ .../2-medium/08-transmission-line.mdx | 100 +++++ .../si-course/2-medium/09-termination.mdx | 57 +++ .../si-course/2-medium/10-first-pcb-spice.mdx | 164 ++++++++ .../si-course/2-medium/11-bits-and-eyes.mdx | 81 ++++ .../si-course/2-medium/12-ibis-buffers.mdx | 81 ++++ .../si-course/2-medium/13-return-path.mdx | 69 +++ .../si-course/2-medium/14-vias.mdx | 92 ++++ .../si-course/2-medium/15-crosstalk.mdx | 89 ++++ .../si-course/2-medium/_category_.json | 1 + .../si-course/3-hard/16-ddr-one-lane.mdx | 57 +++ .../si-course/3-hard/17-strobe.mdx | 52 +++ .../si-course/3-hard/18-corners.mdx | 54 +++ .../si-course/3-hard/19-full-byte.mdx | 38 ++ .../si-course/3-hard/20-routed-copper.mdx | 45 ++ .../si-course/3-hard/21-timing-check.mdx | 43 ++ .../si-course/3-hard/22-power.mdx | 45 ++ .../si-course/3-hard/_category_.json | 1 + .../si-course/_category_.json | 7 + .../spice-simulation/si-course/index.mdx | 99 +++++ ...mulating-pcb-traces-with-tsci-simulate.mdx | 354 ++++++++++++++++ 30 files changed, 2360 insertions(+), 13 deletions(-) create mode 100644 docs/guides/spice-simulation/a-bits-journey-across-the-board.mdx create mode 100644 docs/guides/spice-simulation/si-course/1-simple/01-voltage-divider.mdx create mode 100644 docs/guides/spice-simulation/si-course/1-simple/02-diode-iv.mdx create mode 100644 docs/guides/spice-simulation/si-course/1-simple/03-rc-step.mdx create mode 100644 docs/guides/spice-simulation/si-course/1-simple/04-rc-bode.mdx create mode 100644 docs/guides/spice-simulation/si-course/1-simple/05-parameter-sweeps.mdx create mode 100644 docs/guides/spice-simulation/si-course/1-simple/06-real-circuits.mdx create mode 100644 docs/guides/spice-simulation/si-course/1-simple/_category_.json create mode 100644 docs/guides/spice-simulation/si-course/2-medium/07-trace-is-an-rlc.mdx create mode 100644 docs/guides/spice-simulation/si-course/2-medium/08-transmission-line.mdx create mode 100644 docs/guides/spice-simulation/si-course/2-medium/09-termination.mdx create mode 100644 docs/guides/spice-simulation/si-course/2-medium/10-first-pcb-spice.mdx create mode 100644 docs/guides/spice-simulation/si-course/2-medium/11-bits-and-eyes.mdx create mode 100644 docs/guides/spice-simulation/si-course/2-medium/12-ibis-buffers.mdx create mode 100644 docs/guides/spice-simulation/si-course/2-medium/13-return-path.mdx create mode 100644 docs/guides/spice-simulation/si-course/2-medium/14-vias.mdx create mode 100644 docs/guides/spice-simulation/si-course/2-medium/15-crosstalk.mdx create mode 100644 docs/guides/spice-simulation/si-course/2-medium/_category_.json create mode 100644 docs/guides/spice-simulation/si-course/3-hard/16-ddr-one-lane.mdx create mode 100644 docs/guides/spice-simulation/si-course/3-hard/17-strobe.mdx create mode 100644 docs/guides/spice-simulation/si-course/3-hard/18-corners.mdx create mode 100644 docs/guides/spice-simulation/si-course/3-hard/19-full-byte.mdx create mode 100644 docs/guides/spice-simulation/si-course/3-hard/20-routed-copper.mdx create mode 100644 docs/guides/spice-simulation/si-course/3-hard/21-timing-check.mdx create mode 100644 docs/guides/spice-simulation/si-course/3-hard/22-power.mdx create mode 100644 docs/guides/spice-simulation/si-course/3-hard/_category_.json create mode 100644 docs/guides/spice-simulation/si-course/_category_.json create mode 100644 docs/guides/spice-simulation/si-course/index.mdx create mode 100644 docs/guides/spice-simulation/simulating-pcb-traces-with-tsci-simulate.mdx diff --git a/docs/command-line/tsci-simulate.md b/docs/command-line/tsci-simulate.md index 821c82c3..fcfd5dbc 100644 --- a/docs/command-line/tsci-simulate.md +++ b/docs/command-line/tsci-simulate.md @@ -8,17 +8,7 @@ description: Run circuit simulations from the command line ## Usage ```bash -tsci simulate [options] -``` - -## Subcommands - -### `tsci simulate analog` - -Run an analog SPICE simulation on a circuit file. - -```bash -tsci simulate analog [options] +tsci simulate [options] ``` #### Arguments @@ -26,13 +16,26 @@ tsci simulate analog [options] #### Options - `--disable-parts-engine` – disable the parts engine during circuit evaluation +- `--output ` – fresh report directory (`spiceEngine="pcb_spice"` only) +- `--solver-timeout-ms ` – per-process solver timeout, default 900000 (`spiceEngine="pcb_spice"` only) + +The engine comes from the `spiceEngine` of the experiments in the file: + +- `"ngspice"` – generates a SPICE netlist from the schematic, runs ngspice and prints the results as a table. +- `"pcb_spice"` – extracts the routed copper with FastHenry and FasterCap, runs ngspice with IBIS buffer models and writes `results.html` to `.tscircuit/simulation--/`. Requires `fasthenry`, `FasterCap` and `ngspice` on `PATH`. + +One file can't mix `pcb_spice` with other engines. See +[Simulating PCB Traces with tsci simulate](../guides/spice-simulation/simulating-pcb-traces-with-tsci-simulate.mdx). + +### `tsci simulate return-current` -The command generates a SPICE netlist from your circuit, runs the simulation, and prints the results in a table format. +Run a PCB return-current EM simulation with Palace. See +[``](../elements/simulation-pcbreturncurrentsimulation.mdx). ## Example ```bash -$ tsci simulate analog my-circuit.circuit.tsx +$ tsci simulate my-circuit.circuit.tsx ngspice 1 -> ngspice 1 -> Note: No compatibility mode selected! diff --git a/docs/guides/spice-simulation/a-bits-journey-across-the-board.mdx b/docs/guides/spice-simulation/a-bits-journey-across-the-board.mdx new file mode 100644 index 00000000..64dc6990 --- /dev/null +++ b/docs/guides/spice-simulation/a-bits-journey-across-the-board.mdx @@ -0,0 +1,397 @@ +--- +title: "A Bit's Journey Across the Board" +sidebar_label: "Adventure: A Bit's Journey" +sidebar_position: 4 +description: >- + An adventure-style walkthrough of tsci simulate. Follow one bit from the + practice village of ngspice to a real DDR3 trace on the AM3352 board, through + the field solvers, past the IBIS gatekeepers, to the eye diagram at the end + of the road. +--- + +> Somewhere inside an AM3352 processor, a single bit waits to leave for the +> RAM on the other side of the board. It has 1.5625 nanoseconds to arrive +> looking like itself. This is its journey, and yours. + +This is the story version of +[Simulating PCB Traces with tsci simulate](./simulating-pcb-traces-with-tsci-simulate.mdx). +The code, commands and numbers are the same as in that guide. + +## The map + +``` + 🏘 The Practice Village ─────────────┐ (ngspice, schematic only) + │ + 🏰 The Board Kingdom │ (your real routed board) + │ │ + 🧭 The Cartographer's Hut │ (find the routed pins) + │ │ + 🛡 The IBIS Gatekeepers │ (vendor buffer models) + │ │ + 🛤 The Copper Road │ (pcbSpiceChannel) + │ │ + 🗼 The Tower of Field Solvers │ (FastHenry, FasterCap) + │ │ + 👁 The Eye at the End of the Road ◄──┘ (results.html) +``` + +### Supplies for the road + +- [ ] `bun` and the `tsci` CLI +- [ ] `fasthenry`, `FasterCap` and `ngspice` on your `PATH`. The CLI calls them + by exactly those names. +- [ ] Two vendor IBIS files. You'll collect them at the gate (chapter 4). + +### Laws of the land + +Every traveller learns these the hard way, so here they are up front: + +1. **Two roads only.** `tsci simulate` runs `ngspice` and `pcb_spice` + experiments. `spicey` lives only in the browser preview. +2. **Don't mix the roads.** One file can't hold both `pcb_spice` and other + engines. Give each its own entry file. +3. **A fresh camp every night.** `--output` must point to a directory that + doesn't exist yet. It only works with `pcb_spice`, and so does + `--solver-timeout-ms`. +4. **Time is measured in milliseconds.** A bare number means ms. Write + `"10ps"` or `"512ns"` so nobody gets lost. + +## Chapter 1: The Practice Village + +Every adventurer starts small. In the village, wires are perfect, copper has no +length, and ngspice reads only the schematic. Here you learn to light a +capacitor: + +```tsx title="rc.circuit.tsx" +import { analog } from "tscircuit" + +export default () => ( + + + + + + + + + + +) +``` + +Run it: + +```bash +bunx tsci simulate rc.circuit.tsx +``` + +``` +Index time v(n1) v(n2) i(vsimulation_voltage_source_0) +510 5.000000e-3 3.300000e+0 3.277766e+0 -2.223431e-5 +``` + +After five time constants (τ = RC = 1 ms) the capacitor holds 3.2778 V, which +matches 3.3 × (1 − e⁻⁵). The village elder nods. You're ready to leave. + +`routingDisabled` tells the autorouter to stay home. The village has no roads, +only the netlist. + +## Chapter 2: The Board Kingdom + +Beyond the village is the real thing: the +[AM3352 board](https://github.com/0hmX/am3352-ddr-track-a-local-dfm-wip-20261007), +with its BGA processor, its DDR3 chip, and 46 timing-critical traces routed +through a six-layer stackup. + +```bash +git clone https://github.com/0hmX/am3352-ddr-track-a-local-dfm-wip-20261007 +cd am3352-ddr-track-a-local-dfm-wip-20261007 +bun install --frozen-lockfile +``` + +The kingdom has one rule that newcomers often break: **don't build a copy of +the castle.** You don't redraw the board for simulation. Bring the real board +and add your declarations as extra children: + +```tsx +import { cloneElement } from "react" +import Board from "../index" + +const board = Board() +return cloneElement( + board, + { fabricatorPreset: "jlcpcb_standard" }, + board.props.children, // the kingdom, untouched + <>{/* your expedition's gear */}, +) +``` + +From the throne room (board level), selectors can reach into any district, +like `.Processor > .U1 > .M3`. + +Here's who lives where: + +| Place | Who lives there | +| --- | --- | +| `simulate.circuit.tsx` | The city gate. It points to `run-dq-stages.circuit`. | +| `simulations-next/run-ddr.circuit.tsx` | The scribe, who compiles the board once and keeps `physicalRecords`. | +| `simulations-next/channels.tsx` | The cartographer, `resolveDdrChannel()`. | +| `simulations-next/ibis-models.tsx` | The gatekeepers' armoury. | +| `simulations-next/ibis.circuit.tsx` | `DdrIbisBoard`, the expedition itself. | +| `simulations-next/run-dq-stages.circuit.tsx` | The chosen route: DQ only, the quiet case, `straightened-stages`. | + +## Chapter 3: The Cartographer's Hut + +You'd think the bit leaving from `DDR_D0` arrives at RAM ball `E3`. Don't trust +old maps. DDR byte lanes swap their DQ balls around, and a frozen assumption +means simulating copper that doesn't exist. + +The cartographer doesn't guess. She compiles the kingdom and follows the +copper: + +```tsx +import { Circuit } from "@tscircuit/core" + +const physical = new Circuit({ + platform: { partsEngineDisabled: true, netlistDrcChecksDisabled: true }, +}) +physical.add(cloneElement(Board(), { fabricatorPreset: "jlcpcb_standard" })) +await physical.renderUntilSettled() +export const physicalRecords = physical.getCircuitJson() + +const data = resolveDdrChannel(physicalRecords, "DDR_D0") +// → { source: ".Processor > .U1 > .M3", load: ".DDR3 > .U3 > .E3", trace, … } +``` + +She refuses to hand over a map she isn't sure of. `resolveDdrChannel` throws +if any of these is true: + +- The pin is missing. +- More than one trace leaves the pin. +- An endpoint doesn't sit on its pad. +- The two ends are on different nets. + +A loud error at the hut is better than a quiet wrong answer at the end of the +road. + +## Chapter 4: The IBIS Gatekeepers + +At each end of the road stands a gatekeeper, the chip's I/O buffer. The +gatekeepers decide how hard the bit is pushed out and how it's caught. Their +behaviour is written in vendor IBIS scrolls: + +- `sprm552c-candidate-models.ibs`: TI's reviewed subset. +- `W631GG6MB-12.ibs`: Winbond's scroll, unchanged. + +The kingdom isn't allowed to hand out these scrolls. Fetch them yourself and +place them in `simulations-next/model-review/`. The URLs and SHA-256 seals are +in `review-receipt.json`. **Check the seals.** A scroll with the wrong hash +tells a different story. + +Then swear in the gatekeepers: + +```tsx + {/* TI Model_343, slow 9 mA */} + {/* Winbond ODT_60 */} +``` + +Each oath states: + +- which scroll and component; +- which model each ball uses; +- which pins are the pull-up, pull-down and clamp rails (nothing is assumed); +- the corner (`typical`, `minimum` or `maximum`). + +Give the gatekeepers power, and post a lookout at each end. Each lookout is a +probe referenced to its own local ground: + +```tsx + .U1 > .E5", pin2: ".Processor > .U1 > .A1" }} +/> + + +``` + +## Chapter 5: The Copper Road + +Now you can describe the road itself. This is the expedition charter, an +`analog.transientsimulation` that runs on the `pcb_spice` engine: + +```tsx + .U1 > .A1", + extraction: { + minimumFrequency: "1Hz", + maximumFrequency: "1GHz", + modelFrequency: "100MHz", + filaments: 9, + refinementFilaments: 15, + convergenceTolerance: 0.05, + capacitanceTolerance: 0.02, + reciprocityTolerance: 0.025, + dcResistanceTolerance: 0.002, + maxPanelLength: "0.5mm", + }, + }} +/> +``` + +Then send the travellers: PRBS7, a pseudo-random bit pattern, marching at +640 MT/s, which gives one bit 1.5625 ns: + +```tsx + +``` + +### Two roads diverge + +There are two ways to describe the road, and only one of them gets you there +today: + +| `reduction` | The road | What happened to those who took it | +| --- | --- | --- | +| `"straightened-stages"` | The surveyed highway. The route is straightened into stages over a return bar. The bar comes from `pcb_stackup`: DQ0 on `top`, returning on the `inner1` GND plane 0.1245 mm below, εr 4.1, 2 mm wide. | **They arrived.** Every extraction check passes. | +| `"routed-copper"` | The wild path. It follows every real curve, plane and via. | **Lost in the mountains.** FastHenry stops with numerical errors ([pcb-spice-pipeline#46](https://github.com/tscircuit/pcb-spice-pipeline/issues/46)). Nobody has an accepted result yet. | + +Take the highway. A seasoned traveller checks whether the shortcut mattered: + +- A 1 mm bar and a 2 mm bar give the same eye. +- Straightening adds only about 4% loop inductance compared with the real + curves. + +## Chapter 6: The Tower of Field Solvers + +```bash +tsci simulate simulate.circuit.tsx # or: bun run simulate, about 3–4 minutes +``` + +At the tower, the road is turned into physics: + +1. **FastHenry** works out the copper's resistance and inductance. It does this + twice, with 9 filaments and then 15, and the two answers must agree within + 5%. They agree within 0.7%. +2. **FasterCap** measures the capacitance between the trace and its return. +3. The road becomes a 12-section RLC ladder, the gatekeepers stand at both + ends, and **ngspice** marches the bits across. + +The tower is strict. If an extraction or convergence check fails, it closes +its doors: the CLI stops with an error and writes no result. An old waveform +from an earlier trip doesn't count as a new arrival. + +## Chapter 7: The Eye at the End of the Road + +At the RAM's door, the lookout `DQ_RX` records every bit that arrives. Fold all +of those bits on top of each other, one UI at a time, and an eye opens: + +```tsx + +``` + +Open the report and look: + +```bash +open .tscircuit/simulation-simulate-*/results.html # Plots → Eye +``` + +**The eye is about 0.96 V tall and about 1.45 ns wide, out of a 1.5625 ns +UI.** The bit made it, and it still looks like itself. + +The report also holds the expedition's logbook: + +- the probe ranges; +- the eye gates and windows; +- the stimuli and pin states; +- the gatekeepers' models and corner; +- the channel settings. + +Next to it are CSVs and every FastHenry, FasterCap and ngspice deck and log +from the tower. + +## Epilogue: what the bards leave out + +Every honest tale says what it didn't cover. This eye is **one bit lane with an +ideal strobe**. It isn't a pass/fail verdict for byte 0 or for the DDR +interface. These lands are still unexplored: + +- **The DQS strobes.** They dive to `inner2` stripline, and this road can't + follow them there yet. +- **Neighbouring travellers**, meaning coupling from other nets. +- **Holes in the ground planes**, meaning plane cutouts. +- **The kingdom's power grid**, meaning supply impedance. +- **The gatekeepers' armour**, meaning package parasitics. +- **Via stubs.** + +The stackup itself is still a nominal assumption. + +## Your own quest + +Ready to send a bit across your own board? + +1. Win a battle in the Practice Village first (chapter 1). +2. Give your kingdom a real stackup, with `fabricatorPreset`, and a GND pour on + the layer next to the signal. +3. Visit the cartographer. Find your routed endpoints from Circuit JSON, never + from memory. +4. Swear in the gatekeepers (``), post the lookouts (probes), write + the charter (a `pcb_spice` transient), send the travellers (a stimulus) and + wait for the eye. +5. Give every bound pin an `ibisPinStates` order. No gatekeeper should be left + guessing. +6. Climb the tower with `tsci simulate`. Only read the eye if the extraction + passed. + +Before a long journey, rehearse without the tower. Rendering only declares the +experiments, so a `bun test` can check that your gear is packed: + +```tsx +const circuit = new Circuit() +circuit.add() +await circuit.renderUntilSettled() +const json = circuit.getCircuitJson() +expect(json.filter((r) => r.type === "simulation_experiment")).toHaveLength(2) +expect(json.filter((r) => r.type === "simulation_eye_diagram")).toHaveLength(4) +``` + +## Field guide + +- [``](../../elements/analog-transientsimulation.mdx) +- [``](../../elements/analog-digitalstimulus.mdx) +- [``](../../elements/analog-eyediagram.mdx) +- [``](../../elements/voltageprobe.mdx) +- [`tsci simulate`](../../command-line/tsci-simulate.md) +- [Routing DDR](../routing-ddr.mdx) diff --git a/docs/guides/spice-simulation/si-course/1-simple/01-voltage-divider.mdx b/docs/guides/spice-simulation/si-course/1-simple/01-voltage-divider.mdx new file mode 100644 index 00000000..e71f4cd0 --- /dev/null +++ b/docs/guides/spice-simulation/si-course/1-simple/01-voltage-divider.mdx @@ -0,0 +1,68 @@ +--- +title: "1 · Voltage divider" +sidebar_label: "1 · Voltage divider" +sidebar_position: 1 +description: Your first tsci simulate run, a DC operating point on a resistor divider. +--- + +**Status: ✅ Runs today** + +**Goal:** run a simulation and find your answer in the output. + +## Build + +```tsx title="01-divider.circuit.tsx" +import { analog } from "tscircuit" + +export default () => ( + + + + + + + + + + +) +``` + +This is what each line does: + +- `` is an ideal 5 V source. `pin2` is its negative terminal, + and we use it as ground. +- `` is a wire. `".R1 > .pin2"` reads as "pin 2 of R1". +- `` marks the node you care about, and the node it's measured + against. +- `` is the analysis, the same as `.op` in + SPICE. +- `routingDisabled` skips PCB autorouting, which a schematic simulation doesn't + need. + +## Run + +```bash +bunx tsci simulate 01-divider.circuit.tsx +``` + +## What you see + +``` +Index v(n1) v(n2) i(vsimulation_voltage_source_0) +0 5.000000e+0 1.598639e+0 -3.401361e-4 +``` + +- `v(n1)` = 5 V is the source node. +- `v(n2)` = 1.5986 V is the divider output, your `VOUT`. +- The last column is the source current: −340 µA, flowing out of the `+` + terminal. + +## Check by hand + +- Vout = 5 × 4.7k / (10k + 4.7k) = **1.5986 V** ✓ +- I = 5 / 14.7k = **340.1 µA** ✓ + +## Try this + +Change R2 to `10k`. Before you run it, predict both numbers. diff --git a/docs/guides/spice-simulation/si-course/1-simple/02-diode-iv.mdx b/docs/guides/spice-simulation/si-course/1-simple/02-diode-iv.mdx new file mode 100644 index 00000000..4c8296dc --- /dev/null +++ b/docs/guides/spice-simulation/si-course/1-simple/02-diode-iv.mdx @@ -0,0 +1,65 @@ +--- +title: "2 · Diode I–V curve" +sidebar_label: "2 · Diode I–V" +sidebar_position: 2 +description: Use a DC sweep to trace a diode's forward I–V curve. +--- + +**Status: ✅ Runs today** + +**Goal:** sweep a source and watch a nonlinear part respond. + +## Build + +```tsx title="02-diode-iv.circuit.tsx" +import { analog } from "tscircuit" + +export default () => ( + + + + + + + + + + +) +``` + +`analog.dcsweepsimulation` steps `V1` from 0 V to 2 V and solves the DC +circuit at each step, the same as `.dc` in SPICE. + +## Run + +```bash +bunx tsci simulate 02-diode-iv.circuit.tsx +``` + +## What you see + +One row per step. `v(n2)` is the diode voltage, and the last column is the +current (shown negative, as it flows out of the source): + +| V1 | Diode V | Current | +| --- | --- | --- | +| 0.5 V | 0.4998 V | 2.5 µA | +| 0.6 V | 0.5915 V | 85 µA | +| 0.7 V | 0.6413 V | 0.59 mA | +| 1.0 V | 0.6851 V | 3.15 mA | +| 2.0 V | 0.7210 V | 12.8 mA | + +## Check by hand + +- Below about 0.5 V, almost no current flows and the whole voltage sits across + the diode. +- Above the knee, the diode holds about 0.65–0.72 V, and R1 takes the rest: + (2 − 0.721) / 100 Ω = **12.8 mA** ✓ +- The diode voltage rises about 36 mV between 3.15 mA and 12.8 mA, roughly a + factor of 4. That fits about 60 mV per decade of current. + +## Try this + +Sweep to 5 V and work out the current at the end from R1 alone. diff --git a/docs/guides/spice-simulation/si-course/1-simple/03-rc-step.mdx b/docs/guides/spice-simulation/si-course/1-simple/03-rc-step.mdx new file mode 100644 index 00000000..2c5289ec --- /dev/null +++ b/docs/guides/spice-simulation/si-course/1-simple/03-rc-step.mdx @@ -0,0 +1,63 @@ +--- +title: "3 · RC step response" +sidebar_label: "3 · RC step" +sidebar_position: 3 +description: A transient analysis of an RC charging curve. +--- + +**Status: ✅ Runs today** + +**Goal:** simulate in the time domain. + +## Build + +```tsx title="03-rc-step.circuit.tsx" +import { analog } from "tscircuit" + +export default () => ( + + + + + + + + + + +) +``` + +- `duration` is how long to simulate. +- `timePerStep` is the largest step the solver may take. +- A bare number means **milliseconds**, so write units such as `"10us"`. + +The simulation starts with the capacitor empty, so the source acts like a step +at t = 0. + +## Run + +```bash +bunx tsci simulate 03-rc-step.circuit.tsx +``` + +## What you see + +``` +Index time v(n1) v(n2) i(vsimulation_voltage_source_0) +… 9.996000e-4 3.300000e+0 2.085522e+0 … +510 5.000000e-3 3.300000e+0 3.277766e+0 -2.223431e-5 +``` + +## Check by hand + +- τ = RC = 1k × 1 µF = 1 ms. +- At t = τ: 3.3 × (1 − e⁻¹) = **2.086 V** ✓ (sim: 2.0855 V) +- At t = 5τ: 3.3 × (1 − e⁻⁵) = **3.2778 V** ✓ +- The current at the end is (3.3 − 3.2778) / 1k = **22 µA** ✓ + +## Try this + +Change C1 to `10uF`. Predict V at 5 ms before you run it. The answer is +3.3 × (1 − e⁻⁰·⁵) = 1.30 V. diff --git a/docs/guides/spice-simulation/si-course/1-simple/04-rc-bode.mdx b/docs/guides/spice-simulation/si-course/1-simple/04-rc-bode.mdx new file mode 100644 index 00000000..56ae7619 --- /dev/null +++ b/docs/guides/spice-simulation/si-course/1-simple/04-rc-bode.mdx @@ -0,0 +1,65 @@ +--- +title: "4 · RC Bode plot" +sidebar_label: "4 · RC Bode" +sidebar_position: 4 +description: An AC sweep of an RC low-pass filter. +--- + +**Status: ✅ Runs today** + +**Goal:** simulate in the frequency domain. + +## Build + +```tsx title="04-rc-bode.circuit.tsx" +import { analog } from "tscircuit" + +export default () => ( + + + + + + + + + + +) +``` + +- `acMagnitude="1V"` makes V1 a 1 V small-signal source, so every output reads + directly as gain. +- A decade sweep takes `samplesPerInterval` points per decade. A linear sweep + takes `sampleCount` instead. + +## Run + +```bash +bunx tsci simulate 04-rc-bode.circuit.tsx +``` + +## What you see + +Each node has a real and an imaginary column (`v(n2)_real`, `v(n2)_img`). +Magnitude = √(re² + im²), and dB = 20·log₁₀(magnitude): + +| f | \|Vout\| | dB | +| --- | --- | --- | +| 1.00 kHz | 0.847 | −1.45 | +| 1.58 kHz | 0.709 | **−2.99** | +| 10.0 kHz | 0.157 | −16.07 | +| 100 kHz | 0.0159 | −35.96 | + +## Check by hand + +- fc = 1 / (2π × 1k × 100 nF) = **1.59 kHz**, and the sim shows −2.99 dB at + 1.58 kHz ✓ +- Well above fc the slope is **−20 dB/decade**: −16.07 dB at 10 kHz, then + −35.96 dB at 100 kHz ✓ + +## Try this + +Add a second RC stage after the first. The slope should become −40 dB/decade. diff --git a/docs/guides/spice-simulation/si-course/1-simple/05-parameter-sweeps.mdx b/docs/guides/spice-simulation/si-course/1-simple/05-parameter-sweeps.mdx new file mode 100644 index 00000000..31f01dcb --- /dev/null +++ b/docs/guides/spice-simulation/si-course/1-simple/05-parameter-sweeps.mdx @@ -0,0 +1,55 @@ +--- +title: "5 · Parameter sweeps" +sidebar_label: "5 · Parameter sweeps" +sidebar_position: 5 +description: Repeat an analysis across component values with analog.sweepparameter. +--- + +**Status: 🟡 Partly works.** The file renders, but `tsci simulate` prints only +the nominal run (R1 = 1k) today. The sweep runs aren't printed yet. + +**Goal:** run one experiment for several component values. + +## Build + +```tsx title="05-rc-sweep.circuit.tsx" +import { analog } from "tscircuit" + +export default () => ( + + + + + + + + + + + + +) +``` + +`analog.sweepparameter` goes **directly inside** an analysis and repeats it for +each value. The component's own value doesn't change. You can sweep +`resistance`, `capacitance`, `inductance`, `voltage` (a net) or `current`. + +## What you should see + +Three charging curves, one per R: + +| R1 | τ | V at 1 ms | +| --- | --- | --- | +| 500 Ω | 0.5 ms | 3.3 × (1 − e⁻²) = 2.85 V | +| 1 kΩ | 1 ms | 2.09 V | +| 2 kΩ | 2 ms | 3.3 × (1 − e⁻⁰·⁵) = 1.30 V | + +Until the CLI prints the sweep, you can run each value as its own file. Or +export a component that takes `resistance` as a prop, the way lesson 9 does. + +## Check by hand + +τ scales linearly with R, so doubling R halves how fast the capacitor charges. diff --git a/docs/guides/spice-simulation/si-course/1-simple/06-real-circuits.mdx b/docs/guides/spice-simulation/si-course/1-simple/06-real-circuits.mdx new file mode 100644 index 00000000..326c3ba4 --- /dev/null +++ b/docs/guides/spice-simulation/si-course/1-simple/06-real-circuits.mdx @@ -0,0 +1,32 @@ +--- +title: "6 · Real circuits" +sidebar_label: "6 · Real circuits" +sidebar_position: 6 +description: Practice on rectifiers, transistor switches, filters and converters. +--- + +**Status: ✅ Existing guides** + +**Goal:** get comfortable with nonlinear parts and pulse sources before moving +on to signal integrity. + +Work through these guides. Each one uses the same pattern: parts, traces, a +source, a probe and a transient. + +- [Half-wave rectifier](../../Diodes/half-wave-rectifier.mdx) +- [Full-wave rectifier](../../Diodes/full-wave-rectifier.mdx) +- [Transistor switch](../../Transistors/transistor-switch-example.mdx) +- [High-pass filter](<../../Passive Filters/high-pass-filter.mdx>) +- [Low-pass filter](<../../Passive Filters/low-pass-filter.mdx>) +- [Boost converter](<../../Power Converters/boost-converter.mdx>) + +Run each one with `bunx tsci simulate `. + +## Checkpoint + +Before Part 2, you should be able to: + +- [ ] write a source, a probe and an analysis from memory; +- [ ] pick `duration` and `timePerStep` for the signal you care about; +- [ ] find your node in the ngspice table; +- [ ] check a result on paper. diff --git a/docs/guides/spice-simulation/si-course/1-simple/_category_.json b/docs/guides/spice-simulation/si-course/1-simple/_category_.json new file mode 100644 index 00000000..ad0d577f --- /dev/null +++ b/docs/guides/spice-simulation/si-course/1-simple/_category_.json @@ -0,0 +1 @@ +{ "label": "Part 1 · Simple: schematic circuits", "position": 1, "collapsible": true, "collapsed": false } diff --git a/docs/guides/spice-simulation/si-course/2-medium/07-trace-is-an-rlc.mdx b/docs/guides/spice-simulation/si-course/2-medium/07-trace-is-an-rlc.mdx new file mode 100644 index 00000000..ae6ae7b7 --- /dev/null +++ b/docs/guides/spice-simulation/si-course/2-medium/07-trace-is-an-rlc.mdx @@ -0,0 +1,69 @@ +--- +title: "7 · A trace is an RLC" +sidebar_label: "7 · A trace is an RLC" +sidebar_position: 7 +description: Model a PCB trace as a lumped R, L and C and watch a fast edge ring. +--- + +**Status: ✅ Runs today** + +**Goal:** see why a trace stops being a wire once edges get fast. + +A 10 cm trace over a ground plane has roughly 300 nH/m and 100 pF/m, so about +30 nH and 10 pF in total. In this lesson you put those values in the schematic +by hand. + +## Build + +```tsx title="07-trace-rlc.circuit.tsx" +import { analog } from "tscircuit" + +// 10 Ω driver, 1 ns edge, 10 cm trace lumped as 30 nH + 10 pF. +export default () => ( + + + + + + + + + + + + +) +``` + +## Run + +```bash +bunx tsci simulate 07-trace-rlc.circuit.tsx +``` + +## What you see + +At the receiver (the capacitor node), the rising edge overshoots and then +rings down: + +| Peak | Time | Voltage | +| --- | --- | --- | +| 1 | 3.08 ns | **3.64 V** | +| 2 | 6.53 ns | 3.49 V | +| 3 | 9.98 ns | 3.41 V | +| 4 | 13.43 ns | 3.36 V | + +## Check by hand + +- Ring frequency: f = 1 / (2π√(30 nH × 10 pF)) = **290 MHz**, a period of + 3.45 ns. The peaks in the sim are 3.45 ns apart ✓ +- Damping: ζ = (R/2)·√(C/L) = 5 × √(10p/30n) = **0.091**. Each peak's + overshoot is about 0.56× the one before, and e^(−2πζ) ≈ 0.56 ✓ + +## Try this + +Make the driver slower (`riseTime="5ns"`). Once the edge is slower than the +ring period, the overshoot almost disappears. **The edge rate sets whether a +trace matters, not the clock frequency.** diff --git a/docs/guides/spice-simulation/si-course/2-medium/08-transmission-line.mdx b/docs/guides/spice-simulation/si-course/2-medium/08-transmission-line.mdx new file mode 100644 index 00000000..8e7f705e --- /dev/null +++ b/docs/guides/spice-simulation/si-course/2-medium/08-transmission-line.mdx @@ -0,0 +1,100 @@ +--- +title: "8 · Transmission line" +sidebar_label: "8 · Transmission line" +sidebar_position: 8 +description: Build a 50 Ω line from LC sections and see delay and reflections. +--- + +**Status: ✅ Runs today** + +**Goal:** see a trace as a distributed line, with an impedance and a delay. + +One LC is a single resonance. Many small LCs in a row behave like a +transmission line. + +## Build + +The line becomes a reusable component. Each section is 3 nH + 1.2 pF: + +```tsx title="08-tline.circuit.tsx" +import { analog } from "tscircuit" + +// Z0 = √(L/C) = √(3n / 1.2p) = 50 Ω, delay = 10 × √(LC) = 0.6 ns +const SECTIONS = 10 +export const TransmissionLine = ({ from, to }: { from: string; to: string }) => ( + <> + {Array.from({ length: SECTIONS }, (_, i) => { + const a = i === 0 ? from : `net.TL${i}` + const b = i === SECTIONS - 1 ? to : `net.TL${i + 1}` + return ( + + + + + ) + })} + +) + +export default ({ sourceR = "10", loadR = undefined as string | undefined }) => ( + + + + + {loadR && } + + + + +) +``` + +There are two new ideas in this file: + +- `connections={{ pin1: "net.TX" }}` connects pins by **net name**, so you + don't have to draw every trace. +- The board is an ordinary function, so you can loop, export components and + pass props. Lesson 9 reuses this line with different props. + +## Run + +```bash +bunx tsci simulate 08-tline.circuit.tsx +``` + +The table has a column per node. `TX` is the second node and `RX` is the last. + +## What you see + +With a 10 Ω driver and an open far end: + +| Time | V_TX | V_RX | +| --- | --- | --- | +| 0.5 ns | **0.833 V** | 0.02 V | +| 0.8 ns | 0.834 V | **1.82 V** | +| 2.4 ns | 1.10 V | 0.65 V | +| 4.4 ns | 1.04 V | 1.19 V | + +## Check by hand + +- **Launch:** the line looks like 50 Ω to the driver, so V_TX = 1 V × 50/(10+50) + = **0.833 V** ✓ +- **Delay:** V_RX starts rising after about 10 × √(3n × 1.2p) = **0.6 ns** ✓ +- **Open end:** Γ = +1, so the far end jumps to about 2 × 0.833 = 1.67 V. The + sim peaks a bit higher, at 1.82–1.93 V. That extra is ripple from using only + 10 lumped sections. +- **Ringing:** the reflection returns to a 10 Ω source, where + Γ = (10 − 50)/(10 + 50) = −0.67, so it bounces back and forth. + +## Try this + +Set `SECTIONS = 40` with 0.75 nH + 0.3 pF per section. Z0 and the delay stay +the same, but the ripple shrinks. A lumped model is accurate only while the +edge is several section-delays long. diff --git a/docs/guides/spice-simulation/si-course/2-medium/09-termination.mdx b/docs/guides/spice-simulation/si-course/2-medium/09-termination.mdx new file mode 100644 index 00000000..2908907f --- /dev/null +++ b/docs/guides/spice-simulation/si-course/2-medium/09-termination.mdx @@ -0,0 +1,57 @@ +--- +title: "9 · Termination" +sidebar_label: "9 · Termination" +sidebar_position: 9 +description: Remove reflections with series and parallel termination. +--- + +**Status: ✅ Runs today** + +**Goal:** stop the reflections from lesson 8. + +Reuse the line from lesson 8 and change only the resistors. + +## Build + +```tsx title="09-series-term.circuit.tsx" +import Line from "./08-tline.circuit" +// Series termination: the source resistance matches Z0 +export default () => +``` + +```tsx title="09-parallel-term.circuit.tsx" +import Line from "./08-tline.circuit" +// Parallel termination: 50 Ω to ground at the receiver +export default () => +``` + +## Run + +```bash +bunx tsci simulate 09-series-term.circuit.tsx +bunx tsci simulate 09-parallel-term.circuit.tsx +``` + +## What you see + +| | V_TX at launch | V_RX after the edge arrives | V_RX at 4 ns | +| --- | --- | --- | --- | +| Open, 10 Ω (lesson 8) | 0.833 V | 1.82 V, rings | 1.19 V | +| **Series 50 Ω** | **0.496 V** | 1.12 V, then settles | **1.00 V** | +| **Parallel 50 Ω** | 0.833 V | 0.95 V, then settles | **0.83 V** | + +## Check by hand + +- **Series:** the source launches half the step, 1 × 50/(50 + 50) = 0.5 V ✓. + The open far end doubles it to the full **1.0 V** ✓. The reflection returns + to a matched 50 Ω source, so it stops there. +- **Parallel:** the far end matches Z0, so Γ = 0 and nothing reflects. V_RX = + 1 × 50/60 = **0.833 V** ✓ +- The trade-off: series termination draws no DC current, but the waveform is + only half amplitude along the line. Parallel termination gives a clean + waveform everywhere, but it burns V²/50 Ω. + +## Try this + +Try a series resistor of 33 Ω, or a parallel resistor of 100 Ω. Predict Γ +first, then check the sim. diff --git a/docs/guides/spice-simulation/si-course/2-medium/10-first-pcb-spice.mdx b/docs/guides/spice-simulation/si-course/2-medium/10-first-pcb-spice.mdx new file mode 100644 index 00000000..694567ca --- /dev/null +++ b/docs/guides/spice-simulation/si-course/2-medium/10-first-pcb-spice.mdx @@ -0,0 +1,164 @@ +--- +title: "10 · Your first pcb_spice run" +sidebar_label: "10 · First pcb_spice run" +sidebar_position: 10 +description: Let the board supply R, L and C for a straight microstrip, then compare with the formula. +--- + +**Status: ✅ Runs today** (about 1.5 minutes) + +**Goal:** stop typing in L and C. Extract them from the copper, then check the +result against the microstrip formula. + +This is the bridge lesson. In lessons 7–9 you typed the line's L and C in by +hand. From here on, `pcb_spice` reads them from the routed trace and the +stackup. + +## What changes from Part 1 + +| | `ngspice` | `pcb_spice` | +| --- | --- | --- | +| Line model | Your own L and C | Extracted from the trace and the stackup | +| Tools | ngspice | FastHenry (R, L) and FasterCap (C), then ngspice | +| Drivers | Ideal sources | IBIS buffer models | +| Traffic and eyes | Not available | `analog.digitalstimulus` and `analog.eyediagram` | +| Output | A terminal table | A `results.html` report, CSVs and solver logs | + +You need `fasthenry`, `FasterCap` and `ngspice` on your `PATH`. + +## The teaching buffer + +`pcb_spice` drives pins through IBIS models, so every chip needs one. For +Part 2, we use `lvcmos33-teaching.ibs`. It's a **synthetic** 3.3 V LVCMOS +buffer written for this course, not a real part. It has resistive pull-up +and pull-down drivers with a soft current limit, diode clamps, and a +`[Model Selector]` with three drive strengths: + +| Model | Drive resistance | Current limit | Edge | +| --- | --- | --- | --- | +| `LVCMOS33_4MA` | 75 Ω | 20 mA | 1.6 ns | +| `LVCMOS33_12MA` | 30 Ω | 60 mA | 0.8 ns | +| `LVCMOS33_24MA` | 15 Ω | 120 mA | 0.4 ns | + +The file, the script that generates it, and every example in Part 2 are in +[`tutorial/course`](https://github.com/0hmX/am3352-ddr-track-a-local-dfm-wip-20261007/tree/main/tutorial/course) of the AM3352 repo. The shared parts are in +[`teaching-buffer.tsx`](https://github.com/0hmX/am3352-ddr-track-a-local-dfm-wip-20261007/tree/main/tutorial/course/teaching-buffer.tsx): + +- `Buffer`: a small 4-pad chip with pins IO1, IO2, VDD and GND. GND is a + plated hole, so it reaches every GND plane. +- `teachingIbis(drive)`: the `` props for that chip. +- `extraction`: the solver settings used in every Part 2 lesson. + +## Build + +A 4-layer board with a 20 mm, 0.16 mm-wide trace on `top`, over a GND pour +on `inner1` 0.0994 mm below: + +```tsx title="10-microstrip.circuit.tsx" +import { analog } from "tscircuit" +import { Buffer, extraction, teachingIbis } from "./teaching-buffer" + +export default () => ( + + + + .IO1", ".U2 > .IO1"]} /> + + + + + .VDD", pin2: ".U1 > .GND" }} /> + + + + .IO1": { isOutput: true, isOutputEnabled: true }, + ".U2 > .IO1": { isOutput: false }, + ".U1 > .IO2": { isOutput: false }, + ".U2 > .IO2": { isOutput: false }, + }} + pcbSpiceChannel={{ + reduction: "straightened-stages", + sections: 12, + lanes: [{ name: "LINE", from: ".U1 > .IO1", to: ".U2 > .IO1" }], + referencePinRef: ".U1 > .GND", + extraction, + }} /> + + + +) +``` + +This is what each new piece does: + +- `routingDisabled` plus `pcbPath`: you route the trace yourself, from pad + centre to pad centre. The pipeline checks that the trace ends sit on the + pads. +- `` binds the IBIS file to each chip. +- `ibisPinStates` sets every bound pin as a driver or a receiver for this + experiment. +- `pcbSpiceChannel` sets which lane to extract and how. With + `"straightened-stages"`, the route becomes one straight stage over a + 2 mm-wide return bar on the adjacent GND layer. +- `analog.digitalstimulus` drives a bit pattern from U1's IO1, and + `analog.eyediagram` folds the RX probe into an eye. + +## Run + +```bash +bunx tsci simulate tutorial/course/10-microstrip.circuit.tsx --output out/10 +open out/10/results.html +``` + +## What you see + +The extracted values are in `out/10/stage-0/extraction.json`, taken at +100 MHz: + +| Quantity | Extracted | Per mm | +| --- | --- | --- | +| L | 6.67 nH | 0.334 nH/mm | +| C | 3.37 pF | 0.169 pF/mm | +| Z0 = √(L/C) | **44.5 Ω** | | +| Delay = √(LC) | **150 ps** | 7.5 ps/mm | + +**Eye:** 3.29 V high and 2.0 ns wide, in a 2 ns UI. A 20 mm line is +electrically short for a 0.8 ns edge, so the eye is wide open. + +## Check by hand + +Use the stackup values: h = 0.0994 mm, w = 0.16 mm, t = 35 µm, εr = 4.1. + +- **Microstrip formula** (air above the trace): + Z0 ≈ 87/√(εr + 1.41) · ln(5.98h / (0.8w + t)) = **47.9 Ω**, and + εeff ≈ 3.08, so the delay is about 5.9 ps/mm. +- **The pipeline's model** puts the whole trace in εr 4.1. There's no air + above it, so the delay is √4.1 / c = **6.75 ps/mm**. The extracted value is + 7.5 ps/mm, and C per mm is about 4.1/3.08 = 1.33× the open-air value, as + expected. +- Z0 agrees within about 7 %. The rest comes from the finite 2 mm return bar + and the embedded dielectric. + +The lesson here: check against **the model's assumptions**, not just a +textbook formula. + +## Lessons from getting this to run + +| Symptom | Cause | Fix | +| --- | --- | --- | +| `endpoint_position` | The trace ended at the pad edge | Route pad centre to pad centre with `pcbPath` | +| `mesh_convergence` on R | A wide trace (2.9 mm on a 2-layer board) | Use a thin dielectric and a narrow trace | +| FasterCap `not converging` | A long stage with fine panels | `maxPanelLength: "2mm"` | +| `overlapping_edges` | The IBIS edge table was longer than one bit | Use shorter IBIS waveform tables, or a longer `bitDuration` | + +## Try this + +Change the trace width to 0.3 mm. L per mm should fall, C per mm should rise, +and Z0 should drop to roughly 28–30 Ω (the formula gives 28.6 Ω). diff --git a/docs/guides/spice-simulation/si-course/2-medium/11-bits-and-eyes.mdx b/docs/guides/spice-simulation/si-course/2-medium/11-bits-and-eyes.mdx new file mode 100644 index 00000000..18a94732 --- /dev/null +++ b/docs/guides/spice-simulation/si-course/2-medium/11-bits-and-eyes.mdx @@ -0,0 +1,81 @@ +--- +title: "11 · Bits and eyes" +sidebar_label: "11 · Bits and eyes" +sidebar_position: 11 +description: Drive a long line with a bit pattern and watch the eye close as the bit rate rises. +--- + +**Status: ✅ Runs today** (about 2 minutes per run) + +**Goal:** go from single edges to real traffic, and learn to read an eye. + +## The ideas + +- **UI (unit interval):** the time for one bit. 2 ns per bit is 500 Mb/s. +- **Eye diagram:** cut the received waveform into windows and overlay them. + - **Eye height** is the voltage margin at the centre of the bit. + - **Eye width** is the timing margin at the threshold (VREF = 1.65 V here). +- **ISI (inter-symbol interference):** if one bit's reflections are still + ringing when the next bit arrives, they close the eye. + +## Build + +Use a longer line, 72 mm as a serpentine. Drive it with the strong +24 mA buffer and leave the far end unterminated. The shared `LineBoard` in +[`teaching-board.tsx`](https://github.com/0hmX/am3352-ddr-track-a-local-dfm-wip-20261007/tree/main/tutorial/course/teaching-board.tsx) +is lesson 10's board as a component, with props for the bends, the drive and +the bit rate. + +```tsx title="11-long-line.circuit.tsx" +import { LineBoard } from "./teaching-board" + +// About 72 mm of serpentine: eleven straight runs, none longer than 13 mm. Strong 24 mA driver, nothing at the far end. +export const serpentine = [ + { x: -6, y: 1 }, { x: -6, y: 7 }, { x: -3, y: 7 }, { x: -3, y: -6 }, { x: 0, y: -6 }, + { x: 0, y: 7 }, { x: 3, y: 7 }, { x: 3, y: -6 }, { x: 6, y: -6 }, { x: 6, y: 1 }, +] + +export default () => +``` + +```tsx title="11-long-line-fast.circuit.tsx" +import { LineBoard } from "./teaching-board" +import { serpentine } from "./11-long-line.circuit" +// Same line and driver, 2.5x the bit rate: 0.8 ns per bit. +export default () => +``` + +The pipeline straightens the serpentine into **one** 72 mm stage. Coupling +between neighbouring runs of the serpentine is not modelled. + +## What you see + +Extraction: L = 24.1 nH and C = 11.6 pF, so **Z0 = 45.6 Ω** and the +**delay is 529 ps**. Per mm that matches lesson 10. + +| | 2 ns per bit | 0.8 ns per bit | +| --- | --- | --- | +| RX swing | −0.88 V to 4.20 V | −0.90 V to 4.20 V | +| Eye height | **3.29 V** | **0.04 V** (closed) | +| Eye width | **2.0 ns** of 2 ns | **0.73 ns** of 0.8 ns | + +It's the same line and the same driver. Only the bit rate changed. + +## Check by hand + +- **Overshoot:** the 15 Ω driver sees a 44.5 Ω line, so the step it launches + is 3.3 × 45.6 / (15 + 45.6) ≈ 2.48 V. At the open end it doubles to about + 4.9 V. The clamp diodes catch part of that, which gives the **4.20 V** you + see. +- **Settling:** the reflection takes 2 × 529 ps = **1.06 ns** to come back and + bounce again. + - At 2 ns per bit it settles before the centre of the bit, so the eye is + open. + - At 0.8 ns per bit, one round trip is already longer than a bit, so every + bit inherits the previous bit's ringing, and the eye closes. + +## Try this + +Switch to `drive="LVCMOS33_12MA"`. Its 30 Ω output is much closer to Z0, +so it behaves like the series termination from lesson 9. Predict the eye +first; lesson 12 shows the result. diff --git a/docs/guides/spice-simulation/si-course/2-medium/12-ibis-buffers.mdx b/docs/guides/spice-simulation/si-course/2-medium/12-ibis-buffers.mdx new file mode 100644 index 00000000..359cbf04 --- /dev/null +++ b/docs/guides/spice-simulation/si-course/2-medium/12-ibis-buffers.mdx @@ -0,0 +1,81 @@ +--- +title: "12 · Real buffers (IBIS)" +sidebar_label: "12 · IBIS buffers" +sidebar_position: 12 +description: Swap the driver's drive strength on the same line and compare eyes. +--- + +**Status: ✅ Runs today** (about 2 minutes per run) + +**Goal:** learn what an IBIS model gives you, and why drive strength can be +wrong in either direction. + +## What an IBIS file is + +A chip vendor publishes an `.ibs` file per part. It holds: + +- I–V tables for the pull-up, the pull-down and the clamps; +- V–t waveforms for rising and falling edges into test loads; +- pin capacitance (`C_comp`); +- package R, L and C. + +It describes the pin's behaviour without giving away the transistors. A +`[Model Selector]` lists the settings you can choose, such as drive strength +or ODT. + +`` binds that file to a chip. You have to state: + +| Prop | Why | +| --- | --- | +| `ibisPinNameToModelName` | Which buffer each pin uses, from the selector | +| `ibisPinNameToReferencePortLabels` | Which pins are the pull-up, pull-down and clamp rails. Nothing is guessed. | +| `modelValueCorner` | `typical`, `minimum` (slow, weak) or `maximum` (fast, strong) | +| `includePackageParasitics` | Whether the package R, L and C are included | + +`ibisPinStates` on the experiment sets each pin as a driver or a receiver, +**per experiment**. + +## Build + +Use lesson 11's 72 mm line and change only the drive: + +```tsx title="12-drive-4ma.circuit.tsx" +import { LineBoard } from "./teaching-board" +import { serpentine } from "./11-long-line.circuit" +// Lesson 10's line with a 4 mA driver. +export default () => +``` + +`12-drive-12ma.circuit.tsx` and `12-drive-24ma.circuit.tsx` are the same +except for the `drive` prop. + +## What you see + +At 2 ns per bit: + +| Drive | Output R | RX swing | Eye height | Eye width | +| --- | --- | --- | --- | --- | +| 4 mA | 75 Ω, 20 mA limit | 0.04 → 3.30 V | **0.17 V** | **0.59 ns** | +| 12 mA | 30 Ω | −0.35 → 3.66 V | **3.30 V** | **1.99 ns** | +| 24 mA | 15 Ω | −0.88 → 4.20 V | 3.29 V | 2.0 ns | + +## Check by hand + +- **Too weak (4 mA):** the current limit sets the first step: + 20 mA × 45.6 Ω ≈ **0.9 V**. The line then charges in steps, one per + 1.06 ns round trip, and doesn't reach the 1.65 V threshold early enough. + The eye collapses even though the edge rate is slow. +- **About right (12 mA):** 30 Ω against 45.6 Ω gives Γ = −0.21 at the source. + The far end overshoots once, to 3.66 V, and the next reflection is small. +- **Too strong (24 mA):** 15 Ω gives Γ = −0.50, so the line rings hard + (−0.88 V to 4.20 V). The eye still passes at 2 ns, but it fails at 0.8 ns + (lesson 11). + +Pick the drive whose output impedance is closest to Z0, not the strongest +one. + +## Try this + +Set `corner="minimum"` on the 12 mA board. The weak corner adds 30 % to the +drive resistance. Note how much eye height you lose. You'll use that spread +again in lesson 18. diff --git a/docs/guides/spice-simulation/si-course/2-medium/13-return-path.mdx b/docs/guides/spice-simulation/si-course/2-medium/13-return-path.mdx new file mode 100644 index 00000000..3753464a --- /dev/null +++ b/docs/guides/spice-simulation/si-course/2-medium/13-return-path.mdx @@ -0,0 +1,69 @@ +--- +title: "13 · The return path" +sidebar_label: "13 · Return path" +sidebar_position: 13 +description: Move the ground plane away from the trace and watch L, Z0 and the eye change. +--- + +**Status: ✅ Runs today** (about 1.5 minutes per run) + +**Goal:** see that the return current's path, not just the trace, sets the +line's inductance and impedance. + +## The idea + +Every signal current has a return current. At high frequency, the return +flows in the nearest plane, right under the trace, because that path has the +least loop inductance. The farther the plane is from the trace, the larger +the loop. L rises, C falls, and Z0 rises with both. + +## Build + +Use the same 20 mm, 0.16 mm trace and the same 24 mA driver, with the GND +plane at two distances: + +```tsx title="13-near-plane.circuit.tsx" +import { LineBoard } from "./teaching-board" +// Reference case: same trace and driver, GND plane on inner1, 0.1 mm below. +export default () => +``` + +```tsx title="13-far-plane.circuit.tsx" +import { LineBoard } from "./teaching-board" +// Lesson 11's trace, but on a 2-layer board: the GND plane is 1.43 mm away instead of 0.1 mm. +export default () => +``` + +## What you see + +| GND plane | Distance | L | C | Z0 | Delay | RX eye height | +| --- | --- | --- | --- | --- | --- | --- | +| `inner1` (4-layer) | 0.10 mm | 6.67 nH | 3.37 pF | **44.5 Ω** | 150 ps | **3.14 V** | +| `bottom` (2-layer) | 1.43 mm | 18.1 nH | 1.15 pF | **125 Ω** | 144 ps | **2.70 V** | + +The far-plane eye also gets 34 ps of crossing jitter. The near-plane eye has +about 1 ps. + +## Check by hand + +- **The delay barely changes:** 150 ps vs 144 ps. Delay depends on the + dielectric, not the geometry. +- **L × C stays about constant** (22.5 vs 20.8 × 10⁻²¹), while L/C, and so + Z0, grows about 8×. Moving the plane changes the impedance, not the speed. +- **The 15 Ω driver against 125 Ω** reflects hard, with Γ = −0.79 at the + source. That's why the eye shrinks even on a short line. + +## What about slots in the plane? + +A slot cut across the return path forces the current to detour around it. To +simulate that, you need the full 3D field solve in +[``](../../../../elements/simulation-pcbreturncurrentsimulation.mdx), +run with `tsci simulate return-current`. That needs the +[Palace](https://github.com/awslabs/palace) solver on your machine. +`straightened-stages` only sees which layer the plane is on, not holes in it. + +## Try this + +Put the GND pour on `inner2` of the 4-layer board, leaving `inner1` empty. +The extraction only uses a pour on the copper layer **directly adjacent** to +the trace. Predict what happens, then run it and read what the pipeline says. diff --git a/docs/guides/spice-simulation/si-course/2-medium/14-vias.mdx b/docs/guides/spice-simulation/si-course/2-medium/14-vias.mdx new file mode 100644 index 00000000..2256049d --- /dev/null +++ b/docs/guides/spice-simulation/si-course/2-medium/14-vias.mdx @@ -0,0 +1,92 @@ +--- +title: "14 · Changing layers" +sidebar_label: "14 · Vias" +sidebar_position: 14 +description: Route a trace through two vias and extract each via as its own stage. +--- + +**Status: ✅ Runs today** (about 2 minutes) + +**Goal:** see a via as a short, high-impedance piece of the line. + +## The idea + +A via is a short vertical conductor. It has a few hundred pH to about 1 nH of +inductance, but very little capacitance, because there's no plane right next +to it. To the signal it looks like a short section of high-Z line. + +## Build + +Route top → via → bottom → via → top. There's a GND pour on `inner1` under +the `top` sections and on `inner2` over the `bottom` section: + +```tsx title="14-vias.circuit.tsx" +import { LineBoard } from "./teaching-board" +// Top → via → bottom → via → top. GND pours on inner1 (under top) and inner2 (over bottom). +// Each via needs a wire vertex at the same spot just before it. +export default () => ( + +) +``` + +A via in `pcbPath` needs a wire point at the same spot just before it. +Otherwise the pipeline stops with `via_geometry`. + +When a route has vias, `LineBoard` adds two things to `pcbSpiceChannel`. +The pipeline asks for each of them in turn: + +```tsx +via: { platingThickness: "0.025mm", equivalentWidth: "0.1mm", omitThroughViaStubs: true }, +// The via barrel has no plane under it, so you write its return by hand: +// a GND return 1 mm to the side, like a stitching via next to the signal via. +straightenedStageProfiles: { + via: { + spacing: "0.1mm", relativePermittivity: 4.42, + return: { width: "0.3mm", thickness: "0.025mm", lengthFactor: 1, centerY: "1mm", centerZ: "0mm" }, + }, +}, +``` + +| Error you'll hit | Meaning | +| --- | --- | +| `viaPlatingMm … must be a positive number` | Add `via.platingThickness` | +| `requires explicit omitThroughViaStubs=true` | This reduction can't model stubs, so you have to say you're leaving them out | +| `Missing via return/material profile` | Write the via's return path with `straightenedStageProfiles.via` | + +## What you see + +The route becomes five stages: + +| Stage | Length | L | C | Z0 | Delay | +| --- | --- | --- | --- | --- | --- | +| top | 6.0 mm | 1.98 nH | 1.00 pF | 44.6 Ω | 44 ps | +| **via** | 1.53 mm | **1.24 nH** | **0.10 pF** | **111 Ω** | 11 ps | +| bottom | 8.0 mm | 2.65 nH | 1.32 pF | 44.7 Ω | 59 ps | +| **via** | 1.53 mm | 1.24 nH | 0.10 pF | 111 Ω | 11 ps | +| top | 6.0 mm | 1.98 nH | 1.00 pF | 44.6 Ω | 44 ps | + +The eye at 2 ns per bit with the 24 mA driver is 3.30 V high and 2.0 ns wide. +That's no worse than lesson 13's straight line. + +## Check by hand + +- **Via inductance:** L ≈ 0.2·h·(1 + ln(4h/d)) nH, with h = 1.53 mm and + d = 0.4 mm, gives **1.14 nH**. The extraction gives 1.24 nH ✓ +- **Why the eye doesn't care:** each via is 11 ps long, much shorter than the + 0.4 ns edge. A discontinuity matters roughly when its delay gets close to + the edge's rise time divided by 6, which is about 70 ps here. +- **The return profile matters:** move `centerY` to 3 mm and the via's L + rises. That's the extraction showing you why stitching vias next to signal + vias are good practice. + +## Try this + +Set `centerY: "3mm"` and predict the change in the via's L and Z0 before you +run it. Then remove a via, so the route stays on `top`, and compare the +total delay. diff --git a/docs/guides/spice-simulation/si-course/2-medium/15-crosstalk.mdx b/docs/guides/spice-simulation/si-course/2-medium/15-crosstalk.mdx new file mode 100644 index 00000000..90783595 --- /dev/null +++ b/docs/guides/spice-simulation/si-course/2-medium/15-crosstalk.mdx @@ -0,0 +1,89 @@ +--- +title: "15 · Crosstalk" +sidebar_label: "15 · Crosstalk" +sidebar_position: 15 +description: Two coupled traces, one switching and one quiet, and how to tell when a model can't be trusted. +--- + +**Status: 🟡 Runs, but don't trust the numbers yet.** The two-lane +extraction runs, but its coupling doesn't behave physically as spacing +changes. That makes this lesson about checking a model as much as about +crosstalk. + +**Goal:** measure how much a switching trace disturbs its quiet neighbour, +and sanity-check the result. + +## The idea + +Coupled traces share mutual inductance (Lm) and mutual capacitance (Cm). When +the aggressor switches, the victim sees two kinds of noise: + +- **NEXT** (near-end crosstalk) at the driver end. It scales with + (Cm/C + Lm/L). +- **FEXT** (far-end crosstalk) at the receiver. It scales with + (Cm/C − Lm/L) × length. In microstrip it's usually **negative**, because + inductive coupling dominates. + +Both should **fall** as the spacing grows. + +## Build + +The aggressor (IO1) and the victim (IO2) run straight and parallel for +20 mm. The chips' I/O pad pitch sets the spacing. The victim is driven but +held at 0: + +```tsx title="15-crosstalk.circuit.tsx" +import { LineBoard } from "./teaching-board" + +// Aggressor (IO1) and victim (IO2) run straight and parallel for 20 mm. +// `spacing` is centre to centre in mm; the chips' I/O pads use the same pitch. +export const Crosstalk = ({ spacing = 0.32 }: { spacing?: number }) => ( + +) + +export default () => +``` + +`LineBoard` adds the second lane, a quiet stimulus (`bits: "0"`), and +`VIC_NEAR`/`VIC_FAR` probes when `victimSpacing` is set. The spacing +variants are `15-crosstalk-032`, `-064` and `-128`. + +## What you see + +24 mA aggressor, 2 ns per bit, peak victim noise: + +| Spacing (centre to centre) | Lm/L | Cm/C | NEXT peak | FEXT peak | +| --- | --- | --- | --- | --- | +| 0.32 mm (gap = 1 trace width) | +0.066 | 0.145 | 71 mV | 75 mV | +| 0.64 mm (gap = 3 trace widths) | **−0.019** | 0.046 | 51 mV | **107 mV** | +| 1.28 mm (gap = 7 trace widths) | +0.017 | 0.020 | 13 mV | 27 mV | + +## Check by hand, and why this fails the check + +- **Cm/C falls about 3×** from 0.32 mm to 0.64 mm. That's sensible. +- **Lm/L goes negative** at 0.64 mm. Two parallel traces over a solid plane + always have positive mutual inductance, so this is not physical. +- **FEXT grows** from 0.32 mm to 0.64 mm, the opposite of what it should + do, before falling at 1.28 mm. +- From 0.32 mm to 1.28 mm overall, NEXT falls about 5× and FEXT about 3×. + The end-to-end trend is right; the middle point is the warning sign. + +The cause is the model, not the board. `straightened-stages` puts both +traces over one **2 mm-wide return bar**, not a whole ground plane. At +0.64 mm the traces sit near the bar's edges, and the bar's own current +crowding produces the odd mutual terms. A real plane is tens of mm wide. + +This is the habit the lesson teaches: **when a trend goes the wrong way, +suspect the model before the physics.** + +## What would fix it + +- A return bar much wider than the trace spacing. Try + `straightenedStageProfiles` with a custom `return.width`. +- `reduction: "routed-copper"`, which meshes the real pour, once it + converges (see lesson 20). + +## Try this + +Add a `straightenedStageProfiles` entry with a 10 mm return width, then +check whether Lm/L turns positive and FEXT falls with spacing. diff --git a/docs/guides/spice-simulation/si-course/2-medium/_category_.json b/docs/guides/spice-simulation/si-course/2-medium/_category_.json new file mode 100644 index 00000000..ede350cf --- /dev/null +++ b/docs/guides/spice-simulation/si-course/2-medium/_category_.json @@ -0,0 +1 @@ +{ "label": "Part 2 · Medium: the trace is real", "position": 2, "collapsible": true, "collapsed": false } diff --git a/docs/guides/spice-simulation/si-course/3-hard/16-ddr-one-lane.mdx b/docs/guides/spice-simulation/si-course/3-hard/16-ddr-one-lane.mdx new file mode 100644 index 00000000..c15297ab --- /dev/null +++ b/docs/guides/spice-simulation/si-course/3-hard/16-ddr-one-lane.mdx @@ -0,0 +1,57 @@ +--- +title: "16 · One DQ lane" +sidebar_label: "16 · One DQ lane" +sidebar_position: 16 +description: Run the DQ0 write eye on the routed AM3352 DDR3 board. +--- + +**Status: ✅ Runs today** (needs the vendor TI and Winbond IBIS files) + +**Goal:** run lesson 10 on a real, dense board. + +Everything here is something you've already done. What's new is the scale: a +BGA processor, a DDR3 chip, a 6-layer stackup and 46 routed DDR traces. + +## Run it + +```bash +git clone https://github.com/0hmX/am3352-ddr-track-a-local-dfm-wip-20261007 +cd am3352-ddr-track-a-local-dfm-wip-20261007 +bun install --frozen-lockfile +# put sprm552c-candidate-models.ibs and W631GG6MB-12.ibs in simulations-next/model-review/ +tsci simulate simulate.circuit.tsx # about 3–4 minutes +open .tscircuit/simulation-simulate-*/results.html # Plots → Eye +``` + +## What's new compared with lesson 10 + +| Lesson 10 | Lesson 16 | +| --- | --- | +| A board you wrote for the test | The **real** board, with the simulation added as extra children: `cloneElement(Board(), {}, board.props.children, <>…)` | +| You know which pin connects to which | DDR swaps DQ balls, so `resolveDdrChannel()` compiles the board and **follows the routed copper** to find the endpoints | +| Synthetic teaching IBIS | Vendor models: TI `Model_343` (slow 9 mA) driving, Winbond `ODT_60` receiving | +| A short bit pattern | PRBS7 at 640 MT/s, so UI = 1.5625 ns | + +The full walkthrough is in +[Simulating PCB Traces with tsci simulate](../../simulating-pcb-traces-with-tsci-simulate.mdx). + +## What you see + +DQ0 returns on the `inner1` GND plane 0.1245 mm below, with εr 4.1. The +`straightened-stages` extraction passes every check: L and R change less than +0.7 % between 9 and 15 filaments. + +**Eye: about 0.96 V high and 1.45 ns wide, in a 1.5625 ns UI.** + +## Check by hand + +- 0.1245 mm over a plane is a thin dielectric, so Z0 is low and the loop L per + mm is small. The eye should be wide open, and it is. +- Making the return bar 1 mm or 2 mm wide gives the same eye (0.957 V vs + 0.958 V). Straightening the route adds only about 4 % loop L compared with + the curved route. + +## Scope + +This is one bit with an ideal strobe. It isn't a pass/fail verdict for the +byte. Lessons 17–22 add the rest. diff --git a/docs/guides/spice-simulation/si-course/3-hard/17-strobe.mdx b/docs/guides/spice-simulation/si-course/3-hard/17-strobe.mdx new file mode 100644 index 00000000..76819d65 --- /dev/null +++ b/docs/guides/spice-simulation/si-course/3-hard/17-strobe.mdx @@ -0,0 +1,52 @@ +--- +title: "17 · Add the strobe" +sidebar_label: "17 · Add the strobe" +sidebar_position: 17 +description: Simulate DQ together with its DQS/DQSn strobe pair. +--- + +**Status: 🚧 Not supported yet.** On this board, DQS0/DQSn0 change layer onto +`inner2`, between the `inner1` and `inner3` planes. `straightened-stages` +can't share stages between DQ and DQS yet +([pcb-spice-pipeline#49](https://github.com/tscircuit/pcb-spice-pipeline/issues/49)), +and the via stages still need a hand-written `via` profile. + +**Goal:** check DQ against its own strobe, not an ideal clock. + +## The idea + +DDR3 has no shared data clock. Each byte has a differential strobe, DQS/DQSn. + +- **Writes:** the controller places the DQS edge in the **centre** of the DQ + eye, half a UI later. +- **Reads:** the RAM sends DQS **edge-aligned**, and the controller's PHY + shifts it internally. + +## Build + +The repo already declares this in `simulations-next/run-ddr.circuit.tsx`: + +```tsx + +``` + +That adds: + +- DQS and DQSn stimuli starting at `uiNs / 2`, with DQSn `inverted`; +- a differential probe, ``; +- two experiments, **QUIET** (the strobe parked) and **SWITCHING** (the strobe + toggling every UI); +- a second eye on `DQS_RX`. + +## What you should see + +- A DQS differential eye centred on the DQ eye. +- A **switching − quiet** ΔV table in the report. That's how much the toggling + strobe disturbs DQ through coupling. + +## Check by hand + +The DQS crossing should sit about 0.78 ns (UI/2) after the DQ transitions. A +skew larger than about a quarter UI eats directly into setup or hold. diff --git a/docs/guides/spice-simulation/si-course/3-hard/18-corners.mdx b/docs/guides/spice-simulation/si-course/3-hard/18-corners.mdx new file mode 100644 index 00000000..dc9795d2 --- /dev/null +++ b/docs/guides/spice-simulation/si-course/3-hard/18-corners.mdx @@ -0,0 +1,54 @@ +--- +title: "18 · Corners and settings" +sidebar_label: "18 · Corners and settings" +sidebar_position: 18 +description: Sweep IBIS corners, drive strength and ODT on the DQ0 lane. +--- + +**Status: 🟡 Not run yet.** `DdrIbisBoard` already accepts `corner`, `drive` +and `ramOdt` props. Nobody has run the sweep yet. + +**Goal:** find the worst combination, not the typical one. + +## Settings to cover + +| Prop | Values | What it changes | +| --- | --- | --- | +| `corner` | `typical`, `minimum`, `maximum` | Silicon speed and supply: 1.5, 1.425 and 1.575 V | +| `drive` | `slow9mA`, `fast9mA`, `fast8mA`, `fast12mA` | The TI DDR PHY's output drive and slew | +| `ramOdt` | `ODT_20` … `ODT_120`, `ODT_off` | Winbond on-die termination (writes) | + +## Build + +One entry file per combination, so each gets its own report: + +```tsx title="corner-min-fast12-odt40.circuit.tsx" +import DdrIbisBoard from "./simulations-next/ibis.circuit" +import { physicalRecords } from "./simulations-next/run-ddr.circuit" +import { dqStagesChannel } from "./simulations-next/run-dq-stages.circuit" + +export default () => ( + +) +``` + +## What you should see + +- **Weaker ODT** (`ODT_120`, `ODT_off`): a taller eye, but more overshoot and + ringing. +- **Stronger ODT** (`ODT_20`): flatter edges, a shorter eye, and more DC + current. +- **Minimum corner:** slower edges and a smaller eye. This is often the + worst-case eye height. + +## Check by hand + +With a driver of about R_drv into ODT R_odt, the DC swing ≈ +VDDQ × R_odt / (R_drv + R_odt), centred on VDDQ/2. The eye can't be taller +than that. + +## Try this + +Make a table of eye height and width for all 3 × 4 × 6 combinations, and mark +the worst cell. diff --git a/docs/guides/spice-simulation/si-course/3-hard/19-full-byte.mdx b/docs/guides/spice-simulation/si-course/3-hard/19-full-byte.mdx new file mode 100644 index 00000000..f9abf066 --- /dev/null +++ b/docs/guides/spice-simulation/si-course/3-hard/19-full-byte.mdx @@ -0,0 +1,38 @@ +--- +title: "19 · The full byte lane" +sidebar_label: "19 · Full byte lane" +sidebar_position: 19 +description: All eight DQ bits, DM and the strobe together, with coupling. +--- + +**Status: 🚧 Not supported yet.** `pcbSpiceChannel.lanes` takes at most +three lanes, and shared DQ/DQS stages are blocked by +[pcb-spice-pipeline#49](https://github.com/tscircuit/pcb-spice-pipeline/issues/49). + +**Goal:** find the worst bit in the byte, with its neighbours switching. + +## The idea + +Bits in a byte are routed close together. A victim's worst case is when its +neighbours switch the opposite way. Length mismatch inside the byte also skews +bits against DQS. + +## Target setup + +- **Lanes:** `DQ0` … `DQ7`, `DM0`, `DQS0` and `DQSn0`, each resolved with + `resolveDdrChannel`. +- **Stimuli:** one PRBS per bit, with a different seed or a shifted start for + each, so the neighbours aren't in lockstep. +- **Eyes:** one per DQ, all against the DQS crossing. + +## What you should see + +Ten eyes. The one in the middle of the bus, with the most coupled neighbours +and the longest route, is usually the worst. Report the worst bit, not the +average. + +## Check by hand + +- Skew budget: the length difference to DQS × about 6–7 ps/mm should stay + well inside the setup/hold window from lesson 21. +- Coupling: rerun lesson 15's spacing rule on the tightest pair in the byte. diff --git a/docs/guides/spice-simulation/si-course/3-hard/20-routed-copper.mdx b/docs/guides/spice-simulation/si-course/3-hard/20-routed-copper.mdx new file mode 100644 index 00000000..cc6f165f --- /dev/null +++ b/docs/guides/spice-simulation/si-course/3-hard/20-routed-copper.mdx @@ -0,0 +1,45 @@ +--- +title: "20 · Routed copper" +sidebar_label: "20 · Routed copper" +sidebar_position: 20 +description: Extract the real curved copper, planes and vias instead of straightened stages. +--- + +**Status: 🚧 Not supported yet on this board.** FastHenry fails on the AM3352 +`routed-copper` case: + +- separate meshing of touching GND bands gives `filaments had identical centers` + ([#45](https://github.com/tscircuit/pcb-spice-pipeline/pull/45)); +- after that, finely sampled curves give `mutual inductance = infinity` + ([#46](https://github.com/tscircuit/pcb-spice-pipeline/issues/46)); +- refined meshes don't converge at 1 GHz. + +**Goal:** remove the straightening approximation. + +## The idea + +`straightened-stages` swaps the route for straight segments over a finite +return bar. `routed-copper` meshes what's actually on the board: + +- the curves and 45° bends; +- the whole-board GND planes (`groundMeshSize`); +- the vias (`via: { platingThickness, equivalentWidth, omitThroughViaStubs }`). + +## Build + +This is the default in `ibis.circuit.tsx` when you pass no `pcbSpiceChannel`: + +```tsx + +``` + +## Why it's hard + +DQ0 sits 0.12 mm above `inner1`, so the ground cells under it have to be about +0.1 mm. At that size, the three whole-board planes are far too large to mesh. + +## Check by hand + +Once it runs, compare it with lesson 16. On a solid plane, curved vs straight +loop L differed by about 4 % (9.53 nH vs 9.89 nH). A much larger difference +points to a modelling problem, not physics. diff --git a/docs/guides/spice-simulation/si-course/3-hard/21-timing-check.mdx b/docs/guides/spice-simulation/si-course/3-hard/21-timing-check.mdx new file mode 100644 index 00000000..6a551d52 --- /dev/null +++ b/docs/guides/spice-simulation/si-course/3-hard/21-timing-check.mdx @@ -0,0 +1,43 @@ +--- +title: "21 · Timing check" +sidebar_label: "21 · Timing check" +sidebar_position: 21 +description: Compare the eye against the DDR3 setup/hold and VREF thresholds. +--- + +**Status: 🟡 Not run yet.** The repo has `winbondWriteScreen()` in +`ibis-models.tsx`, and the report has setup/hold margin columns. They haven't +been checked on a run with DQS. + +**Goal:** turn an eye picture into a margin number. + +## Write thresholds (Winbond W631GG6MB, DDR3-1600) + +| Quantity | Value at VDDQ = 1.5 V | +| --- | --- | +| VREF | VDDQ / 2 = 0.75 V | +| VIH(AC) / VIL(AC) | VREF ± 150 mV | +| VIH(DC) / VIL(DC) | VREF ± 100 mV | +| Setup (screen) | 85 ps | +| Hold (screen) | 95 ps | +| Min. DQS differential | 0.3 V | + +## The check + +1. At each DQS crossing, DQ must be past VIH(AC) or VIL(AC) **at least + setup** before the crossing. +2. It must then stay past VIH(DC) or VIL(DC) for **at least hold** after it. +3. Margin = the smallest slack over all bits, edges and corners. + +## Reads + +For reads, swap the direction (`direction="read"`). The RAM drives with +`DATA_34`/`DATA_40`, and the TI PHY terminates. DQS arrives **edge-aligned**, +so you have to model the PHY's sampling shift explicitly. TI doesn't publish a +full read receiver mask, so read margin is an estimate. + +## Check by hand + +Eye width at VREF − (setup + hold) is roughly your timing margin before jitter. +With the lesson 16 numbers, 1.45 ns − 0.18 ns leaves plenty. The real squeeze +comes from DQS skew, corners and crosstalk. diff --git a/docs/guides/spice-simulation/si-course/3-hard/22-power.mdx b/docs/guides/spice-simulation/si-course/3-hard/22-power.mdx new file mode 100644 index 00000000..9380e4ff --- /dev/null +++ b/docs/guides/spice-simulation/si-course/3-hard/22-power.mdx @@ -0,0 +1,45 @@ +--- +title: "22 · Power" +sidebar_label: "22 · Power" +sidebar_position: 22 +description: Add VDDQ impedance and simultaneous switching noise to the eye. +--- + +**Status: 🚧 Not supported yet.** The pipeline uses an ideal `DDR_VDDQ` +source today. Supply impedance, decoupling and SSO coupling aren't modelled. + +**Goal:** stop assuming the rails are ideal. + +## The idea + +When all eight DQ bits switch the same way, their current flows through the +shared VDDQ and GND paths. Any supply impedance turns that current into rail +noise, which shows up in every bit's eye and in VREF. + +## Target setup + +1. Extract the VDDQ plane and vias together with the decoupling capacitors, + including their ESL and ESR. +2. Check the PDN impedance with an AC sweep (lesson 4) at the RAM's VDDQ + balls. Target: below about VDDQ × ripple / ΔI up to the edge-rate knee. +3. Run the full byte (lesson 19), all bits switching in phase, with that PDN + in place of the ideal source. + +## Check by hand + +- Target impedance: Z_target = (1.5 V × 5 %) / ΔI. With 8 bits × about 20 mA, + that's **about 0.47 Ω**. +- The VREF divider must follow VDDQ/2 at low frequency, but be filtered + against the fast switching noise. + +## Done + +At this point you have: + +- the DQ and DQS eyes; +- across corners and settings; +- with coupling, real copper and real power; +- checked against the datasheet thresholds. + +That's what a DDR signal-integrity signoff checks. Check every result by hand, +and treat any simulation you can't explain as unverified. diff --git a/docs/guides/spice-simulation/si-course/3-hard/_category_.json b/docs/guides/spice-simulation/si-course/3-hard/_category_.json new file mode 100644 index 00000000..50743e17 --- /dev/null +++ b/docs/guides/spice-simulation/si-course/3-hard/_category_.json @@ -0,0 +1 @@ +{ "label": "Part 3 · Hard: DDR3 on a real board", "position": 3, "collapsible": true, "collapsed": false } diff --git a/docs/guides/spice-simulation/si-course/_category_.json b/docs/guides/spice-simulation/si-course/_category_.json new file mode 100644 index 00000000..f592100c --- /dev/null +++ b/docs/guides/spice-simulation/si-course/_category_.json @@ -0,0 +1,7 @@ +{ + "label": "Signal Integrity Course", + "position": 2, + "link": { "type": "doc", "id": "guides/spice-simulation/si-course/index" }, + "collapsible": true, + "collapsed": false +} diff --git a/docs/guides/spice-simulation/si-course/index.mdx b/docs/guides/spice-simulation/si-course/index.mdx new file mode 100644 index 00000000..c9ddbd7a --- /dev/null +++ b/docs/guides/spice-simulation/si-course/index.mdx @@ -0,0 +1,99 @@ +--- +title: Signal Integrity Course +sidebar_label: Course overview +sidebar_position: 0 +description: >- + A step-by-step course for electrical engineers new to tscircuit. Start with a + voltage divider in ngspice and finish with a DDR3 eye diagram on a real + routed board. +--- + +This course is for electrical engineers who are new to tscircuit. It takes you +from a voltage divider to an eye diagram on a routed DDR3 trace. Each lesson +adds **one** new idea and ends with a check you can do on paper. + +You don't need to know React. A tscircuit file is a function that returns your +circuit as tags. `` is a resistor, `` is a connection, and +`tsci simulate` runs the analysis you declare. + +## Route + +### Part 1 · Simple: schematic circuits + +Engine: `ngspice`. Traces are ideal wires. + +| # | Lesson | You learn | Check by hand | +| --- | --- | --- | --- | +| 1 | [Voltage divider](./1-simple/01-voltage-divider.mdx) | Sources, probes, DC operating point, reading the output | Vout = V·R2/(R1+R2) | +| 2 | [Diode I–V curve](./1-simple/02-diode-iv.mdx) | DC sweep | Knee near 0.6–0.7 V | +| 3 | [RC step response](./1-simple/03-rc-step.mdx) | Transient analysis | 63 % at τ = RC | +| 4 | [RC Bode plot](./1-simple/04-rc-bode.mdx) | AC sweep | −3 dB at fc = 1/(2πRC) | +| 5 | [Parameter sweeps](./1-simple/05-parameter-sweeps.mdx) | `analog.sweepparameter` | τ scales with R | +| 6 | [Real circuits](./1-simple/06-real-circuits.mdx) | Rectifiers, switches, converters | Datasheet behaviour | + +### Part 2 · Medium: the trace is real + +| # | Lesson | You learn | Check by hand | +| --- | --- | --- | --- | +| 7 | [A trace is an RLC](./2-medium/07-trace-is-an-rlc.mdx) | Fast edges ring on a lumped trace | f ≈ 1/(2π√LC) | +| 8 | [Transmission line](./2-medium/08-transmission-line.mdx) | Z0, delay, reflections | Open end doubles the step | +| 9 | [Termination](./2-medium/09-termination.mdx) | Series vs parallel termination | Clean when R = Z0 | +| 10 | [Your first `pcb_spice` run](./2-medium/10-first-pcb-spice.mdx) | The board supplies the R, L and C | Microstrip Z0 formula | +| 11 | [Bits and eyes](./2-medium/11-bits-and-eyes.mdx) | Bit patterns, UI, eye diagrams, ISI | Eye closes once a round trip exceeds a bit | +| 12 | [Real buffers (IBIS)](./2-medium/12-ibis-buffers.mdx) | ``, drive strength | Best drive ≈ Z0, not the strongest | +| 13 | [The return path](./2-medium/13-return-path.mdx) | Plane distance sets L and Z0 | L grows with distance to the plane | +| 14 | [Changing layers](./2-medium/14-vias.mdx) | Vias and stubs | A dip at the via | +| 15 | [Crosstalk](./2-medium/15-crosstalk.mdx) | Several lanes, aggressor vs victim, sanity-checking a model | Should fall with spacing | + +### Part 3 · Hard: DDR3 on the AM3352 board + +| # | Lesson | You learn | +| --- | --- | --- | +| 16 | [One DQ lane](./3-hard/16-ddr-one-lane.mdx) | Simulate the real board, find routed pins, vendor IBIS models, the DQ0 eye | +| 17 | [Add the strobe](./3-hard/17-strobe.mdx) | DQS/DQSn, centre-aligned writes, quiet vs switching | +| 18 | [Corners and settings](./3-hard/18-corners.mdx) | IBIS corners, drive strength, ODT | +| 19 | [The full byte lane](./3-hard/19-full-byte.mdx) | Eight DQ bits, DM and DQS with coupling, worst bit | +| 20 | [Routed copper](./3-hard/20-routed-copper.mdx) | Real curves, planes and vias | +| 21 | [Timing check](./3-hard/21-timing-check.mdx) | Setup/hold, VREF thresholds, reads | +| 22 | [Power](./3-hard/22-power.mdx) | VDDQ impedance, simultaneous switching noise | + +## Lesson status + +Each lesson starts with a status badge: + +- **✅ Runs today.** We ran the code with `tsci simulate`, and the numbers on + the page come from that run. +- **🟡 Not run yet.** The code uses elements tscircuit already has, but nobody + has run this exact example yet. Expect to adjust it. +- **🚧 Not supported yet.** This is the setup we're aiming for. The page says + what's missing today. + +## How every lesson is laid out + +1. **Goal:** one sentence. +2. **Build:** one `*.circuit.tsx` file. +3. **Run:** `bunx tsci simulate `. +4. **What you see:** the real output. +5. **Check by hand:** the paper calculation it should match. +6. **Try this:** one change to make on your own. + +## Example files + +Every runnable lesson has a file in +[`tutorial/course`](https://github.com/0hmX/am3352-ddr-track-a-local-dfm-wip-20261007/tree/main/tutorial/course) +of the AM3352 repo. That folder also has the synthetic teaching IBIS model +used in Part 2. Clone the repo and run them from its root. + +## Before you start + +```bash +bun install -g tscircuit # or use bunx tsci in a project +brew install ngspice # Part 1 onwards +# Part 2 onwards (pcb_spice): fasthenry and FasterCap on your PATH +``` + +### Reading `tsci simulate` output + +The ngspice engine prints a table. Columns are **node voltages** +(`v(n1)`, `v(n2)`, …) and source currents, not your probe names. In a small +circuit, find your node by its value. Lesson 1 shows how. diff --git a/docs/guides/spice-simulation/simulating-pcb-traces-with-tsci-simulate.mdx b/docs/guides/spice-simulation/simulating-pcb-traces-with-tsci-simulate.mdx new file mode 100644 index 00000000..76c64ff4 --- /dev/null +++ b/docs/guides/spice-simulation/simulating-pcb-traces-with-tsci-simulate.mdx @@ -0,0 +1,354 @@ +--- +title: Simulating PCB Traces with tsci simulate +sidebar_label: PCB Trace Simulation (tsci simulate) +sidebar_position: 3 +description: >- + Run analog simulations from the command line with tsci simulate, from a + schematic RC circuit with ngspice to a routed DDR3 trace with IBIS models and + an eye diagram using pcb_spice. +--- + +## Overview + +`tsci simulate` runs the analog experiments declared in a tscircuit file. It +works with two engines: + +| `spiceEngine` | What it simulates | Output | +| --- | --- | --- | +| `"ngspice"` | The schematic netlist. Traces are ideal wires. | A table printed to the terminal | +| `"pcb_spice"` | The routed copper and the board stackup. FastHenry extracts R and L, FasterCap extracts C, and ngspice runs the result with vendor IBIS buffer models. | A `results.html` report with waveforms, eye diagrams, CSV files and solver logs | + +This guide starts with an RC circuit on ngspice. It then goes through a real +`pcb_spice` run: DQ0 of the DDR3 interface on the +[AM3352 board](https://github.com/0hmX/am3352-ddr-track-a-local-dfm-wip-20261007) +(see also [Routing DDR](../routing-ddr.mdx)). + +``` +board.tsx + ├─ physical design: … + ├─ , + ├─ (pcb_spice only) + └─ … the experiment + bit pattern (pcb_spice) + eye measurement (pcb_spice) + │ render → Circuit JSON + ▼ + tsci simulate board.tsx + ├─ ngspice → netlist → ngspice → terminal table + └─ pcb_spice → copper + stackup → FastHenry (R, L) + FasterCap (C) + → RLC sections + IBIS buffers → ngspice → results.html +``` + +## Rules for `tsci simulate` + +- The CLI runs `ngspice` and `pcb_spice` experiments. `spicey` is only for the + browser preview. +- One file can't mix `pcb_spice` with other engines. Put each engine in its own + entry file. +- `--output ` and `--solver-timeout-ms` only work with `pcb_spice`. The + output directory must not exist yet. The default is + `.tscircuit/simulation--/`. +- A time prop given as a bare number means **milliseconds**. Pass strings such + as `"10ps"` or `"512ns"` to be explicit. + +## Step 1: RC step response with ngspice + +```tsx title="rc.circuit.tsx" +import { analog } from "tscircuit" + +export default () => ( + + + + + + + + + + +) +``` + +Run it: + +```bash +bunx tsci simulate rc.circuit.tsx +``` + +``` +Index time v(n1) v(n2) i(vsimulation_voltage_source_0) +0 1.000000e-7 3.300000e+0 3.299670e-4 -3.299670e-3 +… +510 5.000000e-3 3.300000e+0 3.277766e+0 -2.223431e-5 +``` + +`v(n2)` is the capacitor node. At 5 ms, which is five time constants +(τ = RC = 1 ms), it reaches 3.2778 V. That matches the analytic value +3.3 × (1 − e⁻⁵). + +`routingDisabled` turns off the autorouter. The simulation only uses the +netlist, so routing isn't needed. + +For a frequency sweep, use +[``](../../elements/analog-acsweepsimulation.mdx). +See the [low-pass filter guide](<./Passive Filters/low-pass-filter.mdx>) for an +example. + +## Step 2: Simulate a routed DDR3 trace with pcb_spice + +ngspice treats a trace as an ideal wire. `pcb_spice` uses the routed trace, the +layer stackup and the GND pours, and extracts the trace's R, L and C from them. +It builds an RLC ladder, connects vendor IBIS driver and receiver models to the +two ends, and runs the transient. + +### Run the example + +```bash +git clone https://github.com/0hmX/am3352-ddr-track-a-local-dfm-wip-20261007 +cd am3352-ddr-track-a-local-dfm-wip-20261007 +bun install --frozen-lockfile +# put the two IBIS files in simulations-next/model-review/ (see below) +tsci simulate simulate.circuit.tsx # or: bun run simulate, about 3–4 minutes +open .tscircuit/simulation-simulate-*/results.html # Plots → Eye +``` + +You need `fasthenry`, `FasterCap` and `ngspice` on your `PATH`. The CLI calls +them by those exact names. + +Vendor IBIS files aren't distributed with the repo. Put +`sprm552c-candidate-models.ibs` (TI) and `W631GG6MB-12.ibs` (Winbond) in +`simulations-next/model-review/`. The download URLs, the TI subset +transformation and the SHA-256 hashes are in `review-receipt.json` in that +folder. + +### File map + +| File | Role | +| --- | --- | +| `simulate.circuit.tsx` | CLI entry. Re-exports `simulations-next/run-dq-stages.circuit`. | +| `simulations-next/run-ddr.circuit.tsx` | Compiles the physical board once and exports `physicalRecords`. | +| `simulations-next/channels.tsx` | `resolveDdrChannel()` follows the routed `pcb_trace` from CPU `U1` to RAM `U3`. | +| `simulations-next/ibis-models.tsx` | Builds the `` props. | +| `simulations-next/ibis.circuit.tsx` | `DdrIbisBoard`: the real board with the simulation declarations added as extra children. | +| `simulations-next/run-dq-stages.circuit.tsx` | Selects DQ only, the quiet case, and `straightened-stages`. | + +### Add declarations to the real board + +You don't need a separate simulation copy of the board. Render the board +component and append the declarations to its children: + +```tsx +import { cloneElement } from "react" +import Board from "../index" + +const board = Board() +return cloneElement( + board, + { fabricatorPreset: "jlcpcb_standard" }, + board.props.children, // unchanged physical design + <>{/* simulation declarations */}, +) +``` + +Board-level selectors can reach into subcircuits, for example +`.Processor > .U1 > .M3`. + +### Find the routed pins from Circuit JSON + +DDR byte lanes often permute DQ balls between the controller and the memory, so +don't hard-code them. Compile the board once and follow the copper: + +```tsx +import { Circuit } from "@tscircuit/core" + +const physical = new Circuit({ + platform: { partsEngineDisabled: true, netlistDrcChecksDisabled: true }, +}) +physical.add(cloneElement(Board(), { fabricatorPreset: "jlcpcb_standard" })) +await physical.renderUntilSettled() +export const physicalRecords = physical.getCircuitJson() + +// → { source: ".Processor > .U1 > .M3", load: ".DDR3 > .U3 > .E3", trace, … } +const data = resolveDdrChannel(physicalRecords, "DDR_D0") +``` + +`resolveDdrChannel` throws on anything ambiguous: a missing pin, more than one +trace, an endpoint that isn't on its pad, or two ends on different nets. + +### Declare the experiment + +This is the DQ0 write case, trimmed from `ibis.circuit.tsx`. + +**IBIS buffer models.** Bind one model to each chip. The props set the IBIS +file, the component name, which model each pin uses, and which pins are the +pull-up, pull-down and clamp rails: + +```tsx + {/* TI Model_343, slow 9 mA */} + {/* Winbond ODT_60 */} +``` + +**Supply and probes.** Reference each probe to the local ground ball at its own +end of the line: + +```tsx + .U1 > .E5", pin2: ".Processor > .U1 > .A1" }} +/> + + +``` + +**Transient with a PCB channel.** `pcbSpiceChannel` sets which copper is +extracted and how. `ibisPinStates` sets the direction of each bound pin for +this experiment: + +```tsx + .U1 > .A1", + extraction: { + minimumFrequency: "1Hz", + maximumFrequency: "1GHz", + modelFrequency: "100MHz", + filaments: 9, + refinementFilaments: 15, // coarse vs refined solve + convergenceTolerance: 0.05, // R/L may change at most 5% + capacitanceTolerance: 0.02, + reciprocityTolerance: 0.025, + dcResistanceTolerance: 0.002, + maxPanelLength: "0.5mm", + }, + }} +/> +``` + +**Traffic.** A repeating PRBS7 pattern at 640 MT/s, so one bit (UI) is +1.5625 ns: + +```tsx + +``` + +**Eye diagram.** Fold the receiver probe into an eye. The first 40 ns are +skipped so start-up settling stays out of the eye: + +```tsx + +``` + +`simulationRef` and `probeRef` are selectors, so one board can declare several +experiments. `run-ddr.circuit.tsx` declares quiet and switching-strobe cases +side by side. It adds DQS and DQSn stimuli, shifted half a UI for writes, and a +differential `DQS_RX` probe. + +### Choose a channel reduction + +| `reduction` | What it meshes | Status on the AM3352 board | +| --- | --- | --- | +| `"straightened-stages"` | Each route becomes straight stages over a finite return bar. The bar comes from `pcb_stackup` and the adjacent GND pour: DQ0 on `top` returns on `inner1`, 0.1245 mm away, εr 4.1, 2 mm bar. | Passes every extraction check. The DQ0 eye is about 0.96 V by 1.45 ns in a 1.5625 ns UI. | +| `"routed-copper"` | The actual curved copper, the GND planes (`groundMeshSize`) and the vias (`via`). | No accepted result yet. FastHenry stops with numerical errors ([pcb-spice-pipeline#46](https://github.com/tscircuit/pcb-spice-pipeline/issues/46)). | + +Start with `"straightened-stages"`. Change one thing at a time to test +sensitivity. On this board, a 1 mm and a 2 mm bar give the same eye, and +straightening the route adds about 4% loop inductance compared with the curved +route. + +### Read the report + +`results.html` has tabs for plots and data: + +- **Plots → Eye** shows the folded eye. +- The tables list the probes (minimum and maximum), the eyes (gate, UI, window + count, voltage range), the stimuli and pin states, the selected IBIS buffers + and corner, and the channel settings. +- If both quiet and switching cases ran, a switching-minus-quiet ΔV table + compares them. +- Waveform CSVs (seconds and volts) and every FastHenry, FasterCap and ngspice + deck and log are saved beside the report. + +If an extraction or convergence check fails, the CLI stops with an error and +writes no result. A waveform from an earlier run doesn't count as a result for +the current one. + +### Check the setup without the solvers + +Rendering only declares the experiments, so you can test the setup in a +`bun test` without running the field solvers: + +```tsx +const circuit = new Circuit() +circuit.add() +await circuit.renderUntilSettled() +const json = circuit.getCircuitJson() +expect(json.filter((r) => r.type === "simulation_experiment")).toHaveLength(2) +expect(json.filter((r) => r.type === "simulation_eye_diagram")).toHaveLength(4) +``` + +## What this result does not cover + +The DQ0 eye is one bit lane with an ideal strobe. It isn't a pass/fail verdict +for byte 0 or for the DDR interface. The model leaves out: + +- the DQS strobes, which change layer onto `inner2` stripline; +- coupling from neighbouring nets; +- plane cutouts; +- supply impedance; +- package parasitics (`includePackageParasitics: false`); +- through-via stubs. + +The stackup values are nominal assumptions. + +## Checklist for your own board + +1. Get a small ngspice circuit working first (Step 1). +2. Give the board a real stackup, for example with `fabricatorPreset`, and a + GND pour on the layer next to the signal. +3. Compile once and find the routed endpoints from Circuit JSON. +4. Add ``, the supply, the probes, a `pcb_spice` transient, a + stimulus and an eye as board children. +5. Give every bound IBIS pin an explicit `ibisPinStates` entry. +6. Run `tsci simulate`, and check that the extraction passed before you read + the eye. + +## Related + +- [``](../../elements/analog-transientsimulation.mdx) +- [``](../../elements/analog-digitalstimulus.mdx) +- [``](../../elements/analog-eyediagram.mdx) +- [``](../../elements/voltageprobe.mdx) +- [`tsci simulate`](../../command-line/tsci-simulate.md)