当前位置:   article > 正文

数字信号处理——并行FIR滤波器MATLAB与FPGA实现_4相并行fir滤波器

4相并行fir滤波器

前言

        本文介绍了设计滤波器的FPGA实现步骤,并结合杜勇老师的书籍中的并行FIR滤波器部分进行一步步实现硬件设计,对书中的架构做了复现以及解读,并进行了仿真验证。

并行FIR滤波器FPGA实现

        FIR滤波器的结构形式时,介绍了直接型、级联型、频率取样型和快速卷积型4种。在FPGA实现时,最常用的是最简单的直接型结构。FPGA实现直接型结构的FIR滤波器,可以采用串行结构、并行结构等不同中的结构设计,上文根据书中提供的架构完成了串行 FIR滤波器的实现,本文沿用上文的基本代码结构,按照并行FIR滤波器的架构完成电路描述。

FIR滤波器需求

        设计一个15阶(长度为16)的低通线性相位FIR滤波器,采用窗函数设计,截止频率为500 Hz,采样频率为2 000 Hz;采用FPGA实现并行结构的滤波器,系数的量化位数为12比特,输入数据位宽为12比特,输出数据位宽为29比特,系统时钟为16 kHz。

滤波器系数确定与量化

        确定滤波器的结构后,就根据滤波器进行设计代码仿真,这里引用书中的仿真设计,并将滤波器参数系数量化。确定滤波器系数的方法有很多,可以使用MATLAB中丰富的函数实现,或者使用相关滤波器设计的软件工具,定制满足当前需求的窗函数的滤波器系数。具体量化系数确定可参考上文《数字信号处理-09-串行FIR滤波器MATLAB与FPGA实现》中的相关内容,或者参考杜勇老师的书中的内容。

硬件架构

        下图为杜勇老师的《数字滤波器的MATLAB与FPGA实现》实现的并行FIR滤波器的结构图。因为FIR滤波器参数对称,所以同时计算相应的对称结构的值,将对称系数的X(n)相加后,可调用8个乘法器,完成对滤波器的乘法运算,所以针对并行滤波器的架构数据的输入速率和时钟可以相同,每一个时钟周期流水输出一个滤波后的信号值。图中的8输入的加法器,可以替换成N/2;这样就得到了一个通用化的并行FIR滤波器结构图。

并行FIR滤波器

        并行实现FIR滤波器,虽然浪费了加法器和乘法器的资源,但是提升了整个滤波器实现的性能,当滤波器的系数长度N增大时,数据的吞吐速率不变(暂且不考虑面积增大对性能的影响),但带来的坏处就是会用掉相应倍数的逻辑资源和运算资源,速度和面积本来就是鱼和熊掌的关系,在实际应用中应当做相应的权衡和割舍。

根据架构描述电路

        根据杜勇老师书中提供的架构,对电路进行描述,同样沿用了前文的通用化的模板,后期可根据参数输入来适配不同滤波器长度的设计。

实现模块框图

        接口描述如下:

接口描述

        参数描述如下:

参数描述

