- 项目需求背景
本设计用于对ADC采集后的高速数字信号进行陷波滤波,主要目标是滤除某些指定频率的高频干扰,例如30MHz、60MHz等,同尽量保留有效信号。
二、设计目标:
1、输入输出数据为16位有符号整数;
- 系统时钟为240MHz;
- AD采样率480MSPS;
- 支持指定频率陷波,例如30MHz、60MHz;
- 支持单点陷波和多点陷波;
三、IIR 陷波器基本原理
1.陷波器的作用
陷波器本质上是一种带阻滤波器,用来抑制某一个很窄的频率点。
例如输入信号 = 5MHz有效信号 + 30MHz干扰
经过30MHz陷波器后,希望输出变成:输出信号 ≈ 5MHz有效信号
也就是说,30MHz被压下去,5MHz尽量保留下来。
2.二阶IIR陷波器差分方程
本设计使用二阶IIR陷波结构,每一级滤波器的计算形式为:
y[n] = B0*x[n] + B1*x[n-1] +B2*x[n-2] +C1*y[n-1]+C2*y[n-2]
其中:
x[n]: 当前输入采样点
x[n-1]: 上一个输入采样点
x[n-2]: 上上个输入采样点
y[n]: 当前输出采样点
y[n-1]: 上一个输出采样点
y[n-2]: 上上个输出采样点
通过修改B0、B1、B2、C1、C2这些系数,就可以改变陷波频率。
3.系数计算关系
采样率为Fs,目标陷波频率为f(要滤掉的目标频率),极点半径为r(控制陷波宽窄),系数大致按下面公式计算:
B0 = 1B1 =-2*cos(2*π*f0/Fs)B2 = 1C1 = 2*r*cos(2*π*f0/Fs)C2 =-r2注意: (1)、2*π*f0/Fs:把目标频率f0换算成数字滤波器里面用的“角度”例如Fs = 480MHz,f0 = 30MHZ,那么2*π*f0/Fs= 2 * 3.1415926 * 30 / 480,这个值就是30MHz在480MHz采样率下对应的数字角频率。
(2)、本设计使用Q2.30定点格式,所以要乘以:2^30 = 1073741824。例如:B0
= 1.0× 2^30 = 1073741824
4.为什么使用Q2.30定点格式
因为FPGA不好直接算小数,比如0.98等等,所以我们要换个办法
- 先把所有小数放大2^30 倍,变成整数来算
- 算完之后,再右移30位,相当于除回去
所以B0= 1.0 × 2^30 = 1073741824真正意思是B0= 1.0,只是被放大了2^30,代码中有把放大的值还原回来的代码y0_jisuan = y0_jisuan >>> 30;
一般都用2的30次方,放大越大,小数保存得越细,精度越高。但不是无限大,受32位范围限制
举个例子:数学系数:0.98。放大5倍:0.98 ×25= 31.36,verilog截断小数,保留整数,值为31;还原回来31/25= 0.96875,与原来的0.98误差明显;放大30倍:0.98 ×230= 1052266987.52,verilog截断小数,保留整数,值为1052266987,还原回来1052266987/230≈ 0.98,误差可忽略不计
为什么不用2的31次方或者2的32次方呢?
不行的,因为32位有符号数范围是:-2147483648 ~ +2147483647。2的31次方是2147483648,超过了32位有符号数范围
5.为什么960MSPS是4点并行
原来960MSPS版本中:系统时钟 = 240MHz;ADC采样率 = 960MSPS,所以每个系统时钟周期要处理4个采样点:960 / 240 = 4,也就是四点并行:
data_in0=x[n]data_in1 =x[n+1]data_in2=x[n+2]data_in3=x[n+3]
同理采样率改成480MSPS:480 / 240 = 2,所以每个系统时钟周期只需要处理2个采样点:
6.整体框架图
7.模块说明
(1)IIR_danjie_2dian.v这是单级两点并行IIR陷波器
输入:data_in0;data_in1;data_in_valid
输出:data_out0;data_out1;data_out_valid
含义:data_in0 =当前拍第0个采样点x[n]; data_in1 = 当前拍第1个采样点x[n+1]data_out0 = 当前拍第0个输出点y[n];data_out1 = 当前拍第1个输出点y[n+1]
(1.1)单级IIR差分方程:
y[n] = B0*x[n] + B1*x[n-1] +B2*x[n-2] +C1*y[n-1]+C2*y[n-2]
代码中第0个采样点的计算
y0_jisuan = B0*data_in0 + B1*x1_lishi+B2*x2_lishi +C1*y1_lishi+C2*y2_lishi
y0_jisuan=y0_jisuan>>>30;
对应关系:data_in0= 当前输入x[n]
x1_lishi= 上一个输入x[n-1]
x2_lishi= 上上个输入x[n-2]
y1_lishi= 上一个输出y[n-1]
y2_lishi= 上上个输出y[n-2]
y0_jisuan=y0_jisuan>>>30表示计算完成后右移 30 位,用于把 Q2.30 定点系数放大后的结果还原回来
代码中第1个采样点的计算
因为当前模块每个时钟周期要连续处理两个点,所以在计算完y0_jisuan后,还要继续计算第二个采样点:
y1_jisuan = B0*data_in1 + B1*data_in0+B2*x1_lishi +C1*$signed(y0_jisuan[31:0])+C2*y1_lishi
y1_jisuan=y1_jisuan>>>30;
对应关系:data_in1= 当前拍第1个输入点x[n+1]
data_in0= data_in1的前一个输入点x[n]
x1_lishi= data_in0 的前一个输入点x[n-1]
y0_jisuan= 当前拍第 0 个输出点y[n]
y1_lishi= y0_jisuan 的前一个输出点y[n-1]
这是两点并行IIR的关键:第0点先算出y[n],第1点再用y[n]继续算y[n+1]
(1.2)历史数据更新
每拍计算完两个采样点后,需要更新历史输入和历史输出:
x2_lishi <= data_in0;
x1_lishi <= data_in1;
y2_lishi <= y0_jisuan[31:0];
y1_lishi <= y1_jisuan[31:0];
含义 :x1_lishi= 最新输入点x[n+1]
x2_lishi= 上一个输入点x[n]
y1_lishi= 最新输出点y[n+1]
y2_lishi= 上一个输出点y[n]
这样下一拍新的data_in0/data_in1到来时,滤波器的历史数据仍然是连续的
(1.3)饱和处理
IIR计算中间结果使用64位:reg signed [63:0] y0_jisuan; reg signed [63:0] y1_jisuan;但最后输出只有16位output reg signed [15:0] data_out0; output reg signed [15:0] data_out1;所以需要进行包和处理,以data_out0为例:
if (y0_jisuan > 64'sd32767)
data_out0 <= 16'sd32767;
else if (y0_j isuan < -64'sd32768)
data_out0 <= -16'sd32768;
else
data_out0 <= y0_jisuan[15:0];
如果计算结果超过16位有符号数最大值,就限制为+32767;如果计算结果小于16位有符号数最小值,就限制为 -32768,如果没有超出范围就正常输出低16位,这样可以避免结果溢出后发生错误翻转
为什么是-32768~+32767呢?因为16位有符号数能表示的范围只有-32768~+32767,超过这个范围,16位就装不下了,就可能发生溢出翻转,变成一个错误的负数
IIR.v
IIR.v是顶层模块,内部例化两个IIR_danjie_2dian;大体结构为
`timescale 1ns / 1ps ////////////////////////////////////////////////////////////////////////////////// // Company: // Engineer: // // Create Date: 2026/06/12 20:10:16 // Design Name: // Module Name: IIR_danjie_2dian // Project Name: // Target Devices: // Tool Versions: // Description: // // Dependencies: // y[n] = B0·x[n] + B1·x[n-1] + B2·x[n-2] + C1·y[n-1] + C2·y[n-2] // Revision: // Revision 0.01 - File Created // Additional Comments: // ////////////////////////////////////////////////////////////////////////////////// module IIR_danjie_2dian(clk,rst_n,notch_en,B0,B1,B2,C1,C2,data_in0,data_in1,data_in_valid,data_out0,data_out1,data_out_valid); input clk; input rst_n; input notch_en; input signed [31:0] B0;//系数采用Q2.30格式 input signed [31:0] B1;//前馈系数 input signed [31:0] B2;//前馈系数 input signed [31:0] C1;//反馈系数 input signed [31:0] C2;//反馈系数 input signed [15:0] data_in0;//两路并行输入 input signed [15:0] data_in1;//两路并行输入 input data_in_valid;//输入有效信号 output reg signed [15:0] data_out0; output reg signed [15:0] data_out1; output reg data_out_valid; reg signed [15:0] x1_lishi;//历史输入数据,x1_lishi为上一个采样点,x2_lishi为上上个采样点 reg signed [15:0] x2_lishi; reg signed [31:0] y1_lishi;//历史输出数据,y1_lishi为上一个采样点,y2_lishi为上上个采样点 reg signed [31:0] y2_lishi; reg signed [63:0] y0_jisuan;//通道0 reg signed [63:0] y1_jisuan; always @(posedge clk or negedge rst_n) if (!rst_n) begin x1_lishi <= 16'sd0; x2_lishi <= 16'sd0; y1_lishi <= 32'sd0; y2_lishi <= 32'sd0; data_out0 <= 16'sd0; data_out1 <= 16'sd0; data_out_valid <= 1'b0; y0_jisuan <= 64'sd0; y1_jisuan <= 64'sd0; end else begin data_out_valid <= data_in_valid; if (data_in_valid) begin if (notch_en) begin y0_jisuan = B0 * data_in0+ B1 * x1_lishi+ B2 * x2_lishi+ C1 * y1_lishi+ C2 * y2_lishi; //第0个采样点 y0_jisuan = y0_jisuan >>> 30; y1_jisuan = B0 * data_in1+ B1 * data_in0 + B2 * x1_lishi+ C1 * $signed(y0_jisuan[31:0])+ C2 * y1_lishi;//第1个采样点 y1_jisuan = y1_jisuan >>> 30; //饱和处理data_out0 if (y0_jisuan > 64'sd32767) data_out0 <= 16'sd32767; else if (y0_jisuan < -64'sd32768) data_out0 <= -16'sd32768; else data_out0 <= y0_jisuan[15:0]; //饱和处理data_out1 if (y1_jisuan > 64'sd32767) data_out1 <= 16'sd32767; else if (y1_jisuan < -64'sd32768) data_out1 <= -16'sd32768; else data_out1 <= y1_jisuan[15:0]; //更新历史数据 x2_lishi <= data_in0; x1_lishi <= data_in1; y2_lishi <= y0_jisuan[31:0]; y1_lishi <= y1_jisuan[31:0]; end else begin data_out0 <= data_in0; data_out1 <= data_in1; x1_lishi <= 16'sd0; x2_lishi <= 16'sd0; y1_lishi <= 32'sd0; y2_lishi <= 32'sd0; y0_jisuan <= 64'sd0; y1_jisuan <= 64'sd0; end end end endmodule(2.1)第一级陷波器
IIR_danjie_2dian IIR_0(
.clk(clk),
.rst_n(rst_n),
.notch_en(notch_en[0]),
.B0(B0_0),
.B1(B1_0),
.B2(B2_0),
.C1(C1_0),
.C2(C2_0),
.data_in0(data_in0),
.data_in1(data_in1),
.data_in_valid(data_in_valid),
.data_out0(data_0_0),
.data_out1(data_0_1),
.data_out_valid(valid_0)
);
其中: notch_en[0] 控制第一级是否开启
B0_0、B1_0、B2_0、C1_0、C2_0是第一级IIR系数
data_in0、data_in1 是原始输入数据
data_0_0、data_0_1 是第一级滤波后的中间数据
(2.2)第二级陷波器
代码略
其中: notch_en[1] 控制第二级是否开启
B0_1、B1_1、B2_1、C1_1、C2_1是第二级IIR系数
data_0_0、data_0_1 是第一级输出的中间数据
data_out0、data_out1 是最终滤波输出
(2.3)notch_en控制关系
notch_en[0]控制第一级IIR
notch_en[1]控制第二级IIR
不同取值含义如下:
notch_en = 2'b00 两级都关闭,输入直通输出
notch_en = 2'b01 只开启第一级陷波器
notch_en = 2'b10 只开启第二级陷波器
notch_en = 2'b11 两级都开启,实现多点陷波
例如:
第一级配置为30MHz系数,第二级配置为60MHz系数
notch_en = 2'b11 表示第一级滤除30MHz,第二级滤除60MHz;整体实现30MHz + 60MHz多点陷波
(3)480MHz采样率下的关键参数
当前设计按照以下条件工作:ADC采样率Fs = 480MHz;系统时钟clk = 240MHz;每拍处理采样点数=2点;因此等效处理吞吐率为240MHz × 2 = 480MSPS
(3.1)信号周期对应采样点数
在Fs = 480MHz时,各频率对应的周期采样点数为:5MHz 周期采样点数 = 480 / 5 = 96点
30MHz 周期采样点数 = 480 / 30 = 16点
60MHz 周期采样点数 = 480 / 60 = 8点
因此在仿真输入中:5MHz用/96.0;30MHz用/16.0;60MHz用/8.0
(3.2)30MHz陷波系数,Fs = 480MHz
当目标陷波频率为30MHz,采样率为480MHz 时,对应系数为:
B0 = 32'sd1073741824;
B1 = -32'sd1984016189;
B2 = 32'sd1073741824;
C1 = 32'sd1944335865;
C2 = -32'sd1031221648;
(3.3)36MHz陷波系数,Fs = 480MHz
当目标陷波频率为60MHz,采样率为480MHz 时,对应系数为:
B0 = 32'sd1073741824;
B1 = -32'sd1518500250;
B2 = 32'sd1073741824;
C1 = 32'sd1488130245;
C2 = -32'sd1031221648;
(4)总结
4.1 IIR_danjie_2dian.v负责实现单级两点并行IIR陷波计算
4.2 IIR.v负责将两个单级IIR陷波器级联,实现单点或多点陷波
4.3设计特点
4.3.1 支持480MSPS输入数据吞吐
4.3.2 使用 240MHz系统时钟
4.3.3 每拍处理2个连续采样点
4.3.4 输入输出均为16位有符号数据
4.3.5 系数使用32位Q2.30定点格式
4.3.6 支持单级开启或两级同时开启
4.3.7 支持30MHz、60MHz等指定频率陷波
4.3.8 可通过修改B0、B1、B2、C1、C2系数改变陷波频率
本设计支持单点陷波和多点陷波切换,输入输出均为16位有符号数据。为适配480MSPS ADC与240MHz系统时钟,设计采用两点并行处理结构,即每个240MHz时钟周期同时处理2个连续采样点,从而等效支持480MSPS数据吞吐。滤波核心采用两级二阶IIR陷波器级联结构,每级陷波器均可通过独立系数配