查过很多资料,对于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