# 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 decided 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 such 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 will take a relatively 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.

PLLs are used in a wide range of application from the old FM radio to 5G cellular mobile communications. They combine the spectral purity of quartz oscillators with the agility of voltage controlled oscillators.

This document doesn't aim to introduce readers to PLL theory. For that purpose there are several books and online resources. A good starting point could be the following lessons of Dr. Shouribrata Chatterjee from IIT Delhi:

SystemC AMS is an extension to SystemC for analog, mixed-signal and RF functionality. The SystemC AMS 2.0 standard was released on April 6, 2016 as IEEE Std 1666.1-2016. [1] At the time of writing, it is available for download under the Apache 2.0 license in COSEDA Technologies website.

After the first implementation of this simulator, in which I used only freely available tools, COSEDA Technologies kindly allowed me to test their COSIDE® software for a period of time. COSIDE® software is GUI based and features the following tools and functionalities:

• Schematic Editor
• Waveform Viewer
• State Chart Editor
• TLM Modeling
• IP Protected Models
• SystemC / SystemC AMS Model Export
• SystemC AMS Libraries

It takes care of all the hassle of compiling the SystemC/SystemC-AMS libraries and source code. Also it provides much better debug functionalities than the free tools. Not to mention that the schematic editor is a must have for complex systems. It can save countless man hours in development and debugging in professional applications.

For academic applications, or when budget limitations don't allow purchasing dedicated software, the free tools can be used, at the cost of more development time.

In this document both implementations are presented.

## 2. SystemC-AMS Simulation Domains

Before we jump to the system implementation, it is important to understand the 3 different simulation domains in SystemC-AMS. There are three different domains and all the three are used in this example. They are:

• Discrete Event (DE): This simulation domain is inherited from SystemC. It's event driven (e.g. by clock edges) and it's the perfect domain to describe digital circuits. From the computational point of view, this is the fast domain as the algorithm only needs to compute new states, when one of the sensibility conditions occurs. For instance, a D flip-flop will remain in the same state until a new rising edge of the clock signal.

• Timed Data Flow (TDF): In this domain, the designer has to describe the time domain behavior of the circuit programmatically. So it can define its own time-domain equations and events. The designer also needs to specify a time-step for witch the state should be updated. In terms of computational power this domain can be very costly for small time-steps. However, in most of the control systems, the time-step can be relaxed as the system error is reduced or the system moves to a steady state. This technique is called Dynamic Timed Data Flow (DTDF).

• Electrical Linear Networks (ELN): In this domain, the designer can draw electrical circuits with linear components such as resistors, capacitors and inductors. The SystemC-AMS library will then derive the equations from the circuit and solve them for each time-step. The computational effort is similar to TDF domain.

## 3. System Overview

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

Figure 3.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) generate 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.

## 4. Building blocks

### 4.1 Phase Frequency Detector

The PFD has 3 possible states:

• Idle: During this state, the output of the PFD is 0. The charge pump does not source or sink any current to the loop filter.
• Up: During this state, the output of the PFD is +1. The charge pump sources current to the loop filter which in turn increases the control voltage at the VCO input.
• Down: During this state, the output of the PFD is -1. The charge pump sinks current from the loop filter which in turn decreases the control voltage at the VCO input.

The state diagram for the PFD is represented in figure 4.1.

Figure 4.1: PFD state diagram

There are 3 states and 3 output levels. Which means that this state machine requires two flip-flops and two output bits. This state machine is implemented in the circuit of figure 4.2.

Figure 4.2: Frequency Shift Detector circuit.

#### 4.1.1 Implementation With Open-Source Tools

This block can be modeled as a discrete time element which is sensitive to positive edges of the clock inputs. The implementation is strait forward:

/**
* Processing thread
*/
void sc_pfdetector::sig_proc(void) {
static int state = 0;
int next_state;

switch(state) {
case IDLE:
if(sc_in_fref && sc_in_fdiv) {
// Both fref and fdiv went up during last
// timestep: keep IDLE
next_state = IDLE;
} else if(sc_in_fref) {
sc_out_up.write(true);
next_state = UP;
} else if(sc_in_fdiv) {
sc_out_dn.write(true);
next_state = DN;
}
break;
case UP:
if(sc_in_fdiv) {
sc_out_up.write(false);
next_state = IDLE;
}
break;
case DN:
if(sc_in_fref) {
sc_out_dn.write(false);
next_state = IDLE;
}
break;
}

state = next_state;

}

