Showing posts with label PLL. Show all posts
Showing posts with label PLL. Show all posts

Sep 25, 2026

Verilog Code for Digital PLL

Digital PLL in Verilog

Digital Phase-Locked Loop (DPLL) in Verilog

A Digital PLL locks the output clock phase to a reference clock using a Phase Detector, a Digital Loop Filter (Integrator), and a Digitally Controlled Oscillator (DCO).

Reference Clock ──► Phase Detector ──► Loop Filter (Accumulator) ──► DCO/VCO ──► Output Clock ▲ │ └──────────────────────────────────────────────────────────┘

1. Architecture Overview

The core components of a digital PLL are:

  • Phase Detector: Measures the phase difference between the reference clock and the feedback clock.
  • Loop Filter: Integrates the phase error over time to generate a stable control word. This acts as a low-pass filter for the digital domain.
  • DCO (VCO): Generates the output clock by accumulating the control word. The output frequency is proportional to the control word value.

2. Complete Verilog Implementation

Note: This implementation uses the reference clock as the timing source for all digital logic. The DCO generates an output clock that is an integer fraction of the reference clock (typically 1/N).

`timescale 1ns / 1ps

/**
 * ============================================================================
 * Digital Phase-Llocked Loop (DPLL)
 * ============================================================================
 * 
 * Structure:
 *   1. Phase Detector: Measures phase error between ref_clk and out_clk
 *   2. Loop Filter:    Digital integrator (accumulator)
 *   3. DCO:            Digitally Controlled Oscillator
 *
 * The DPLL adjusts the output clock's frequency/phase to match the 
 * reference clock, with a fixed division ratio N.
 *
 * ============================================================================
 */

module digital_pll #(
    parameter integer DIV_RATIO  = 2,      // Output freq = Ref freq / DIV_RATIO
    parameter integer ACC_WIDTH  = 16,     // Accumulator width
    parameter integer LOCK_WINDOW = 16     // Cycles to confirm lock
)(
    input  wire                    ref_clk,       // Reference clock
    input  wire                    rst_n,         // Active-low reset
    input  wire                    enable,        // PLL enable
    output reg                     out_clk,       // Output clock
    output wire                    lock,          // Lock indicator
    output wire [ACC_WIDTH-1:0] ctrl_word     // DCO control word (debug)
);

    // =========================================================================
    // Internal Signals
    // =========================================================================
    reg  [ACC_WIDTH-1:0] accumulator;
    reg                  out_clk_reg;
    reg                  lock_flag;

    // Phase detector signals
    reg  ref_clk_d, ref_clk_dd;
    reg  out_clk_d, out_clk_dd;
    wire ref_rise  = ref_clk_d  & ~ref_clk_dd;
    wire out_rise  = out_clk_d  & ~out_clk_dd;

    // Tick counter for phase measurement
    reg  [ACC_WIDTH-1:0] tick_counter;
    reg                  counting;

    // Loop filter (integrator)
    reg  [ACC_WIDTH-1:0] integrator;

    // Lock counter
    reg  [7:0]           lock_counter;

    // =========================================================================
    // 1. Edge Detectors
    // =========================================================================
    always @(posedge ref_clk or negedge rst_n) begin
        if (!rst_n) begin
            ref_clk_d  <= 1'b0;
            ref_clk_dd <= 1'b0;
            out_clk_d  <= 1'b0;
            out_clk_dd <= 1'b0;
        end else begin
            ref_clk_d  <= ref_clk;
            ref_clk_dd <= ref_clk_d;
            out_clk_d  <= out_clk;
            out_clk_dd <= out_clk_d;
        end
    end

    // =========================================================================
    // 2. Phase Detector
    // =========================================================================
    // Measures the time difference between a reference edge and the
    // most recent output edge by counting reference clock cycles.

    always @(posedge ref_clk or negedge rst_n) begin
        if (!rst_n) begin
            tick_counter <= 0;
            counting     <= 1'b0;
        end else if (enable) begin
            if (ref_rise) begin
                // Start counting at reference edge
                tick_counter <= 0;
                counting     <= 1'b1;
            end else if (counting) begin
                if (out_rise) begin
                    // Stop at output edge
                    counting <= 1'b0;
                else begin
                    tick_counter <= tick_counter + 1'b1;
                end
            end
        else begin
            tick_counter <= 0;
            counting     <= 1'b0;
        end
    end

    // =========================================================================
    // 3. Phase Error Calculation
    // =========================================================================
    // Expected ticks per ref cycle = DIV_RATIO
    // Actual measured ticks = tick_counter
    // Error = Expected - Actual

    wire signed [ACC_WIDTH:0] raw_error = 
        $signed({1'b0, DIV_RATIO}) - $signed({1'b0, tick_counter});

    // =========================================================================
    // 4. Digital Loop Filter (Integrator)
    // =========================================================================
    // The integrator accumulates the phase error to produce the control
    // word. This provides the integral action needed for zero steady-state
    // error.

    always @(posedge ref_clk or negedge rst_n) begin
        if (!rst_n) begin
            integrator <= 0;
        else if (enable) begin
            // Update integrator with clamping
            wire signed [ACC_WIDTH+1:0] new_int = 
                $signed({1'b0, integrator}) + {1'b0, raw_error};

            if (new_int > (1 << ACC_WIDTH) - 1)
                integrator <= (1 << ACC_WIDTH) - 1;
            else if (new_int < -(1 << ACC_WIDTH))
                integrator <= 0;
            else
                integrator <= (new_int >= 0) ? new_int[ACC_WIDTH-1:0] : 
                                             (0 - (-new_int)[ACC_WIDTH-1:0]);
        else begin
            integrator <= 0;
        end
    end

    assign ctrl_word = integrator;

    // =========================================================================
    // 5. Digitally Controlled Oscillator (DCO)
    // =========================================================================
    // The DCO accumulates the control word on every ref_clk cycle.
    // When the accumulator overflows, the output clock toggles.

    always @(posedge ref_clk or negedge rst_n) begin
        if (!rst_n) begin
            accumulator <= 0;
            out_clk_reg <= 0;
        end else if (enable) begin
            accumulator <= accumulator + ctrl_word;

            // Toggle output on overflow
            if (accumulator + ctrl_word >= (1 << ACC_WIDTH))
                out_clk_reg <= ~out_clk_reg;
            else
                out_clk_reg <= out_clk_reg;
        end
    end

    assign out_clk = out_clk_reg;

    // =========================================================================
    // 6. Lock Detection
    // =========================================================================
    // Lock is declared when the phase error remains small for
    // a consecutive number of cycles.

    always @(posedge ref_clk or negedge rst_n) begin
        if (!rst_n) begin
            lock_counter <= 0;
        else if (enable) begin
            if ($abs(raw_error) <= 2) begin
                if (lock_counter < LOCK_WINDOW)
                    lock_counter <= lock_counter + 1'b1;
            else begin
                lock_counter <= 0;
            end
        else begin
            lock_counter <= 0;
        end
    end

    assign lock = (lock_counter == LOCK_WINDOW);

endmodule
    

3. Testbench


`timescale 1ns / 1ps

