FPGA 基于累加器的 FIR 滤波器原理与工程实例
2026/6/25 16:17:49 网站建设 项目流程

一、FIR 滤波器基础理论

1. FIR 数学公式

有限长单位冲激响应滤波器(FIR)差分方程:

y[n]=∑k=0N−1​h[k]⋅x[n−k]

  • N:滤波器阶数(抽头数);
  • h[k]:滤波器系数(固定,对称线性相位);
  • x[n−k]:当前与过去 N 个输入采样;
  • y[n]:滤波输出。

核心运算:乘累加 MAC (Multiply-Accumulate),每输出 1 个采样,需要 N 次乘法 + N 次加法。

2. 传统结构痛点

直接并行 MAC:N 个乘法器,阶数高时消耗大量 DSP48 资源; 串行分时 MAC:只用 1 个乘法器,分时复用,但需要时钟提速。基于累加器的 FIR是串行分时 MAC 的最简实现架构,核心只用一组乘法器 + 累加寄存器,适合资源受限 FPGA。

二、基于累加器的 FIR 核心原理

1. 架构核心思想

将求和运算拆分为逐次乘累加

sum0​sum1​sum2​y[n]​=h[0]⋅x[n]=sum0​+h[1]⋅x[n−1]=sum1​+h[2]⋅x[n−2]…=sumN−1​​

流程拆解:

  1. 输入移位寄存器缓存 N 个历史采样 x[n],x[n−1],...,x[n−N+1];
  2. 计数器遍历 0~N-1,依次取出系数h[k]和对应采样x[n−k];
  3. 乘法器算出单路乘积,送入累加器持续叠加;
  4. 一轮 N 次循环结束,累加器输出滤波结果,随后清零准备下一组采样。

2. 硬件模块划分

整体分 5 个子模块:

  1. 输入移位寄存器组(Shift Register)缓存连续 N 个输入采样,每次新采样到来右移 1 位,保存历史数据;
  2. 系数 ROM预存 FIR 抽头系数h[0]∼h[N−1],FPGA 用 Block ROM 实现,系数可定点量化;
  3. 循环计数器(Cnt)0~N-1 循环计数,同时作为 ROM 读地址、移位寄存器选择地址;
  4. 单路乘法器(DSP48E1)分时复用,同一时刻只计算一组h[k]×x[n−k];
  5. 累加器(Accumulator)带同步清零,每次乘积输入持续相加,计数到 N-1 时输出结果。

3. 时序工作逻辑(关键)

设滤波器阶数N=8,系统时钟远高于采样时钟(过采样分时运算):

  1. 采样时钟上升沿:采集新输入xnew​,移位寄存器整体右移;复位计数器、累加器;
  2. 系统时钟连续 8 拍:
    • 拍 0:cnt=0 → 读 h [0]、取 x [n] → 乘积存入累加器;
    • 拍 1:cnt=1 → 读 h [1]、取 x [n-1] → 累加器 = 原值 + 新乘积;
    • ……
    • 拍 7:cnt=7 → 最后一组乘加,累加器输出有效滤波值 y;
  3. 等待下一次采样触发,重复循环。

约束条件:系统时钟频率 ≥ N × 采样频率,保证在两次采样间隔内完成 N 次乘累加。

4. 定点量化说明(FPGA 必做)

浮点数系数无法直接硬件实现,定点化处理:

  1. MATLAB 生成浮点 h [k];
  2. 放大2Q倍取整,Q 为小数位宽(常用 Q15、Q16);
  3. 输出累加后右移 Q 位还原幅值。

例:Q15 量化,系数范围 [-1,1] → 乘以 32768 取 16 位整数存入 ROM。

三、线性相位 FIR 优化(节省资源)

绝大多数 FIR 采用对称系数 h[k]=h[N−1−k],基于累加器架构可减半运算次数:

y[n]=∑k=0(N/2)−1​h[k]⋅(x[n−k]+x[n−(N−1−k)])

硬件改动:

  • 先将对称位置两个采样相加,再与系数相乘;
  • 循环次数从 N 次变为 N/2 次,大幅降低运算时钟要求,累加器架构同样兼容。

四、Verilog 工程实例(8 阶低通 FIR,基于累加器串行 MAC)

1. 参数定义

  • 阶数 N=8;
  • 输入位宽:12bit 采样数据;
  • 系数 Q15 定点 16bit;
  • 累加器位宽:32bit(防止溢出);
  • 系统时钟 50MHz,采样频率 5MHz(50M ≥ 8×5M,满足时序)。

2. 完整代码

verilog

