A synchronous buck converter, simulated the most literal way possible: step through time, work out which switch is on, integrate the inductor current, integrate the output voltage, repeat. No averaged model, no small-signal approximation — just the two state equations
V_o = (1/C) ∫ (I_L − I_o) dt
I_L = (1/L) ∫ V_x dt
written as a loop. The output voltage settles near VDDH·D, and the reason is almost embarrassingly simple: in steady state the average voltage across an inductor must be zero, so the switch node's average — which is VDDH for a fraction D of the time and zero otherwise — has to equal V_o.
Two things the averaged model in a textbook will not show you, and this one will. The startup transient: the L and C form a resonant tank with almost nothing damping it, so switching on into a discharged output produces an inductor current far larger than anything in steady state. And the settling time: that tank rings for a long time, so an average taken too early is not an average of anything.
for i in range(1,N):
ts = t[i]
# Model switch
if(ts % T < dtc*T):
pmos = 1
else:
pmos = 0
dt = (t[i]-t[i-1])
# Current voltage across the inductor
vx[i] = pmos*(VDDH) - Rs*ix[i-1] - vo[i-1]
# Current in inductor, trapezoidal approximation of area
ix[i] = ix[i-1] + 1/L * (vx[i] + vx[i-1])/2*dt
io[i] = 1/R * vo[i-1]
vo[i] = vo[i-1] + 1/C * ((ix[i] + ix[i-1])/2 - (io[i] + io[i-1])/2)*dt
Source: jupyter/buck.ipynb.
The integration is the notebook's, verbatim. Building this page turned up a
real problem in that notebook, since fixed: it averaged over the second half
of a 10 µs run while R·C is 1 ms, so the tank was
still ringing and it printed a negative efficiency. Its
settled mode now starts at the operating point and runs long
enough to mean something. You can still reproduce the old behaviour here by
unticking the warm start and shortening t_end. For the same power stage under a completely different control
scheme, see the PFM buck.