前言:复习大学课程中数字信号处理的内容,尽管学了很多理论知识,但与实际的联系还是比较少,也就是说学到的知识不能很好地运用于实践,所以我打算对理论知识进行验证,从MATLAB和FPGA入手,完成滤波器的设计,体会模拟信号数字处理的过程,同时也对这个过程进行记录,方便回顾和完善。

设计一个FIR带通滤波器,截止频率为2400Hz和3600Hz,阻带最小衰减20dB以上,测试信号为1K、2K、3K、4K、5Khz的混频信号,要求3KHz信号顺利通过而过滤其他频率信号。

思路:

1、设计该滤波器,采样频率应该为信号最高频率的2倍以上,我设计的采样频率为40khz,采用凯赛窗设计FIR带通滤波器,通过matlab仿真得到该滤波器的幅频响应:

python 使用带通滤波器后 前面是波 后面是直线 带通滤波器fir_matlab

该滤波器在2KHz处的衰减将近60dB,在1K、4K、5KHz处的衰减也达到了40dB,基本符合设计要求,但为了达到这样的效果,该滤波器的长度被我增加到了64(阶数为63),故它的成本较高。

Matlab代码如下:

fs = 40*10^3;
N = 64;   
window = kaiser(N)';
fp = [2400*2/fs 3600*2/fs];
b = fir1(N-1,fp,'bandpass',window);

2、使用FPGA产生测试信号,使用quartus Ⅱ软件的NCO ip核可以产生不同频率的正弦波信号,下面以1KHz正弦波为例:

python 使用带通滤波器后 前面是波 后面是直线 带通滤波器fir_sed_02

相位累加器为32位宽,角度分辨率位宽16位,幅度位宽10位,输入时钟为板载时钟50M,输出1KHz的正弦信号,完成配置后生成ip核,实例化后产生1Khz的正弦波信号,代码如下:

