Phase Locked Loop simulator in SystemC-AMS

Keywords: Phase Locked Loop, PLL, SystemC-AMS

1. Introduction

When I started to learn SystemC-AMS one of the first circuits I wanted to implement was a Phase Locked Loop. The reason for this is because I have had an experience simulating a 2.4GHz PLL while I was member of the Microelectronics Student's Group, and I realized how difficult is to simulate this circuit, specially when the output and the reference frequency are several orders of magnitude apart. The simulator has to use a small time-step to accommodate the higher frequency but at the same time, the PLL might take a long time to lock and reach the steady state.

Another reason to choose this circuit is because it contains both analog and digital circuits, so it is possible to use different simulation domains like discrete time, continuous time and linear systems as can be seen later.

The building blocks of the PLL, their connection and their simulation domain are shown in the following picture.

System level diagram

Figure 1.1: System level diagram.

The Phase Frequency Detector (PFD) compares the phase of the reference clock with the phase of the feedback signal. If the phase of the reference clock is ahead of the feedback signal, it will tell the charge pump to "go faster". If the feedback signal is ahead instead, will tell to "go slower". Using these up/down signals, the Charge Pump (CP) generates pulses of positive or negative current. This current is applied to the Loop Filter (LF), which integrates it and generates the control voltage that is applied to the Voltage Controlled Oscillator (VCO) and defines the frequency of the output. The Frequency Divider takes the output signal and divides its frequency by a predefined factor, generating the feedback signal that is compared with the reference clock.

2. Building blocks

2.1 Phase Frequency Detector

The most common PFD circuit is shown in figure 2.1.

Frequency Shift Detector circuit

Figure 2.1: Frequency Shift Detector circuit.

This block can be modeled as a discrete time element which is sensitive to positive edges of the clock inputs. Some delay cells were added to model the propagation delays. The implementation is strait forward:

 * Processing thread
