FPGA数字信号处理(三)串行FIR滤波器Verilog设计

本文基于FPGA平台,介绍了串行FIR滤波器的VerilogHDL设计方法,包括全串行和半串行结构的区别,以及如何在Vivado环境下进行仿真验证。

该篇是FPGA数字信号处理的第三篇,选题为DSP系统中极其常用的FIR滤波器。本文将在上一篇“FPGA数字信号处理(二)并行FIR滤波器Verilog设计” https://blog.youkuaiyun.com/fpgadesigner/article/details/80594627的基础上,继续介绍串行结构FIR滤波器的Verilog HDL设计方法。

串行FIR

并行FIR使用n/2(借助线性相位FIR滤波器h(n)的对称性)个乘法器同时做乘法,将结果累加作为FIR滤波输出。这样的好处是每个时钟都能完成一次运算,得到一个输出结果。缺点是资源使用会随着FIR阶数的增加不断增加。
这里写图片描述
串行FIR是通过增加计算周期来减少资源使用的结构,如上图所示。使用一个加法器将对称系数对应的数据相加,一个乘法器进行数据和系数的乘法,即所有的系数相乘依次占用一个乘法器,同时累加器不断累加。因此需要n/2个时钟周期才能将所有的数据计算完毕,得到输出结果。

通常把只使用一个加法器依次进行“对称系数的对应数据相加”的工作,这种结构称为全串行FIR滤波器;使用n/2个加法器分别完成“对称系数的对应数据相加”,这种结构称为半串行FIR滤波器。

FPGA设计

全串行结构和半串行结构的区别仅在于使用加法器数量的不同,两者计算速度相同,显然选择全串行结构FIR更优。设计参考自杜勇老师的《数字滤波器的MATLAB与FPGA实现》。本设计将在Vivado环境下进行仿真。

使用MATLAB设计一个2kHz采样,500Hz截止的15阶低通滤波器(h(n)长度为16),量化位数为12bit,输入信号位宽也为12bit。Verilog设计代码如下。

模块接口:

module Xilinx_SerialFIR_liuqi
(
    input rst,                    //复位信号,高电平有效
    input clk,                    //FPGA系统时钟,频率为16kHz
    input signed [11:0] Xin,        //数据输入频率为2khZ
    output signed [28:0]Yout      //滤波后的输出数据
);

接口与上一篇代码相同。不过有一个隐形的区别是系统时钟,并行结构由于一个时钟输出一个数据,系统时钟与输入数据频率(即采样频率)相同即可;但是串行结构由于需要n/2倍(此处为8倍)个周期完成一次运算,则FPGA系统时钟需要是采样频率的n/2倍。
接下来先实例化全串行FIR滤波器结构所需的乘法器和加法器IP核。

reg signed [12:0] add_a,add_b;
wire signed [12:0] add_s;

c_addsub_0 add
(
    .A (add_a),
    .B (add_b),
    .S (add_s)
);

reg signed [11:0] coe;     //12bit量化滤波器系数
wire signed [24:0] Mout;  

mult_gen_0 mult
(
    .CLK (clk),
    .A   (coe),
    .B   (add_s),
    .P   (Mout)
);

加法器和乘法器的位宽问题在上一篇中已经介绍,这里不再赘述。由于每8个时钟才完成一次计算,因此需要一个系统时钟的8分频时钟,来控制输入数据的移位和滤波结果数据的输出。这里没有采用分频时钟的控制方式,而是使用一个模8计数器来达到同样的效果:

reg [2:0] cnt;
reg [11:0] Xin_Reg[15:0];
reg [3:0] i,j;

always @ (posedge clk or posedge rst)
    if (rst) cnt <= 'd0;
    else cnt <= cnt + 1'b1;

