FIR滤波器的FPGA实现和matlab验证(二)

并行FIR滤波器设计说明

FIR滤波器升级款

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

输入频率:    7.5MHz 和 250KHz
采样频率:    50MHz
阻带:           1MHz ~ 6MHz
阶数:           15(N-1=15)

由 FIR 滤波器结构可知,阶数为 15 时,FIR 的实现需要 16 个乘法器,15 个加法器和 15 组延时寄存器。为了稳定第一拍的数据,可以再多用一组延时寄存器,即共用 16 组延时寄存器。

由于 FIR 滤波器系数的对称性,乘法器可以少用一半,即共使用 8 个乘法器。

并行设计,就是在一个时钟周期内对 16 个延时数据同时进行乘法、加法运算,然后在时钟驱动下输出滤波值。这种方法的优点是滤波延时短,但是对时序要求比较高。

为方便快速仿真,也可以直接使用乘号*完成乘法运算,设计中加入宏定义 SAFE_DESIGN 来选择使用哪种乘法器。

  • 原信号

    利用 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)) ;


% coef * 2^11 
  • 滤波器系数

    用matlab的fdatool工具生成coef,即滤波器系数,此处为15阶,即有15个coef

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

  • Verilog实现

/***********************************************************
>> V201001 : Fs:50Mhz, fstop:1Mhz-6Mhz, order: 15
************************************************************/
`define SAFE_DESIGN
 
module fir_paraller    (
    input                rstn,  //复位,低有效
    input                clk,   //工作频率,即采样频率
    input                en,    //输入数据有效信号
    input        [11:0]  xin,   //输入混合频率的信号数据
    output               valid, //输出数据有效信号
    output       [28:0]  yout   //输出数据,低频信号,即250KHz
    );
 
    //data on delay
    reg [3:0] en_r;
    always @(posedge clk or negedge rstn) begin
        if(!rstn)begin
            en_r[3:0]<=1'b0;
        end
        else begin
            en_r[3:0] <={en_r[2:0],en};
        end
    end

    //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
	 
    //利用系数对称,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'b0;
            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
    //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 ;
    wire        [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_step
            mult_man #(13,12) 
            u_mult_paraeller(
                .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];//multted

`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

  
    //数据有效延迟
    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<={valid_mult_r[2:0],valid_mult7};
        end
    end
  
//积分累加,8组25bit数据->1组29bit数据
`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<='b0;
            sum2<='b0;
            yout_t<='b0;
        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
`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("D:/my_profile/fpga_project/runoob/7_3_1fir_paraller/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_paraller u_fir_paral (
      .xin         (xin),
      .clk         (clk),
      .en          (en),
      .rstn        (rst_n),
      .valid       (valid),
      .yout        (yout));
 
endmodule

配上流程框图结构,大概是

串行FIR滤波器设计说明

输入频率为 7.5 MHz 和 250 KHz 的正弦波混合信号,经过 FIR 滤波器后,高频信号 7.5MHz 被滤除,只保留 250KHz 的信号。

输入频率:    7.5MHz 和 250KHz
采样频率:    50MHz
阻带:           1MHz-6MHz
阶数:           15 (N=15)

串行设计,就是在16个时钟周期内对16个延时数据分时依次进行乘法、加法运算,然后在时钟的驱动下输出滤波值,考虑到FIR 滤波器系数的对称性,计算一个滤波输出值的周期可以减少到 8 个。串行设计时每个周期只进行一次乘法运算,所以设计中只需一个乘法器即可。此时数据需要每 8 个时钟周期有效输入一次,但是为了保证输出信号频率的正确性,工作时钟需要为采样频率的 8 倍,即 400MHz。这种方法的优点是资源耗费少,但是工作频率要求高,数据不能持续输出

串行设计

  • 生成信号,此处引用并行FIR滤波器生成的信号

  • Verilog

/**********************************************************
>> Description : fir study with serial tech
>> V190403     : Fs:50Mhz, fstop:1-6Mhz, order:16, sys clk:400MHz
***********************************************************/
`define SAFE_DESIGN
 
module fir_serial_low(
    input                rstn,
    input                clk,   // 系统工作时钟,400MHz
    input                en ,   // 输入数据有效信号
    input        [11:0]  xin,   // 输入混合频率的信号数据
    output               valid, // 输出数据有效信号
    output       [28:0]  yout   // 输出数据
    );
 
   //delay of input data enable
    reg [11:0]            en_r ;
    always @(posedge clk or negedge rstn) begin
        if (!rstn) begin
            en_r[11:0]      <= 'b0 ;
        end
        else begin
            en_r[11:0]      <= {en_r[10:0], en} ;
        end
    end
 
    //fir coeficient
    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 ;
 
    //(1) 输入数据移位部分
    reg [2:0]            cnt ;
    integer              i, j ;
    always @(posedge clk or negedge rstn) begin
        if (!rstn) begin
            cnt <= 3'b0 ;
        end
        else if (en || cnt != 0) begin
            cnt <= cnt + 1'b1 ;    //8个周期计数
        end
    end
 
    reg [11:0]           xin_reg[15:0];
    always @(posedge clk or negedge rstn) begin
        if (!rstn) begin
            for (i=0; i<16; i=i+1) begin
                xin_reg[i]  <= 12'b0;
            end
        end
        else if (cnt == 3'd0 && en) begin    //每8个周期读入一次有效数据
            xin_reg[0] <= xin ;
            for (j=0; j<15; j=j+1) begin
                xin_reg[j+1] <= xin_reg[j] ; // 数据移位
            end
        end
    end
 
    //(2) 系数对称,16个移位寄存器数据进行首位相加
    reg  [11:0]          add_a, add_b ;
    reg  [11:0]          coe_s ;
    wire [12:0]          add_s ;
    wire [2:0]           xin_index = cnt>=1 ? cnt-1 : 3'd7 ;
    always @(posedge clk or negedge rstn) begin
        if (!rstn) begin
            add_a  <= 13'b0 ;
            add_b  <= 13'b0 ;
            coe_s  <= 12'b0 ;
        end
        else if (en_r[xin_index]) begin //from en_r[1]
            add_a  <= xin_reg[xin_index] ;
            add_b  <= xin_reg[15-xin_index] ;
            coe_s  <= coe[xin_index] ;
        end
    end
    assign add_s = {add_a} + {add_b} ;  
 
    //(3) 乘法运算,只用一个乘法
    reg        [24:0]    mout ;
`ifdef SAFE_DESIGN
    wire                 en_mult ;
    wire [3:0]           index_mult = cnt>=2 ? cnt-1 : 4'd7 + cnt[0] ;
    mult_man #(13, 12)   u_mult_single    //例化自己设计的流水线乘法器
        (.clk        (clk),
         .rstn       (rstn),
         .data_rdy   (en_r[index_mult]),  //注意数据时序对应
         .mult1      (add_s),
         .mult2      (coe_s),
         .res_rdy    (en_mult),  
         .res        (mout)
        );
 
