フェーズ ロック ループ (PLL) は、一般的に使用される周波数および位相追跡アルゴリズムであり、信号復調、AC グリッド接続、およびその他の分野で広く使用されています。この記事では、オールデジタル位相ロック ループの原理を紹介し、その後 Verilog の実装とシミュレーションを提供します。
PLLロック原理
フェーズロックループの構造は下図に示されており、主に位相検出器、ループフィルタ、電圧制御発振器などで構成されています。
このうち、位相検出器は乗算器であり、基準信号ui u_iが設定されます。あなた私は 、本地信号 u o u_o あなたああどちらも正弦波信号です。
ui ( t ) = cos ( ω 1 t + φ 1 ) u_i(t)=cos(\omega_1 t+\varphi_1)あなた私は( t )=コス(ああ1t+ファイ1)
uo ( t ) = cos ( ω 2 t + ϕ 2 ) u_o(t)=cos(\omega_2 t+\varphi_2)あなたああ( t )=コス(ああ2t+ファイ2)
積と差の式によると、ui u_iあなた私は 与 u o u_o あなたああの積にはω 1 + ω 2 \omega_1+\omega_2が含まれますおお1+おお2 和 ω 1 − ω 2 \omega_1-\omega_2 おお1−おお22 つの周波数成分、LF ローパス フィルター処理後、2 つの差周波信号のみが残ります。
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{整列}あなたc=cos [(ああ1−おお2) t+( f1−ファイ2)]=cos [ 2 π ( f1−f2) t+( f1−ファイ2) ]
f 2 = f 0 + K 0 uc f_2=f_0+K_0 u_c を使用します。f2=f0+K0あなたc位相ロックは、電圧制御発振器 (一般に DDS テクノロジーによって生成されるデジタル) の周波数を制御することによって実現できます。
入力信号が基準周波数f 0 f_0を基準にしていると仮定します。f0存在Δ f \デルタ fΔ f周波数偏差の場合、位相ロックが完了した後、2 つの信号は固定位相偏差Δ φ \Delta \varphiを持ちます。Δ φ、次の関係
Δ f = K 0 cos ( Δ φ ) \Delta f=K_0cos(\Delta \varphi)f_ _=K0cos ( Δ φ )
もちろん、ここではΔ φ \Delta \varphiΔφの符号は決定できません。
Verilogの実装
PLLモジュールのメインプログラムは以下の通りです。
/*
* 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
ローパスフィルターとそのパラメーターコードは次のとおりです。
/*
* 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
FIR フィルターのこの部分については、私の以前のブログ投稿を参照してください。
シミュレーション
シミュレーションテストコードは以下の通りです
`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
DDSコードは次のとおりです
/*
* 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
対応するサイン ルックアップ テーブルは次のとおりです (このモジュールは線形補間法を使用しており、リソース消費量をわずかに増加させるだけで量子化誤差を 2 桁削減します。この部分は私の以前のブログ投稿にもあります)。
/*
* 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
シミュレーション結果は以下の通り