The same power stage as the PWM buck — same inductor, same capacitor, same two switches — under a completely different control law. There is no clock and no duty cycle. There is a three-state machine:
Every pulse delivers the same packet of charge, so the loop regulates by changing how often it fires. Draw twice the current and it fires twice as often. That is why the bottom right panel — switching frequency against load current — is close to a straight line through the origin, and it is the entire reason PFM exists: at light load a PWM converter still switches at full rate and burns the gate charge to do it, while a PFM converter simply goes quiet.
The price is ripple and spectrum. The output sawtooths between VREF and wherever the pulse pushes it, and the switching frequency moves around with the load, so the noise it makes is not at one predictable place. That is a bad trade in a radio and a good one in a battery-powered sensor that is asleep most of the time.
state = 0 #0 = IDLE, 1 = UP, DWN = 2
for i in range(1,N):
if(state == 0): # IDLE
pmos = 0
vx[i] = -100*ix[i-1] # model a high impedance
if(vo[i-1] < VREF):
state = 1
t_start = ts
elif(state == 1): # UP (PMOS on)
vx[i] = VDDH - Rs*ix[i-1] - vo[i-1]
pmos = 1
if(ts - t_start > t_switch):
state = 2
elif(state == 2): # DOWN (NMOS on)
pmos = 0
vx[i] = 0 - Rs*ix[i-1] - vo[i-1]
if(ix[i-1] < 0):
state = 0
Source: jupyter/buck_pfm.ipynb. The state machine and the integration are the notebook's. Its own comment on the IDLE branch — "Want to model a high impedance. This works, but a bit nasty" — is worth reading: the 100 Ω term is a numerical stand-in for both switches being off, and it is why the inductor current decays instead of sitting at exactly zero between pulses.