• 数字信号处理-10-并行FIR滤波器MATLAB与FPGA实现


    前言

    本文介绍了设计滤波器的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增大时,数据的吞吐速率不变(暂且不考虑面积增大对性能的影响),但带来的坏处就是会用掉相应倍数的逻辑资源和运算资源,速度和面积本来就是鱼和熊掌的关系,在实际应用中应当做相应的权衡和割舍。

    根据架构描述电路

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

    实现模块框图

    接口描述如下:

    接口描述

    参数描述如下:

    参数描述

    代码如下:

    `timescale 1ns / 1ps
    module Fir_Parallel(
    
            input clk,//!系统时钟
            input rst,//!复位信号
            input signed [SIGN_IN_WIDTH-1:0] signal_in,//!信号输入
            output signed [SIGN_OUT_WIDTH-1:0] signal_out//!信号输出,信号输出速度和输入速度相同
        );
    
        //
        parameter  integer SIGN_IN_WIDTH    = 12   ;//!信号输入位宽
        parameter  integer SIGN_OUT_WIDTH	= 29   ;//!信号输出位宽
        parameter  integer FIR_COE_WIDTH	= 12   ;//!滤波器系数位宽
        parameter  integer FIR_COE_NUM	= 16   ;//!滤波器长度
        localparam integer FIR_WIDTH_DIV_2	= FIR_COE_NUM/2 ;
    
        function [FIR_COE_WIDTH-1:0] coe_data;
        input [FIR_WIDTH_DIV_2-1:0] index;
        begin
            case(index)
            'd0:coe_data='h000;
            'd1:coe_data='hffd;
            'd2:coe_data='h00f;
            'd3:coe_data='h02e;
            'd4:coe_data='hf8b;
            'd5:coe_data='hef9;
            'd6:coe_data='h24e;
            'd7:coe_data='h7ff;
            endcase
        end
        endfunction
        integer i;
        genvar j;
        //!滤波器系数加载
        wire signed [FIR_COE_WIDTH-1:0] coe[FIR_WIDTH_DIV_2-1:0]; 
        generate
            for (j=0; j
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104

    代码解读

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

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

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

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

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

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

    电路架构优化

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

    仿真设计

    仿真数据设计

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

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

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

    滤波仿真效果

    仿真激励文件编写

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

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

    仿真波形

    延迟分析

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

    延时分析

  • 相关阅读:
    ASP.NET Core 6.0 基于模型验证的数据验证
    「Spring」认证安全架构指南
    Windows C++ 环境下 eigen、osqp、osqp-eigen安装教程
    uniapp+node.js前后端做帖子模块:帖子的点赞/取消点赞(社区管理平台的小程序)
    网络面试题整理
    Spring Boot Actuator使用指南
    JAVA基础-正则表达式(12)
    【F280039C】Serial Peripheral Interface (SPI)
    直线段扫描算法
    微信小程序怎么构建npm?
  • 原文地址:https://blog.csdn.net/weixin_41445387/article/details/128051389