void sc_pfdetector::sig_proc(void) {
    double diff = sc_in_fref - sc_in_fdiv;

    if(diff > 0) {
    } else if (diff < 0) {
    } else {

and the corresponding simulation waveforms are as follows:

Frequency Shift Detector waveforms

Figure 2.2: Frequency Shift Detector waveforms (interactive version).

2.2 Charge Pump and Loop Filter

The implementation of the CP models three imperfections of the real circuit: the current mismatch, the transistor triode region, and leakage current.[1] The current mismatch and leakage current will cause the reference spurs. The transistor triode region will result in the increasing of the settling time.

Ignoring the body effect, the equation for the current in the saturation region is:

$$I_\text{DSAT} = \frac{\mu_n C_\text{ox} W}{2L}\left(V_\text{GS} - V_\text{th}\right)^2 \tag{1}$$

Since IDSAT and Vth are specified:

$$\frac{\mu_n C_\text{ox} W}{L} = \frac{2 I_\text{DSAT}}{\left(V_\text{GS} - V_\text{th}\right)^2} \tag{2}$$

When the output voltage is less than VGS − Vth, the transistors operate in the triode region, following the equation:

$$ I_\text{D} = \frac{\mu_n C_\text{ox} W}{L} \left( \left(V_\text{GS} - V_{th}\right)V_\text{DS} - \frac{{V_\text{DS}}^2}{2} \right) \tag{3}$$

Mixing the last two equations and knowing that VGS = Vdd, the following equation is obtained:

$$ I_\text{D} = \frac{2I_\text{DSAT}}{V_\text{DD} - V_{th}} V_\text{DS} - \frac{I_\text{DSAT}}{\left(V_\text{DD} - V_{th}\right)^2} \left.V_\text{DS}\right.^2 \tag{4} $$

Specifying, for example, Vdd=3.3V, IDSAT=100μA, and Vth=0.7V the solution is:

$$ I_\text{D} \approx 77 V_\text{DS} - 14.8 \left.V_\text{DS}\right.^2 \left(\mu A\right) \tag{5}$$

Thus, the ID versus VDS looks like:

ID versus VDS

Figure 2.3: ID versus VDS (interactive version).

The charge pump was implemented in a Timed Data Flow model, and the processing thread looks like:

 * Processing thread
void sca_tdf_chargepump::processing(void) {

    double charge = 0,
            discharge = 0,
            mismatch = 0,
            i = 0;

    // Discharge (NMOS)
    if(sca_tdf_in_vcp>mosfet_vtn) {
        // Saturation
    } else {
        // Triode
        double vds = sca_tdf_in_vcp;

    // Charge (PMOS)
    if(sca_tdf_in_vcp<mosfet_vtp) {
        // Saturation
    } else {
        // Triode
        double vds = vdd-sca_tdf_in_vcp;;


     * Set the output current depending on the inputs state
     * (up/down)
    if(sc_in_up && !sc_in_dn) {
        i = charge;
    else if(!sc_in_up && sc_in_dn) {
        i = discharge;
    else if(!sc_in_up && !sc_in_dn) {
        i = -current_leak;
    else if(sc_in_up && sc_in_dn) {
        i = mismatch;

     * Limit the output current if the loop filter input voltage
     * is out of limits (V_CTRL < 0 || V_CTRL > vdd)
    if((sca_tdf_in_vcp>vdd && i>0) || (sca_tdf_in_vcp<0 && i<0)) {

    sca_tdf_out_ictrl = i;

This current flows to - or from - the loop filter generating a voltage Vcp. Note that this voltage has to be feed back to the charge pump, in order to calculate the operating point of the transistors (VDS). The filter has a great importance to the circuit performance and stability.

Charge Pump and Loop Filter

Figure 2.4: Charge Pump and Loop Filter equivalent schematic.

Figure 2.4 shows a 3rd order filter. Where the output voltage is obtained across the terminals of C3. In case that the filter is configured to 2nd order, R3 and C3 are removed, and Vctrl is connected to the same node as Vcp. There is some theory about choosing the right values for the filter components for optimized performance, which I will not cover here. But I recommend a good book with many practical examples called "PLL performance, simulation, and design" [2]. The is also a good website to calculate these values giving the desired specifications [3].

2.3 Voltage controlled oscillator

The VCO converts the control voltage into a frequency following the equations:

$$ f(t) = f_{min} + k_{vo} V_{ctrl} \tag{6}$$

Where, fmin is the minimum oscillation frequency when Vctrl is zero, and kvo is the VCO gain. If the control voltage was constant, the expression for the output voltage would be simply:

$$ v_{o}(t) = V_{cm} + V_{cm} \sin \big( (f_{min} + k_{vo} V_{ctrl}) \times 2 \pi \times t \big) \tag{7}$$

But since the control voltage is not a constant, the math is not so strait forward. Instead, the instant frequency need to be integrated in order to obtain the instantaneous angle for each time step. The equations for this is:

$$ v_{o}(t) = V_{cm} + V_{cm} \sin \Bigg( \int_{0}^{t} \omega(\tau) d\tau \Bigg) \tag{8}$$


$$ \omega(\tau) = 2\pi\big(f_{min}+k_{vo} V_{ctrl}(\tau)\big)$$

Note that the integral can be limited between 0 and 2π. In SystemVerilog AMS there is a function called idtmod that implement this integral. On this case, it was implemented with the following C++ code:

double idtmod(double frequency, double time) {
    static double time0 = 0;
    static double phi0 = 0;

    double T { 1 / frequency };
    double phi = phi0 + 2*M_PI*(time-time0)/T;

    phi0 = phi = fmod(phi, 2*M_PI);     // Limit the phase between 0 and 2*pi
    time0 = time;                       // Save the time for the next integration

    return phi;

The result is illustrated in figure 2.5


Figure 2.5: Instant phase calculated by idtmod.

The processing thread is then very simple to implement as follows.

 * Processing thread
void sca_tdf_vcoscillator::processing(void) {
    double frequency = kvo* + fmin;
    double time = sc_time_stamp().to_seconds();

    double phi=idtmod(frequency, time);



2.4 Frequency divider

Lastly, the Frequency Divider divides the output frequency by a factor of N.

$$f_{div} = \frac{f_{out}}{N} \tag{7} $$

The code of the processing function looks like:

 * Processing thread
void sca_tdf_divider::processing(void) { // our workhorse method

    if( >= vcm and state == false) {
        state = true;
        counter += 2;
        if(counter >= factor) {
            counter = 0;
            state_div = !state_div;
    else if( < vcm){
        state = false;

It also converts the output signal from timed data flow domain to discrete time to be used as input in the phase detector.

3. Downloading and compiling

The code is available to download on github. To download using git just run:

git clone

This simulator can be compiled for macOS, Linux and Windows. However the following requirements need to be satisfied:

  • C++ compiler compatible with the C++14 standard.
  • SystemC Library 2.3.2 or greater
  • SystemC-AMS Library 2.1 or greater

There are several tutorials available to compile SystemC libraries on the following operating systems:

3.1 Compiling with GCC on Windows, macOS and Linux

If you need to adjust the gcc version to be used (for instance on macOS) use the following commands:

export CXX=g++-7
export LD=g++-7

If needed adjust the following lines in the Makefile to adapt to your case:


and then write


If no errors occurs the binary is available in the bin folder.

3.2 Compiling with Visual Studio on Windows

Open the file vstd\pll.sln and then go to Project > Properties > C/C++ > General > Additional Include Directories and correct the paths for SystemC and SystemC-AMS.

Do the same for the library folder on Project > Properties > Linker > General > Additional Library Directories.

Then Build > Build solution to compile.

4. How to use

Once the program is successfully compiled, calling it with the --help option will return something like:

(SystemC AMS Copyright notice)

SystemC-AMS PLL simulator 1.0
  pll.exe [OPTION...]

  -c, --config arg  Configuration file (default: options.json)
  -f, --format arg  Output file format (vcd or csv) (default: vcd)
  -h, --help        Print help (default: false)

4.1 System parameters

Most of the design parameters mentioned in section 2 can be defined using a configuration file in json format. The program will look for a configuration file called option.json, but other names might be used when activating the --config option.

The available parameters are:

  • System Level:
    • vdd: Supply voltage
    • vcm: Common mode voltage (dc offset for all ac signals)
    • fref: Reference clock frequency
    • tstep: Simulation time-step
    • tsim: Simulation time
  • Phase Shift detector:
    • tr: Propagation delay (0->1)
    • td: Propagation delay (1->0)
  • Charge Pump:
    • current_up: Charging current when the PMOS is saturated.
    • current_dn: Discharging current when the NMOS is saturated.
    • current_leak: Leaking current when both transistors are off.
    • mosfet_vth: Threshold voltage of the transistors.
  • Loop Filter:
    • order: Filter orders
    • c1: C1 value
    • r2: R2 value
    • c2: C2 value
    • r3: R3 value (no effect for 2nd order filter)
    • c3: C3 value (no effect for 2nd order filter)
  • Voltage Controlled Oscillator
    • kvo: VCO gain (MHz/V),
    • fmin: Minimum frequency (when Vctrl=0)
  • Frequency Divider:
    • n: Division factor

An example of this file is shown in section 5.

4.2 Data format and waveform viewers

In terms of output format there are two options available: VCD (default) or CSV. These are the formats available on SystemC AMS. There are many VCD viewers available on the web, some of them are free. [4] One that I recommend is called Impulse, it is multi-platform and is available as a plug-in for Eclipse (or Cevelop in my case).

Impule wave viewer

Figure 4.1: Impulse wave viewer.

Another good option for macOS is Scansion.

The CSV format is more suitable to use with gnuplot, Python (matplotlib, or plotly), R (programming language) or even in Excel.

5. Example: A 2.4GHz PLL with 1MHz clock reference

For the example I have chosen a 2.4-2.5GHz PLL with a reference of 1MHz. A higher reference frequency would be better as can be seen later, but for demonstration purposes its good enough. Also, this increases the computational problem as the reference clock is at least 2400 times slower than the output.

I have simulated 4 different setups. Two with 45° phase margin (2nd and 3rd order loop filter) and two with 60° phase margin. The configuration file 2450MHz output frequency, 60° phase margin and 3rd order loop filter looks like:

    "system": {
        "vdd": 3.3,
        "vcm": 1.65,
        "fref": 1e6,
        "tstep": 8e-12,
        "tsim": 2e-4
    "phase_detector": {
        "tr": 100e-12,
        "tf": 100e-12
    "charge_pump": {
        "current_up": 100e-6,
        "current_dn": 100e-6,
        "current_leak": 50e-9,
        "mosfet_vth": 0.7
    "loop_filter": {
        "order": 3,
        "c1": 21e-12,
        "r2": 85.5e3,
        "c2": 364e-12,
        "r3": 10e3,
        "c3": 48e-12
    "vco": {
        "kvo": 36.363636e6,
        "fmin": 2.39e9
    "divider": {
        "n": 2450

The values for the loop filter were calculated using the online calculator mentioned in sub-section 2.2. [3]

Filter calculator

Figure 5.1: Filter calculator

Figures 5.2 and 5.3 show the startup simulation for the four configurations different used.

PLL Startup, 45° phase margin, 2nd order vs 3rd order loop

Figure 5.2: PLL Startup, 45° phase margin, 2nd order vs 3rd order loop filter (interactive version).

PLL Startup, 60° phase margin, 2nd order vs 3rd order loop

Figure 5.3: PLL Startup, 60° phase margin, 2nd order vs 3rd order loop filter (interactive version).

As expected, the configurations with 60° degree phase margin show a lower overshoot and the undershoot is practically negligible.

The PSD for the 60° degree phase margin, 3rd order loop filter configuration is shown in figure 5.4 and compared with the 2nd order filter configuration in figure 5.5.

PLL PSD, 60° phase margin, 3rd order loop

Figure 5.4: PLL PSD, 60° phase margin, 3rd order loop filter (interactive version).

PLL PSD, 60° phase margin, 2nd order vs 3rd order loop

Figure 5.5: PLL PSD, 60° phase margin, 2nd order vs 3rd order loop filter (interactive version).

The several peaks outside the center frequency was expected and are caused by the 1 MHz reference clock. So every 1MHz there is one peak and they are called reference-related spurs. But still, the first peaks to the right and left of the center frequency have an attenuation of more than 20 dB, was specified in the filter calculator (figure 5.1). If this attenuation is not enough, the filter can be improved, or the reference frequency can be increased.

As expected the 3rd order loop filter has a better performance in terms of attenuation in the broadband area. But the difference is less only around 4 dB.

The frequency obtained was 2.45022 MHz which represents and error of 9 PPM.

6. References

  1. Ma, Kezheng, Rene Van Leuken, Maja Vidojkovic, et al. "A Precise and High Speed Charge-Pump PLL Model Based on SystemC/SystemC-AMS" International Journal of Electronics and Telecommunications, 58.3 (2012): 225-232. Retrieved 8 Feb. 2018, from doi:10.2478/v10177-012-0031-5. [Available online] 

  2. D. K. Banerjee, "PLL performance, simulation, and design". Indianapolis, IN.: Dog Ear Publishing, 2006. 

  3. A. C. F. Changpuak, “Free Online Engineering Calculator to quickly estimate the Component values used for a 3rd Order Loopfilter for Charge Pump PLL”. Accessed: 08-Feb-2018. [Available online] 

  4. Waveform viewer. Accessed: 16-Feb-2018. [Available online]