查过很多资料,对于IIR滤波器结构和原理介绍很多,但是,真正对于FPGA的快速设计介绍很少。我对IIR滤波器的MATLAB仿真和FPGA硬件仿真做了充分的对比,关于IIR滤波器设计和实现做一下总结:
说明:IIR滤波器最佳实现结构、IIR滤波器结果舍入处理已做过文档说明。
1. FDAtool设计IIR滤波器参数——结构为直接I型
比较简单,记录一下几种常用滤波器区别:
l 巴特沃斯滤波器:通带和阻带有最大平坦度,但是过渡带比较平缓,当需要比较陡峭的过渡带时,需要较大的阶数;巴特沃斯滤波器对于系数量化影响相对较弱。
l Chebyshev滤波器:分为I型和II型,I型通带等纹波,阻带平坦;II型通带平坦,阻带等纹波,过渡带较巴特沃斯滤波器陡峭;
l 椭圆滤波器:在通带和阻带都是等纹波,过渡带最陡峭,对于相同的过渡带指标,椭圆滤波器需要的阶数最少,但是对于系数量化最敏感。
2. FDAtool系数量化
说明:FPGA计算都是基于二进制补码计算的,采用定点数,在实际设计时需要对FPGA硬件资源有一定了解。
量化分为两部分:增益量化和零点极点量化,一般为了节省硬件资源,增益量化和系数量化采用相同的位数,只是他们缩放倍数不一样,在实现时需要考虑。
设计例子如下:
关于滤波器系数量化位数选择:个人建议在保证滤波器稳定的前提下,结合FPGA硬件结构来选择。比如:xilinx dsp48e IP core可以实现25x18位的硬件乘法器,实际设计时,16位量化位数滤波器就达到稳定,最终还是选择18位,不用白不用,同时,输出结果也更加准确,增加滤波器系数量化位数,还可以允许更低位数的数据输入。
上图界面中还需要确定Input/output参数量化位数,这两个参数我们可以不管,到此为止,fdatool的工作已经完成。
那么对于任意位数的输入数据滤波器都是有意义的?当然不是。通过实际MATLAB仿真,滤波器有输入最小位数限制,否则输出全为0,这也不难理解,因为在FPGA实现时需要进行两次截尾或舍入处理,数据过小,就会输出为零。通常对于小的输入数据可以进行左移位。
IIR滤波器的最小数据输入位数可以通过MATLAB定点仿真来确定,这个是很有必要的,后面会说明。
3. 滤波器输入数据位宽确定——乘法器类型确定
一般的硬件乘法器IP core都可以达到100M以上,所以一般的几十兆以下采样率的滤波器都采用串行结构实现,即所有运算复用一个IP core。结合xilinx dsp48e特点,可以设计成乘加器,这样只用耗费一个dsp资源就可以实现所有滤波器计算。
共用一个dsp,就要求每次数据的输入和输出位宽相同,方便处理,同时保证不会溢出。
简单直接I型IIR滤波器结构如下:
乘加运算包括:增益计算、零点计算、极点计算。增益计算:输入乘以增益(18位);极点计算:输出乘以极点系数(18位)。X,y具有相同的数据位宽,由于y后面有除法操作,所以y的表示位宽直接影响到滤波器的数据输出,y的位宽需要通过MATLAB量化仿真来确定。例如:在实际设计时,使用16位输入量化,不能得到正确的输出结果,输入要到18位以上才能得到相对正确的结果,最终取20位数据输入,小数据可以通过左移位来满足要求。
4. 滤波器参数获取——fdatool强大功能
Targets ->generate HDL选择需要的hdl语言和保存位置即可获得IIR滤波器程序,如下:我只用到它的量化参数,其他由于IP和具体器件有关,代码自己编写:
parameter signed [17:0] scaleconst1 = 18'b011000001001011000; //sfix18_En21
parameter signed [17:0] coeff_b1_section1 = 18'b010000000000000000; //sfix18_En16
parameter signed [17:0] coeff_b2_section1 = 18'b100000000100011110; //sfix18_En16
parameter signed [17:0] coeff_b3_section1 = 18'b010000000000000000; //sfix18_En16
parameter signed [17:0] coeff_a2_section1 = 18'b100000001001000011; //sfix18_En16
parameter signed [17:0] coeff_a3_section1 = 18'b001111110111001010; //sfix18_En16
parameter signed [17:0] scaleconst2 = 18'b001001011100101101; //sfix18_En21
parameter signed [17:0] coeff_b1_section2 = 18'b010000000000000000; //sfix18_En16
parameter signed [17:0] coeff_b2_section2 = 18'b100000001011101011; //sfix18_En16
parameter signed [17:0] coeff_b3_section2 = 18'b010000000000000000; //sfix18_En16
parameter signed [17:0] coeff_a2_section2 = 18'b100000010111111001; //sfix18_En16
parameter signed [17:0] coeff_a3_section2 = 18'b001111101000010101; //sfix18_En16
parameter signed [17:0] scaleconst3 = 18'b000011101111100011; //sfix18_En21
parameter signed [17:0] coeff_b1_section3 = 18'b010000000000000000; //sfix18_En16
parameter signed [17:0] coeff_b2_section3 = 18'b010000000000000000; //sfix18_En16
parameter signed [17:0] coeff_b3_section3 = 18'b000000000000000000; //sfix18_En16
parameter signed [17:0] coeff_a2_section3 = 18'b110000001110111110; //sfix18_En16
parameter signed [17:0] coeff_a3_section3 = 18'b000000000000000000; //sfix18_En16
这些参数自动生成,直接调用。
5. 代码编写——直接I型IIR滤波器级联实现
直接I型二阶IIR滤波器verilog代码:IP例化为20X18位乘加器,一级需要至少7个时钟周期,以上设计为8个周期:
module IIROrder2FormI
#(
parameter DataWidth
= 20,
parameter CoeffientWidth
= 18,
parameter GainScaleWidth
= 20,
parameter signed [CoeffientWidth
- 1 :0] Gain
= 18'h4_6862,
parameter signed [0 : CoeffientWidth
* 3 - 1] NumCoeffient
= {18'h4_0000,18'h8_002A,18'h4_0000},
parameter signed [0 : CoeffientWidth
* 2 - 1] DenCoeffient
= {18'h8_01DE,18'h3_FE2E}
)
(
input Clk
,
input Reset
,
input DataInValid
,
input [DataWidth
-1:0] DataIn
,
output DataOutValid
,
output [DataWidth
-1:0] DataOut
);
parameter STATENUM
= 5'd8;
reg signed [DataWidth
-1:0] DataOutTemp
;
wire signed [DataWidth
- 1:0] DataRoundOut
;
wire signed [DataWidth
- 1:0] DataRoundOut2
;
(* dont_touch
="true" *)
//////////multAdder signal////////////
reg signed [DataWidth
- 1:0] DataA
;
(* dont_touch
="true" *)
reg signed [CoeffientWidth
- 1:0] DataB
;
reg signed [DataWidth
+ CoeffientWidth
+ 1:0] DataC
;
wire signed [DataWidth
+ CoeffientWidth
+ 1:0] DataP
;
reg SUBTRACT
;
wire signed [47:0] PCOUT
;
//numeri////////////////////////
reg signed [DataWidth
- 1:0] DataNumIn
;
reg signed [DataWidth
- 1:0] DataNumRegDelay1
;
reg signed [DataWidth
- 1:0] DataNumRegDelay2
;
/////*********** DenCoeffient ***************//////////////
reg signed [DataWidth
- 1 :0] DataDenRegDelay1
;
reg signed [DataWidth
- 1 :0] DataDenRegDelay2
;
reg [4:0] StateCounter
;
reg ConvState
;
reg DataOutValidTemp
;
assign DataOutValid
= DataOutValidTemp
;
always @(posedge Clk
)
begin
if(Reset
)
ConvState
<= 1'b0;
else
case(ConvState
)
1'b0 : begin
if(DataInValid
== 1'b1)
ConvState
<= 1'b1;
else
ConvState
<= 1'b0;
end
1'b1 : begin
if(StateCounter
== (STATENUM
- 5'd2))
ConvState
<= 1'b0;
else
ConvState
<= 1'b1;
end
default: ConvState
<= 1'b0;
endcase
end
always @(posedge Clk
)
begin
if(Reset
)
StateCounter
<= 0;
else if(StateCounter
== (STATENUM
- 5'd2))
StateCounter
<= 0;
else if(ConvState
== 1'b1)
StateCounter
<= StateCounter
+ 1'd1;
else
StateCounter
<= StateCounter
;
end
always @(posedge Clk
)
begin
if (Reset
)
begin
DataNumRegDelay1
<= 0;
DataNumRegDelay2
<= 0;
DataDenRegDelay1
<= 0;
DataDenRegDelay2
<= 0;
SUBTRACT
<= 1'b0;
DataOutTemp
<= 0;
DataNumIn
<= 0;
DataOutValidTemp
<= 1'b0;
end
else
begin
case(StateCounter
)
5'd0: begin
DataOutValidTemp
<= 1'b0;
DataA
<= DataIn
;
DataB
<= Gain
;
DataC
<= $signed(0);
SUBTRACT
<= 1'b0;
end
5'd1: begin
DataA
<= DataRoundOut
;
DataB
<= NumCoeffient
[0 : CoeffientWidth
- 1];
DataC
<= $signed(0);
SUBTRACT
<= 1'b0;
DataNumIn
<= DataRoundOut
;
DataNumRegDelay1
<= DataNumIn
;
DataNumRegDelay2
<= DataNumRegDelay1
;
end
5'd2: begin
DataA
<= DataNumRegDelay1
;
DataB
<= NumCoeffient
[CoeffientWidth
: CoeffientWidth
* 2 - 1];
DataC
<= DataP
;
SUBTRACT
<= 1'b0;
end
5'd3: begin
DataA
<= DataNumRegDelay2
;
DataB
<= NumCoeffient
[CoeffientWidth
* 2 : CoeffientWidth
* 3 - 1];
DataC
<= DataP
;
SUBTRACT
<= 1'b0;
DataDenRegDelay1
<= DataOutTemp
;
DataDenRegDelay2
<= DataDenRegDelay1
;
end
5'd4: begin
DataA
<= DataDenRegDelay1
;
DataB
<= DenCoeffient
[0 : CoeffientWidth
- 1];
DataC
<= DataP
;
SUBTRACT
<= 1'b1;
end
5'd5: begin
DataA
<= DataDenRegDelay2
;
DataB
<= DenCoeffient
[CoeffientWidth
: CoeffientWidth
* 2 - 1];
DataC
<= DataP
;
SUBTRACT
<= 1'b1;
end
5'd6: begin
DataOutTemp
<= DataRoundOut2
;
DataOutValidTemp
<= 1'b1;
end
// 5'd7: begin
// DataOutTemp <= DataRoundOut2;
// end
default : begin
DataA
<= DataA
;
DataB
<= DataB
;
DataC
<= DataC
;
SUBTRACT
<= SUBTRACT
;
DataNumRegDelay1
<= DataNumRegDelay1
;
DataNumRegDelay2
<= DataNumRegDelay2
;
DataDenRegDelay1
<= DataDenRegDelay1
;
DataDenRegDelay2
<= DataDenRegDelay2
;
DataOutTemp
<= DataOutTemp
;
DataNumIn
<= DataNumIn
;
DataOutValidTemp
<= DataOutValidTemp
;
end
endcase
end
end
assign DataOut
= DataOutTemp
;
RoundData
#(
.DataInWidth
(DataWidth
+ GainScaleWidth
),
.DataOutWidth
(DataWidth
)
)U_FilterDataIn
(
.DataIn
({{(GainScaleWidth
- CoeffientWidth
){DataP
[DataWidth
+ CoeffientWidth
- 1 ]}} ,DataP
[DataWidth
+ CoeffientWidth
- 1 :0 ]}),
.OverFlow
(DataP
[DataWidth
+ CoeffientWidth
- 1]),
.DataOut
(DataRoundOut
)
);
RoundData
#(
.DataInWidth
(DataWidth
+ CoeffientWidth
-2 ),
.DataOutWidth
(DataWidth
)
)U_DENround
(
.DataIn
(DataP
[DataWidth
+ CoeffientWidth
- 3 :0 ]),
.OverFlow
(DataP
[DataWidth
+ CoeffientWidth
- 2]),
.DataOut
(DataRoundOut2
)
);
MultAdder20x18x40 U_MultSub
(
// .CLK(Clk),
// .CE(1'b1),
// .SCLR(1'b0),
.A
(DataA
),
.B
(DataB
),
.C
(DataC
),
.SUBTRACT
(SUBTRACT
),
.P
(DataP
),
.PCOUT
(PCOUT
));
endmodule
顶层文件:
module IIRLowPass12500K2Sections
(
input Clk
,
input Reset
,
input DataInValid
,
input [19:0] DataIn
,
output DataOutValid
,
output [19:0] DataOut
);
parameter signed [17:0] scaleconst1
= 18'b010010101111011011; //sfix18_En18
parameter signed [17:0] coeff_b1_section1
= 18'b010000000000000000; //sfix18_En16
parameter signed [17:0] coeff_b2_section1
= 18'b100000001100000111; //sfix18_En16
parameter signed [17:0] coeff_b3_section1
= 18'b010000000000000000; //sfix18_En16
parameter signed [17:0] coeff_a2_section1
= 18'b100000101001110100; //sfix18_En16
parameter signed [17:0] coeff_a3_section1
= 18'b001111011001101111; //sfix18_En16
parameter signed [17:0] scaleconst2
= 18'b000100001010000011; //sfix18_En18
parameter signed [17:0] coeff_b1_section2
= 18'b010000000000000000; //sfix18_En16
parameter signed [17:0] coeff_b2_section2
= 18'b100001000101100101; //sfix18_En16
parameter signed [17:0] coeff_b3_section2
= 18'b010000000000000000; //sfix18_En16
parameter signed [17:0] coeff_a2_section2
= 18'b100001111010010010; //sfix18_En16
parameter signed [17:0] coeff_a3_section2
= 18'b001110001010010000; //sfix18_En16
parameter DataWidth
= 20;
parameter CoeffientWidth
= 18;
parameter GainScaleWidth
= 18;
wire signed [DataWidth
- 1 :0] SectionTemp
;
wire VaildTemp
;
IIROrder2FormI
#(
.DataWidth
(DataWidth
),
.CoeffientWidth
(CoeffientWidth
),
.GainScaleWidth
(GainScaleWidth
),
.Gain
(scaleconst1
),
.NumCoeffient
({coeff_b1_section1
,coeff_b2_section1
,coeff_b3_section1
}),
.DenCoeffient
({coeff_a2_section1
,coeff_a3_section1
})
)
Section1
(
.Clk
(Clk
),
.Reset
(Reset
),
.DataInValid
(DataInValid
),
.DataIn
(DataIn
),
.DataOutValid
(VaildTemp
),
.DataOut
(SectionTemp
)
);
IIROrder2FormI
#(
.DataWidth
(DataWidth
),
.CoeffientWidth
(CoeffientWidth
),
.GainScaleWidth
(GainScaleWidth
),
.Gain
(scaleconst2
),
.NumCoeffient
({coeff_b1_section2
,coeff_b2_section2
,coeff_b3_section2
}),
.DenCoeffient
({coeff_a2_section2
,coeff_a3_section2
})
)
Section2
(
.Clk
(Clk
),
.Reset
(Reset
),
.DataInValid
(VaildTemp
),
.DataIn
(SectionTemp
),
.DataOutValid
(DataOutValid
),
.DataOut
(DataOut
)
);
endmodule