IIR陷波滤波器设计笔记
2026/7/4 17:24:40 网站建设 项目流程
  • 项目需求背景

本设计用于对ADC采集后的高速数字信号进行陷波滤波,主要目标是滤除某些指定频率的高频干扰,例如30MHz、60MHz等,同尽量保留有效信号。

二、设计目标:

1、输入输出数据为16位有符号整数;

  1. 系统时钟为240MHz;
  2. AD采样率480MSPS;
  3. 支持指定频率陷波,例如30MHz、60MHz;
  4. 支持单点陷波和多点陷波;

三、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 = 480MHzf0 = 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等等,所以我们要换个办法

  1. 先把所有小数放大2^30 倍,变成整数来算
  2. 算完之后,再右移30位,相当于除回去

所以B0= 1.0 × 2^30 = 1073741824真正意思是B0= 1.0,只是被放大了2^30,代码中有把放大的值还原回来的代码y0_jisuan = y0_jisuan >>> 30;

  1. 一般都用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,误差可忽略不计

  1. 为什么不用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位就装不下了,就可能发生溢出翻转,变成一个错误的负数

  1. 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陷波器级联结构,每级陷波器均可通过独立系数配

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询