El bucle de bloqueo de fase (PLL) es un algoritmo de seguimiento de fase y frecuencia de uso común, que se usa ampliamente en la demodulación de señales, la conexión a la red de CA y otros campos. Este artículo presenta el principio del bucle de bloqueo de fase totalmente digital y luego proporciona la implementación y simulación de Verilog.
Principio de bloqueo PLL
La estructura del bucle de bloqueo de fase se muestra en la siguiente figura, que se compone principalmente de un detector de fase, un filtro de bucle, un oscilador controlado por voltaje, etc.
Entre ellos, el detector de fase es un multiplicador y la señal de referencia ui u_i está configuradatuyo, señal de casa uo u_otuoAmbas son señales sinusoidales
ui ( t ) = cos ( ω 1 t + φ 1 ) u_i(t)=cos(\omega_1 t+\varphi_1)tuyo( t )=porque ( oh1t+Fi1)
uo ( t ) = cos ( ω 2 t + ϕ 2 ) u_o(t)=cos(\omega_2 t+\varphi_2)tuo( t )=porque ( oh2t+Fi2)
Según el producto y la fórmula de diferencia, ui u_ituyodale uo u_otuoEl producto de contendrá ω 1 + ω 2 \omega_1+\omega_2Vaya1+Vaya2Suma ω 1 − ω 2 \omega_1-\omega_2Vaya1−Vaya2Dos componentes de frecuencia, después del filtrado de paso bajo de LF, solo queda la señal de frecuencia diferente de los dos
uc = cos [ ( ω 1 − ω 2 ) t + ( φ 1 − φ 2 ) ] = cos [ 2 π ( f 1 − f 2 ) t + ( φ 1 − φ 2 ) ] \begin{aligned} u_c&=cos[(\omega_1-\omega_2)t+(\varphi_1-\varphi_2)]\\ &=cos[2\pi(f_1 - f_2)t+(\varphi_1-\varphi_2)] \end{alineado}tuc=porque [( oh1−Vaya2) t+( f1−Fi2)]=porque [ 2 π ( f1−F2) t+( f1−Fi2) ]
使用f 2 = f 0 + K 0 uc f_2=f_0+K_0 u_cF2=F0+k0tucEl bloqueo de fase se puede lograr controlando la frecuencia del oscilador controlado por voltaje (digital generalmente generado por tecnología DDS).
Supongamos que la señal de entrada es relativa a la frecuencia de referencia f 0 f_0F0existeΔ f \Delta fΔ f desviación de frecuencia, entonces las dos señales tendrán una desviación de fase fijaΔ φ \Delta \varphiΔ φ , las siguientes relaciones
Δ f = K 0 cos ( Δ φ ) \Delta f=K_0cos(\Delta \varphi)f_ _=k0cos ( Δ φ ) Por supuesto, también debemos prestar atención al Δ φ \Delta \varphi
aquíNo se puede determinar el signo de Δφ .
implementación de verilog
El programa principal del módulo PLL es el siguiente
/*
* file : ADPLL.v
* author : 今朝无言
* lab : WHU-EIS-LMSWE
* date : 2023-08-03
* version : v1.0
* description : 锁相环
* Copyright © 2023 WHU-EIS-LMSWE, All Rights Reserved.
*/
module ADPLL(
input clk,
input rst_n,
input signed [15:0] A, //参考信号
input signed [15:0] B, //本地信号
output signed [15:0] df //频偏
);
parameter CLK_FREQ = 1_000_000; //采样频率
reg signed [15:0] df = 16'd0;
//-----------------------multi---------------------------------
reg signed [31:0] multi = 32'd0;
always @(posedge clk) begin
if(~rst_n) begin
multi <= 32'd0;
end
else begin
multi <= A*B;
end
end
//------------------------FIR---------------------------------
wire signed [15:0] multi_filt [1:3];
localparam FIR_N = 20; //FIR阶数
wire [16*(FIR_N+1)-1:0] FIR_params;
FIR_params_0d1 FIR_params_inst(
.params (FIR_params)
);
wire clk_div10;
wire clk_div100;
clkdiv #(.N(10)) clkdiv10(
.clk_in (clk),
.clk_out (clk_div10)
);
clkdiv #(.N(100)) clkdiv100(
.clk_in (clk),
.clk_out (clk_div100)
);
//低通滤波 多级低通滤波,中间穿插下采样
FIR_filter #(.N(FIR_N + 1))
FIR_filter_inst1(
.clk (clk),
.rst_n (rst_n),
.filter_params (FIR_params),
.data_in (multi[31:16]),
.data_out (multi_filt[1])
);
//低通滤波
FIR_filter #(.N(FIR_N + 1))
FIR_filter_inst2(
.clk (clk_div10),
.rst_n (rst_n),
.filter_params (FIR_params),
.data_in (multi_filt[1]),
.data_out (multi_filt[2])
);
//低通滤波
FIR_filter #(.N(FIR_N + 1))
FIR_filter_inst3(
.clk (clk_div100),
.rst_n (rst_n),
.filter_params (FIR_params),
.data_in (multi_filt[2]),
.data_out (multi_filt[3])
);
//---------------------control---------------------------------
always @(posedge clk_div100) begin
df <= multi_filt[3]; // df=K*multi_filt,此处省略鉴相灵敏度K,外部请自行设置合理的K值s
end
endmodule
El filtro de paso bajo y su código de parámetro son los siguientes
/*
* file : FIR_filter.v
* author : 今朝无言
* lab : WHU-EIS-LMSWE
* date : 2023-07-03
* version : v1.0
* description : FIR 滤波器
*/
module FIR_filter(
input clk,
input rst_n,
input [16*N-1:0] filter_params,
input signed [15:0] data_in,
output reg signed [15:0] data_out
);
parameter N = 32; //滤波器参数个数
parameter div_N = 16; //sum结果除 2^div_N,作为 filter 的输出
//FIR 滤波器参数
reg signed [15:0] b[0:N-1];
integer m;
always @(*) begin
for(m=0; m<N; m=m+1) begin
b[m] <= filter_params[(m << 4) +: 16];
end
end
reg signed [15:0] shift_reg[0:N-1];
integer i;
always @(posedge clk) begin
if(~rst_n) begin
for(i=N-1; i>=0; i=i-1) begin
shift_reg[i] <= 16'd0;
end
end
else begin
for(i=N-1; i>0; i=i-1) begin
shift_reg[i] <= shift_reg[i-1];
end
shift_reg[0] <= data_in;
end
end
reg signed [31:0] multi[0:N-1];
integer j;
always @(*) begin
for(j=0; j<N; j=j+1) begin
multi[j] <= shift_reg[j] * b[j];
//这里可以考虑使用multiplier IP核,使用LUT搭建(而这里直接乘使用的是DSP资源,一般的FPGA芯片只有几百个)
end
end
reg signed [47:0] sum;
integer k;
always @(*) begin
sum = 0;
for(k=0; k<N; k=k+1) begin
sum = sum + multi[k];
end
end
always @(posedge clk) begin
data_out <= sum[47-div_N : 32-div_N];
end
endmodule
/*
* file : FIR_params.v
* author : 今朝无言
* lab : WHU-EIS-LMSWE
* date : 2023-08-04
* version : v1.0
* description : FIR 滤波器 lowpass N=20 fc=0.1 fs
*/
module FIR_params_0d1(
output [335:0] params
);
assign params[15:0] = 16'h0000;
assign params[31:16] = 16'h0057;
assign params[47:32] = 16'h0131;
assign params[63:48] = 16'h0302;
assign params[79:64] = 16'h0616;
assign params[95:80] = 16'h0A6D;
assign params[111:96] = 16'h0FA8;
assign params[127:112] = 16'h1518;
assign params[143:128] = 16'h19E1;
assign params[159:144] = 16'h1D28;
assign params[175:160] = 16'h1E53;
assign params[191:176] = 16'h1D28;
assign params[207:192] = 16'h19E1;
assign params[223:208] = 16'h1518;
assign params[239:224] = 16'h0FA8;
assign params[255:240] = 16'h0A6D;
assign params[271:256] = 16'h0616;
assign params[287:272] = 16'h0302;
assign params[303:288] = 16'h0131;
assign params[319:304] = 16'h0057;
assign params[335:320] = 16'h0000;
endmodule
Para conocer esta parte del filtro FIR, consulte la publicación de mi blog anterior.
simulación
El código de prueba de simulación es el siguiente.
`timescale 100ns/1ns
module PLL_tb();
reg clk_1M = 1'b1;
always #5 begin
clk_1M <= ~clk_1M;
end
reg rst_n = 1'b1;
//---------------------参考信号A-------------------------------
wire [15:0] A_out_tmp;
wire signed [15:0] A_out; //参考信号
localparam f0 = 24'd10_000;
localparam df = -24'd9; //频率偏差
DDS #(
.Freq(1_000_000)
)
DDS_inst1(
.clk (clk_1M),
.rst_n (rst_n),
.fout (f0+df),
.phase0 (16'd0),
.sin_out (A_out_tmp)
);
assign A_out = A_out_tmp - 16'd32768;
//---------------------本地信号B-------------------------------
wire [15:0] B_out_tmp;
wire signed [15:0] B_out;
wire signed [23:0] df2; //控制本地信号的频偏
DDS #(
.Freq (1_000_000)
)
DDS_inst2(
.clk (clk_1M),
.rst_n (rst_n),
.fout (f0+df2),
.phase0 (16'd0),
.sin_out (B_out_tmp)
);
assign B_out = B_out_tmp - 16'd32768;
//-----------------------PLL---------------------------------
wire signed [15:0] df_PLL;
ADPLL #(
.Freq (1_000_000)
)
PLL_inst(
.clk (clk_1M),
.rst_n (rst_n),
.A (A_out), //参考信号
.B (B_out), //本地信号
.df (df_PLL) //频偏
);
assign df2 = df_PLL/64;
//-----------------------tb---------------------------------
initial begin
rst_n <= 1'b0;
#5000;
rst_n <= 1'b1;
#100;
#1000000;
$stop;
end
endmodule
El código DDS es el siguiente
/*
* file : DDS.v
* author : 今朝无言
* Lab : WHU-EIS-LMSWE
* date : 2023-05-17
* version : v1.0
* description : 根据给定频率输出正弦信号
* Copyright © 2023 WHU-EIS-LMSWE, All Rights Reserved.
*/
module DDS(
input clk,
input rst_n,
input [23:0] fout, //输出正弦波的频率 1k-10M 要24位
input [15:0] phase0, //初相
output [15:0] sin_out
);
parameter Freq = 100_000_000; //clk频率,Hz
//-----------------相位累加器-----------------------
reg [47:0] int_f_16 = 48'd0; //相位累加器,x-16定点数
wire [55:0] dphi_16; //相位步进
//dphi*Freq=fout*T, T=65536
assign dphi_16 = (fout << 32)/Freq;
always @(posedge clk or negedge rst_n) begin
if(~rst_n) begin
int_f_16 <= 48'd0;
end
else begin
int_f_16 <= int_f_16 + dphi_16;
end
end
//-----------------正弦查找表-----------------------
wire [15:0] phase;
sin_gen sin_gen_inst(
.clk (clk),
.phase (phase), //相位
.sin_out (sin_out)
);
assign phase = phase0 + (int_f_16 >> 16);
endmodule
La tabla de búsqueda de senos correspondiente es la siguiente (este módulo utiliza el método de interpolación lineal, que reduce el error de cuantificación en dos órdenes de magnitud con solo un pequeño aumento en el consumo de recursos; esta parte también se puede encontrar en mi publicación de blog anterior)
/*
* file : sin_gen.v
* author : 今朝无言
* Lab : WHU-EIS-LMSWE
* date : 2023-05-17
* version : v1.0
* description : 根据给定相位输出正弦信号
* Copyright © 2023 WHU-EIS-LMSWE, All Rights Reserved.
*/
module sin_gen(
input clk,
input [15:0] phase, //相位,0~65535对应[0~2pi)
output [15:0] sin_out
);
//---------------------正弦查找表-------------------------
wire [7:0] addr1;
wire [7:0] addr2;
wire [15:0] sin_dat1;
wire [15:0] sin_dat2;
//sin rom, 16bit, 256 depth
sin_rom sin_rom_inst1(
.clka (clk),
.addra (addr1),
.douta (sin_dat1)
);
sin_rom sin_rom_inst2(
.clka (clk),
.addra (addr2),
.douta (sin_dat2)
);
//-----------线性插值获取更精确的相位分辨率-------------------
assign addr1 = (phase>>8);
assign addr2 = (phase>>8)+1;
wire [15:0] phase1;
wire [15:0] phase2;
assign phase1 = addr1<<8;
assign phase2 = addr2<<8;
reg [15:0] phase_d0;
reg [15:0] phase_d1; //由于rom数据2拍后才给出,因此phase需要与之同步
reg [15:0] phase1_d0;
reg [15:0] phase1_d1;
always @(posedge clk) begin
phase_d0 <= phase;
phase_d1 <= phase_d0;
phase1_d0 <= phase1;
phase1_d1 <= phase1_d0;
end
wire [31:0] multi;
assign multi = (sin_dat2 > sin_dat1)?
(sin_dat2 - sin_dat1)*(phase_d1 - phase1_d1) :
(sin_dat1 - sin_dat2)*(phase_d1 - phase1_d1);
assign sin_out = (sin_dat2 > sin_dat1)?
sin_dat1 + (multi >> 8) : sin_dat1 - (multi >> 8);
endmodule
Los resultados de la simulación son los siguientes.