module fir_accumulator #( parameter TAP_NUM = 8, // 滤波器阶数 parameter DATA_WIDTH = 12, // 输入采样位宽 parameter COE_WIDTH = 16, // 系数Q15位宽 parameter ACC_WIDTH = 32, // 累加器位宽防溢出 parameter Q_BIT = 15 // 定点小数位 ) ( input wire clk, // 50MHz系统时钟 input wire rst_n, // 低电平复位 input wire sample_en, // 采样使能5MHz input wire [DATA_WIDTH-1:0] x_in, // 输入采样 output reg y_valid, // 输出有效标志 output reg [DATA_WIDTH-1:0] y_out // 滤波输出 ); // ====================== 1. 移位寄存器缓存采样 ====================== reg [DATA_WIDTH-1:0] shift_reg [0:TAP_NUM-1]; integer i; always @(posedge clk or negedge rst_n) begin if(!rst_n) begin for(i=0; i<TAP_NUM; i=i+1) shift_reg[i] <= 'd0; end else if(sample_en) begin shift_reg[0] <= x_in; for(i=1; i<TAP_NUM; i=i+1) shift_reg[i] <= shift_reg[i-1]; end end // ====================== 2. 系数ROM 8阶低通FIR Q15定点 ====================== reg [COE_WIDTH-1:0] coe_rom [0:TAP_NUM-1]; initial begin // MATLAB生成8阶低通系数 Q15量化 coe_rom[0] = 16'h0240; coe_rom[1] = 16'h0780; coe_rom[2] = 16'h0C80; coe_rom[3] = 16'h1000; coe_rom[4] = 16'h1000; coe_rom[5] = 16'h0C80; coe_rom[6] = 16'h0780; coe_rom[7] = 16'h0240; end // ====================== 3. 循环计数器 0~7 ====================== reg [3:0] cnt; always @(posedge clk or negedge rst_n) begin if(!rst_n) begin cnt <= 'd0; end else if(sample_en) begin cnt <= 'd0; end else if(cnt < TAP_NUM - 1) begin cnt <= cnt + 1'b1; end end // ====================== 4. 分时乘法器 ====================== wire [DATA_WIDTH-1:0] x_sel; wire [COE_WIDTH-1:0] h_sel; wire [ACC_WIDTH-1:0] mult_prod; assign x_sel = shift_reg[cnt]; assign h_sel = coe_rom[cnt]; // 乘法器:有符号乘,自动调用DSP48 assign mult_prod = $signed(x_sel) * $signed(h_sel); // ====================== 5. 累加器核心 ====================== reg signed [ACC_WIDTH-1:0] accum; always @(posedge clk or negedge rst_n) begin if(!rst_n) begin accum <= 'd0; end else if(sample_en) begin accum <= 'd0; // 新采样到来清空累加器 end else begin accum <= accum + mult_prod; // 逐次累加乘积 end end // ====================== 6. 输出锁存与有效标志 ====================== always @(posedge clk or negedge rst_n) begin if(!rst_n) begin y_valid <= 1'b0; y_out <= 'd0; end else if(cnt == TAP_NUM - 1) begin y_valid <= 1'b1; // 右移Q_BIT位,截断至输入位宽 y_out <= accum[ACC_WIDTH-1 : Q_BIT]; end else begin y_valid <= 1'b0; end end endmodule

五、代码关键逻辑解读

  1. 移位寄存器sample_en(采样时钟)到来时存入新采样,所有历史数据右移,shift_reg[k]对应x[n−k];
  2. 系数 ROMinitial块预存定点系数,高阶滤波器可改用 IP 核 Block ROM 节省逻辑;
  3. 计数器时序采样使能时 cnt 清零,之后每个系统时钟 + 1,依次读取每一组采样与系数;
  4. 累加器工作每次采样刷新自动清零,每个时钟叠加一次乘积,完成全部 8 次乘加后输出;
  5. 定点缩放累加结果右移 15 位抵消 Q15 放大,截取高位得到与输入同位宽输出。

六、仿真验证思路(ModelSim/MATLAB 联合)

  1. MATLAB 生成正弦叠加带噪输入波形,导出 txt 采样数据;
  2. 仿真激励读取采样,驱动sample_en
  3. 抓取y_out输出波形,与 MATLAB 浮点 FIR 滤波结果对比;
  4. 观察y_valid仅在每轮 8 次运算结束时拉高,输出平滑滤波波形,高频噪声被抑制。

七、架构优缺点分析

优点

  1. 资源占用极低:仅使用 1 个硬件乘法器(1 个 DSP48),阶数提升不增加乘法器;
  2. 结构简单易实现:无复杂并行布线,时序收敛容易;
  3. 系数修改方便:系数存在 ROM,可动态更新实现可调滤波。