always @ (posedge clk or posedge rst)
    if (rst) begin   //清0
        Xin_Reg[15] <= 'd0;   
        for (i=0; i<15; i=i+1'b1)
            Xin_Reg[i] <= 'd0;    end
    else begin
        if (cnt == 'd7) begin
            for (j=0; j<15; j=j+1'b1)   //移位
                Xin_Reg[j+1] <= Xin_Reg[j];
            Xin_Reg[0] <= Xin;
        end
    end

每当cnt从0计数到7时,8个时钟已经完成了一次运算,输入数据移位。需要注意的是清零那一部分程序,杜老的程序中有考虑不周的地方:如果仅用for循环清零,那么Xin_Reg[15]是无法被清零到的。因此需要单独对这个寄存器清0。

可能有人会想到将for循环的控制语句修改为:for (i=0; i<=15; i=i+1’b1)或for (i=0; i<16; i=i+1’b1),这都是错误的。前者是因为Verilog中<=仅作为赋值语句,没有“小于等于”的功能;后者是因为i是一个4位寄存器,循环加下去会无限满足这个for条件,无法跳出。各位不妨在Vivado中综合一下试试。

接下来就是在cnt的控制下,依次使用实例化好的乘法器和加法器完成运算。由于输入数据为二进制补码带符号数,因此在加法器相加前需要使用Verilog中的拼接运算符{}扩展符号位到最高位:

always @ (posedge clk or posedge rst)
    if (rst) begin
        add_a <= 'd0; add_b <= 'd0; coe <= 'd0;
    end
    else begin
        if (cnt == 'd0) begin //第一个周期
            add_a <= {Xin_Reg[0][11],Xin_Reg[0]};
            add_b <= {Xin_Reg[15][11],Xin_Reg[15]};
            coe <= 12'h000;
        end
        else if (cnt == 'd1) begin //第二个周期
            add_a <= {Xin_Reg[1][11],Xin_Reg[1]};
            add_b <= {Xin_Reg[14][11],Xin_Reg[14]};
            coe <= 12'hffd;
        end    
        else if (cnt == 'd2) begin //第三个周期
            add_a <= {Xin_Reg[2][11],Xin_Reg[2]};
            add_b <= {Xin_Reg[13][11],Xin_Reg[13]};
            coe <= 12'h00f;
        end
        else if (cnt == 'd3) begin //第四个周期
            add_a <= {Xin_Reg[3][11],Xin_Reg[3]};
            add_b <= {Xin_Reg[12][11],Xin_Reg[12]};
            coe <= 12'h02e;
        end
        else if (cnt == 'd4) begin //第五个周期
            add_a <= {Xin_Reg[4][11],Xin_Reg[4]};
            add_b <= {Xin_Reg[11][11],Xin_Reg[11]};
            coe <= 12'hf8b;
        end
        else if (cnt == 'd5) begin //第六个周期
            add_a <= {Xin_Reg[5][11],Xin_Reg[5]};
            add_b <= {Xin_Reg[10][11],Xin_Reg[10]};
            coe <= 12'hef9;
        end
        else if (cnt == 'd6) begin //第七个周期
            add_a <= {Xin_Reg[6][11],Xin_Reg[6]};
            add_b <= {Xin_Reg[9][11],Xin_Reg[9]};
            coe <= 12'h24e;
        end
        else begin                //第八个周期
            add_a <= {Xin_Reg[7][11],Xin_Reg[7]};
            add_b <= {Xin_Reg[8][11],Xin_Reg[8]};
            coe <= 12'h7ff;
        end                                               
    end

对乘法器的计算结果进行累加,得到滤波输出结果:

reg signed [28:0] sum;
reg signed [28:0] yout;

always @ (posedge clk or posedge rst)
    if (rst) begin
        sum <= 'd0; yout <= 'd0;
    end
    else begin
        if (cnt == 'd2) begin
            yout <= sum; sum <= 'd0; sum <= sum+Mout;
        end
        else sum <= sum+Mout;
    end

assign Yout = yout;

可以看到串行结构的FIR乘法、加法运算是在8个时钟内完成,因此每8个时钟才能获得一个输出。这里选择在cnt为2时才输出数据和清零,是考虑到乘法器和累加运算都需要占用一个时钟周期。

仿真与工程下载

使用MATLAB生成一个200khz+800kHz的混合频率信号,写入txt文件,。编写Testbench读取txt文件对信号滤波,文件操作方法参考“Testbench编写指南(一)文件的读写操作”https://blog.youkuaiyun.com/fpgadesigner/article/details/80470972
对正弦信号的滤波如下图所示:
这里写图片描述
发现Yout的幅度变化很低,检测后发现24-29bit都是符号位,将0-24bit提取为一个新的虚拟总线Yout_bus。明显看到经过500Hz低通滤波器滤波后,输入的200+800Hz信号只剩下200Hz的单频信号。

完整的Vivado工程(含testbench仿真)可以在这里下载:https://download.youkuaiyun.com/download/fpgadesigner/10463087。Quartus环境下的设计与仿真工程可以参考杜勇老师《数字滤波器的MATLAB与FPGA实现》书中所带光盘里的内容。很推荐大家购买这本书学习。

### 并行 FIR 滤波器Verilog 设计方法 并行 FIR(Finite Impulse Response)滤波器因其线性相位特性,在数字信号处理领域中被广泛使用。在 FPGA 上通过 Verilog 实现并行结构FIR 滤波器,可以显著提高数据处理速度,适用于对实时性要求较高的场景。 #### 1. **设计原理与结构** FIR 滤波器的基本公式为: $$ y(n) = \sum_{k=0}^{N-1} h(k) \cdot x(n-k) $$ 其中 $ h(k) $ 是滤波器系数,$ x(n-k) $ 是输入序列,$ N $ 是滤波器阶数。为了实现高速处理,并行 FIR 滤波器将所有乘法和加法操作同时执行,而不是串行进行。这意味着每个抽头都需要一个独立的乘法器[^3]。 对于一个 15 阶的 FIR 滤波器(即 16 个系数),需要 16 个乘法器、15 个加法器以及 16 组延时寄存器来保存输入样本。如果滤波器系数具有对称性,则可以通过复用乘法器的方式减少硬件资源消耗,例如仅需 8 个乘法器即可完成相同功能[^3]。 #### 2. **模块划分与 Verilog 实现** 在 Verilog 中实现并行 FIR 滤波器时,通常将其划分为以下几个关键部分: - **输入延时链**:用于存储历史输入样本。 - **乘法单元**:将当前输入样本与对应的滤波器系数相乘。 - **累加单元**:将所有乘积结果相加以得到最终输出。 - **控制逻辑**:确保各阶段操作同步进行。 以下是一个简化的 Verilog 示例代码,展示了一个 4 阶并行 FIR 滤波器设计框架: ```verilog module fir_filter_parallel ( input clk, input rst_n, input [15:0] x_in, // 输入信号 output reg [15:0] y_out // 输出信号 ); // 定义滤波器系数(示例) parameter COEFFS = 4; parameter [15:0] h0 = 16'h0001; parameter [15:0] h1 = 16'h0002; parameter [15:0] h2 = 16'h0003; parameter [15:0] h3 = 16'h0004; // 延时寄存器 reg [15:0] delay_reg [0:COEFFS-1]; // 乘法结果寄存器 reg [31:0] mul_result [0:COEFFS-1]; // 累加结果寄存器 reg [31:0] acc_result; // 延时链更新 always @(posedge clk or negedge rst_n) begin if (!rst_n) begin for (integer i = 0; i < COEFFS; i = i + 1) delay_reg[i] <= 16'd0; end else begin delay_reg[0] <= x_in; for (integer i = 1; i < COEFFS; i = i + 1) delay_reg[i] <= delay_reg[i - 1]; end end // 乘法运算 always @(posedge clk) begin mul_result[0] <= delay_reg[0] * h0; mul_result[1] <= delay_reg[1] * h1; mul_result[2] <= delay_reg[2] * h2; mul_result[3] <= delay_reg[3] * h3; end // 累加运算 always @(posedge clk) begin acc_result <= mul_result[0] + mul_result[1] + mul_result[2] + mul_result[3]; end // 输出赋值 always @(posedge clk) begin y_out <= acc_result[31:16]; // 截断高位作为输出 end endmodule ``` 上述代码展示了如何通过并行方式计算 FIR 滤波器的输出。每个输入样本都被延迟并通过乘法器与对应系数相乘,最后通过累加器求和得出结果。 #### 3. **优化策略** - **系数对称性利用**:若滤波器系数满足对称条件,可减少一半的乘法器数量,从而节省 FPGA 资源。 - **定点数量化**:在实际硬件实现中,浮点数不便于处理,因此需要将滤波器系数转换为定点数表示形式。这一过程可在 MATLAB 中完成,并导出至 Verilog 代码中使用[^2]。 - **流水线设计**:通过引入流水线级,提升系统时钟频率,增强吞吐量。 #### 4. **仿真与验证** 在设计完成后,应使用仿真工具如 ModelSim 或 Vivado 进行功能验证。可以使用 MATLAB 生成测试激励文件,并导入到 Verilog 测试平台中,比较 FPGA 输出与 MATLAB 计算结果的一致性,以确保设计正确性[^2]。 ---
评论 27
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值