wire signed [9:0]   sin_1k;
nco      nco_inst1
(
    .out_valid(),
    .fsin_o(sin_1k),
    .phi_inc_i(32'd85_899),
    .reset_n(1'b1),
    .clken(1'b1),
    .clk(sys_clk)
);

生成的波形如下:

python 使用带通滤波器后 前面是波 后面是直线 带通滤波器fir_sed_03

3、verilog代码中采用全串行结构设计FIR滤波器,由于matlab设计出的FIR滤波器一定是线性相位结构,并且具有对称性,故可采用以下结构图设计滤波器:

python 使用带通滤波器后 前面是波 后面是直线 带通滤波器fir_matlab_04

采样率为40KHz,故数据输入频率为40KHz,则滤波器时钟CLK为1.28MHz,该时钟可通过由PLL ip核产生。由于滤波器系数的对称性,故只需要对32个滤波器系数与输入数据进行累加乘运算就可以实现滤波功能

python 使用带通滤波器后 前面是波 后面是直线 带通滤波器fir_乘法器_05

          

python 使用带通滤波器后 前面是波 后面是直线 带通滤波器fir_数据_06

为了实现该算法,首先需要生成一个乘法器ip核,完成乘法运算,配置如下:

python 使用带通滤波器后 前面是波 后面是直线 带通滤波器fir_fpga开发_07

该乘法器输入位宽分别为14bit和12bit,并且为有符号型,例化该乘法器,将加和数据与滤波器系数输入,获得一次乘法输出,代码如下:

mult mult_inst
(
      .clock ( clk ),
      .dataa (dataa),
      .datab ( datab ),

      .result ( Mout )
);

结构图所示的是一个15阶的全串行FIR滤波器结构,而我设计的滤波器为63阶,虽然阶数不同,但核心的算法相同。由于数据输入频率为40KHz,故滤波器时钟频率应该为40KHz * 32 = 1.28MHz,这样才能保证在输入一个新数据前,完成32次乘运算,也就是输出一个y(n)。具体算法如下:

首先,产生一个周期为32的计数器count,每一个CLK上升沿到来,count自增1,故每当count归0,相当于时间经过了40KHz。

verilog代码如下:

reg [4:0]    count; 
always@(posedge clk or negedge sys_rst)
      if(sys_rst == 1'b0)
            count <= 5'd0;
      else
            count <= count + 1'b1;

然后,将新输入的数据保存到寄存器中,这里采用了移位寄存器,保存64个对称的数据,verilog代码如下:

reg signed [12:0]  xin_reg[0:63];
reg [5:0]i,j;
always@(posedge clk or negedge sys_rst)
      if(sys_rst == 1'b0)
            begin
                  for(i=0;i<63;i=i+1)
                       xin_reg[i] <= 13'd0;
            end
      else
            begin
                  if(count == 5'd31)
                       begin
                             for(j=0;j<63;j=j+1)
                                   xin_reg[j+1] <= xin_reg[j];
                                   xin_reg[0]  <= xin;
                       end
            end

这里定义了64给个有符号的13位寄存器,分别存储输入的数据。

接着,完成两个对称数据相加,例如:x(n)+x(n-63),… ,并且将其结果送入乘法器,同时乘法器另一个乘数端口送入对应的滤波器系数,例如h(0),… ,由于代码过长,这里只展示部分,其他同理,verilog代码如下:

always@(posedge clk or negedge sys_rst)
      if(sys_rst == 1'b0)
            begin
                  adda <= 13'd0;
                  addb <= 13'd0;
                  datab <= 12'd0;
            end
      else
            begin
                  if(count == 5'd0)
                       begin
                             adda <= {xin_reg[0][12],xin_reg[0]};
                             addb <= {xin_reg[63][12],xin_reg[63]};
                             datab <= -12'd74;
                       end
                  …

                  …

                  …
            end

assign dataa = adda + addb;

至此,完成了一次乘法器的输出,而滤波器的输出是由32个类似的输出累加得到,由于乘法器的输入位宽分别为14bit和12bit,故输出位宽为26bit,对32个输出累加的话,需要扩展5位,故滤波器输出为31bit的有符号数,verilog代码如下:

reg signed [30:0]  sum;
reg signed [30:0]  Yout;
always@(posedge clk or negedge sys_rst)
      if(sys_rst == 1'b0)
            begin
                  sum <= 31'd0;
                  Yout <= 31'd0;
            end
      else
            if(count == 5'd2)
                  begin
                       Yout <= sum;
                       sum <= Mout;
                  end
            else
                  sum <= sum + Mout;

assign out = Yout;

需要说明的是,因为在count == 0时,给两个加数赋值和给滤波器系数赋值,而真正获得新的数据是在count == 1时,故在count == 1时将两个乘数送入乘法器,而我配置的乘法器有一级流水线,所以乘法器输出在count == 2的时候,通过以上分析,可以明白为什么在count == 2时才会将乘法器输出Mout送给sum,而其他时刻是进行累加运算,至于何时将累加和送给输出呢?同理,由于count的周期为32,所以从count == 2 到count == 31 到 count == 0 ,再到count == 1,刚好完成一个周期,也就是32次累加乘运算,所以在count == 2时(注意D触发器的延时,当count == 2时,此时的sum为count == 1时的sum加上count == 1时的Mout,所以仍然是32个数据的求和,而不是33个数据之和),将滤波数据送出,应该特别注意上面括号中的分析,可以对比波形理解触发器的延时特性。

最后,编写板载测试文件,将滤波器的输出(有符号的31位数据)转换为DA模块的8位无符号DA数据,然后将DA模块时钟和8位数据送到模块中,完成板载输出测试,verilog代码如下:

module boardtst
(
      input wire                   sys_clk             ,
      input wire                   sys_rst             ,

      output wire [7:0]            DA                  ,
      output wire                  DA_clk                  
);


wire signed [30:0] yout;
fullserial_filter u3
(
      .clk         (clk_1p28M)   ,
      .sys_rst     (sys_rst)     ,
      .xin         (sin_wave)    ,          

      .out         (yout)             
);
assign DA = yout[30:23] + 8'd128;
assign DA_clk = sys_clk;

这里要注意的是有符号数据如何转化成无符号数据,因为滤波器输出为31位的有符号输出,而DA模块接收的是8位的无符号型,故应该截取高8位,这相当于将数据右移了23位,然后再加上128,于是完成有符号到无符号的转换。

下面是测试信号与滤波后的信号:

测试信号:

 

python 使用带通滤波器后 前面是波 后面是直线 带通滤波器fir_fpga开发_08

滤波后输出信号:

python 使用带通滤波器后 前面是波 后面是直线 带通滤波器fir_sed_09

python 使用带通滤波器后 前面是波 后面是直线 带通滤波器fir_sed_10

可以看到,虽然将3KHz的信号分离了出来,但波形并不平滑,这是由于采样速率和转换频率较低造成的。

需要补充的是,滤波器系数的量化位宽会影响到滤波器的性能,通过matlab仿真观察:

python 使用带通滤波器后 前面是波 后面是直线 带通滤波器fir_乘法器_11

可以看出,与未量化相比,8bit量化已经很好地满足了滤波器的性能要求,故使用FPGA设计该滤波器时,将乘法器的输入设置成14bit与8bit就已经足够了。

Matlab代码如下:

b8 = round(b/max(abs(b))*(2^7-1));
b12 = round(b/max(abs(b))*(2^11-1));
b14 = round(b/max(abs(b))*(2^13-1));

pm = 20*log10(abs(fft(b,1024)));pm = pm - max(pm);
pm8 = 20*log10(abs(fft(b8,1024)));pm8 = pm8 - max(pm8);
pm12=20*log10(abs(fft(b12,1024)));pm12=pm12-max(pm12;
pm14=20*log10(abs(fft(b14,1024)));pm14=pm14-max(pm14;

x_f = [0:fs/length(pm):fs/2];

mfpm = pm(1:length(x_f));
mfpm8 = pm8(1:length(x_f));
mfpm12 = pm12(1:length(x_f));
mfpm14 = pm14(1:length(x_f));

plot(x_f,mfpm,'-',x_f,mfpm8,'.-',x_f,mfpm12,'x',x_f,mfpm14,'--');
xlabel('hz');ylabel('db');
legend('未量化','8比特量化','12比特量化','14比特量化');
grid;

至此,完成了设计目标,但该实验仍有很多不足,敬请大家批评指正!

参考文献:

[1] 夏宇闻.Verilog数字系统设计教程(第三版).北京:北京航空航天大学出版社,2013

[2] 杜勇.数字滤波器的MATLAB与FPGA实现——Altera/Verilog版(第二版).北京:电子工业出版社,2019