赞
踩
对于FIR滤波器主要涉及到滤波器的设计和滤波器的实现,设计和实现的区别如下图所示:
实现是 forward problem,设计是 inverse problem
What are inverse problems?
本文主要涉及到FIR滤波器的实现,在实现的过程中,h[k]都是已知的,而h[k]的求解一般是设计的过程。具体内容包括,FIR滤波器的基本原理,串行FIR滤波器设计(此设计为滤波器实现的“设计”和FIR滤波器的设计不同,自行理会),并行FIR滤波器设计,串并FIR滤波器设计,分布式FIR滤波器设计,快速卷积型 FIR 滤波器、多通道 FIR 滤波器、多频响 FIR 滤波器。对于快速卷积型 FIR 滤波器、多通道 FIR 滤波器、多频响 FIR 滤波器会简单介绍,其中串行、并行、串并、分布式FIR滤波器设计会给出相应源码和仿真模型,如果条件允许会抽出一个源码在FPGA上运行,并进行实验分析。
对于基本原理,有不懂得或者忘记得,强烈建议去MOOC上看下数字信号处理(北交大 陈后金老师)的视频(注册免费观看),很经典,老师讲解的非常不错,还有相关习题可以练习。
总结下:
长度(抽头数)为N、阶数为 N-1 的 FIR 系统的转移函数、差分方程和单位冲激响应分别如式(2.1)、式(2.2)和式(2.3)所示。
H和Xn分别称为系数向量和输入向量。则式(2.2)可进一步表示为
从以上表达式可以看出,H(z)实际上是z-1的多项式,它的 N-1 个极点全部位于 z = 0处,所以一个 FIR 滤波器始终是稳定的。它的零点分布可以处于有限 Z 平面的任何位置,当全部零点位于单位圆内时,就成为最小相位系统。仅有零点存在也正是 FIR 滤波器被称作全零点滤波器的原因。
定义一次滤波运算:由当前输入和过去输入计算当前输出。式 (2.2) 表明完成一次滤波运算需要 N 次乘法和 N-1 次加法。
FIR 滤波器的两种结构:直接型和转置型,两者都是从时域角度描述的。
根据上一节介绍,画出四抽头FIR滤波器直接型结构。
基于式 (2.2) 的卷积形式,FIR 滤波器数据流的动态变化过程如图 2.2 所示。这里以 4抽头为例说明。
根据图 2.2,可将 FIR 滤波器视为一个数据窗,只对位于窗内的数据进行乘加操作以完成滤波运算。串行 FIR 滤波器的设计思想是只用一个乘法器和一个累加器按时间顺序依次完成一次滤波运算所需的N1次乘法和 N-1 次加法。设计的关键环节是延时线,这可通过移位寄存器或者单端口 RAM 来实现。
基于移位寄存器的串行 FIR 滤波器其设计思想是输入数据的动态流动是以自身的流动而非通过地址的改变实现的。对图 2.2 进一步分析可知,每完成一次滤波运算,输入数据沿数据窗推进一格即总有一个新的数据进入数据窗并占据数据窗的第一个位置,数据窗内的原有数据整体向前推移一个系数位,且总有一个数据移出数据窗。以fs表示输入数据采样率,它反映了输入数据进入数据窗的速率;以fclk 表示滤波运算的工作速率,它反映了乘法器的工作速率。定义输入数据的生命周期是指输入数据从进入数据窗至移出数据窗所持续的时间。例如,图2.2所示的输入数据x(0)的生命周期为4 Tclk(其中Tclk=1/fclk)。对于N抽头FIR滤波器,一次滤波运算需要N次乘法运算,故fs和fclk之间满足下式所示的关系式。
输入数据的这种流动特性可采用如图 2.3 所示的移位寄存器实现。在 Xilinx 7 系列FPGA 中,该模型可用查找表实现,可调用原语( Primitive) SRL16E。此外,也可调用 LogiCORE IP RAM-Based Shift Register 该结构具有很强的扩展性,这里仅以深度为 4 举例说明。
图 2.3 中, ce 作为写数据使能端,当其为高时将前级输出数据写入下一级。显然 ce 的周期决定了数据的持续时间,反映了数据的生命周期。 din 作为输入数据端,所有的输入数据由此依时间顺序进入移位寄存器。 addr 作为数据选择器 MUX 的控制端,决定了输出数据来自于哪个寄存器,显然 addr 的变化速率决定了输出数据率。 dout 是移位寄存器的输出数据端。 可见, 该结构具有 “FIFO 进 RAM 出” 的特点即数据总是由初级寄存器进入, 而输出取决于地址控制端。 需要注意的是, 最后一级延时单元通常采用寄存器实现, 其余延时单元采用查找表实现。 由于查找表本身并没有复位端口, 因此, 若该结构带复位功能, 那么只能复位末级寄存器。
基于移位寄存器的串行 FIR 滤波器硬件结构如图 2.4 所示。 整个系统由移位寄存器、系数 ROM 控制单元和乘加器构成。 实际应用时, 为了流水处理通常在移位寄存器 MUX的输出端增加一级寄存。系数存放在 ROM 中,可根据实际情况选择分布式 RAM( Distributed RAM ) 或 Block RAM 图中虚线框部分可用 Xilinx 7 系列 FPGA 中的 DSP48E1实现,对其进行动态配置完成乘加与乘累加运算之间的切换, 这可由图中的 load 信号控制。也可以将乘法运算与累加运算分别采用独立模块实现。 滤波运算最终结果 firout 由末级捕获寄存器的使能信号( 也称为捕获信号) 使能输出。
图 2.4 所示各信号之间的时序关系如图 2.5 所示。
此处以 4 抽头 FIR 滤波器为例。显然, addr 是一个模值为 4 的计数器, 由 sclr 清零, 既是 MUX 的控制信号又是系数 ROM 的读地址信号。 ce 由 addr 译码产生, 延时 3 个时钟周期作为 capture 信号。 这缘于信号由乘加器输入端 A 至输出端 P 共需要 3 个时钟周期。 由于此设计中 ce 周期为 4Tclk,将 其 延 时 3 个时钟周期将与原始信号重合, 故 capture 即为 ce。SRL16E 本身并没有复位端口, 这里的 sclr是 SRL16E 输出端口所添加流水寄存器的复位信号。
在设计时还要考虑加法器的位宽, 以保证累加运算不溢出。 假定输入数据位宽为B, 滤波器系数位宽为 C,抽头数为 N, 那么输入数据与系数相乘的结果位宽为 B+C, 累加 H次,故输出数据位宽为
其中, ⌈ ⌉表示向上取整。式(2.8) 尽管可以保证不溢出,但却造成了硬件资源的浪费。从另一个角度考虑,输出数据的最大值为
只要保证位宽满足ymax 的需求,即可确定加法器的位宽为:
结论:
FIR滤波器设计方法是以直接逼近所需离散时间系统的频率响应为基础。设计方法包括窗函数法和最优化方法(等同纹波法),其中窗函数方法是设计FIR数字滤波器是最常用的方法之一。
在时域中,FIR滤波器的输入/输出就是一个输入信号与单位脉冲相应的卷积。离散方程为y(n)=x(n)*h(n)=∑x(k)h(n-k)=∑h(k)x(n-k),其中y(n)为滤波输出,x(n)为采样数据,h(n)为滤波抽头系数.设计FIR滤波器就是要找到N个系数。N-1阶滤波器通常需要N个系数描述,通常需要N个乘法器和N-1个2输入加法器实现。根据FIR表达式,滤波器实质上就是进行乘累加运算,乘累加的次数由滤波器阶数决定。其串行结构如图
系数表的产生我们利用matlab的FDATool来完成,也可以自己进行参数,利用FDATool比较方便。
详细使用教程: https://blog.csdn.net/Pieces_thinking/article/details/83656785
首先打开matlab的FDATool工具,选择一个8阶的低通滤波,通频率为500Hz,截止频率为750Hz。采样频率2000Hz。
FDATool界面左下侧排列了一组工具按钮,其功能分别如下所述:
选择其中的选择Design Filter按钮,进入设计滤波器界面,进行下列选择,如图所示。滤波器类型(Filer Type)为低通(Low Pass)
设计方法(Design Method)为FIR/IIR,分别采用Equiripple、Least-squares、Window、Constr.LeastPth-norm 、Constrained Equiripple、Constr.Band Equiripple(FIR滤波器设计)和 Butterworth、Chebyshev Type I、Chebyshev Type I、Elliptic、Maximally flat、Least Pth-norm、Constr.LeastPth-norm(IIR滤波器设计)。
在我们设置好FDATool的参数后,我们可以点击Analysis–>Filter Coefficients来观察FIR滤波器的系数。
这里的系数全是有符号型的小数,我们在FPGA中需要用整数作为滤波器的系数,所以我们要进行系数的归一化,点击左下角设置量化参数(Set Quantization Parameters),Filter arithmetic选择Fixed-point(定点)。然后就可以导出Xilink的.coe文件了。Targets–>XILINK Coefficients(.COE) File.保存后matlab自动打开该.coe文件。
在Matlab中编写以下文件并运行:
function [y,yref] = Serial_Shift_registers_FIR(x,h)
% x: input data vector
% h: fir coef [h(0),h(l),....h(N-l)]
x_len = length(x);
h_len = length(h);
y = zeros(x_len,1);
tap_delay = zeros(h_len,1);
for k=1:x_len
tap_delay = [x(k);tap_delay(1:h_len-1)];
for n=1:h_len
y(k) = y(k)+tap_delay(n)*h(n);
end
end
yref = filter(h,1,x);
输入以下语句运行
Serial_Shift_registers_FIR([10,11,12,13,14,15,16,17,18,19,20],[252,242,37,79,37,242,252,7,252])
结果:
针对以上分析,编写Verilog文件如下:
`timescale 1ns / 1ps // // Company: // Engineer: // // Create Date: 2018/11/19 09:11:33 // Design Name: // Module Name: Serial_Shift_registers_FIR // Project Name: // Target Devices: // Tool Versions: // Description: // // Dependencies: // // Revision: // Revision 0.01 - File Created // Additional Comments: // // module Serial_Shift_registers_FIR( Clk,Rst_n,data_in,data_out ); parameter word_data_in = 8; parameter word_data_out = 2*word_data_in+1; parameter order = 8; parameter c0 = 8'hfc; parameter c1 = 8'hf2; parameter c2 = 8'h25; parameter c3 = 8'h4f; parameter c4 = 8'h25; parameter c5 = 8'hf2; parameter c6 = 8'hfc; parameter c7 = 8'h07; parameter c8 = 8'hfc; input Clk; input Rst_n; input [word_data_in-1:0]data_in; output wire [word_data_out-1:0]data_out; reg [word_data_in-1:0]samples[order-1:0]; reg [word_data_in-1:0]data_in_tmp1; reg [word_data_in-1:0]data_in_tmp2; // wire [word_data_out-1:0]data_out=0; integer i; always@(posedge Clk) if(!Rst_n)begin data_in_tmp1<=8'd0; data_in_tmp2<=8'd0; end else begin data_in_tmp1<=data_in; data_in_tmp2<=data_in_tmp1; end always@(posedge Clk)//同步复位,完成移位功能 if(!Rst_n)begin for(i=0;i<order;i=i+1) samples[i]<=0; end else begin samples[0]<=data_in_tmp2; for(i=1;i<order;i=i+1) samples[i]<=samples[i-1]; end //串行实现累加 assign data_out = c0*data_in_tmp2 + c1*samples[0] + c2*samples[1] + c3*samples[2] + c4*samples[3] + c5*samples[4] + c6*samples[5] + c7*samples[6] + c8*samples[7]; endmodule
和matlab文件类似,只不过将滤波器系数直接添加到文件中了,可以根据需求自己修改滤波器系数,这种结构的写法,很方便很好理解,就是移位相乘、相加,编写TestBench,如下:
`timescale 1ps / 1ps // // Company: // Engineer: // // Create Date: 2018/11/19 09:51:23 // Design Name: // Module Name: SSRFIR_tb // Project Name: // Target Devices: // Tool Versions: // Description: // // Dependencies: // // Revision: // Revision 0.01 - File Created // Additional Comments: // // module SSRFIR_tb( ); parameter word_data_in = 8; parameter word_data_out = 2*word_data_in+1; reg Clk; reg Rst_n; reg [word_data_in-1:0]data_in; wire [word_data_out-1:0]data_out; Serial_Shift_registers_FIR u1 ( .Clk(Clk), .Rst_n(Rst_n), .data_in(data_in), .data_out(data_out) ); // Results reg [15:0] cnt; reg signed [15:0] e [0:10]; // Coefficient array initial begin #0 Clk = 1'b0; data_in <=0; e[0]=10; e[1]=11; e[2]=12; e[3]=13; e[4]=14; e[5]=15; e[6]=16; e[7]=17; e[8]=18; e[9]=19; e[10]=20; #10 Rst_n = 1'b0; #20 Rst_n = 1'b1; cnt <=0; #1000000 $stop; end always #10 begin Clk = ~Clk; end always @ (posedge Clk) begin if(!Rst_n) cnt <= 1'b0; else begin if(cnt == 16'd10000-1) cnt = 1'b0; else cnt = cnt + 1'b1; end end always @ (posedge Clk) begin case (cnt) 20: data_in <= 0; 21: data_in <=e[0]; 22: data_in <=e[1]; 23: data_in <=e[2]; 24: data_in <=e[3]; 25: data_in <=e[4]; 26: data_in <=e[5]; 27: data_in <=e[6]; 28: data_in <=e[7]; 29: data_in <=e[8]; 30: data_in <=e[9]; 31: data_in <=e[10]; 32: data_in <=1'b0; default: data_in<=data_in; endcase end endmodule
利用Vivado和Modsim仿真,结果如下图,和MATLAB的结果一样。
以上设计方法,主要是为了直观理解基于移位寄存器的串行 FIR 滤波器设计,实际使用过程中由于浪费的资源过多,很少使用,下面介绍一个优化后的滤波器设计(参考《数字滤波器的MATLAB与FPGA实现》),如下:
`timescale 1ns / 1ps //---------------------------------------------------------- // 全串行FIR滤波器 // //---------------------------------------------------------- module Serial_Shift_registers_FIR ( input rst, input clk, //系统时钟16kHz input signed [11:0] Xin, //输入数据频率2kHz output signed [28:0] Yout ); //---------------------------------------------------------- // 实例化乘法器和加法器IP核 //---------------------------------------------------------- reg signed [12:0] add_a,add_b; wire signed [12:0] add_s; c_addsub_1 add ( .A (add_a), .B (add_b), .S (add_s) ); reg signed [11:0] coe; //12bit量化滤波器系数 wire signed [24:0] Mout; mult_gen_1 mult ( .CLK (clk), .A (coe), .B (add_s), .P (Mout) ); //---------------------------------------------------------- // 模8计数器,控制输入数据移位和滤波结果输出 //---------------------------------------------------------- reg [2:0] cnt; reg [11:0] Xin_Reg[15:0]; reg [3:0] i,j; always @ (posedge clk or posedge rst) if (rst) cnt <= 'd0; else cnt <= cnt + 1'b1; always @ (posedge clk or posedge rst) if (rst) begin //清0 Xin_Reg[15] <= 'd0; for (i=0; i<15; i=i+1'b1) Xin_Reg[i] <= 'd0; end else begin if (cnt == 'd7) begin for (j=0; j<15; j=j+1'b1) //移位 Xin_Reg[j+1] <= Xin_Reg[j]; Xin_Reg[0] <= Xin; end end //---------------------------------------------------------- // 计算 //---------------------------------------------------------- always @ (posedge clk or posedge rst) if (rst) begin add_a <= 'd0; add_b <= 'd0; coe <= 'd0; end else begin if (cnt == 'd0) begin //第一个周期 add_a <= {Xin_Reg[0][11],Xin_Reg[0]}; add_b <= {Xin_Reg[15][11],Xin_Reg[15]}; coe <= 12'h000; end else if (cnt == 'd1) begin //第二个周期 add_a <= {Xin_Reg[1][11],Xin_Reg[1]}; add_b <= {Xin_Reg[14][11],Xin_Reg[14]}; coe <= 12'hffd; end else if (cnt == 'd2) begin //第三个周期 add_a <= {Xin_Reg[2][11],Xin_Reg[2]}; add_b <= {Xin_Reg[13][11],Xin_Reg[13]}; coe <= 12'h00f; end else if (cnt == 'd3) begin //第四个周期 add_a <= {Xin_Reg[3][11],Xin_Reg[3]}; add_b <= {Xin_Reg[12][11],Xin_Reg[12]}; coe <= 12'h02e; end else if (cnt == 'd4) begin //第五个周期 add_a <= {Xin_Reg[4][11],Xin_Reg[4]}; add_b <= {Xin_Reg[11][11],Xin_Reg[11]}; coe <= 12'hf8b; end else if (cnt == 'd5) begin //第六个周期 add_a <= {Xin_Reg[5][11],Xin_Reg[5]}; add_b <= {Xin_Reg[10][11],Xin_Reg[10]}; coe <= 12'hef9; end else if (cnt == 'd6) begin //第七个周期 add_a <= {Xin_Reg[6][11],Xin_Reg[6]}; add_b <= {Xin_Reg[9][11],Xin_Reg[9]}; coe <= 12'h24e; end else begin //第八个周期 add_a <= {Xin_Reg[7][11],Xin_Reg[7]}; add_b <= {Xin_Reg[8][11],Xin_Reg[8]}; coe <= 12'h7ff; end end //---------------------------------------------------------- // 对乘法结果进行累加 //---------------------------------------------------------- reg signed [28:0] sum; reg signed [28:0] yout; always @ (posedge clk or posedge rst) if (rst) begin sum <= 'd0; yout <= 'd0; end else begin if (cnt == 'd2) begin yout <= sum; sum <= 'd0; sum <= sum+Mout; end else sum <= sum+Mout; end assign Yout = yout; endmodule
使用MATLAB设计一个2kHz采样,500Hz截止的15阶低通滤波器(h(n)长度为16),量化位数为12bit,输入信号位宽也为12bit。
系统时钟与输入数据频率(即采样频率)相同即可;但是串行结构由于需要n/2倍(此处为8倍)个周期完成一次运算,则FPGA系统时钟需要是采样频率的n/2倍。
实例化全串行FIR滤波器结构所需的乘法器和加法器IP核需要自己添加。
使用MATLAB生成一个200khz+800kHz的混合频率信号,写入txt文件,。编写Testbench读取txt文件对信号滤波。
Xilinx_SerialFIR_tb.v文件
`timescale 1 ns/ 1 ns //设置仿真时间单位:ns module Xilinx_SerialFIR_tb(); reg [11:0] Xin; reg clk; reg rst; reg clk_data; //数据时钟,速率为系统时钟clk的1/8; // wires wire [28:0] Yout; Serial_Shift_registers_FIR u1 ( .Xin(Xin), .Yout(Yout), .clk(clk), .rst(rst) ); parameter clk_period=625; //设置时钟信号周期(频率):1.6MHz parameter clk_period_data=clk_period*8; parameter clk_half_period=clk_period/2; parameter clk_half_period_data=clk_half_period*8; parameter data_num=2000; //仿真数据长度 parameter time_sim=1250000; //仿真时间data_num*clk_period initial begin //设置时钟信号初值 clk=1; clk_data=1; //设置复位信号 rst=1; #100 rst=0; //设置仿真时间 #12500000 $finish; //设置输入信号初值 Xin=12'd10; end //产生时钟信号 always #clk_half_period clk=~clk; always #clk_half_period_data clk_data=~clk_data; //从外部TX文件(SinIn.txt)读入数据作为测试激励 integer Pattern; reg [11:0] stimulus[1:data_num]; initial begin $readmemb("G:/USE/FILE/DSP/FPGA/Serial_Shift_registers_FIR_B/sin.txt",stimulus); Pattern=0; repeat(data_num) begin Pattern=Pattern+1; Xin=stimulus[Pattern]; #clk_period_data;//数据周期为时钟周期的8倍 end end endmodule
发现Yout的幅度变化很低,检测后发现24-29bit都是符号位,将0-24bit提取为一个新的虚拟总线Yout_bus。明显看到经过500Hz低通滤波器滤波后,输入的200+800Hz信号只剩下200Hz的单频信号。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。