EE461g — Implementing PWL Diode and Zener Models with Behavioral Sources
Real diode behavior follows the Shockley equation, which is nonlinear and can make hand analysis difficult. Piecewise linear (PWL) models approximate the I-V curve using straight-line segments, making circuit analysis tractable while retaining the essential behavior.
In SPICE, we can implement PWL models using behavioral sources (the B element), which let us define current as an arbitrary function of voltage. This gives us direct control over the model, unlike the built-in .model D(...) which always uses the exponential Shockley equation.
We build up diode models in three levels of complexity:
| Model | Forward Bias | Reverse Bias | Parameters |
|---|---|---|---|
| Model 1 — Ideal | Short circuit (VD = 0) | Open circuit (ID = 0) | None |
| Model 2 — Constant Vf | VD = Vf | Open circuit | Vf |
| Model 3 — Vf + rd | VD = Vf + ID·rd | Open circuit | Vf, rd |
The most useful PN model uses a forward voltage drop plus a series (dynamic) resistance. In lecture notes this resistance is often called rd; in SPICE models it is typically called Rs. They represent the same thing: the slope of the I-V curve in the conducting region. Implemented as a behavioral current source:
* PWL PN Diode subcircuit
.subckt PWL_DIODE anode cathode
* If V(anode,cathode) > Vf: I = (V - Vf)/Rs (on, linear slope)
* Otherwise: I = V/Roff (off, tiny leakage)
B1 anode cathode I = (V(anode,cathode) > 0.7) ?
+ (V(anode,cathode) - 0.7)/5.0 + V(anode,cathode)/1e6
+ : V(anode,cathode)/1e6
.ends PWL_DIODE
B element defines a behavioral source. The syntax
B1 n+ n- I = <expression> creates a current source whose value is computed
from node voltages at each simulation step. The ternary operator
condition ? value_if_true : value_if_false implements the piecewise breakpoints.
A Zener diode behaves like a regular diode in forward bias, but also conducts in reverse breakdown at a well-defined voltage VZ. The PWL model therefore has three regions:
| Region | Condition | Current | Behavior |
|---|---|---|---|
| Forward | VAK > Vf | I = (VAK − Vf) / Rs | Same as regular diode |
| Off | −VZ ≤ VAK ≤ Vf | I ≈ 0 (VAK / Roff) | Both junctions off |
| Reverse breakdown | VAK < −VZ | I = (VAK + VZ) / Rs | Zener conduction |
where VAK = V(anode) − V(cathode).
* PWL Zener Diode subcircuit
* Parameters: VF=0.7V, VZ=5.1V, RS=5ohm, Roff=1Mohm
.subckt PWL_ZENER anode cathode
B1 anode cathode I =
+ (V(anode,cathode) > 0.7) ?
+ (V(anode,cathode) - 0.7)/5.0 + V(anode,cathode)/1e6
+ : ( (V(anode,cathode) < -5.1) ?
+ (V(anode,cathode) + 5.1)/5.0 + V(anode,cathode)/1e6
+ : V(anode,cathode)/1e6 )
.ends PWL_ZENER
The behavioral source uses nested ternary operators. Written as pseudocode:
if V(anode,cathode) > 0.7: # Forward bias
I = (V - 0.7) / 5.0 + V / 1e6
elif V(anode,cathode) < -5.1: # Reverse breakdown
I = (V + 5.1) / 5.0 + V / 1e6
else: # Off region
I = V / 1e6
V/1e6 term adds a tiny leakage current in all regions.
This serves two purposes: (1) it models real-world leakage, and (2) it prevents
numerical convergence problems in SPICE by ensuring the device always has a finite
(though very high) impedance.
| Parameter | Symbol | Meaning | Typical Value |
|---|---|---|---|
| Forward voltage | Vf | Turn-on voltage in forward bias | 0.7 V (Si) |
| Zener voltage | VZ | Reverse breakdown voltage | 3.3, 5.1, 12 V, etc. |
| On-state resistance | Rs | Inverse slope of I-V in conducting region (smaller Rs = steeper curve) | 1–20 Ω |
| Off-state resistance | Roff | Leakage resistance when off | 100 kΩ–1 MΩ |
A classic application: using a Zener diode to regulate voltage. The Zener is reverse-biased, so it clamps the output at approximately VZ.
Save the code below to a file (e.g., regulator.cir) and run it with:
ngspice -b regulator.cir
* Zener Voltage Regulator - PWL Model
* Demonstrates piecewise linear Zener in a regulator circuit
* --- Zener subcircuit definition ---
.subckt PWL_ZENER anode cathode
B1 anode cathode I =
+ (V(anode,cathode) > 0.7) ?
+ (V(anode,cathode) - 0.7)/5.0 + V(anode,cathode)/1e6
+ : ( (V(anode,cathode) < -5.1) ?
+ (V(anode,cathode) + 5.1)/5.0 + V(anode,cathode)/1e6
+ : V(anode,cathode)/1e6 )
.ends PWL_ZENER
* --- Circuit ---
Vin in 0 DC 0
Rs in out 100
* Zener: pin order matches .subckt definition (anode cathode)
* first pin (0) -> anode = GND
* second pin (out) -> cathode = Vout
* This puts the Zener in reverse bias for regulation.
Xz1 0 out PWL_ZENER
* --- DC sweep ---
.dc Vin -15 15 0.01
* --- Output ---
.control
run
set gnuplot_terminal=png
gnuplot zener_regulator v(out) v(in)
+ title 'Zener Regulator: Vout vs Vin'
+ xlabel 'Input Voltage (V)'
+ ylabel 'Output Voltage (V)'
quit
.endc
.end
When Vin goes negative, Vout also goes negative. Now V(anode,cathode) = −Vout becomes positive. Once it exceeds Vf (i.e., Vout < −Vf), the Zener is forward-biased and conducts like a regular diode. This clamps Vout near −Vf ≈ −0.7V. The three operating regions of this circuit are:
| Input Range | Zener State | Vout |
|---|---|---|
| Vin > VZ | Reverse breakdown | ≈ VZ (clamped, slight slope from Rs) |
| −Vf ≤ Vin ≤ VZ | Off | ≈ Vin (passes through) |
| Vin < −Vf | Forward-biased | ≈ −Vf (clamped, slight slope from Rs) |
Adjust the parameters below and click Simulate to see the Zener regulator response computed in your browser. The simulation sweeps Vin from −Vmax to +Vmax and solves the circuit at each step using Newton-Raphson iteration.
Ideal (orange) assumes Rs = 0 and Roff = ∞: Vout = Vin when off, Vout = VZ when regulating, Vout = −Vf when forward-biased. PWL (blue) includes finite Rs so Vout has a small slope in the regulating region.
Standalone I-V curve of the Zener subcircuit (not the regulator circuit). Shows the three PWL regions with your chosen VZ, Vf, and Rs.
.subckt ... .ends before the main circuit so ngspice can resolve X instance references.B source convention: positive current flows from the first node to the second. V(anode,cathode) is the voltage across the device in the expected direction.V/Roff fixes this.cond1 ? val1 : (cond2 ? val2 : val3) — parentheses around the inner ternary improve readability but are optional in ngspice.+ at the start of the next line.-5.1 and +5.1 values in the expression to your desired VZ.Two Zener diodes connected cathode-to-cathode clip both positive and negative peaks:
* Back-to-back Zener Limiter - Complete Netlist
* --- Zener subcircuit (same as before) ---
.subckt PWL_ZENER anode cathode
B1 anode cathode I =
+ (V(anode,cathode) > 0.7) ?
+ (V(anode,cathode) - 0.7)/5.0 + V(anode,cathode)/1e6
+ : ( (V(anode,cathode) < -5.1) ?
+ (V(anode,cathode) + 5.1)/5.0 + V(anode,cathode)/1e6
+ : V(anode,cathode)/1e6 )
.ends PWL_ZENER
* --- Circuit ---
Vin in 0 SIN(0 10 1k)
Rs in out 100
* Cathode-to-cathode configuration:
* Positive swing: Xz1 forward + Xz2 breakdown -> clips at Vz + Vf
* Negative swing: Xz1 breakdown + Xz2 forward -> clips at -(Vz + Vf)
Xz1 out mid PWL_ZENER
Xz2 0 mid PWL_ZENER
.tran 1u 3m
.control
run
plot v(in) v(out)
.endc
.end
You can use separate Rs,fwd and Rs,rev values for more accurate modeling. Here the forward resistance is 2.6 Ω and the reverse (breakdown) resistance is 10 Ω:
.subckt PWL_ZENER_ASYM anode cathode
B1 anode cathode I =
+ (V(anode,cathode) > 0.7) ?
+ (V(anode,cathode) - 0.7)/2.6 + V(anode,cathode)/1e6
+ : ( (V(anode,cathode) < -5.1) ?
+ (V(anode,cathode) + 5.1)/10.0 + V(anode,cathode)/1e6
+ : V(anode,cathode)/1e6 )
.ends PWL_ZENER_ASYM