新闻详情

新闻详情

首页 / 资讯中心 / 详情

FPGA中DA分布式算法实现FIR滤波器,用查找表替代乘法器

发布时间:2026/9/16 4:09:56来源:尧图网络
FPGA中DA分布式算法实现FIR滤波器,用查找表替代乘法器
简介DA分布式FIR滤波器基于Verilog硬件描述语言实现在Xilinx Vivado 2019.2中完成开发采用纯Verilog设计、不依赖特定IP核可方便移植到Quartus II或ISE环境适合FPGA开发者与数字信号处理工程师学习高性能滤波器实现与跨平台代码迁移。资源共88个文件、约1.55MB除核心DA_table.v和da_fir.v源文件外还包含Vivado工程文件.xpr、综合与实现报告.rpt、IP核配置及仿真相关文件覆盖从设计输入到布局布线的完整流程。同时附有FPGA与MATLAB说明文档用于了解滤波器系数生成与响应验证方法帮助理解分布式算法如何将乘法转化为移位加法、降低硬件资源消耗。目前已有907人学习下载对希望掌握DA算法、熟悉Vivado工具链并具备Quartus/ISE移植能力的工程人员颇具参考价值。1. 做FIR滤波时为什么我放弃了乘法器改用DA分布式算法做 FIR 滤波器很多设计者第一反应是“抽头乘系数再加起来”于是 16 阶 8bit 输入的滤波器在 FPGA 上要占用十几个 DSP Slice。资源不紧张还行一旦要把同一片器件塞进多路滤波、FFT 或解调逻辑乘法器就成了瓶颈。DA 分布式算法思路不一样它把固定系数的乘累加拆成查找表和移位累加用 LUT 换 DSP特别适合 16 阶左右、系数固定且对称的 FIR。这个工程跑在 Vivado 2019.2 上纯 Verilog 编写核心文件只有 DA_table.v 和 da_fir.v一个生成查找表一个做移位累加。因为没有使用 Xilinx 原语工程可以直接搬到 Quartus II 或 ISE对做 FPGA 数字信号处理的人来说等于拿到了一套跨工具链的分布式 FIR 模板。2. DA 算法基础FIR 的乘法为什么能换成查表2.1 从乘累加公式到位平面分解一个 N 阶 FIR 输出公式是 y[n] sum(h[k] * x[n-k])其中 k 从 0 到 N-1h[k] 固定。硬件常规做法是例化乘法器每个抽头一个乘加器换成 DA 之后整个乘累加过程可以被“位平面”重写。把输入 x[k] 看成二进制补码宽度为 Wx[k] -b_{MSB} * 2^{W-1} sum(b_j * 2^j)其中 b_j 是第 j bit。代入 FIR 公式后交换求和次序得到y[n] sum_{j0}^{W-1} (2^j * F(b_{j,n}))这里 F(b_{j,n}) 只与同一时刻所有抽头信号的同一个 bit 有关给定当前 16 个抽头各自第 j bit 组成的 16 位向量F 就固定了。这个 F 可以预先算好存进查找表输入地址是 16 位查找表输出是系数加权和。对每一位查一次表再按位权移位累加W 次完成一个输出采样。这段推导中容易出错的是补码符号位。最高位的权重是负的所以符号位查出来的部分和应该做减法而不是加法。很多初版 DA 实现仿真波形尾部不对基本都是这个地方加了正号。2.2 用 MATLAB 生成系数与 DA_table.v工程里的 fpgamatlab.txt 就是这个用途先用 fir1 设计低通系数再定点量化。常见做法是把系数放大到 12bit 有符号范围也就是 [-2048, 2047]输入数据按 8bit 有符号补码进入。下方脚本可直接生成 Verilog 查找表文件。% fpga_matlab_da_gen.m N 16; % 抽头数 h fir1(N-1, 0.2); % 归一化通带 0.2 pi 的低通系数 coef_width 12; hq round(h * (2^(coef_width-1) - 1)); hq max(-2^(coef_width-1), min(2^(coef_width-1)-1, hq)); fid fopen(DA_table.v, w); fprintf(fid, // DA lookup table, N%d, coef_width%d\n, N, coef_width); fprintf(fid, module DA_table (\n); fprintf(fid, input [%d:0] addr,\n, N-1); fprintf(fid, output reg [%d:0] sum\n, coef_width3); fprintf(fid, );\n); fprintf(fid, always (*) begin\n); fprintf(fid, case (addr)\n); for addr 0:2^N-1 bits dec2bin(addr, N) - 0; sum_val hq * bits(:); fprintf(fid, %d\d%d: sum %d\d%d;\n, N, addr, coef_width4, sum_val); end fprintf(fid, endcase\n); fprintf(fid, end\n); fprintf(fid, endmodule\n); fclose(fid);逻辑说明hq * bits(:)计算“当前抽头 bit 向量”对应的系数和例子里 16 个抽头会生成 65536 行 case。查找表位宽取 16bit是因为 12bit 系数 16 项求和最大绝对值约 12416bit留了 4bit 裕量防止中间累加溢出。合成选项如果仅做验证65536 行的 case 也能综合就是 LUT 占用偏大若做对称优化表规模会小很多细节放到第三章。这里的coef_width最后如果改到 16bit位宽要同步改否则综合工具会报 truncation warning。2.3 查找表里到底存了什么用一个 4 抽头例子直观理解。假设四个系数分别是 h0、h1、h2、h3地址最低位对应最旧抽头还是最新抽头需要写代码时统一这里定义 bit0 对应 h0。addr (bin)参与求和的系数查找表输出 sum0000无00001h0h00010h1h10100h2h21000h3h31011h0 h2 h3h0h2h3可以看到每一项都是纯加法没有乘法。DA“分布式”的含义就是把系数分散到输入数据的每一位上每次只查当前位的组合结果最后的完整输出靠移位累加完成。3. da_fir.v 实现移位、查表、累加的时序细节3.1 顶层接口与参数设计da_fir.v 是整个工程的核心建议把它当成一个可复用 IP 来读。我通常这样定义端口信号方向位宽说明clkinput1系统时钟rst_ninput1低有效复位dininput8有符号输入补码din_validinput1输入有效脉冲doutoutput16滤波结果dout_validoutput1输出有效脉冲参数可以留成parameter N16, INPUT_W8, COEF_W12。这样做的好处是换一个滤波器规格时不需要改模块内部逻辑只改参数并重新生成 DA_table.v。纯 Verilog 实现里没有使用 Xilinx RAM 或 DSP 原语所以综合时不依赖 Vivado 自带的 IP这也是能移植到 Quartus II 的基础。3.2 移位寄存器与按位查表输入数据首先要对齐到 16 个抽头窗口。我习惯用一个数组做移位寄存器而不是写 16 行dout[0] din。这样代码短也方便后续改成任意抽头数。reg [7:0] shift_reg [0:15]; integer i; always (posedge clk or negedge rst_n) begin if (!rst_n) begin for (i 0; i 16; i i 1) shift_reg[i] 8d0; end else if (din_valid) begin shift_reg[0] din; for (i 1; i 16; i i 1) shift_reg[i] shift_reg[i-1]; end end reg [15:0] raddr; always (*) begin raddr 16d0; for (i 0; i 16; i i 1) raddr[i] shift_reg[i][bit_index]; end逻辑说明shift_reg[0]是最新输入shift_reg[15]是最旧采样。bit_index由后级状态机提供从 0 递增到 7。raddr的第 i bit 对应第 i 个抽头的第bit_indexbitaddr 的顺序必须和 MATLAB 生成查找表时定义的 bit 顺序一致否则系数会错位滤波结果会变成乱序加权。这个组合逻辑块的输出会送到 DA_table 的 addr 端口。如果表规模较大查表输出本身有组合延迟配合 100MHz 左右时钟时建议在 DA_table 输出后加一级寄存器。3.3 补码符号位必须做减法查表得到 F(b_j) 之后需要逐位累加。下面是一个可读性优先的累加状态机实际工程中可改成流水线覆盖输出。localparam IDLE 3d0; localparam ACC 3d1; localparam SUB 3d2; reg [2:0] state; reg [15:0] acc; reg [2:0] bit_cnt; always (posedge clk or negedge rst_n) begin if (!rst_n) begin acc 16d0; bit_cnt 3d0; state IDLE; dout_valid 1b0; end else begin dout_valid 1b0; case (state) IDLE: begin if (din_valid) begin acc 16d0; bit_cnt 3d0; state ACC; end end ACC: begin if (bit_cnt 3d7) begin acc acc - table_out; // bit7 是符号位 state SUB; end else begin acc acc (table_out bit_cnt); bit_cnt bit_cnt 1b1; end end SUB: begin dout acc; dout_valid 1b1; state IDLE; end endcase end end逻辑说明ACC状态里对 bit0 到 bit6 做算术左移累加table_out bit_cnt表示乘上 2^bit_cnt。等 bit_cnt 到 7 时当前查的是补码符号位权重是 -2^7所以执行acc - table_out。SUB状态只用来寄存最终结果让dout保持一个完整时钟周期方便下游模块采样。如果在这里把符号位处理成加法会导致直流分量错误。验证方法给一个恒定的 8’d10 输入正确输出应该是系数和乘以 10负权重加错会导致输出明显偏小。3.4 用 FIR 对称性把查找表从 65536 行降到 256 行16 阶线性相位 FIR 满足 h[k] h[15-k]因此可以先合并对称抽头再做查表。这样查表地址从 16 位降到 8 位规模从 65536 降到 256代价是多了 8 个对称加法器。对于 Artix-7 这类 LUT 富裕、乘法器紧张的器件这种交换通常很划算。wire signed [8:0] pre_sum[0:7]; assign pre_sum[0] {shift_reg[0][7], shift_reg[0]} {shift_reg[15][7], shift_reg[15]}; assign pre_sum[1] {shift_reg[1][7], shift_reg[1]} {shift_reg[14][7], shift_reg[14]}; // 其余抽头类似说明两个 8bit 补码相加可能产生 9bit 结果所以pre_sum用 9bit 有符号数。{shift_reg[0][7], shift_reg[0]}是符号扩展。对称合并之后查表表的地址就是pre_sum[0..7]而查表结果仍是 12bit 系数乘 8bit 输入求和数值范围没有变化。资源消耗大概是实现方式查找表规模额外加法器综合资源预期直接 DA2^16 65536 行无LUT 多对称 DA2^8 256 行8 个 9bit 加法器LUT 少4. 在 Vivado 2019.2 中跑通工程与仿真4.1 project_1 工程文件结构说明解压后看到project_1.xpr、project_1.srcs、project_1.runs、project_1.ip_user_files等这是 Vivado 标准工程目录。各目录作用如下路径作用project_1.xprVivado 工程主文件记录源文件、约束、器件型号project_1.srcs/sources用户源码包括 da_fir.v、DA_table.vproject_1.srcs/sim仿真测试文件project_1.runs/synth_1综合结果与 runme.logproject_1.runs/impl_1布局布线结果与时序报告fpgamatlab.txt系数设计说明与 MATLAB 对比信息4.1.1 用 Tcl 重建避免路径漂移如果打开 xpr 时提示文件路径错误通常是因为工程压缩包在其他机器解压后路径变化。最省事的做法是不改工程文件用 Vivado Tcl Console 重建。create_project da_fir_proj ./da_fir_proj -part xc7a35tcsg324-1 add_files -norecurse ./src/da_fir.v ./src/DA_table.v add_files -fileset sim_1 ./src/tb_da_fir.v set_property top da_fir [current_fileset] set_property top tb_da_fir [get_filesets sim_1] launch_simulation -mode behavioral run 20 us参数说明-part xc7a35tcsg324-1指定 Artix-7如果实际板卡是其他型号改成对应 part nameDA 逻辑本身不依赖具体器件。-fileset sim_1把 testbench 加入仿真文件集。run 20 us行为仿真跑 20 微秒假设时钟周期 10ns能覆盖 2000 个输入采样。4.2 测试平台把硬件输出导出成文本要验证滤波器行为建议用 testbench 把dout写进文本文件再和 MATLAB 结果对比。下面是一个能直接跑的仿真框架module tb_da_fir; reg clk 0; reg rst_n 0; reg din_valid 0; reg signed [7:0] din 0; wire signed [15:0] dout; wire dout_valid; integer fd, i; always #5 clk ~clk; initial begin fd $fopen(fir_out.txt, w); rst_n 0; #30 rst_n 1; for (i 0; i 500; i i 1) begin (posedge clk); din_valid 1; din $random; end (posedge clk); din_valid 0; #200; $fclose(fd); $finish; end always (posedge clk) begin if (dout_valid) $fwrite(fd, %0d %0d\n, $time, dout); end da_fir dut( .clk(clk), .rst_n(rst_n), .din(din), .din_valid(din_valid), .dout(dout), .dout_valid(dout_valid) ); endmodule说明din $random生成的是 32bit 随机数赋值给 8bit 端口会自动截断产生噪声输入如果想对比特定频率响应可以在 MATLAB 中生成正弦波量化后写入文件再用$readmemb读到 tb 中。$fwrite只记录dout_valid为高的时刻避免在流水线中间采样。最后用 MATLAB 把filter(hq,1,din)的定点结果与fir_out.txt比较。4.3 仿真结果异常时先看哪几个信号行为仿真失败优先查三处raddr在 bit_index 变化时是否毛刺。Vivado 的 xsim 在组合逻辑仿真里通常不会像门级仿真那样产生 glitch但如果 data type 错误会直接 X。table_out是否在bit_cnt7前稳定。如果acc在ACC状态比预期多一个时钟检查查表模块是否有额外寄存器。输出dout是否为有符号值。如果只看[15:0]的十进制补码负数会被当成大正数改用$signed(dout)显示。综合后的报告也要看。report_utilization里如果 DSP48E 数量不是 0说明代码中存在*运算并被工具推断为硬件乘法器。想确认是否保持了 DA 结构在综合后原理图里搜索LUT和MULT的实例数量即可。5. 移植到 Quartus II 或 ISE 的差异与验证技巧5.1 工具链差异判断工程里没有 Xilinx 原语所以移植第一步不是改代码而是确认目标工具的 Verilog 版本。ISE 对 Verilog-2001 支持稳定代码里建议避免使用、-:等扩展写法本工程写法能直接兼容。Quartus II 对signed表达式的推导比 Vivado 严格常见报错是“non-constant operand”出现这种问题时把算术表达式写成完整的补码加法和移位不要用隐式 signed 运算。检查项Vivado 2019.2Quartus IIISE工程文件.xpr.qpf.xise约束文件.xdc.sdc.ucf仿真工具xsimModelSimiSim/ModelSim对initial支持综合忽略行为仿真生效基本忽略忽略5.2 移植中最容易踩的两个坑第一个是DA_table.v的 65536 行 case。Quartus 综合时可能因为case太长产生 memory inference 警告甚至把查找表综合成冗余逻辑。常见做法是对称合并后只保留 256 行表或者在生成时把大表拆成两个 8bit 地址的小表级联。第二个是寄存器初始值。Vivado 和 Quartus 行为仿真允许initial给 reg 赋初值但上板后这些初值会被忽略。复位分支必须把所有acc、shift_reg、状态机寄存器全部归零。我的习惯是写一个synch_reset内部复位在rst_n拉低后的下一个时钟沿让状态机回到IDLE避免异步复位释放时产生亚稳态。reg rst_n_r; always (posedge clk or negedge rst_n) begin if (!rst_n) rst_n_r 1b0; else rst_n_r 1b1; end5.3 一个实用的移植验证技巧移植后不要急着上板先把 Vivado 仿真导出的fir_out.txt和 Quartus/ISE 仿真导出的结果做逐点对比。两份数据如果是纯组合路径加流水应该完全一致如果只在最高位差 1检查是不是仿真时把table_out从 16bit 截断到 8bit如果数据逐点相差固定倍数通常是符号位加减方向搞反了。更快的定位方法是让 MATLAB 脚本直接生成lookup.hexVerilog testbench 用$readmemh加载同一个查找表这样三套 FPGA 工具共享同一份系数可以排除“表内容不一致”这个变量。把$readmemh写进 DA_table 例化位置替换掉原来的组合逻辑 case就能在不重新运行 MATLAB 的情况下验证不同综合器的实现是否一致。本文还有配套的精品资源点击获取
网站建设高端定制企业官网
RELATED

相关资讯

更多精彩内容,欢迎继续阅读

较早相关资讯

最新相关资讯

LSM6DSL与高稳晶振协同抑制温度漂移的运动传感方案 2026/9/16 5:03:59

LSM6DSL与高稳晶振协同抑制温度漂移的运动传感方案

1. 这不是“又一个运动传感器方案”——LSM6DSL与R7KA8D2KFLCAC组合的真实价值锚点你可能已经看过太多标题里带“高精度”“实时跟踪”“工业级”的传感器方案,点进去发现不过是把LSM6DSL的数据手册参数复制粘贴一遍,再配上一段模糊的加速度曲线图。但这…

阅读更多 →
嵌入式高精度时间中枢:MCP79510+RA8D2硬件协同设计 2026/9/16 5:03:59

嵌入式高精度时间中枢:MCP79510+RA8D2硬件协同设计

1. 项目概述:这不是“时间管理App”,而是一套嵌入式系统级时间中枢看到标题里“卓越的时间管理”这六个字,别急着点开日历软件——这根本不是讲怎么用Notion做周计划,也不是教你怎么番茄钟打卡。它说的是在一块没有操作系统、没有…

阅读更多 →
多时间尺度调度如何出创新点?综合能源系统优化实战指南 2026/9/16 5:03:59

多时间尺度调度如何出创新点?综合能源系统优化实战指南

1. 这个方向为什么能持续出成果:先看懂多时间尺度调度到底在解决什么问题我最早接触综合能源系统优化调度这个方向时,第一反应是"这不就是把电、气、热几个系统的优化问题耦合在一起算一遍吗"。真做下去才发现,事情远没那么简单。整…

阅读更多 →
AI短漫剧全链路生产实战:从ComfyUI部署到成本优化 2026/9/16 5:03:59

AI短漫剧全链路生产实战:从ComfyUI部署到成本优化

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

阅读更多 →
操作系统课程C语言源码阅读指南:从AbstractMachine到线程切换 2026/9/16 5:03:59

操作系统课程C语言源码阅读指南:从AbstractMachine到线程切换

简介:基于南京大学蒋炎岩教授2024春学期《操作系统》课程的源码包,是操作系统课程配套代码资源,面向高校学生、考研复习者及对OS底层机制感兴趣的自学者,能帮助读者结合真实代码理解操作系统设计与实现。压缩包共19个文件&#xf…

阅读更多 →
TMF8801+R7KA8D2KFLCAC高精度抗干扰ToF测距方案 2026/9/16 5:00:59

TMF8801+R7KA8D2KFLCAC高精度抗干扰ToF测距方案

1. 这不是“测距仪”,而是一套可嵌入、可编程、能穿透烟雾的光学距离感知系统你手头拿到的 TMF8801 和 R7KA8D2KFLCAC,不是两颗普通芯片——它们是当前消费级与工业级边缘设备中,少有的能同时兼顾高精度(1mm)、抗干扰性…

阅读更多 →

今日资讯

本周资讯

本月资讯

看完文章仍有疑问?

联系尧图顾问,获取一对一建站咨询

立即免费咨询 📞 400-888-8888
📞