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_WIDTHimproves 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_WIDTHas 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