module digital_pll_tb;

    // Parameters
    parameter REF_PERIOD = 10;  // 100 MHz reference
    parameter DIV_RATIO  = 2;   // Output = 50 MHz

    // Signals
    reg  ref_clk;
    reg  rst_n;
    reg  enable;
    wire out_clk;
    wire lock;
    wire [15:0] ctrl_word;

    // Instantiate DUT
    digital_pll #(
        .DIV_RATIO (DIV_RATIO),
        .ACC_WIDTH (16),
        .LOCK_WINDOW(16)
    ) dut (
        .ref_clk   (ref_clk),
        .rst_n     (rst_n),
        .enable    (enable),
        .out_clk   (out_clk),
        .lock      (lock),
        .ctrl_word (ctrl_word)
    );

    // Reference Clock Generation
    initial begin
        ref_clk = 0;
        forever #(REF_PERIOD/2) ref_clk = ~ref_clk;
    end

    // Stimulus
    initial begin
        rst_n = 0;
        enable = 0;

        // Reset
        repeat (5) @(posedge ref_clk);
        rst_n = 1;

        // Enable PLL
        repeat (5) @(posedge ref_clk);
        enable = 1;

        // Run until locked
        wait (lock);
        $display("PLL Locked at time %t", $time);

        // Run for a while after lock
        repeat (100) @(posedge ref_clk);

        // Disable PLL
        enable = 0;
        repeat (10) @(posedge ref_clk);

        $finish;
    end

    // Waveform Dump
    initial begin
        $dumpfile("pll.vcd");
        $dumpvars(0);
    end

endmodule
    

4. Key Parameters Table

Parameter Description Default
DIV_RATIO Division ratio. Output frequency = Reference frequency / DIV_RATIO. 2
ACC_WIDTH Bit width of the accumulator and integrator. Higher values give finer frequency resolution but slower lock time. 16
LOCK_WINDOW Number of consecutive cycles with small phase error required to declare lock. 16