缺点

  1. 吞吐率受限:完成一次输出需要 N 个系统时钟,采样频率受系统时钟约束;
  2. 高阶滤波器时钟压力大:N 很大时必须大幅提升系统时钟,无法满足高速采样场景;

适用场景

低速采集系统(温感、音频、低频传感器)、FPGA 逻辑资源紧张、滤波器阶数中等(N<128)。

八、拓展改进方案

  1. 对称系数减半运算增加加法器先合并对称采样,循环次数减半,降低时钟频率要求;
  2. 多周期流水线累加在乘法器后插入寄存器,切割关键路径,提升最高系统时钟;
  3. 多通道时分复用多组输入共用同一套累加器 FIR,进一步节省 DSP;
  4. 分布式 RAM 存采样高阶 FIR 时用 Block RAM 代替移位寄存器,减少大量寄存器消耗。

九、与并行 FIR 对比总结

表格

架构乘法器数量时钟要求资源消耗最高采样速率
并行直接型 FIRN 个采样时钟即可极高最高
基于累加器串行 FIR1 个N× 采样时钟极低低速

基于累加器的 FIR 是面积换取速度的典型串行架构,是 FPGA 入门 FIR 最经典、最容易理解的实现方式。

for(i=1; i<TAP_NUM; i=i+1) shift_reg[i] <= shift_reg[i-1];什么意思

逐行拆解这条移位寄存器代码

verilog

for(i=1; i<TAP_NUM; i=i+1) shift_reg[i] <= shift_reg[i-1];

1. 先看懂变量定义

shift_reg是一个寄存器数组,用来存 FIR 滤波器连续 N 个采样点:

verilog

reg [DATA_WIDTH-1:0] shift_reg [0:TAP_NUM-1];
  • TAP_NUM:FIR 抽头总数(例子里是 8)
  • 数组下标:shift_reg[0]、shift_reg[1] ... shift_reg[7]
  • shift_reg[0]:最新进来的采样x[n]
  • shift_reg[1]:上一拍采样x[n-1]
  • shift_reg[2]:前两拍采样x[n-2]……
  • shift_reg[7]:最早的旧采样x[n-7]

2. for 循环执行逻辑(硬件综合后等效移位链)

循环条件:

  • 初始i=1
  • 只要i < TAP_NUM就执行一次赋值,然后i=i+1

TAP_NUM=8举例,循环会依次执行这 7 条并行赋值:

verilog

shift_reg[1] <= shift_reg[0]; shift_reg[2] <= shift_reg[1]; shift_reg[3] <= shift_reg[2]; shift_reg[4] <= shift_reg[3]; shift_reg[5] <= shift_reg[4]; shift_reg[6] <= shift_reg[5]; shift_reg[7] <= shift_reg[6];

3. 整体移位流程(配合前面一句)

完整移位逻辑是两段合在一起:

verilog

shift_reg[0] <= x_in; // 第1步:新数据放进最左边shift_reg[0] for(i=1; i<TAP_NUM; i=i+1) shift_reg[i] <= shift_reg[i-1]; // 第2步:所有旧数据整体右移一格

移位过程演示(8 阶 FIR)

假设移位前:[0]=A(新)、[1]=B、[2]=C、[3]=D、[4]=E、[5]=F、[6]=G、[7]=H

采样使能到来,先把新采样x_in=X送入[0],再执行 for 循环移位:

  1. shift_reg[1]取原来[0]的 A
  2. shift_reg[2]取原来[1]的 B
  3. ……
  4. shift_reg[7]取原来[6]的 G

移位后状态:[0]=X、[1]=A、[2]=B、[3]=C、[4]=D、[5]=E、[6]=F、[7]=G最末尾旧数据 H 被直接丢弃,实现先进先出的采样缓存

4. 为什么 i 从 1 开始,不从 0?

  • shift_reg[0]单独赋值新输入x_in,不需要从左边拿数据;
  • 如果 i 从 0 开始,会出现shift_reg[0] <= shift_reg[-1],下标越界,语法报错。

5. 硬件层面本质

Verilog 里always@(posedge clk)内部的 for 循环是并行执行,不是软件 C 语言串行循环: 综合后生成一条级联移位寄存器链,每来一次采样时钟,整组采样集体右移一位,专门给 FIR 提供延时序列x[n],x[n-1],...,x[n-N+1],满足卷积公式的延时需求。

6. 一句话总结

这条 for 循环的作用: 当有新采样输入时,把移位寄存器里所有历史采样依次向右挪一格,腾出第一个位置存放最新采样,丢掉最老旧的采样,为 FIR 滤波提供不同延时的输入数据。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询