串行FIR滤波器---Verilog设计

数字滤波器

数字滤波器从实现结构上划分,有FIR和IIR两种。FIR的特点是:线性相位、消耗资源多;IIR的特点是:非线性相位、消耗资源少。由于FIR系统的线性相位特点,设计中绝大多数情况都采用FIR滤波器。

线性相位系统的意义,这里的线性相位指的是在设计者关心的通带范围内,LTI系统满足线性相位要求:

  1. 从延时的角度看:保证了输入信号的相位响应是线性的,即保证了输入信号的延时特性。
  2. 从相位的角度看:输入的各频率成分的信号之间,相对相位是固定的。通过线性相位系统后,相对相位关系保持不变。

对于关心相位的系统,比如调制解调系统,需要使用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实现的是直接型。“直接”是指直接由卷积公式得到:
串行FIR滤波器---Verilog设计_第1张图片

![这里写图片描述](https://img-blog.csdn.net/20180811230713498?watermark/2/text/aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2FsYW5nYWl4aWFveGlhbw==/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70)
![这里写图片描述](https://img-blog.csdn.net/20180811230737326?watermark/2/text/aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2FsYW5nYWl4aWFveGlhbw==/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70) 本设计是基于FPGA实现一个8阶的FIR数字低通滤波器。本次设计首先利用MATLAB中的FDAtool工具设计出一个采样频率为10KHZ、截止频率为3KHZ的FIR低通滤波器,,通过FDAtool导出8点系数,然后将系数放大256倍,以便于在FPGA中使用,最后通过Xilinx的Vivado进行Verilog语言编写滤波器算法,然后通过仿真结果和MATLAB仿真结果的比较来验证该滤波器的正确性。系统的总体设计框图如下:
![这里写图片描述](https://img-blog.csdn.net/20180811230344978?watermark/2/text/aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2FsYW5nYWl4aWFveGlhbw==/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70)

首先由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仿真结果如下:


串行FIR滤波器---Verilog设计_第2张图片

编写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自带的仿真工具仿真如下:


串行FIR滤波器---Verilog设计_第3张图片

对比MATLAB仿真的结果,两者完全一样,验证了在FPGA中的FIR滤波器算法。

你可能感兴趣的:(FPGA-Verilog)