This code implements the state machine illustrated in figure 2.1. The only condition not illustrated in the diagram is when both Vref and Vdiv go up during the last time-step period. In this case, both transitions should be ignored.

The C++ implementation of the PFD can be found in my GitHub repository:

#### 4.1.2 Implementation with COSIDE®

Drawing the schematic in COSIDE® is extremely simple and intuitive for any person used to draw schematics in any other editor. The interface is presented in figure 4.3.

Figure 4.3: COSIDE® schematic editor interface.

COSEDA provides a complete and comprehensive library of components read to use at a distance of a mouse click.

There is a basic library with all kinds of analog and digital components and sources. There is a library dedicated to RF components, such as converters, mixers, noise sources, modulators, demodulators, etc. Of course, the user is able to create custom components and use them on a top level diagram as can be seen below in this document.

The schematic diagram of the PFD was implemented using two D-type flip-flops and a AND gate from the COSIDE® library as illustrated in figure 4.4.

Figure 4.4: PFD implementation using COSIDE® schematic view.

Having such a quick and easy interface to create the schematics also facilitates the creation of dedicated testbenches for each block.

That is also possible using open source tools, but it's a much more time consuming task. Creating a CPP file with all the interconnections, sources and stimulus can be a confusing task and requires a lot of concentration and organization.

Figure 4.5 displays the testbench I have used to test the PFD.

Figure 4.5: PFD testbench schematic.

COSIDE® also provides an embedded waveform viewer. From what I have experienced it is much faster than the freely available waveform viewers I have tested.

Figure 4.6: PFD simulation results.

### 4.2 Charge Pump

The implementation of the CP models three imperfections of the real circuit: the current mismatch, the transistor triode region, and leakage current.[2] 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:

Figure 3.7: ID versus VDS.

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
discharge=-current_dn;
} else {
// Triode
double vds = sca_tdf_in_vcp;
discharge=-((2.0/(vdd-mosfet_vth))*current_dn*vds-(1.0/pow(vdd-mosfet_vth,2.0))*current_dn*pow(vds,2.0));
}

// Charge (PMOS)
if(sca_tdf_in_vcp<mosfet_vtp) {
// Saturation
charge=current_up;
} else {
// Triode
double vds = vdd-sca_tdf_in_vcp;
charge=(2.0/(vdd-mosfet_vth))*current_up*vds-(1.0/pow(vdd-mosfet_vth,2.0))*current_up*pow(vds,2.0);
}

mismatch=charge+discharge;

