数字滤波器
数字滤波器从实现结构上划分,有FIR和IIR两种。FIR的特点是:线性相位、消耗资源多;IIR的特点是:非线性相位、消耗资源少。由于FIR系统的线性相位特点,设计中绝大多数情况都采用FIR滤波器。
线性相位系统的意义,这里的线性相位指的是在设计者关心的通带范围内,LTI系统满足线性相位要求:
- 从延时的角度看:保证了输入信号的相位响应是线性的,即保证了输入信号的延时特性。
- 从相位的角度看:输入的各频率成分的信号之间,相对相位是固定的。通过线性相位系统后,相对相位关系保持不变。
对于关心相位的系统,比如调制解调系统,需要使用FIR滤波器;对于只关心频率成分的系统,比如只是提取某一频率分量,为了节省资源,使用IIR滤波器即可。
1、 FIR滤波器基本知识
FIR的最大特点就是其系统响应 h(n)是一个N点的有限长序列,FIR的输出y(n)本质上就是输入信号x(n)和h(n)的卷积(根据傅里叶变换性质,时域卷积等于频域相乘,因此卷积相当于筛选频谱中的各频率分量的增益倍数,某些频率分量保留,某些频率分量衰减,从而实现滤波效果)。FIR在实现上的本质是带抽头延迟的加法器和乘法器的组合,每一个乘法器对应一个系数。由理论知识可知,只有当FIR的h(n)对称时,FIR滤波器才具有线性相位特性。使用MATLAB等工具设计FIR时,得到的h(n)也都是具有对称性的。
FIR滤波器的实现结构主要有直接型、级联型、频率取样型、格型四种。其中最适合FPGA实现的是直接型。“直接”是指直接由卷积公式得到:
首先由MATLAB生成一个由三个正弦波叠加的待滤波信号,三个正弦波的频率分别是1KHZ,3KHZ,4KHZ。然后将待滤波信号送入Modesim仿真,观察滤波后的波形,再利用MATLAB里面的conv函数将滤波器系数和待滤波信号卷积并观察滤波后的波形图,将Modesim的仿真结果和MATLAB的仿真结果比较并验证在FPGA中滤波器算法的正确性。
2、FIR滤波器原理
在学习数字信号处理时,滤波器是重点,数字滤波器有很多种,比如FIR、IIR、LMS等滤波算法,FIR (Finite Impulse Response)滤波器的特点是它的冲击响应是有限的,它跟过去的信号无关,所以在使用时容易实现,速度快。
要理解FIR滤波器,需要知道信号的频域跟时域的关系,信号的频谱就是信号在频域上的表现形式,如果一个信号由2个正弦波叠加(图1)而成,我们在时域是看不出什么规律的,将信号进行傅里叶变换到频域(图2),我们就可以
很清楚的看到该信号是由2个正弦波叠加而成的。
如果我们要对图1这个信号进行滤波,从频域上看,将信号的频谱乘以图4所示的矩形波,结果的频谱很显然就只剩下了低频段的这个正弦波,那么我们知道在频域和一个矩形波相乘就可以将高频滤出,在时域怎么做呢?我们学过信号与系统,知道频域卷积定理,这个定理的内容就是说:两个信号在频域相乘,那么在时域就相当于卷积,在时域相乘,在频域就相当于卷积。知道了这个定理,我们将图4的频域信号反变换到时域,变成图3所示的信号,我们将这个信号和图1的信号进行卷积,得到的结果就是图5所示的波形,这个波形的频谱如图6所示。这样我们便完成了对信号的滤波。MATLAB中的FDAtool就是用来根据需要的滤波器生成图3所示的滤波器系数。
3、MATLAB生成信号
3.1滤波器系数设计
在MATLAB中输入fdatool即可打开滤波器设计工具,如图7所示。里面可以设置滤波器的类型,采样频率,截止频率等。本设计设置的参数如图8所示。
图7
图8
然后将此滤波器系数导出,然后用以下命令将系数放大、取整:
>> Num
Num =
-0.0325 -0.0384 0.0784 0.2874 0.3984 0.2874 0.0784 -0.0384 -0.0325
>> Num=round(Num*256)//将系数放大并取整
Num =
-8 -10 20 74 102 74 20 -10 -8
最终生成的系数Num即可用于FPGA进行FIR滤波器实现。
3.2 待滤波信号的设计
本设计用于仿真的输入波形是三个正弦波叠加而成,分别是1KHZ、3KHZ、4KHZ。下面是用于生成待滤波信号的m文件内容:
%*********产生.data文件 用于FPGA仿真************%
Fs = 10000; %采样频率决定了两个正弦波点之间的间隔
N = 4096; %采样点数
N1 = 0 : 1/Fs : N/Fs-1/Fs;
s = sin(1000*2*pi*N1) + sin(3000*2*pi*N1) +sin(4000*2*pi*N1);%三种正弦波
fidc = fopen('E:\my_project\vivado_project\FIR\mem.txt','wb'); %将结果写入mem.txt文件,便于modesim使用
for x = 1 : N
A = round(s(x)*20);
if (A >= 0)
bin_x = dec2bin(A, 8); % 正数的反码和补码都和原码一样
fprintf(fidc,'%s\n',bin_x);
else
bin_x = dec2bin(2^8 + A, 8);
fprintf(fidc,'%s\n',bin_x);
end
end
fclose(fidc);
4、FPGA实现FIR算法
实现FIR滤波器的过程其实就是实现卷积的过程,卷积的公式如下,从如下公式
module FIR#(
parameter WIDTH = 8
)
(
input I_clk,
input I_rst_p,
input signed [WIDTH -1:0] I_data,
output signed [2*WIDTH-1:0] O_data
);
wire signed [7:0] coeff1 = -8;//matlab fir生成系数 * 256
wire signed [7:0] coeff2 = -10;
wire signed [7:0] coeff3 = 20;
wire signed [7:0] coeff4 = 74;
wire signed [7:0] coeff5 = 102;
reg signed [WIDTH -1:0] sample_0;
reg signed [WIDTH -1:0] sample_1;
reg signed [WIDTH -1:0] sample_2;
reg signed [WIDTH -1:0] sample_3;
reg signed [WIDTH -1:0] sample_4;
reg signed [WIDTH -1:0] sample_5;
reg signed [WIDTH -1:0] sample_6;
reg signed [WIDTH -1:0] sample_7;
reg signed [WIDTH -1:0] sample_8;
//--------------------------------------加法
reg signed [WIDTH:0] add_data_0;//9 bit
reg signed [WIDTH:0] add_data_1;
reg signed [WIDTH:0] add_data_2;
reg signed [WIDTH:0] add_data_3;
reg signed [WIDTH:0] add_data_4;
reg signed [2*WIDTH:0] mult_1;//17 bit
reg signed [2*WIDTH:0] mult_2;
reg signed [2*WIDTH:0] mult_3;
reg signed [2*WIDTH:0] mult_4;
reg signed [2*WIDTH:0] mult_5;
reg signed [2*WIDTH+1:0] add_level_1;// 18 bit
reg signed [2*WIDTH+1:0] add_level_2;
reg signed [2*WIDTH+1:0] add_level_3;
wire signed [2*WIDTH+3:0] data_out;//20 bit
//====================================================输入数据,移位寄存器
always@(posedge I_clk or posedge I_rst_p)
begin
if(I_rst_p)
begin
sample_1 <= 'h0;
sample_2 <= 'h0;
sample_3 <= 'h0;
sample_4 <= 'h0;
sample_5 <= 'h0;
sample_6 <= 'h0;
sample_7 <= 'h0;
sample_8 <= 'h0;
end
else
begin
sample_0 <= I_data;
sample_1 <= sample_0;
sample_2 <= sample_1;
sample_3 <= sample_2;
sample_4 <= sample_3;
sample_5 <= sample_4;
sample_6 <= sample_5;
sample_7 <= sample_6;
sample_8 <= sample_7;
end
end
//=============================================fir系数对称 add data 思想:共享资源
always@(posedge I_clk or posedge I_rst_p)
begin
if(I_rst_p)
begin
add_data_0 <= 'h0;
add_data_1 <= 'h0;
add_data_2 <= 'h0;
add_data_3 <= 'h0;
add_data_4 <= 'h0;
end
else
begin
add_data_0 <= sample_0 + sample_8;
add_data_1 <= sample_1 + sample_7;
add_data_2 <= sample_2 + sample_6;
add_data_3 <= sample_3 + sample_5;
add_data_4 <= {sample_4[WIDTH -1],sample_4} ;
end
end
//===========================================乘法
always@(posedge I_clk or posedge I_rst_p)
begin
if(I_rst_p)
begin
mult_1 <= 'h0;
mult_2 <= 'h0;
mult_3 <= 'h0;
mult_4 <= 'h0;
mult_5 <= 'h0;
end
else
begin
mult_1 <= add_data_0 * coeff1;
mult_2 <= add_data_1 * coeff2;
mult_3 <= add_data_2 * coeff3;
mult_4 <= add_data_3 * coeff4;
mult_5 <= add_data_4 * coeff5;
// mult_5 <= sample_4 * coeff5;
end
end
//==========================================================累加
always@(posedge I_clk or posedge I_rst_p)
begin
if(I_rst_p)
begin
add_level_1 <= 'h0;
add_level_2 <= 'h0;
add_level_3 <= 'h0;
end
else
begin
add_level_1 <= mult_1 + mult_2;
add_level_2 <= mult_3 + mult_4;
add_level_3 <={mult_5[2*WIDTH],mult_5} ;
end
end
assign data_out = add_level_1 + add_level_2 + add_level_3;
assign O_data = data_out[15:0];
endmodule
基本使用流水线结构,避免在关键路径上算法速度受影响。在学习使用Veriog来实现FIR的过程中,对于有符号数的乘法的掌握很重要。 add_data_4 <= {sample_4[WIDTH -1],sample_4} ,在需要扩位的时候,如果是负数,则最高位和中间位都为1;如果是整数,都为0;
5、测试和对比
使用MATLAB生成一个1khz+3kHz+4KHz的混合频率信号,写入txt文件。注意,输入信号定义为signed,如果MATLAB生成的数据为正数且数据较(最高位为1),在FPGA中会被认为是负数来出来,从而引发错误(因为这个问题博主用了一天来找bug).Matlab代码如下:
Fs = 10000; %采样频率决定了两个正弦波点之间的间隔
N = 4096; %采样点数
N1 = 0 : 1/Fs :N/Fs-1/Fs;
in =round((sin(1000*2*pi*N1) + sin(3000*2*pi*N1) + sin(4000*2*pi*N1))*20);
coeff =[-8,-10,20,74,102,74,20,-10,-8];
out =conv(in,coeff);%卷积滤波
%==========================================
fidc = fopen('E:\my_project\vivado_project\FIR\out.txt','wt'); %将结果写入out.txt文件,便于和modesim数据对比
for x = 1 :4104
%fprintf(fidc,'%s\n',num2str(out(x))/256);
fprintf(fidc,'%d\n',(out(x)));
end
fclose(fidc);
subplot(2,1,1);
plot(in);
xlabel('滤波前');
axis([0 200 -150 150]);
subplot(2,1,2);
plot(out);
xlabel('滤波后');
axis([100 200 -5000 5000]);
MATLAB仿真结果如下:
编写Testbench读取txt文件对信号滤波,使用$readmemb;
module tb_FIR(
);
reg I_clk;
reg [7:0] I_data;
reg I_rst_p;
reg [7:0] mem[1:4096];
wire [15:0] O_data;
//wire [15:0] O_data;
reg [12:0] addr;
FIR FIR_inst(
.I_clk(I_clk),
.I_data(I_data),
.I_rst_p(I_rst_p),
.O_data(O_data)
);
initial
begin
$readmemb("E:/my_project/vivado_project/FIR/mem.txt",mem);//将待滤波信号读入mem
I_rst_p= 1;
I_clk= 0;
#50;
I_rst_p= 0;
#50000;
$stop;
end
initial
forever
#10 I_clk = ~I_clk;
always@(posedge I_clk or posedge I_rst_p)
if(I_rst_p)
I_data <= 8'b0 ;
else
I_data <= mem[addr];
always@(posedge I_clk or posedge I_rst_p)
if(I_rst_p)
addr <= 12'd0;
else
addr <= addr + 1'd1;
endmodule
使用Vivado自带的仿真工具仿真如下:
对比MATLAB仿真的结果,两者完全一样,验证了在FPGA中的FIR滤波器算法。