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