/**
* 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)) {
i=0;
}

sca_tdf_out_ictrl = i;
}

The thread sca_tdf_chargepump::processing runs once per time-step. In the first part of the code, the ids of both transistors are calculated for the current operating region. In the second part, the resulting output current is computed based on the previous results and the state of the input signals up and down. Lastly, the final condition is used to prevent the voltage in the charge-pump output to rise above VDD or below ground.

As mentioned, COSIDE® has a great library of components, but it lacks non-linear devices such as transistors. Thus, this block was implemented in a similar way in the open-source and in the COSIDE® version.

The open-source code implementation looks like:

### 4.3 Loop Filter

The current from the charge-pump flows to - or from - the loop filter generating a voltage Vcp. Note that this voltage has to be feeded 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.

Figure 4.8: Charge Pump and Loop Filter equivalent schematic.

Figure 4.8 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. The theory behind the filter design is not covered here. But there are many book about the subject like "PLL performance, simulation, and design" [3] which has many practical examples. There is also online calculators [4] and even applications from trusted sources like Texas Instruments [5].

#### 4.3.1 Implementation With Open-Source Tools

The loop-filter was implemented using the following code:

/**
* Constructor implementation
*/
sc_lfilter::sc_lfilter (sc_module_name name_,
int order_,
double c1_value_,
double r2_value_,
double c2_value_,
double r3_value_,
double c3_value_ ):
sc_module(name_)
{
// Variables initialization
order=order_;
r0_value=1e-6;
c1_value=c1_value_;
r2_value=r2_value_;
c2_value=c2_value_;
r3_value=r3_value_;
c3_value=c3_value_;

// Convert the TDF input to an ELN current
i_in = new sca_eln::sca_tdf_isource("i_in", 1.0);
i_in->inp(sca_tdf_in_ictrl);
i_in->p(gnd);
i_in->n(node0);

// Shunt resistor used for debug purposes
r0 = new sca_eln::sca_r("r0", 1e-6);
r0->p(node0);
r0->n(node1);

// Convert the output voltage from ELN to TDF
v_cp = new sca_eln::sca_tdf_vsink("v_cp", 1.0);
v_cp->p(node1);
v_cp->n(gnd);
v_cp->outp(sca_tdf_out_vcp);

c1 = new sca_eln::sca_c("c1", c1_value);
c1->p(node1);
c1->n(gnd);

// First part
r2 = new sca_eln::sca_r("r2", r2_value);
r2->p(node1);
r2->n(node2);
c2 = new sca_eln::sca_c("c2", c2_value);
c2->p(node2);
c2->n(gnd);

// Convert the output voltage from ELN to TDF
v_out = new sca_eln::sca_tdf_vsink("v_vout", 1.0);
v_out->n(gnd);
v_out->outp(sca_tdf_out_vctrl);

if(order==2) {
v_out->p(node1);
}
if(order==3) {
// If the order is 3, add the extra components
r3 = new sca_eln::sca_r("r3", r3_value);
c3 = new sca_eln::sca_c("c3", c3_value);
r3->p(node1);
r3->n(node3);
c3->p(node3);
c3->n(gnd);
v_out->p(node3);
}

}

The full implementation can be found at:

#### 4.3.2 Implementation with COSIDE®

Once again the advantage of COSIDE® IDE become very clear. Instead of the 70 lines of code of previous section (plus the header file), it is possible to quickly draw the schematic:

Figure 3.9: Loop filter schematic in COSIDE®.

### 4.4 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 changes with time, 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}$$

where,

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

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 3.10

Figure 3.10: 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*sca_tdf_in_vctrl.read() + fmin;
double time = sc_time_stamp().to_seconds();

double phi=idtmod(frequency, time);

sca_tdf_out_fout.write((double)(vcm+vcm*sin(phi)));
sca_tdf_out_fout_freq.write(frequency);

}

The same code was used for implementation with open-source tools and COSIDE®:

### 4.5 Frequency divider

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

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

#### 4.5.1 Implementation With Open-Source Tools

The code of the processing function looks like:

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

if(sca_tdf_in_fin.read() >= vcm and state == false) {
state = true;
counter += 2;
if(counter >= factor) {
counter = 0;
state_div = !state_div;
}
}
else if(sca_tdf_in_fin.read() < vcm){
state = false;
}
sc_out_fout.write(state_div);
}

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

#### 4.5.2 Implementation with COSIDE®

In COSIDE®, once again the schematic editor was used to design the schematic of the frequency divider. The implementation is illustrated in figure 3.11.

Figure 3.11: Frequency divider schematic in COSIDE®.

### 4.6 Top-level

#### 4.6.1 Implementation With Open-Source Tools

In the main source file, all the block are instantiated and connected. Two extra libraries were used:

The full source file can be found in the github repository.

#### 4.6.2 Implementation with COSIDE®

Figure 3.12 shows the top level implementation in COSIDE®. For obvious reasons, it's much more readable and easy to understand than the C++ equivalent.

Figure 3.12: PLL top level schematic in COSIDE®.

In this case, a component called Parameter Reader was used to parse the configuration from a text file. This component is part of the COSIDE® library.

## 5. Simulations results

Four simulations were conducted on both COSIDE® and Open Source versions of the PLL simulator. In all of them, the output frequency was set to 2.45-GHz. A second-order and a third-order loop-filter was tested and both were calculated for 45° of phase margin (60° would be a better design, but having some overshoot forces the PFD to generate negative (down) pulses). Figure 5.1 shows the calculation of the loop-filter using the Texas Instruments calculator. As stated before, the charge pump output current is 100µA. There is two configurations for the leakage current: 100nA and 50uA.

Figure 5.1: Texas Instruments Loop-Filter Calculator.