`else
    always @(posedge clk or negedge rstn) begin
        if (!rstn) begin
            mout   <= 25'b0 ;
        end
        else if (|en_r[8:1]) begin
            mout   <= coe_s * add_s ;  //直接乘
        end
    end
    wire                 en_mult = en_r[2];
`endif
 
    //(4) 积分累加,8组25bit数据 -> 1组 29bit 数据
    reg        [28:0]    sum ;
    reg                  valid_r ;
    //mult output en counter
    reg [4:0]            cnt_acc_r ;
    always @(posedge clk or negedge rstn) begin
        if (!rstn) begin
            cnt_acc_r <= 'b0 ;
        end
        else if (cnt_acc_r == 5'd7) begin  //计时8个周期
            cnt_acc_r <= 'b0 ;
        end
        else if (en_mult || cnt_acc_r != 0) begin //只要en有效,计时不停
            cnt_acc_r <= cnt_acc_r + 1'b1 ;
        end
    end
 
    always @(posedge clk or negedge rstn) begin
        if (!rstn) begin
            sum      <= 29'd0 ;
            valid_r  <= 1'b0 ;
        end
        else if (cnt_acc_r == 5'd7) begin //在第8个累加周期输出滤波值
            sum      <= sum + mout;
            valid_r  <= 1'b1 ;
        end
        else if (en_mult && cnt_acc_r == 0) begin //初始化
            sum      <= mout ;
            valid_r  <= 1'b0 ;
        end
        else if (cnt_acc_r != 0) begin //acculating between cycles
            sum      <= sum + mout ;
            valid_r  <= 1'b0 ;
        end
    end
 
    //时钟锁存有效的输出数据,为了让输出信号不是那么频繁的变化
    reg [28:0]           yout_r ;
    always @(posedge clk or negedge rstn) begin
        if (!rstn) begin
            yout_r <= 'b0 ;
        end
        else if (valid_r) begin
            yout_r <= sum ;
        end
    end
    assign yout = yout_r ;
 
    //(5) 输出数据有效延迟,即滤波数据丢掉前15个滤波值
    reg [4:0]    cnt_valid ;
    always @(posedge clk or negedge rstn) begin
        if (!rstn) begin
            cnt_valid      <= 'b0 ;
        end
        else if (valid_r && cnt_valid != 5'd16) begin
            cnt_valid      <= cnt_valid + 1'b1 ;
        end
    end
    assign valid = (cnt_valid == 5'd16) & valid_r ;

endmodule
  • testbench
module test ;
    //input
    reg          clk ;
    reg          rst_n ;
    reg          en ;
    reg  [11:0]  xin ;
    //output
    wire [28:0]  yout ;
    wire         valid ;

    parameter    SIMU_CYCLE   = 64'd1000 ;
    parameter    SIN_DATA_NUM = 200 ;

//=====================================
// 8*50MHz clk generating
    localparam   TCLK_HALF     = (10_000 >>3);
    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 * 8  * SIMU_CYCLE) ;
        $finish ;
    end
 
//=======================================
// read cos data into register
    reg          [11:0] stimulus [0: SIN_DATA_NUM-1] ;
    integer      i ;
    initial begin
        $readmemh("../tb/cosx0p25m7p5m12bit.txt", stimulus) ;
        en = 0 ;
        i = 0 ;
        xin = 0 ;
        # 200 ;
        forever begin
            repeat(7)  @(negedge clk) ; //空置7个周期,第8个周期给数据
            en          = 1 ;
            xin         = stimulus[i] ;
            @(negedge clk) ;
            en          = 0 ;         //输入数据有效信号只持续一个周期即可
            if (i == SIN_DATA_NUM-1)  i = 0 ;
            else  i = i + 1 ;
        end
    end
 
    fir_serial_low       u_fir_serial (
        .clk         (clk),
        .rstn        (rst_n),
        .en          (en),
        .xin         (xin),
        .valid       (valid),
        .yout        (yout));

endmodule
posted @ 2022-03-26 17:54  heart-z  阅读(365)  评论(0)    收藏  举报