代码如下:

  1. `timescale 1ns / 1ps
  2. module Fir_Parallel(
  3.         input clk,//!系统时钟
  4.         input rst,//!复位信号
  5.         input signed [SIGN_IN_WIDTH-1:0] signal_in,//!信号输入
  6.         output signed [SIGN_OUT_WIDTH-1:0] signal_out//!信号输出,信号输出速度和输入速度相同
  7.     );
  8.     //
  9.     parameter  integer SIGN_IN_WIDTH    = 12   ;//!信号输入位宽
  10.     parameter  integer SIGN_OUT_WIDTH = 29   ;//!信号输出位宽
  11.     parameter  integer FIR_COE_WIDTH = 12   ;//!滤波器系数位宽
  12.     parameter  integer FIR_COE_NUM = 16   ;//!滤波器长度
  13.     localparam integer FIR_WIDTH_DIV_2 = FIR_COE_NUM/2 ;
  14.     function [FIR_COE_WIDTH-1:0] coe_data;
  15.     input [FIR_WIDTH_DIV_2-1:0index;
  16.     begin
  17.         case(index)
  18.         'd0:coe_data='h000;
  19.         'd1:coe_data='hffd;
  20.         'd2:coe_data='h00f;
  21.         'd3:coe_data='h02e;
  22.         'd4:coe_data='hf8b;
  23.         'd5:coe_data='hef9;
  24.         'd6:coe_data='h24e;
  25.         'd7:coe_data='h7ff;
  26.         endcase
  27.     end
  28.     endfunction
  29.     integer i;
  30.     genvar j;
  31.     //!滤波器系数加载
  32.     wire signed [FIR_COE_WIDTH-1:0] coe[FIR_WIDTH_DIV_2-1:0]; 
  33.     generate
  34.         for (j=0; j<FIR_WIDTH_DIV_2; j=j+1)
  35.             assign coe[j] = coe_data(j);
  36.     endgenerate
  37.         
  38.     //!寄存输入信号
  39.     reg [SIGN_IN_WIDTH-1:0Sign_in_Reg[FIR_COE_NUM-1:0];
  40.     //将数据存入移位寄存器sign_in_Reg中
  41.    
  42.     always @(posedge clk)begin
  43.         if (rst=='b1)begin
  44.             //初始化寄存器值为0
  45.             for (i=0; i<FIR_COE_NUM; i=i+1)
  46.                 Sign_in_Reg[i]=12'd0;
  47.         end
  48.         else begin
  49.             for (i=0; i<FIR_COE_NUM-1; i=i+1)
  50.                 Sign_in_Reg[i+1<= Sign_in_Reg[i];
  51.             Sign_in_Reg[0<= signal_in;
  52.         end
  53.     end
  54.     reg signed [SIGN_IN_WIDTH:0add_sum[FIR_WIDTH_DIV_2-1:0];
  55.     //为了保证加法运算不溢出,输入输出数据均扩展为SIGN_IN_WIDTH+1比特。
  56.     //对称结构只需要计算FIR_WIDTH_DIV_2
  57.     //一级流水
  58.     always @(posedge clk) begin
  59.         if (rst=='b1)begin
  60.             for (i=0; i<FIR_WIDTH_DIV_2; i=i+1)
  61.                 add_sum[i]<= 'd0;
  62.   end
  63.   else begin
  64.             for (i=0; i<FIR_WIDTH_DIV_2; i=i+1)
  65.                 add_sum[i]<= {Sign_in_Reg[i][SIGN_IN_WIDTH-1],Sign_in_Reg[i]} + 
  66.                              {Sign_in_Reg[FIR_COE_NUM-1-i][SIGN_IN_WIDTH-1],Sign_in_Reg[FIR_COE_NUM-1-i]};
  67.         end
  68.     end
  69.     
  70.     (*use_dsp48="yes"*) reg signed [SIGN_IN_WIDTH+FIR_COE_WIDTH:0] mult_out[FIR_WIDTH_DIV_2-1:0];
  71.     always @(posedge clk ) begin
  72.         if (rst=='b1)begin
  73.             for (i=0; i<FIR_WIDTH_DIV_2; i=i+1)
  74.                 mult_out[i]<= 'd0;
  75.         end
  76.         else begin
  77.             for (i=0; i<FIR_WIDTH_DIV_2; i=i+1)
  78.                 mult_out[i] <= add_sum[i] * coe[i];
  79.         end
  80.     end
  81.    
  82.     assign signal_out = sign_out;
  83.     reg signed [SIGN_OUT_WIDTH-1:0sum;
  84.     reg signed [SIGN_OUT_WIDTH-1:0sign_out;
  85.  always @(posedge clk)begin
  86.         if (rst)begin 
  87.     sum <= 'd0; 
  88.     sign_out <= 'd0;
  89.   end
  90.   else begin
  91.             sign_out <= sum;
  92.             // sum = 'd0;
  93.    // for (i=0; i<FIR_WIDTH_DIV_2; i=i+1)
  94.             // sum =   sum + mult_out[i];
  95.             sum <=  mult_out[0] + mult_out[1] + mult_out[2] + mult_out[3] +
  96.                     mult_out[4] + mult_out[5] + mult_out[6] + mult_out[7];
  97.         end
  98.     end
  99. endmodule

代码解读

        关于加载滤波器系数的部分,我这里使用了function做了包装,以便于后续修改滤波器长度时,可以通过脚本生成function去增加滤波器系数的长度。

  1. function [FIR_COE_WIDTH-1:0] coe_data;
  2.     input [FIR_WIDTH_DIV_2-1:0index;
  3.     begin
  4.         case(index)
  5.         'd0:coe_data='h000;
  6.         'd1:coe_data='hffd;
  7.         'd2:coe_data='h00f;
  8.         'd3:coe_data='h02e;
  9.         'd4:coe_data='hf8b;
  10.         'd5:coe_data='hef9;
  11.         'd6:coe_data='h24e;
  12.         'd7:coe_data='h7ff;
  13.         endcase
  14.     end
  15.     endfunction

        针对乘法运算,这里没有使用IP,但是为了使得该部分运算使用DSP资源,更好地提升性能,因此该信号的运算使用dsp48资源,所以在信号声明时前面加了(*use_dsp48="yes"*)

        关于杜勇老师书中写的信号与系数相乘后的结果针对sum信号使用了阻塞赋值的部分,个人觉得这个在时序逻辑中是不太好的设计,使用的代码如下,虽然会简化乘累加的过程,但是针对实际使用的工程来说,这个是不好的代码风格。

  1. always @(posedge clk)begin
  2. if (rst=='b1)begin
  3. sum = 'd0;
  4. sign_out <= 'd0;
  5. end
  6. else begin
  7. sign_out <= sum;
  8. sum = 'd0;
  9. for (i=0; i<FIR_WIDTH_DIV_2; i=i+1)
  10. sum = sum + mult_out[i];
  11. end
  12. end

        所以这里我直接做了展开处理,将8个结果做了加法。

电路架构优化

        我认为在随着滤波器规模变大运算的数据位宽增加时,信号与系数相乘后的结果进行累加操作的部分,组合逻辑的延时相对会增加很多,为了进一步提升电路架构的性能,可对该部分进行加法树的平衡,打拍优化加法树结构,应该有可能进一步提升电路架构的性能。

仿真设计

仿真数据设计

        为了验证并行设计代码的正确性。这里使用MATLAB脚本产生了一个混频信号,混频的频率为100hz和700hz的叠加,然后将混频信号进行量化处理并导出txt文件以供仿真文件读取。

  1. clc;close all;clear all;
  2.  Fs = 2000; %采样频率
  3. = 2^10; %采样点数
  4. f1=300; %正弦波1频率
  5. f2=400; %正弦波1频率
  6. t=[0:N-1]/Fs; %时间序列
  7. s1 = sin(2*pi*f1*t) ;
  8. s2 = sin(2*pi*f2*t) ;
  9. = s1 .* s2;
  10. figure(1);
  11. subplot(1,2,1);
  12. plot(t,s,'r','LineWidth',1.2);
  13. title('时域波形');
  14. axis([0,100/Fs,-3,3]);
  15. set(gca,'LineWidth',1.2);
  16. %转化为位宽12bit数据
  17. s_12bit=s./max(s).*(2.^11 - 1); % DA输入波形,量化到16bit
  18. s_12bit(find(s_12bit<0) ) = s_12bit(find(s_12bit<0) ) + 2^12 - 1;
  19. s_12bit = fix(s_12bit);
  20. s_12bit = dec2hex(s_12bit);
  21. % %生成文件
  22. fid= fopen('sin_data.txt','w+');
  23. %生成十六进制
  24. for i=1:N
  25.     fprintf(fid,'%s',s_12bit(i,:));
  26.     fprintf(fid,'\r\n');
  27. end
  28. fclose(fid);
  29. %% 设计验证
  30. N=16;      %滤波器长度
  31. fs=2000;   %采样频率
  32. fc=500;    %低通滤波器的截止频率
  33. B=12;      %量化位数
  34. %生成各种窗函数
  35. w_kais=blackman(N)';
  36. %采用fir1函数设计FIR滤波器
  37. b_kais=fir1(N-1,fc*2/fs,w_kais);
  38. ss=conv(b_kais,s);
  39. subplot(1,2,2);
  40. plot(t(20:1000),ss(20:1000));
  41. title('滤波后信号');
  42. axis([0,100/Fs,-1,1]);
  43. set(gca,'LineWidth',1.2);

        运行仿真后,根据设计的滤波器系数进行仿真,发现可以正常滤波除去高频分量。

滤波仿真效果

仿真激励文件编写

  1. `timescale 1ns / 1ps
  2. module Fir_Parallel_tb;
  3.     // Parameters
  4.     localparam integer SIGN_IN_WIDTH = 12;
  5.     localparam integer SIGN_OUT_WIDTH = 29;
  6.     localparam integer FIR_COE_WIDTH = 12;
  7.     localparam integer FIR_COE_NUM = 16;
  8.     // Ports
  9.     reg clk = 1;
  10.     reg rst = 1;
  11.     reg [SIGN_IN_WIDTH-1:0] signal_in;
  12.     wire [SIGN_OUT_WIDTH-1:0] signal_out;
  13.     Fir_Parallel #(
  14.                        .SIGN_IN_WIDTH(SIGN_IN_WIDTH ),
  15.                        .SIGN_OUT_WIDTH(SIGN_OUT_WIDTH ),
  16.                        .FIR_COE_WIDTH(FIR_COE_WIDTH ),
  17.                        .FIR_COE_NUM (FIR_COE_NUM )
  18.                    )Fir_Parallel_dut (
  19.                        .clk (clk ),
  20.                        .rst (rst ),
  21.                        .signal_in (signal_in ),
  22.                        .signal_out  ( signal_out)
  23.                    );
  24.     reg  [11:0] mem [0:99];
  25.     reg  [9:0] addr ;
  26.     // reg  [11:0]data_out ;
  27.     always #(10*1)
  28.     begin
  29.         if(rst==0)
  30.             addr = addr + 10'd1;
  31.         signal_in  =  mem[addr][11:0];
  32.     end
  33.     always
  34.         #5  clk = ! clk ;
  35.     initial
  36.     begin
  37.         signal_in =0;
  38.         $readmemh("sin_data.txt",mem);
  39.         addr  = 10'd0;
  40.         #10;
  41.         rst   = 0;
  42.     end
  43. endmodule

        运行仿真,查看波形可见,滤波效果和仿真结果一致。

仿真波形

延迟分析

        该架构的数据输入后,每四个时钟周期后输出一个数据,其中,一个时钟周期用于X(n)的加和,一个时钟周期用于计算信号和滤波器系数相乘的结果,一个时钟周期用于乘法输出后的数据做累加处理,一个时钟用于读取累加后的结果。

延时分析

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/Monodyee/article/detail/519698
推荐阅读
相关标签
  

闽ICP备14008679号