The combinations are then:

• 2nd order loop-filter, 100nA leak (Download: JSON, PAR).
• 2nd order loop-filter, 50nA leak (Download: JSON, PAR).
• 3rd order loop-filter, 100nA leak (Download: JSON, PAR).
• 3rd order loop-filter, 50nA leak (Download: JSON, PAR).

Figure 5.2 shows the transient response for the 2nd order loop-filter version with 100nA leakage in the charge-pump simulated with COSIDE® and the Open Source versions. As expected both version results are identical (hint: use the interactive version to zoom in and see the difference). There is a slight overt-shoot in the response, as expected from a system with 45° phase margin, and then stabilizes in the desired 2.45-GHz.

Figure 5.2: Transient simulation, COSIDE® vs Open Source, 2nd order filter (interactive version).

Figure 5.3 shows the transient response for the 3rd order loop-filter. Again, both simulator versions have the same results.

Figure 5.3: Transient simulation, COSIDE® vs Open Source, 3rd order filter (interactive version).

As expected, the 3rd order filter has a smoother output as the extra components act as a low-pass filter. Figure 5.4 shows both simulations side-by-side and figure 5.5 shows a comparison between both ripple on the output after steady state is reached.

Figure 5.4: Transient simulation, 2nd vs 3rd order filter (interactive version).

Figure 5.5: Transient simulation, 2nd vs 3rd order filter (detail) (interactive version).

As previously stated, this ripple is caused by the leakage current on the charge pump, which forces the PLL to compensate for it - generating sporadic UP pulses - in order to keep the frequency close to the chosen value.

The frequency spectrum of the PLL is illustrated on figure 5.6. It shows a comparison between 2nd order and 3rd order loop-filter. As expected, the 3rd has a smaller reference spur (-37.8-dBc versus -33.2-dBc). This reference spur is the representation of the ripple shown in the transient simulation results but in the frequency domain. The center frequency is pretty accurate with a variation of only +2 PPM from the desired value. This is only possible because many non-idealities are not simulated in this model, such as mismatches, voltage offsets, noise, etc. However, the reader can take this example as a reference and add other non-idealities.

Figure 5.6: Frequency Spectrum, 2nd vs 3rd order filter (interactive version).

When comparing with existing literature, this design has worst performance in terms if phase noise and reference spur. [6] This is because the overall gain of this configuration is quite small. The open-loop gain is the defined by:

$$G(s)=\frac{I_{cp}}{2 \pi} . F(s) . \frac{k_{vco}}{s}.\frac{1}{N} \tag{10}$$

Where F(s) is the transfer function of the loop filter. The only variables directly proportional to the gain (Icp and kvco) are relatively small. Icp can be easily increased at expense of bigger passive components in the filter. The VCO gain can be also easily increased.

Another way to reduce the spurs is to reduce the leakage in the charge pump which is causing them in the first place. In real systems however, spurs can be caused by coupling between the reference oscillator and the PLL output stage. This coupling in turn can be caused by bad design (i.e. bad isolation between different domains) and/or power supply coupling. For this reason, power-management blocks for RF systems need ensure a good power supply rejection ratio (PSRR).

Figure 5.7 shows a comparison between 100nA leakage and 50nA leakage and the difference is more than 5-dBc.

Figure 5.7: Frequency Spectrum, 100nA vs 50nA leak (interactive version).

## 6. Download

The open-source version of this example is available on my GitHub repository. It can be compiled Windows, MacOS and Linux.

The [email protected] implementation is kindly maintained by COSEDA and it's available on their website, at:

## 7. References

1. En.wikipedia.org. (2019). SystemC AMS. [online] [Accessed 9 Feb. 2019]. [Available online]

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

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

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

5. “Loop Filter Calculation Tool,” LM741 Operational Amplifier | TI.com. [Online]. [Accessed: 23-Feb-2019]. [Available online]

6. A. Paidimarri, N. Ickes and A. P. Chandrakasan, "A 0.68V 0.68mW 2.4GHz PLL for ultra-low power RF systems," 2015 IEEE Radio Frequency Integrated Circuits Symposium (RFIC), Phoenix, AZ, 2015, pp. 397-400. doi: 10.1109/RFIC.2015.7337789