Design | Schematics | Embedded CPU | JAM STAPL | Loop Filter | Spectra | MASH | Simulation | Back to projects |
![]() |
![]() |
I present a fast and accurate PLL simulator written in C++. Its speed is due to a fixed time step of one tenth the reference period. For accuracy, I have used an interpolation technique based on the work of Michael H. Perrott [1]. The phase noise of my 15-25 MHz fractional-N synthesizer is accurately predicted. The simulator can easily be adapted to model other PLLs. Illustrating this flexibility, the graphs above compare lock transients obtained using different phase detectors. At only 400 lines, this short program should be easy to understand:
![]() |
Right-click and Save-Target-As to download PLLSim.cpp. You'll also need FFTW3. |
My approach differs from Michael Perrott's in the point at which discretization occurs. I do it at the PFD output, whereas Michael discretizes the PFD inputs. Michael's gate and flip-flop models handle edge timing encoded in the amplitude of their inputs.
struct TPFD { // Tri-state dual D-type PFD
DFF ref_dff, vco_dff;
NAND nand;
int up, dn, nc[2], resetb;
TPFD(int& ref_clk, int& vco_clk) :
ref_dff(up, nc[0], vdd, ref_clk, vdd, resetb),
vco_dff(dn, nc[1], vdd, vco_clk, vdd, resetb) {
nand.Init(resetb, up, dn);
}
double Vout() {
return 5*(up-dn);
}
};
|
![]() |
At the top of the object hierarchy, struct PLL performs the discretization described above. It integrates the area under the CT PFD output over the preceding time step to calculate the average voltage. The state of the PFD inputs (ref_clk and vco_clk) may toggle part way through the integration, at which point the NAND simulation is re-invoked, and the integration continues using a possibly different PFD output voltage. There may be no change e.g. after a falling clock edge.
- 1.2965014348116D-07 - 2.5908183440525D-07z + 6.5535767118830D-10z2 + 2.5995564463350D-07z3 + 1.2986859603822D-07z4 |
------------------------------------------------------------------------------------------------------------------- |
9.1394248665582D-01 - 3.7394418756410D+00z + 5.7370353567608D+00z2 - 3.9115359677753D+00z3 + z4 |
Replacing the coefficients with constants, the recurrence formula is:
Y(n) N0 + N1.z + N2.z2 + N3.z3 + N4.z4 |
---- = -------------------------------- |
X(n) D0 + D1.z + D2.z2 + D3.z3 + z4 |
y[n+4] = x[n]*N0 + x[n+1]*N1 + x[n+2]*N2 + x[n+3]*N3 + x[n+4]*N4 - (y[n]*D0 + y[n+1]*D1 + y[n+2]*D2 + y[n+3]*D3) |
Later, when I tried to reduce the simulation time step from 1µs to 100ns, I encountered difficulties. The fourth-order transfer function had to be split into four first-order filters to avoid numerical precision issues. Here's the latest script. The loop filter is now modeled as a cascade of four first-order systems.
kVco = 2e6; // VCO gain (Hz/V)
Ts = 1e-6; // Time step
fs = 1/Ts; // Sampling rate
theta_p = 2 * 10^(-86/20); // Modulation index
fm = 1000; // Modulating frequency
fd_pk = theta_p * fm; // Peak deviation
fd_rms = fd_pk / sqrt(2); // RMS deviation
Vrms_1Hz = fd_rms / kVco; // Volts RMS per sqrt(Hz)
Vrms = Vrms_1Hz * sqrt(fs/2) // Volts RMS in Nyquist BW
Vrms =
0.0000251
This noise is injected at its input and the VCO is modeled as an integrator:
// Loop filter
v = LPF(v);
// VCO
v += 25.1e-6 * Noise_1V_rms(); // 25.1 µV (rms)
omega = 2*PI*(15e6 + kVco*v); // Angular frequency
phi += omega*Ts; // Output phase
It is then only a matter of determining in which sample interval the VCO divider overflows, and interpolating the exact edge position in CT:
// VCO Divider
if (phi >= 2*PI*divisor) {
phi -= 2*PI*divisor;
divisor = N + mash.Clock(F);
v = Interpolate(!ref, 1.0 - phi/omega/Ts); // Edge position
} else
v = Interpolate(!ref);
![]() |
![]() |
Phase noise plots typically plateau close to the carrier. Phase noise peaking can indicate an under-damped loop response, or a poor choice of loop bandwidth. PLLs exhibit a high-pass response to VCO noise - in this case with 40 dB/decade slope inside loop bandwidth. Since VCO noise rolls-off at -20 dB/decade, that leaves a net 20 dB/decade slope. Increasing loop bandwidth would push the peak further out, flattening it off, at the price of lowering reference attenuation. The simulation predicts a 100 KHz reference spur at -90 dBc. This is only present using the AD9901 PFD, the output of which is a 5V pk-pk 100 KHz square wave; the spur is below VCO noise when the tri-state PFD is simulated. Unlike reality, the simulated TPFD is perfect.
![]() |
![]() |
|
![]() |
![]() |
For the 2nd-order MASH, setting F=42949673 (i.e. 2^32/100 for a fractional output frequency of 1 KHz) generates fractional-N spurs; however, the noise at the PFD output must be white when F=1, because this produces a beautiful curve of the loop gain transfer function.
Copyright © Andrew Holme, 2007. |
![]() ![]() |