5. Design Considerations

  • Stability: The integrator acts as a loop filter. A higher ACC_WIDTH improves resolution but may slow down the lock time. Ensure the loop bandwidth is well below the Nyquist rate of the reference clock.
  • Lock Time: The time to lock depends on the initial phase error and the integrator gain. For faster locking, you can add a proportional term to the loop filter (PI controller).
  • Area/Power: The accumulator and integrator are the main contributors to area. Keep ACC_WIDTH as small as possible while maintaining sufficient frequency resolution.
  • Simulation vs. Silicon: In silicon, the DCO is often implemented as a series of delay cells (ring oscillator) controlled by the digital word, rather than a simple accumulator. The accumulator-based approach is ideal for simulation and FPGA implementations.
  • Multi-Phase Output: To generate multiple phase-shifted clocks, you can derive additional outputs by sampling the accumulator at different thresholds.

6. References

  • "Digital Phase-Locked Loops: A New Approach" – IEEE Proceedings
  • "All-Digital PLLs for High-Speed SerDes" – Analog Dialogue
  • Standard textbooks on PLL design: Phase-Locked Loops: Design, Simulation, and Application by Gary C. Manatis

May 26, 2016

Digital PLL



A phase-locked loop or phase lock loop (PLL) is a control system that generates an output signal whose phase is related to the phase of an input signal.

VCO ->  Voltage Controlled Oscillator
The oscillator generates a periodic signal. The phase detector compares the phase of that signal with the phase of the input periodic signal and adjusts the oscillator to keep the phases matched. Bringing the output signal back toward the input signal for comparison is called a feedback loop since the output is "fed back" toward the input forming a loop.

Basic architecture of PLL -> 





There are certain limitation in digital to implement it in digital logic , one I can think of is the high frequency. To generate a target frequency , VCO may run at higher frequency which typically in Ghz and digital circuit can not run at such a high frequency. 

There are various method to do the calibration. Few of them are listed below.

1. Through binary search algorithm 

Understand it through an example. (Note -> all values/parameters are logical for understanding purpose )

Below are the parameters available. 
Ref clock -> 250 MHz  (just to make calculation simple, typical ref clock is ~100MHz) 

Target frequency and ref frequency is same, So in given time frame, counter value can be compared to adjust the voltage of VCO to increase/decrease frequency. Below is the calculation.

VCO voltage range -> 0.0 V to 5.0V  , Digital range is 8-bit value ('h0 -> 0.0 V , 'hFF -> 5.0V)
In frequency , 0.0V  -> 0hz , 5.0V -> 4 Ghz 

Target Frequency -> 250Mhz   (Target VCO voltage -> 3.5 V  ->  [2Ghz / Freq_DIV ('d8)] ) 
To start algorithm ->  Will take the middle digital value which will convert into voltage in VCO through DAC.  Signal name ->  vco_val[7:0]  

Start value vco_val ->  8'h80    ,  VCO voltage -> 2.5 V , Frequency generated by VCO -> 1.5Ghz

After Phase Detection and filter logic , 

When this value compared with ref value, result will increase frequency (means increase voltage).



Pictorial View of frequency, converging into Target Frequency.



Pros/Con for this Algorithm ->

Pros :
1. Algorithm is fast , number of iteration will be less if compared with other method.

Con :
1. Additional logic/hardware required to do the calculation.
2. Since there will be large voltage difference during 1st few iteration, this could be an issue in analog component , check with analog designer.


2. Maintaining Step size with FSM 
There could be 2 or more steps with this type of algo. Basically with a known step size (voltage increment or decrements to increase or decrease frequency in VCO ) , moving towards closure to target frequency and then reduce step size to reach more closure of target frequency. There are pros and con doing this.

Major Component in Digital PLL 

1. Phase Detector
2. Filter
3. Phase controller
4. Frequency Divider

1. Phase Detector
This block detect the phase of ref signal and output signal from frequency divider. It generate Lead/Lag signal based on the phase difference.

2 Filter
Filter is required to filter out the unwanted pulse.

3. Phase controller
Phase controller get feedback from Filter , based on the feedback , phase controller can increase or decrease the frequency and wait for next feedback.

4. Frequency Divider
Since system clock will be running at much higher frequency , To get the target frequency , frequency divider should be programmed correctly. It is normally configurable and configure at the beginning of calibration. through out calibration , it is constant. 


If you have any question, please put it in comment.
Thanks for reading my blog.

SiteMap