Why Simulate Before You Fabricate?
A Brief History of Circuit Simulation
A PCB re-spin costs $200–$2,000+ in fabrication, assembly, and engineering time. A simulation costs you an afternoon. SPICE simulation catches circuit bugs — wrong resistor values, unstable feedback loops, insufficient decoupling — before you commit to physical copper. Signal and power integrity analysis ensures your high-speed signals arrive clean and your power rails stay stable under load.
Simulation Tool Landscape
| Tool | Type | Cost | Best For |
|---|---|---|---|
| LTspice | SPICE (Analog Devices) | Free | Power supply, analog circuits |
| ngspice | SPICE (open source) | Free | KiCad integration, scripting |
| QSPICE | SPICE (Qorvo) | Free | Modern UI, C/C++ models |
| Altium SPICE | Integrated SPICE | Included | In-schematic simulation |
| HyperLynx | Signal/Power integrity | $$$$ | High-speed PCB verification |
| Ansys RedHawk | Power integrity | $$$$ | IC/package-level PDN analysis |
| Ansys HFSS | EM simulation | $$$$ | Antenna, connector, high-freq design |
| Cadence Spectre | SPICE (Cadence) | $$$$ | Custom IC, analog/mixed-signal |
| Synopsys PrimeTime | Static timing analysis | $$$$ | IC timing signoff |
| Siemens Calibre | DRC/LVS/signoff | $$$$ | IC physical verification (industry standard) |
| Python + SciPy | Numerical analysis | Free | Custom models, automation |
SPICE Circuit Simulation
SPICE Fundamentals
SPICE (Simulation Program with Integrated Circuit Emphasis) solves Kirchhoff’s equations numerically for arbitrary circuits. You define a netlist of components and connections, specify an analysis type (DC, AC, transient), and SPICE computes voltages and currents at every node over time or frequency.
flowchart LR
A[Schematic / Netlist] --> B[Component Models]
B --> C[Analysis Type]
C --> D[SPICE Solver]
D --> E[Waveform Viewer]
E --> F[Design Validation]
# Example ngspice netlist: Voltage divider
# Save as voltage_divider.spice
cat <<'EOF' > voltage_divider.spice
* Voltage Divider Simulation
V1 in 0 DC 3.3
R1 in out 10k
R2 out 0 10k
.op
.end
EOF
# Run ngspice (if installed)
# ngspice voltage_divider.spice
# Expected output: V(out) = 1.65V (half of 3.3V)
echo "Expected: V(out) = 3.3 × 10k/(10k+10k) = 1.65V"
Expected: V(out) = 3.3 × 10k/(10k+10k) = 1.65V
ngspice output (if run):
Node voltages:
V(in) = 3.300000e+00
V(out) = 1.650000e+00
Branch currents:
I(V1) = -1.650000e-04 (165µA through divider)
Transient Analysis
Transient analysis simulates circuit behaviour over time — essential for power supply startup, digital signal timing, and filter response. It computes node voltages at discrete time steps.
import numpy as np
import matplotlib
matplotlib.use('Agg') # Non-interactive backend
import matplotlib.pyplot as plt
# Simulate RC circuit transient response
# V_out(t) = V_in × (1 - e^(-t/RC))
R = 10e3 # 10kΩ
C = 100e-9 # 100nF
tau = R * C # Time constant
t = np.linspace(0, 5 * tau, 1000)
v_in = 3.3
v_out = v_in * (1 - np.exp(-t / tau))
print(f"R = {R/1e3:.0f}kΩ, C = {C*1e9:.0f}nF")
print(f"Time constant τ = RC = {tau*1e6:.0f}µs")
print(f"Rise to 63.2%: {tau*1e6:.0f}µs → V = {v_in * 0.632:.2f}V")
print(f"Rise to 95%: {3*tau*1e6:.0f}µs → V = {v_in * 0.95:.2f}V")
print(f"Rise to 99%: {5*tau*1e6:.0f}µs → V = {v_in * 0.99:.2f}V")
# Key points for hardware design
print(f"\nHardware implication:")
print(f"A 10kΩ pull-up with 100nF decoupling takes {5*tau*1e6:.0f}µs to settle")
print(f"For a 1MHz I2C bus, this is too slow (1µs bit time)")
R = 10kΩ, C = 100nF Time constant τ = RC = 1000µs Rise to 63.2%: 1000µs → V = 2.09V Rise to 95%: 3000µs → V = 3.14V Rise to 99%: 5000µs → V = 3.27V Hardware implication: A 10kΩ pull-up with 100nF decoupling takes 5000µs to settle For a 1MHz I2C bus, this is too slow (1µs bit time)
AC Sweep & Bode Plots
AC sweep analysis measures how a circuit responds across a range of frequencies — producing a Bode plot (gain and phase vs frequency). This is critical for verifying filter designs and feedback loop stability.
import numpy as np
# Low-pass RC filter frequency response
# H(f) = 1 / (1 + j × 2π × f × R × C)
R = 10e3 # 10kΩ
C = 100e-9 # 100nF
f_cutoff = 1 / (2 * np.pi * R * C)
frequencies = np.logspace(1, 7, 100) # 10 Hz to 10 MHz
omega = 2 * np.pi * frequencies
H = 1 / (1 + 1j * omega * R * C)
gain_db = 20 * np.log10(np.abs(H))
phase_deg = np.degrees(np.angle(H))
print(f"Low-pass RC filter: R={R/1e3:.0f}kΩ, C={C*1e9:.0f}nF")
print(f"Cutoff frequency (-3dB): {f_cutoff:.0f} Hz ({f_cutoff/1e3:.2f} kHz)")
print(f"\nFrequency Response:")
print(f"{'Frequency':>12} | {'Gain (dB)':>10} | {'Phase (°)':>10}")
print("-" * 40)
check_freqs = [100, 1000, f_cutoff, 10000, 100000, 1000000]
for f in check_freqs:
h = 1 / (1 + 1j * 2 * np.pi * f * R * C)
g = 20 * np.log10(abs(h))
p = np.degrees(np.angle(h))
label = " ← fc" if abs(f - f_cutoff) < 1 else ""
print(f"{f:>10.0f} Hz | {g:>8.1f} dB | {p:>8.1f}°{label}")
Low-pass RC filter: R=10kΩ, C=100nF
Cutoff frequency (-3dB): 159 Hz (0.16 kHz)
Frequency Response:
Frequency | Gain (dB) | Phase (°)
----------------------------------------
100 Hz | -0.8 dB | -32.1°
1000 Hz | -16.2 dB | -80.9°
159 Hz | -3.0 dB | -45.0° ← fc
10000 Hz | -36.1 dB | -89.1°
100000 Hz | -56.0 dB | -89.9°
1000000 Hz | -76.0 dB | -90.0°
DC Operating Point Analysis
DC operating point analysis computes the steady-state DC voltages and currents in your circuit. Use it to verify bias points for amplifiers, voltage divider outputs, and power rail levels.
import numpy as np
# Verify a voltage reference circuit
# Using resistive divider with buffer
v_supply = 5.0
r1 = 10e3 # Top resistor
r2 = 6.8e3 # Bottom resistor
# Thevenin equivalent
v_th = v_supply * r2 / (r1 + r2)
r_th = (r1 * r2) / (r1 + r2)
print("DC Operating Point: Voltage Reference Circuit")
print("=" * 45)
print(f"V_supply: {v_supply}V")
print(f"R1: {r1/1e3:.1f}kΩ, R2: {r2/1e3:.1f}kΩ")
print(f"V_out (divider): {v_th:.3f}V")
print(f"R_thevenin: {r_th/1e3:.2f}kΩ")
# Current through divider
i_divider = v_supply / (r1 + r2)
p_total = v_supply * i_divider
print(f"\nDivider current: {i_divider*1e3:.2f}mA")
print(f"Power dissipated: {p_total*1e3:.2f}mW")
print(f"R1 dissipation: {i_divider**2 * r1 * 1e3:.2f}mW")
print(f"R2 dissipation: {i_divider**2 * r2 * 1e3:.2f}mW")
DC Operating Point: Voltage Reference Circuit ============================================= V_supply: 5.0V R1: 10.0kΩ, R2: 6.8kΩ V_out (divider): 2.024V R_thevenin: 4.05kΩ Divider current: 0.30mA Power dissipated: 1.49mW R1 dissipation: 0.89mW R2 dissipation: 0.60mW
DC Operating Point: Voltage Reference Circuit ============================================= V_supply: 5.0V R1: 10.0kΩ, R2: 6.8kΩ V_out (divider): 2.024V R_thevenin: 4.05kΩ Divider current: 0.30mA Power dissipated: 1.49mW R1 dissipation: 0.89mW R2 dissipation: 0.60mW
Signal Integrity
Reflections & Termination
When a signal reaches the end of a transmission line, energy reflects back toward the source if the line impedance doesn’t match the load. These reflections cause ringing, overshoot, and false triggering — the bane of high-speed digital design.
flowchart LR
DRV["Driver Z_out=25Ω"] -->|"Trace Z0=50Ω"| LOAD["Load Z_L=∞ (open)"]
LOAD -->|"Reflected wave"| DRV
import numpy as np
# Reflection coefficient calculator
# Γ = (Z_L - Z_0) / (Z_L + Z_0)
z0 = 50 # Characteristic impedance of trace
loads = [
("Open circuit (no termination)", 1e9),
("Short circuit", 0.001),
("50Ω matched termination", 50),
("100Ω (2× impedance)", 100),
("33Ω series termination", 33),
("Typical CMOS input (~10MΩ)", 10e6),
]
print("Reflection Coefficient Analysis")
print(f"Trace impedance Z0 = {z0}Ω")
print("=" * 55)
print(f"{'Load':>35} | {'Γ':>8} | {'Reflected %':>12}")
print("-" * 55)
for name, z_l in loads:
gamma = (z_l - z0) / (z_l + z0)
pct = abs(gamma) * 100
print(f"{name:>35} | {gamma:>+7.3f} | {pct:>10.1f}%")
print("\nKey insight: CMOS inputs look like open circuits!")
print("→ Always terminate high-speed lines (>50MHz)")
Reflection Coefficient Analysis
Trace impedance Z0 = 50Ω
=======================================================
Load | Γ | Reflected %
-------------------------------------------------------
Open circuit (no termination) | +1.000 | 100.0%
Short circuit | -1.000 | 100.0%
50Ω matched termination | +0.000 | 0.0%
100Ω (2× impedance) | +0.333 | 33.3%
33Ω series termination | -0.205 | 20.5%
Typical CMOS input (~10MΩ) | +1.000 | 100.0%
Key insight: CMOS inputs look like open circuits!
→ Always terminate high-speed lines (>50MHz)
Rise Time & Bandwidth
A signal becomes a transmission line problem when the trace length is comparable to the signal’s wavelength. The rule of thumb: if trace length > λ/10, you need controlled impedance.
import numpy as np
# When does a trace become a transmission line?
# Critical length = rise_time × propagation_speed / 10
# FR4 propagation speed ≈ 0.5 × speed of light
c = 3e8 # Speed of light (m/s)
v_prop = 0.5 * c # Propagation in FR4 (m/s)
print("Critical Trace Length Calculator")
print("=" * 50)
print(f"{'Rise Time':>12} | {'BW (0.35/tr)':>12} | {'Critical Length':>15}")
print("-" * 50)
rise_times = [10e-9, 5e-9, 2e-9, 1e-9, 0.5e-9, 0.2e-9]
for tr in rise_times:
bw = 0.35 / tr
critical_length = tr * v_prop / 10
tr_str = f"{tr*1e9:.1f}ns" if tr >= 1e-9 else f"{tr*1e12:.0f}ps"
bw_str = f"{bw/1e6:.0f}MHz" if bw < 1e9 else f"{bw/1e9:.1f}GHz"
cl_str = f"{critical_length*100:.1f}cm" if critical_length > 0.01 else f"{critical_length*1000:.1f}mm"
print(f"{tr_str:>12} | {bw_str:>12} | {cl_str:>15}")
print("\nExample: STM32 GPIO at 50MHz (tr≈5ns)")
print("Critical length = 7.5cm → anything longer needs Z-control")
Critical Trace Length Calculator
==================================================
Rise Time | BW (0.35/tr) | Critical Length
--------------------------------------------------
10.0ns | 35MHz | 15.0cm
5.0ns | 70MHz | 7.5cm
2.0ns | 175MHz | 3.0cm
1.0ns | 350MHz | 1.5cm
500ps | 700MHz | 0.8cm
200ps | 1.8GHz | 3.0mm
Example: STM32 GPIO at 50MHz (tr≈5ns)
Critical length = 7.5cm → anything longer needs Z-control
Eye Diagrams
An eye diagram overlays many bit transitions on top of each other, revealing signal quality at a glance. A wide-open “eye” means clean signalling; a closed eye means excessive jitter, noise, or inter-symbol interference.
Power Integrity
Power Delivery Network (PDN) Impedance
The Power Delivery Network is the system of voltage regulators, copper planes, vias, and decoupling capacitors that deliver clean power to every IC. The goal is to keep PDN impedance below a target value across the entire frequency range.
import numpy as np
# PDN target impedance calculator
# Z_target = ΔV_allowed / I_transient
# ΔV = ripple tolerance (typically 5% of VDD)
# I_transient = peak current step
v_dd = 3.3 # Supply voltage
ripple_pct = 0.05 # 5% ripple tolerance
i_transient = 0.5 # 500mA current step (MCU wakeup)
delta_v = v_dd * ripple_pct
z_target = delta_v / i_transient
print("PDN Target Impedance Calculator")
print("=" * 40)
print(f"VDD: {v_dd}V")
print(f"Ripple tolerance: {ripple_pct*100:.0f}% = {delta_v*1e3:.0f}mV")
print(f"Transient current: {i_transient*1e3:.0f}mA")
print(f"Target impedance: {z_target:.3f}Ω = {z_target*1e3:.0f}mΩ")
print(f"\nThis means the PDN impedance must be < {z_target*1e3:.0f}mΩ")
print(f"from DC to the MCU's clock frequency ({48}MHz)")
# What provides low impedance at each frequency?
print(f"\n--- Who handles each frequency range? ---")
bands = [
("DC - 1kHz", "Voltage Regulator (feedback loop)"),
("1kHz - 1MHz", "Bulk capacitors (10µF - 100µF)"),
("1MHz - 100MHz", "Decoupling caps (100nF MLCC)"),
("100MHz - 1GHz", "Small caps (100pF - 1nF) + PCB planes"),
]
for band, source in bands:
print(f" {band}: {source}")
PDN Target Impedance Calculator ======================================== VDD: 3.3V Ripple tolerance: 5% = 165mV Transient current: 500mA Target impedance: 0.330Ω = 330mΩ This means the PDN impedance must be < 330mΩ from DC to the MCU's clock frequency (48MHz) --- Who handles each frequency range? --- DC - 1kHz: Voltage Regulator (feedback loop) 1kHz - 1MHz: Bulk capacitors (10µF - 100µF) 1MHz - 100MHz: Decoupling caps (100nF MLCC) 100MHz - 1GHz: Small caps (100pF - 1nF) + PCB planes
Decoupling Capacitor Strategy Simulation
import numpy as np
# Impedance of parallel capacitors
# Z_cap = 1/(2πfC) with ESR and ESL
# Z = ESR + j(2πf×ESL - 1/(2πf×C))
caps = [
{"label": "100µF bulk", "C": 100e-6, "ESR": 0.05, "ESL": 5e-9},
{"label": "10µF MLCC", "C": 10e-6, "ESR": 0.01, "ESL": 1e-9},
{"label": "100nF MLCC", "C": 100e-9, "ESR": 0.01, "ESL": 0.5e-9},
{"label": "100pF MLCC", "C": 100e-12,"ESR": 0.05, "ESL": 0.3e-9},
]
freqs = [1e3, 10e3, 100e3, 1e6, 10e6, 100e6]
print("Capacitor Impedance vs Frequency (Ω)")
print("=" * 70)
header = f"{'Freq':>8} |"
for cap in caps:
header += f" {cap['label']:>12} |"
print(header)
print("-" * 70)
for f in freqs:
row = f"{f/1e3:>5.0f}kHz |" if f < 1e6 else f"{f/1e6:>5.0f}MHz |"
for cap in caps:
omega = 2 * np.pi * f
z_c = 1 / (omega * cap['C'])
z_l = omega * cap['ESL']
z_total = np.sqrt(cap['ESR']**2 + (z_l - z_c)**2)
row += f" {z_total:>10.4f}Ω |"
print(row)
print("\nStrategy: Use ALL four values in parallel")
print("Each cap dominates a different frequency band")
Capacitor Impedance vs Frequency (Ω)
======================================================================
Freq | 100µF bulk | 10µF MLCC | 100nF MLCC | 100pF MLCC |
----------------------------------------------------------------------
1kHz | 1.5915Ω | 15.9155Ω | 1591.5494Ω |**************|
10kHz | 0.1592Ω | 1.5916Ω | 159.1549Ω |**************|
100kHz | 0.0509Ω | 0.1592Ω | 15.9155Ω |**************|
1MHz | 0.0500Ω | 0.0159Ω | 1.5916Ω | 1591.5494Ω |
10MHz | 0.0506Ω | 0.0100Ω | 0.1276Ω | 159.1534Ω |
100MHz | 0.3092Ω | 0.0528Ω | 0.0298Ω | 15.8657Ω |
Strategy: Use ALL four values in parallel
Each cap dominates a different frequency band
Ariane 5 Flight 501 — When Simulation Didn’t Match Reality (1996)
On June 4, 1996, the European Space Agency’s Ariane 5 rocket self-destructed 37 seconds after launch. The cause: a 64-bit floating-point value (horizontal velocity) was converted to a 16-bit signed integer, causing an overflow. The software was inherited from Ariane 4 and had been validated through simulation — but only for Ariane 4’s flight profile.
The simulation failure: The inertial reference system (SRI) software worked perfectly in simulation because the test cases used Ariane 4 trajectory data, where horizontal velocity never exceeded the 16-bit integer range (±32,767). Ariane 5’s more powerful boosters produced higher horizontal acceleration early in flight, pushing the value past 32,767 just 37 seconds in. The simulation validated the wrong operating envelope.
Engineering lesson: Simulation is only as good as your test vectors. Always simulate worst-case conditions, not just nominal operation. In embedded hardware, this means simulating maximum load transients, minimum input voltage, maximum temperature, and component tolerance extremes — not just the typical datasheet values. A $370 million payload was destroyed because nobody asked “what if the number is bigger than expected?”
Xbox 360 Red Ring of Death — Thermal Simulation Underestimated Solder Fatigue (2005–2008)
Microsoft shipped the Xbox 360 in November 2005 with a known but underestimated thermal issue. The GPU (ATI Xenos) generated more heat than the cooling solution could dissipate during sustained gaming. Over thousands of thermal cycles, the lead-free BGA solder joints cracked, disconnecting the GPU from the motherboard — indicated by three red LEDs, the infamous “Red Ring of Death.”
The simulation gap: Microsoft’s thermal simulation predicted the GPU junction temperature under steady-state conditions but underestimated the thermal cycling stress on BGA solder joints. The simulation modelled heat flow through the heatsink and PCB copper but didn’t account for the coefficient of thermal expansion (CTE) mismatch between the silicon die (2.6 ppm/°C) and the FR4 PCB (14-17 ppm/°C) over 10,000+ power cycles. Each heat-cool cycle flexed the package by microns, propagating cracks through the solder balls.
Engineering lesson: Thermal simulation must include cyclic fatigue analysis, not just steady-state temperature. Use Coffin-Manson solder fatigue models in your simulation workflow. Microsoft’s $1.15 billion warranty extension could have been avoided with proper thermo-mechanical simulation during the design phase. Always simulate CTE mismatch and solder joint reliability for BGA packages.
Exercises
Exercise 1: RC Filter Design & Verification
You need a low-pass filter on an ADC input to remove high-frequency noise from a temperature sensor signal. The sensor outputs 0–3.3V analog with useful information up to 100 Hz. The ADC samples at 1 kHz.
- Calculate R and C values for a single-pole RC low-pass filter with a -3dB cutoff at 100 Hz. Choose standard component values (E96 series for resistors, E12 for capacitors).
- Using the AC sweep code from this article, compute the attenuation at the ADC Nyquist frequency (500 Hz). Is this sufficient to prevent aliasing?
- What is the time constant of your filter? How long does the output take to settle to 99% of a step input?
- If the ADC has 100pF input capacitance, how does this affect your filter’s cutoff frequency? Recalculate.
Hint: fc = 1/(2πRC) = 100Hz → RC = 1.59ms. Try R = 15kΩ, C = 100nF → fc = 106 Hz. At 500Hz: attenuation = -20log10(√(1+(500/106)²)) ≈ -13.5 dB. Time constant = 1.5ms, 99% settling = 7.5ms. With 100pF ADC input: C_total = 100.1nF (negligible effect here, but matters at lower R values).
Exercise 2: Signal Integrity Analysis
You are routing an SPI bus at 50 MHz (clock frequency, tr ≈ 2ns) between an STM32 MCU and an external flash memory on a 4-layer PCB. The trace length is 8cm on Layer 1 with Z0 = 50Ω.
- Is 8cm longer than the critical trace length for a 2ns rise time? Show your calculation.
- The flash memory’s MISO pin has an input capacitance of 8pF. Model this as a load impedance at 50 MHz and calculate the reflection coefficient. Will you see ringing?
- Design a series termination resistor value for the MCU’s MOSI output. The STM32 GPIO output impedance is approximately 25Ω in high-speed mode. What resistor value achieves a matched source termination?
- Calculate the one-way propagation delay for the 8cm trace in FR4. Is there a timing concern for SPI at 50 MHz (20ns clock period)?
Hint: Critical length = 2ns × 1.5e8 / 10 = 3cm. 8cm > 3cm → YES, transmission line effects matter. Flash capacitive load at 50MHz: Z_L = 1/(2π×50e6×8e-12) = 398Ω. Γ = (398-50)/(398+50) = +0.78 → severe ringing. Series termination: R_term = Z0 - Z_out = 50 - 25 = 25Ω (use 22Ω standard). Propagation delay = 8cm / 1.5e8 = 0.53ns (fine for 20ns period).
Exercise 3: PDN Analysis & Decoupling
Your STM32F4 MCU draws 100mA steady state but has current transients of 300mA when toggling 16 GPIO pins simultaneously (driving LEDs). The 3.3V rail has 5% ripple tolerance. You currently have one 10µF bulk capacitor near the LDO and one 100nF MLCC on each of the MCU’s 4 VDD pins.
- Calculate the PDN target impedance for the 300mA transient with 5% ripple on 3.3V.
- Using the decoupling simulation code, determine if four 100nF MLCCs in parallel meet this target at 100 MHz (the MCU’s clock frequency). Assume ESR=10mΩ, ESL=0.5nH per cap.
- The GPIO toggle frequency is 1 MHz. At this frequency, what is the combined impedance of the 10µF bulk + four 100nF MLCCs? Is it below the target?
- Propose a complete decoupling strategy (specific capacitor values, quantities, and placement) to keep PDN impedance below the target from 10 kHz to 200 MHz.
Hint: Target Z = 165mV / 300mA = 0.55Ω = 550mΩ. Four 100nF in parallel: Z at 100MHz ≈ 0.03Ω/4 = 7.5mΩ (well below target). At 1MHz: Z_100nF×4 ≈ 0.4Ω, Z_10µF ≈ 0.016Ω → parallel ≈ 16mΩ (fine). Strategy: keep existing caps + add 2× 100pF (0402) closest to VDD pins for >100MHz coverage.
Simulation Report Tool
Document your simulation results with this tool for design reviews and compliance records.
Simulation Report Generator
Enter your simulation parameters and results to generate a professional report. Download as Word, Excel, or PDF.
Conclusion & Next Steps
Simulation is your safety net against expensive board re-spins. You now know how to run SPICE transient and AC analysis for power supplies and filters, evaluate signal integrity with reflection coefficients and critical length calculations, and verify power delivery network impedance with multi-cap strategies.
Next in the Series
In Part 7: DFX (Design for Excellence), we’ll make your design production-ready — fabrication constraints, panelization, assembly orientation rules, fiducial placement, stencil aperture design, and test point strategies for manufacturing at scale.