侧边栏壁纸
    • 累计撰写 302 篇文章
    • 累计收到 527 条评论
    Verilog 实现并行 FIR 滤波器设计
    我的学记|刘航宇的博客

    Verilog 实现并行 FIR 滤波器设计

    刘航宇
    2022-08-13 / 0 评论 / 340 阅读 / 正在检测是否收录...

    FIR(Finite Impulse Response)滤波器是一种有限长单位冲激响应滤波器,又称为非递归型滤波器。

    FIR 滤波器具有严格的线性相频特性,同时其单位响应是有限长的,因而是稳定的系统,在数字通信、图像处理等领域都有着广泛的应用。

    FIR 滤波器原理

    FIR 滤波器是有限长单位冲击响应滤波器。直接型结构如下:
    Test
    FIR 滤波器本质上就是输入信号与单位冲击响应函数的卷积,表达式如下:
    Test
    FIR 滤波器有如下几个特性:

    (1) 响应是有限长序列。
    (2) 系统函数在 |z| > 0 处收敛,极点全部在 z=0 处,属于因果系统。
    (3) 结构上是非递归的,没有输出到输入的反馈。
    (4) 输入信号相位响应是线性的,因为响应函数 h(n) 系数是对称的。
    (5) 输入信号的各频率之间,相对相位差也是固定不变的。
    (6) 时域卷积等于频域相乘,因此该卷积相当于筛选频谱中各频率分量的增益倍数。某些频率分量保留,某些频率分量衰减,从而实现滤波的效果。

    并行 FIR 滤波器设计

    设计说明
    输入频率为 7.5 MHz 和 250 KHz 的正弦波混合信号,经过 FIR 滤波器后,高频信号 7.5MHz 被滤除,只保留 250KHz 的信号。设计参数如下:


    由 FIR 滤波器结构可知,阶数为 15 时,FIR 的实现需要 16 个乘法器,15 个加法器和 15 组延时寄存器。为了稳定第一拍的数据,可以再多用一组延时寄存器,即共用 16 组延时寄存器。由于 FIR 滤波器系数的对称性,乘法器可以少用一半,即共使用 8 个乘法器。
    并行设计,就是在一个时钟周期内对 16 个延时数据同时进行乘法、加法运算,然后在时钟驱动下输出滤波值。这种方法的优点是滤波延时短,但是对时序要求比较高。

    并行设计

    设计中使用到的乘法器模块代码,可参考流水线式设计的乘法器。
    为方便快速仿真,也可以直接使用乘号 * 完成乘法运算,设计中加入宏定义 SAFE_DESIGN 来选择使用哪种乘法器。
    FIR 滤波器系数可由 matlab 生成,具体见附录。

    /***********************************************************
    >> V201001 : Fs:50Mhz, fstop:1Mhz-6Mhz, order: 15
    ************************************************************/
    `define SAFE_DESIGN
     
    module fir_guide    (
        input                rstn,  //复位,低有效
        input                clk,   //工作频率,即采样频率
        input                en,    //输入数据有效信号
        input        [11:0]  xin,   //输入混合频率的信号数据
        output               valid, //输出数据有效信号
        output       [28:0]  yout   //输出数据,低频信号,即250KHz
        );
     
        //data en delay
        reg [3:0]            en_r ;
        always @(posedge clk or negedge rstn) begin
            if (!rstn) begin
                en_r[3:0]      <= 'b0 ;
            end
            else begin
                en_r[3:0]      <= {en_r[2:0], en} ;
            end
        end
     
       //(1) 16 组移位寄存器
        reg        [11:0]    xin_reg[15:0];
        reg [3:0]            i, j ;
        always @(posedge clk or negedge rstn) begin
            if (!rstn) begin
                for (i=0; i<15; i=i+1) begin
                    xin_reg[i]  <= 12'b0;
                end
            end
            else if (en) begin
                xin_reg[0] <= xin ;
                for (j=0; j<15; j=j+1) begin
                    xin_reg[j+1] <= xin_reg[j] ; //周期性移位操作
                end
            end
        end
     
       //Only 8 multipliers needed because of the symmetry of FIR filter coefficient
       //(2) 系数对称,16个移位寄存器数据进行首位相加
        reg        [12:0]    add_reg[7:0];
        always @(posedge clk or negedge rstn) begin
            if (!rstn) begin
                for (i=0; i<8; i=i+1) begin
                    add_reg[i] <= 13'd0 ;
                end
            end
            else if (en_r[0]) begin
                for (i=0; i<8; i=i+1) begin
                    add_reg[i] <= xin_reg[i] + xin_reg[15-i] ;
                end
            end
        end
     
        //(3) 8个乘法器
        // 滤波器系数,已经过一定倍数的放大
        wire        [11:0]   coe[7:0] ;
        assign coe[0]        = 12'd11 ;
        assign coe[1]        = 12'd31 ;
        assign coe[2]        = 12'd63 ;
        assign coe[3]        = 12'd104 ;
        assign coe[4]        = 12'd152 ;
        assign coe[5]        = 12'd198 ;
        assign coe[6]        = 12'd235 ;
        assign coe[7]        = 12'd255 ;
        reg        [24:0]   mout[7:0];
     
    `ifdef SAFE_DESIGN
        //流水线式乘法器
        wire [7:0]          valid_mult ;
        genvar              k ;
        generate
            for (k=0; k<8; k=k+1) begin
                mult_man #(13, 12)
                u_mult_paral          (
                  .clk        (clk),
                  .rstn       (rstn),
                  .data_rdy   (en_r[1]),
                  .mult1      (add_reg[k]),
                  .mult2      (coe[k]),
                  .res_rdy    (valid_mult[k]), //所有输出使能完全一致  
                  .res        (mout[k])
                );
            end
        endgenerate
        wire valid_mult7     = valid_mult[7] ;
     
    `else
        //如果对时序要求不高,可以直接用乘号
        always @(posedge clk or negedge rstn) begin
            if (!rstn) begin
                for (i=0 ; i<8; i=i+1) begin
                    mout[i]     <= 25'b0 ;
                end
            end
            else if (en_r[1]) begin
                for (i=0 ; i<8; i=i+1) begin
                    mout[i]     <= coe[i] * add_reg[i] ;
                end
            end
        end
        wire valid_mult7 = en_r[2];
    `endif
     
        //(4) 积分累加,8组25bit数据 -> 1组 29bit 数据
        //数据有效延时
        reg [3:0]            valid_mult_r ;
        always @(posedge clk or negedge rstn) begin
            if (!rstn) begin
                valid_mult_r[3:0]  <= 'b0 ;
            end
            else begin
                valid_mult_r[3:0]  <= {valid_mult_r[2:0], valid_mult7} ;
            end
        end
    
    `ifdef SAFE_DESIGN
        //加法运算时,分多个周期进行流水,优化时序
        reg        [28:0]    sum1 ;
        reg        [28:0]    sum2 ;
        reg        [28:0]    yout_t ;
        always @(posedge clk or negedge rstn) begin
            if (!rstn) begin
                sum1   <= 29'd0 ;
                sum2   <= 29'd0 ;
                yout_t <= 29'd0 ;
            end
            else if(valid_mult7) begin
                sum1   <= mout[0] + mout[1] + mout[2] + mout[3] ;
                sum2   <= mout[4] + mout[5] + mout[6] + mout[7] ;
                yout_t <= sum1 + sum2 ;
            end
        end
     
    `else
        //一步计算累加结果,但是实际中时序非常危险
        reg signed [28:0]    sum ;
        reg signed [28:0]    yout_t ;
        always @(posedge clk or negedge rstn) begin
            if (!rstn) begin
                sum    <= 29'd0 ;
                yout_t <= 29'd0 ;
            end
            else if (valid_mult7) begin
                sum    <= mout[0] + mout[1] + mout[2] + mout[3] + mout[4] + mout[5] + mout[6] + mout[7];
                yout_t <= sum ;
            end
        end
    `endif
        assign yout  = yout_t ;
        assign valid = valid_mult_r[0];
    
    endmodule

    testbench

    testbench 编写如下,主要功能就是不间断连续的输入 250KHz 与 7.5MHz 的正弦波混合信号数据。输入的混合信号数据也可由 matlab 生成,具体见附录。

    `timescale 1ps/1ps
     
    module test ;
       //input
        reg          clk ;
        reg          rst_n ;
        reg          en ;
        reg [11:0]   xin ;
        //output
        wire         valid ;
        wire [28:0]  yout ;
     
        parameter    SIMU_CYCLE   = 64'd2000 ;  //50MHz 采样频率
        parameter    SIN_DATA_NUM = 200 ;      //仿真周期
    
    //=====================================
    // 50MHz clk generating
        localparam   TCLK_HALF     = 10_000;
        initial begin
            clk = 1'b0 ;
            forever begin
                # TCLK_HALF ;
                clk = ~clk ;
            end
        end
     
    //============================
    //  reset and finish
        initial begin
            rst_n = 1'b0 ;
            # 30   rst_n = 1'b1 ;
            # (TCLK_HALF * 2 * SIMU_CYCLE) ;
            $finish ;
        end
     
    //=======================================
    // read signal data into register
        reg          [11:0] stimulus [0: SIN_DATA_NUM-1] ;
        integer      i ;
        initial begin
            $readmemh("../tb/cosx0p25m7p5m12bit.txt", stimulus) ;
            i = 0 ;
            en = 0 ;
            xin = 0 ;
            # 200 ;
            forever begin
                @(negedge clk) begin
                    en          = 1'b1 ;
                    xin         = stimulus[i] ;
                    if (i == SIN_DATA_NUM-1) begin  //周期送入数据控制
                        i = 0 ;
                    end
                    else begin
                        i = i + 1 ;
                    end
                end
            end
        end
     
        fir_guide u_fir_paral (
          .xin         (xin),
          .clk         (clk),
          .en          (en),
          .rstn        (rst_n),
          .valid       (valid),
          .yout        (yout));
     
    endmodule

    仿真结果

    由下图仿真结果可知,经过 FIR 滤波器后的信号只有一种低频率信号(250KHz),高频信号(7.5MHz)被滤除了。而且输出波形是连续的,能够持续输出。

    但是,如红圈所示,波形起始部分呈不规则状态,对此进行放大。
    Test
    波形起始端放大后如下图所示,可见不规则波形的时间段,即两根竖线之间的时间间隔是 16 个时钟周期。

    因为数据是串行输入,设计中使用了 16 组延时寄存器,所以滤波后的第一个正常点应该较第一个滤波数据输出时刻延迟 16 个时钟周期。即数据输出有效信号 valid 应该再延迟 16 个时钟周期,则会使输出波形更加完美。
    Test

    附录:matlab 使用

    生成 FIR 滤波器系数

    打开 matlab,在命令窗口输入命令: fdatool。

    然后会打开如下窗口,按照 FIR 滤波器参数进行设置。

    这里选择的 FIR 实现方法是最小二乘法(Least-squares),不同的实现方式滤波效果也不同。
    Test
    点击 File -> Export

    将滤波器参数输出,存到变量 coef 中,如下图所示。
    Test
    此时 coef 变量应该是浮点型数据。对其进行一定倍数的相乘扩大,然后取其近似的定点型数据作为设计中的 FIR 滤波器参数。这里取扩大倍数为 2048,结果如下所示。
    Test

    生成输入的混合信号

    利用 matlab 生成混合的输入信号参考代码如下。
    信号为无符号定点型数据,位宽宽度为 12bit,存于文件 cosx0p25m7p5m12bit.txt。

    clear all;close all;clc;
    %=======================================================
    % generating a cos wave data with txt hex format
    %=======================================================
    
    fc          = 0.25e6 ;      % 中心频率
    fn          = 7.5e6 ;       % 杂波频率
    Fs          = 50e6 ;        % 采样频率
    T           = 1/fc ;        % 信号周期
    Num         = Fs * T ;      % 周期内信号采样点数
    t           = (0:Num-1)/Fs ;      % 离散时间
    cosx        = cos(2*pi*fc*t) ;    % 中心频率正弦信号
    cosn        = cos(2*pi*fn*t) ;    % 杂波信号
    cosy        = mapminmax(cosx + cosn) ;     %幅值扩展到(-1,1) 之间
    cosy_dig    = floor((2^11-1) * cosy + 2^11) ;     %幅值扩展到 0~4095
    fid         = fopen('cosx0p25m7p5m12bit.txt', 'wt') ;  %写数据文件
    fprintf(fid, '%x\n', cosy_dig) ;
    fclose(fid) ;
     
    %时域波形
    figure(1);
    subplot(121);plot(t,cosx);hold on ;
    plot(t,cosn) ;
    subplot(122);plot(t,cosy_dig) ;
     
    %频域波形
    fft_cosy    = fftshift(fft(cosy, Num)) ;
    f_axis      = (-Num/2 : Num/2 - 1) * (Fs/Num) ;
    figure(5) ;
    plot(f_axis, abs(fft_cosy)) ;
    2
    Verilog教程-CIC 滤波器设计
    « 上一篇 2022-08-14
    Verilog教程语句块
    下一篇 » 2022-08-09

    评论 (0)

    取消