15-25 MHz Fractional-N Synthesizer

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.

Discretization

The sampling rate is 1 MHz, giving a 1 µs time step. For convenience, 100 KHz reference edges are modeled as always falling at step centre; however, divided VCO edges may occur anywhere in continuous time (CT). The CT PFD output, which toggles no more than twice in any time step, is converted to discrete time (DT) by taking an average over each sample interval. The resultant DT signal contains the same energy per sample period as the CT signal being modeled:

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.

Phase detector

Logic elements such as gates and flip-flops are modeled using C++ objects. At the bottom of the hierarchy are NAND gates from which everything above is synthesized. Synthesis is performed by constructors, which invoke constructors of more primitive elements, and finally NANDs: based on the TTL 74LS74, the DFF contains six; and the XOR gate, which is used in the AD9901 PFD, contains five. A model of the entire circuit is built as an array of simple gates. The wires or "nets" which interconnect them are simply integer variables, typically local variables or members of the method/class which drives them.

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);
    }
};
 
It was only necessary to code the behavior of a NAND gate - all higher logic is described declaratively. For correct operation of flip-flops, which contain feedback loops, gate propagation delays must be taken into account; however, this is not factored into edge timing at the PFD output, although it could be quite easily, since the simulator tracks logic state changes one gate delay at a time as they ripple through the netlist. Metastability is guaranteed if the flip-flops are not correctly initialised!

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.

Loop filter

Building on the original filter design script, the analogue loop filter was converted to its digital equivalent in SCILAB using the Bilinear Transform. Originally, this was done by making the substitution s = (2/Ts)*(z-1)/(z+1) using horner:

 f =
                 2.783D+08 + 165289.26s
    --------------------------------------------------
    3333333.3s + 381.51515s2 + 0.0137018s3 + 0.0000002s4


z=poly(0,'z');
Ts=1e-6;
format(20,'e');
horner(f, (2/Ts)*(z-1)/(z+1))
 ans =

  - 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.

VCO

For an FM signal, peak phase deviation θp a.k.a modulation index, modulating frequency fmod and peak frequency deviation Δf are related by:

When θp«1, sideband level in dBc is:

The dominant noise source in this synthesizer is the VCO. Mini-Circuits, the manufacturer, specify its phase noise as -86 dBc/Hz at 1 KHz offset, rolling-off at -20dB/decade. Using the above NBFM approximation, this can be modeled as an equivalent RMS noise voltage at the VCO input:
 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

Average predicted phase noise peaks around -80 dBc/Hz at an offset of 500 Hz from the carrier, in close agreement with the spectrum analyzer measurement:

 

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.

Fractional-N noise

It's interesting to run the simulation without VCO noise. This reveals fractional-N noise at around -100 dBc/Hz, not far below the dominant VCO noise. Notice how little is gained having a 4th- over a 3rd-order MASH:

 
42949673 ≈ 2^32/100  
For the 4th-order MASH, at offsets below 5 KHz, there's a discrepancy between results obtained at different time steps. The coarse step fails to capture the intrinsic nonlinearity of PWM, which causes quantisation noise to fold down, creating in-band distortion which cannot be removed by the loop filter. I wrote a short paper [2] describing a digital means to cancel this effect.

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.

References

  1. Behavioral Simulation of Fractional-N Frequency Synthesizers and Other PLL Circuits
    Michael H. Perrott, MIT
  2. Reducing PWM distortion phase noise in fractional-N synthesizers
    Andrew Holme