Layer 9 — SMPS showcase (buck via YAML)¶
V0 through V8 built every v2 layer with dedicated unit and
integration tests. V9 is the end-to-end integration
showcase: load a real circuit from YAML, drive a 100 kHz
PWM, and verify steady-state output against the analytical
formula V_out = V_in · D.
This is the "v2 is production-ready" milestone — every layer participates, every API is exercised through user- facing entrypoints, against a power-electronics-textbook target.
What this showcase exercises¶
| Layer | Used for |
|---|---|
| Layer 0 (numeric / sparse) | Eigen + KLU under the hood |
| Layer 1 (topology) | Graph, BranchKind, SwitchStateMask |
| Layer 2 (devices) | VoltageSource, L, C, R, Diode, MOSFET-w-body-diode |
| Layer 2 V1 (MOSFET helpers) | add_mosfet_with_body_diode in YAML |
| Layer 4 (PWL cache) | PwlStateSpaceCache::build enumerates 8 segments (3 switches) |
| Layer 5 (run_transient) | dynamic-path trap-companion + event-iteration |
| Layer 5 V2 (diode auto-commutation) | Q1 body diode + D_FW auto-flip |
| Layer 6 (builder) | populated by YAML loader |
| Layer 8 (YAML) | yaml::load_file reads buck.yaml |
The buck circuit (examples/buck.yaml)¶
┌───── Q1 ────────────┬── L ──┬── vout
│ (MOSFET + │ │
24V │ body diode) │ ├── C_out (47 µF)
│ │ │
│ D_FW ┴── gnd ├── R_L (5 Ω)
│ (1k S on, │
│ 1n S off, └── gnd
│ V_th=0.7V)
└── gnd
YAML:
circuit:
devices:
- {type: voltage_source, name: Vin, from: vin, to: gnd, V: 24.0}
- {type: mosfet_with_body_diode, name: Q1, drain: vin, source: sw}
- {type: diode, name: D_FW, anode: gnd, cathode: sw,
g_on: 1.0e3, g_off: 1.0e-9, V_th: 0.7}
- {type: inductor, name: L1, from: sw, to: vout, L: 100.0e-6}
- {type: capacitor, name: Cout, from: vout, to: gnd, C: 47.0e-6}
- {type: resistor, name: R_L, from: vout, to: gnd, R: 5.0}
simulation:
t_start: 0.0
t_end: 5.0e-3
dt: 1.0e-7
Steady-state math¶
For a buck converter with duty cycle D and assuming no
losses:
V_out = V_in · D
For our defaults (V_in=24V, D=0.5): V_out = 12 V.
With realistic losses (MOSFET R_on = 1 mΩ + small inductor winding resistance), efficiency η ≈ 99.6 %:
V_out ≈ V_in · D · η = 24 · 0.5 · 0.996 ≈ 11.95 V
Measured (from the Python runner): V_out_mean = 11.998 V — within 50 mV of the analytical target.
Output ripple¶
The LC filter has time constant τ = √(L·C) ≈ 68 µs and cutoff f_c ≈ 2.3 kHz. At 100 kHz switching frequency, the filter's attenuation is:
A_filter = (f_c / f_sw)² ≈ (2.3 / 100)² ≈ 5e-4
So the switching ripple is bounded by:
ΔV ≈ V_in · (1−D) · D · A_filter · ... ≈ ~10 mV p-p
The test enforces ripple < 1 V p-p (a generous bound).
Python runner¶
$ python examples/scripts/run_buck.py
Loading examples/buck.yaml ...
num_branches = 7
dt = 1e-07 s
t_end = 0.005 s
cache built ✓
Running transient ...
50001 samples
===== Steady-state V_out =====
Mean: 11.998 V
Ripple: 0.012 V (p-p)
Target: 12.000 V (= V_in · D)
Error: 0.002 V
If matplotlib is installed, the script also saves a
run_buck_output.png plot of V_out vs time.
C++ test¶
Two test cases in
core/tests/v2/showcases/test_buck_open_loop.cpp:
D = 50 % → V_out ≈ 12 V(the main scenario).D = 25 % → V_out ≈ 6 V(verifies the relationship holds across operating points).
Both run in well under 1 s on a modern Mac.
Extending the pattern¶
The same recipe — YAML → builder → cache → PWM switch_fn → run_transient — applies to:
- Boost converter: swap to
examples/boost.yaml(V1 add-on). The PWM bit-0 still controls the MOSFET; topology changes via the YAML. - Flyback (
examples/flyback.yaml, already shipped): same pattern, just with a transformer in the topology. - Half-bridge / full-bridge inverters: multiple PWM signals via multi-bit masks. The switch_fn returns the appropriate combination per time step.
- Closed-loop control: replace the open-loop PWM with a stateful controller (lambda capturing accumulators). V0 deliberately stays open-loop; closed-loop is V1.
What V0 deliberately does NOT do¶
- Closed-loop feedback: V0 is fixed-duty open-loop.
Adding a P/PI/PID controller requires a stateful
switch_fn — the current
SwitchScheduleFnis astd::function<SwitchStateMask(Real)>(stateless by signature, though lambdas can capture state). V1 may add a dedicated controller-block abstraction. - Boost / flyback / half-bridge showcases: V0 ships buck only as proof-of-concept. The flyback YAML exists (from Layer 8) but isn't yet wired to a runner.
- Loss / efficiency analysis: V0 measures V_out only. Power-integration over branches (R_on losses, diode conduction losses, switching losses) is a candidate for Layer 10.
- Frequency-domain analysis: V0 is time-domain only.
Files¶
- NEW
core/tests/v2/showcases/test_main.cpp - NEW
core/tests/v2/showcases/test_buck_open_loop.cpp - MODIFIED
core/CMakeLists.txt(pulsim_v2_showcase_teststarget +PULSIM_EXAMPLES_DIRenv var) - NEW
examples/scripts/run_buck.py - MODIFIED
python/bindings_v2_kernel.cpp(+SwitchStateMask.set/get, +Graph.num_switches) - NEW
openspec/changes/pulsim-v2-smps-showcase/