FPGA实现FIR滤波器:从原理到Verilog与DAC接口实战

发布时间:2026/9/15 14:12:40

FPGA实现FIR滤波器:从原理到Verilog与DAC接口实战 简介一套完整的基于FPGA的FIR数字滤波器课程设计与实验资源面向FPGA初学者、数字信号处理课程学生及电子设计竞赛参与者解决从原理理解、D/A硬件平台搭建到滤波器下载验证的全流程问题。资源包为7z格式共378个文件约19.8MB主要包含Quartus II工程与数据库文件如.cdb/.hdb/.qpf/.sof、Verilog/VHDL源码.v、存储器初始化文件.mif、实验报告与说明文档.docx/.pdf/.txt以及波形截图.jpg便于按工程流程对照学习。已有236人学习下载适合快速完成课设或验证FIR滤波算法。资料围绕任务书要求展开涵盖数字滤波器工作原理与设计方法详解、基于半成品硬件构建D/A功能平台的过程以及VHDL/Verilog代码下载到硬件后的验证与理论分析实验文档详细目录结构完整可参考实现步骤、实验现象与结果分析能显著缩短课程设计和竞赛备赛周期。1. 从一段被拆开的 FIR 工程讲起手头这份工程里躺着一堆fir_dac_*.map.atm、fir_dac.sldhu_*.map.atm和*.v.bak文件懂行的一眼就明白这是 Quartus II 跑过完整综合、映射、布线后的残留产物fir.v.bak和reg32.v.bak说明源码经历过至少一轮改版。做 FIR 数字滤波器用 FPGA 而不是单片机或 DSP关键在于 FIR 的乘加运算天然并行FPGA 可以在一拍内完成 N 个滤波器系数的并行乘加用上 DSP 块后更省逻辑而 CPU 只能用循环串行累加。这篇博客就把 FIR 从原理、Verilog/VHDL 实现、Quartus II 工程搭建到板级验证的完整链路拆开讲适合正在做数字信号处理课设、或想把手头 FPGA 板子用起来的工程师。2. FIR 滤波器原理与 FPGA 内部实现结构2.1 线性相位 FIR 的原理与抽头结构FIRFinite Impulse Response有限脉冲响应滤波器的输出只取决于当前和过去的有限个输入样本没有反馈回路因此天生稳定。其数学表达式为y[n] sum_{k0}^{N-1} h[k] * x[n-k]h[k] 是滤波器系数x[n-k] 是输入信号的延迟样本。工程上最常用的是线性相位 FIR即系数关于中心对称h[k] h[N-1-k]。线性相位的好处是滤波器对所有频率分量的延迟一致不会造成波形相位失真这对后续信号分析、通信解调很关键。FPGA 实现 FIR 的结构是抽头延迟链加乘加树。输入 x[n] 进入一个由 D 触发器串联构成的移位寄存器每经过一个触发器就产生一个延迟样本 x[n-k]所有延迟样本和滤波器系数相乘后累加。由于乘加运算彼此独立FPGA 可以把 N 个乘法器并行展开在一到两个时钟周期内完成整个滤波计算。相比之下在 ARM 或 DSP 上做同样阶数比如 32 阶的 FIR需要 32 次乘法和 31 次加法串行执行主频再高也有数十个周期的开销。利用系数对称性可以进一步省资源。以 32 阶线性相位 FIR 为例h[0] h[31]h[1] h[30]以此类推那么y[n] h[0]*(x[n] x[n-31]) h[1]*(x[n-1] x[n-30]) ...只需要 16 组乘加单元而非 32 组乘法器数量直接减半。对于中等规模 FPGA如 Cyclone IV 系列16 个 18x18 乘法器是完全充裕的剩余逻辑还能做 DAC 控制等外围模块。这正是课程设计里fir_dac_global_asgn_op.abo这类工程同时包含滤波器核和 DAC 接口逻辑的原因——整体资源占用被压得很低。2.2 系数设计与量化位宽选择系数设计使用 MATLAB 的 FDA Tool 或者 Python 的scipy.signal.remez都可以这里以常见的低通滤波器为例采样率 Fs 50 MHz通带截止频率 5 MHz阻带起始频率 8 MHz通带纹波 0.1 dB阻带衰减 60 dB。利用 Parks-McClellan 算法计算所得的系数浮点值必须量化到固定位宽才能在 FPGA 中参与运算。系数位宽的选择直接决定滤波性能和资源开销。FPGA 内嵌的 DSP 块如 Altera 的 ALTMULT_ADD通常是 18x18 位乘法器系数位宽取 12~16 位是平衡点。设系数位宽为 16 位采样数据位宽为 12 位对应 12 位 DAC相乘结果为 28 位。累加过程中数据位宽还会扩展因此最终输出要做一个舍入或截断策略。下表列出不同配置下的量化参数参数项典型值说明采样数据位宽12 bit对应外部 ADC/DAC 分辨率系数位宽16 bit量化后系数范围 [-1, 1)乘法结果位宽12 16 28 bitDSP 块单次乘法输出累加器位宽28 log2(N/2) bit对称结构 N/2 个加法项这里是 32/216 项需扩 5 bit最终输出位宽12 bit截取高 12 位并四舍五入注意最终截位直接取高 12 位会造成直流偏置和噪声常见做法是加上 2^(k-1) 做四舍五入再截位其中 k 是丢弃的低位数量。以下是 Verilog 实现对称 FIR 核心运算的代码片段采用两段式流水线提高时序性能// 32阶线性相位FIR利用系数对称性仅使用16个乘法器 module fir_symmetric #( parameter DATA_WIDTH 12, parameter COEF_WIDTH 16, parameter TAP_NUM 32 )( input wire clk, input wire rst_n, input wire signed [DATA_WIDTH-1:0] x_in, output reg signed [DATA_WIDTH-1:0] y_out ); // 系数固化实际由MATLAB量化后生成 localparam signed [COEF_WIDTH-1:0] h[0:15] {...}; reg signed [DATA_WIDTH-1:0] x_delay[0:TAP_NUM-1]; reg signed [DATA_WIDTHCOEF_WIDTH-1:0] mul_out[0:TAP_NUM/2-1]; reg signed [DATA_WIDTHCOEF_WIDTH4:0] sum_reg; integer i; always (posedge clk or negedge rst_n) begin if (!rst_n) begin for (i 0; i TAP_NUM; i i 1) x_delay[i] 0; end else begin x_delay[0] x_in; for (i 1; i TAP_NUM; i i 1) x_delay[i] x_delay[i-1]; end end // 对称配对h[k]*(x[n-k] x[n-31k]) always (posedge clk or negedge rst_n) begin if (!rst_n) begin for (i 0; i TAP_NUM/2; i i 1) mul_out[i] 0; end else begin for (i 0; i TAP_NUM/2; i i 1) mul_out[i] h[i] * ($signed(x_delay[i]) $signed(x_delay[TAP_NUM-1-i])); end end // 累加直接展平求和并四舍五入截位 always (posedge clk or negedge rst_n) begin if (!rst_n) begin sum_reg 0; y_out 0; end else begin sum_reg mul_out[0] mul_out[1] mul_out[2] mul_out[3] mul_out[4] mul_out[5] mul_out[6] mul_out[7] mul_out[8] mul_out[9] mul_out[10] mul_out[11] mul_out[12] mul_out[13] mul_out[14] mul_out[15]; y_out sum_reg[DATA_WIDTHCOEF_WIDTH4:5] (sum_reg[4] ? 1b1 : 1b0); end end endmodule这段代码里有几个值得留意的地方。第一所有输入输出和中间变量都用signed声明确保乘法器做有符号乘法而不是无符号乘法否则负输入样本会导致错误的乘积结果。第二对称配对先做加法再乘法利用x_delay[i] x_delay[TAP_NUM-1-i]把两个延迟样本合并随后与系数相乘节省一半的 DSP 乘法器。第三累加器位宽扩了 5 位即DATA_WIDTHCOEF_WIDTH4这是为了容纳 16 个乘法结果累加时的进位扩展如果位宽不足会出现严重的溢出噪声。3. Quartus II 工程搭建与半成品 DAC 平台对接3.1 从备份文件还原工程结构拿到手的是一个已被 Quartus II 综合过的工程目录里面的.map.atm是映射阶段生成的网状文件.sldhu_*.map.atm表明工程里调用过 HardCopy 或系统级设计工具做布局评估.v.bak是源码备份。还原工程时不需要重新建工程直接打开目录下原有的.qpf工程文件即可。如果工程文件损坏则需要新建工程并把主要源码重新添加进去从文件名可以判断模块划分fir.v.bakFIR 滤波器主体模块rom_top.v.bak系数 ROM 顶层或 ROM 例化封装fir_dac.v.bak滤波器与 DAC 的顶层集成模块reg32.v.bak32 位寄存器模块可能用于配置滤波器参数或 DAC 控制字key.v.bak按键消抖输入模块这些模块组合起来就是题目所说的“半成品硬件”——一块带有 ADC/DAC、FPGA、时钟源和按键的最小系统板滤波器核心逻辑由学生自己完成。工程里的.qsf文件中会保存器件型号如 EP4CE6E22C8、引脚分配和时序约束建议先打开.qsf确认顶层实体是fir_dac还是fir再决定从哪个文件开始修改。3.2 引脚约束与 DAC 接口时序半成品板上的 DAC 芯片常见为并行输入的 12 位器件如 AD7945 或 TI 的 DAC902与 FPGA 的连接包括 12 位数据总线、片选信号 CS、写信号 WR 和可能的时钟引脚。Quartus II 中引脚分配在Assignments - Pin Planner中完成也可以直接编辑.qsf文件。下面是一段典型的引脚约束set_location_assignment PIN_AB7 -to clk_50m set_location_assignment PIN_AA7 -to rst_n set_location_assignment PIN_AB12 -to dac_data[11] set_location_assignment PIN_AB11 -to dac_data[10] ... set_location_assignment PIN_AC9 -to dac_wr set_location_assignment PIN_AD10 -to dac_cs set_instance_assignment -name IO_STANDARD 3.3-V LVTTL -to dac_data[11]注意引脚编号没有通用性必须对照你手头板卡的原理图修改。很多初学者在 Verilog 仿真完全正确的情况下下载后发现输出全是高阻或不稳定八成是引脚没有约束、或者IO_STANDARD与实际电平标准不匹配板载 DAC 如果是 5 V 供电还需确认 FPGA 引脚是否兼容 5 V 输入必要时增加电平转换。DAC 接口的时序核心是建立时间和保持时间。以 DAC902 为例其典型写时序要求 WR 信号高电平宽度不小于 20 ns数据在 WR 上升沿前建立至少 10 ns。FPGA 侧主时钟 50 MHz周期为 20 ns所以最简单的做法是 50 MHz 时钟直接产生 WR 信号每两个周期输出一个有效数据。以下代码实现了一个精简的 DAC 发送状态机// 12位并行DAC接口控制器数据率 clk/2 module dac_if ( input wire clk, input wire rst_n, input wire [11:0] data_in, output reg [11:0] dac_data, output reg dac_wr, output reg dac_cs ); reg dac_toggle; always (posedge clk or negedge rst_n) begin if (!rst_n) begin dac_toggle 1b0; dac_data 12d0; dac_wr 1b1; dac_cs 1b1; end else begin dac_toggle ~dac_toggle; if (dac_toggle) begin dac_data data_in; dac_wr 1b0; end else begin dac_wr 1b1; end end end endmodule这段状态机的逻辑是dac_toggle以系统时钟的一半频率翻转当它为高时把滤波结果送入dac_data并将 WR 拉低当它为低时WR 拉高形成上升沿DAC 捕获数据。CS 直接拉低表示整个滤波周期内片选一直有效。切换 WR 的时机必须放在数据稳定之后也就是先更新dac_data再拉高 WR代码里用dac_toggle的两个相位自然实现了这个先后顺序避免出现数据变化与写信号同时发生的毛刺。如果后续要扩展更高的数据率可以考虑用 PLL 把时钟倍频到 100 MHz让 WR 高电平缩短到 10 ns但要注意 FIFO 深度和跨时钟域处理这个后面章节会讲到。4. Modelsim 仿真验证与实验结果理论分析4.1 仿真激励构造与波形观察Quartus II 自带 ModelSim 集成或独立版 ModelSim仿真前需要把工程源码和激励文件都添加进仿真工程。FIR 滤波器的验证思路是用一个混合信号低频正弦叠加高频噪声作为输入观察输出是否只保留低频分量。激励代码用 Verilog 写testbench从文件读取测试数据或直接生成正弦波timescale 1ns/1ps module tb_fir_dac; reg clk; reg rst_n; reg [11:0] stimulus; wire [11:0] filt_out; // 被测模块例化此处省略参数映射 fir_dac uut ( .clk (clk ), .rst_n(rst_n), .x_in (stimulus), .y_out(filt_out) ); // 50 MHz时钟周期20 ns initial begin clk 0; forever #10 clk ~clk; end // 激励产生1 MHz正弦波 20 MHz正弦波模拟噪声 integer idx; real t, raw, signal_part, noise_part; initial begin rst_n 0; stimulus 12d0; #100; rst_n 1; // 利用正弦函数生成离散样本量化到 12 位有符号范围 for (idx 0; idx 1024; idx idx 1) begin t idx * 20.0 / 1000.0; // 时间步进20 ns对应50MHz采样率 signal_part 1500.0 * $sin(2.0 * 3.1415926 * 1.0e6 * t / 1.0e9); noise_part 500.0 * $sin(2.0 * 3.1415926 * 20.0e6 * t / 1.0e9); raw signal_part noise_part; stimulus $rtoi(raw); #20; end #1000; $finish; end initial begin $dumpfile(fir_tb.vcd); $dumpvars(0, tb_fir_dac); end endmodule这段激励里要解释清楚几个关键点。#10翻转时钟对应 50 MHz 主频而每个样本保持时间为 20 ns也就是滤波器输入每拍更新一次。$sin函数是 Verilog 内置系统函数参数必须是以时间为单位的弧度值所以先把时间 t 换算成以 ns 为单位的绝对时间再乘角频率。signal_part的幅度设为 1500noise_part设为 500量化到 12 位有符号数范围 -2048 到 2047信号幅度低于满量程是为了防止加法溢出。仿真运行后在波形窗口添加stimulus和filt_out可以明显看到输出中高频成分被削平。如果做频谱级验证可以在 ModelSim 里把输入输出数据通过$fwrite写入文本文件然后导入 MATLAB 用fft()分析。下面是导出数据的脚本片段integer fout; initial begin fout $fopen(output_samples.txt, w); // 在采样时刻将输出写入文件方便MATLAB后处理 always (posedge clk) begin if (idx 10 idx 512) begin $fwrite(fout, %0d\n, $signed(filt_out)); end end end写入文件时用$signed确保负值以补码数值写入文本否则会看到大量十六进制的FFFxxx而无法直接绘图。导入 MATLAB 后做 512 点 FFT对比滤波前后的幅度谱理论上 1 MHz 的谱线幅度保持不变20 MHz 的谱线衰减超过 45 dB对应 12 位系数量化的实际阻带衰减。4.2 滤波器冲激响应与群延迟验证除正弦信号外冲激响应测试也是 FIR 验证的必备项。把激励电路的输出stimulus强制改为一个脉冲即第一个样本设为 2047其余样本全为 0。仿真后观察filt_out是否等于量化后的系数值序列并且关于中心对称。这一步能够快速发现系数排列顺序是否颠倒、或者对称索引是否写错。群延迟验证在课程设计中常常被忽略但我觉得值得强调。线性相位 FIR 的群延迟恒为 (N-1)/2 个采样周期对 32 阶滤波器就是 15.5 个周期。在一个输入正弦信号跳变沿处测量输出波形相对输入的延迟时间应当恰好等于 15.5 乘以采样周期即 310 ns。用 ModelSim 的光标功能可以量出这一延迟。如果实测结果明显偏离说明滤波器系数不对称或数据路径中存在额外流水级。这里要补充一下工程文件fir_dac.root_partition.map.atm的用途。.map.atm是 Quartus II 在 Analysis Synthesis 阶段生成的网表快照用于增量编译中的根分区。在 ModelSim 仿真时是不需要这些文件的但在 Quartus II 中执行Start Analysis Elaboration时如果工程的根分区被锁定改代码后不会自动重编。遇到这种情况右键工程目录里的根分区名称选择 Unassign Partition再重新编译即可。5. 时序收敛与高采样率下的实现技巧当 FIR 阶数达到 64 阶以上、或采样率要求超过 100 MHz 时简单的单级累加结构会开始碰壁。综合工具报告负的 Setup Slack通常是因为累加链太长导致单周期内组合逻辑路径过大。解决办法是插入流水寄存器把累加过程拆成多拍完成。例如将 32 个乘法结果的累加拆成树形结构第一拍完成两两相加的 16 个结果第二拍完成 8 个结果依此类推相当于用 5 级流水换 Fmax 的显著提升。代价是输出延迟多出几个时钟周期但由于 FIR 是线性时不变系统固定延迟不影响幅频响应只是群延迟增大了几个采样周期。另一个高频场景的常见技巧是采用半并行结构。如果 FPGA 资源只够放下 4 个乘法器但需要 32 阶滤波可以让一个乘法器分时复用把整个滤波计算分摊到 8 个时钟周期内完成。此时控制逻辑需要一个计数器当前抽头编号与系数 ROM 读地址联动每周期只有一组乘加在工作输出数据率降到原来的 1/8。这个思路在资源受限的小型 FPGA如 Cyclone 10 LP 的入门型号上很实用代价是牺牲吞吐率适合慢速 DAC 输出或传感器信号处理场景。最后提一个我在实际调试中踩过好几次的坑D/A 输出波形上有大量尖峰但仿真完全正常。这通常不是逻辑问题而是数据总线在切换时不同位的走线延迟不一致导致的毛刺DAC 在 WR 上升沿瞬间采到了中间态。处理方法是提高 WR 信号的建立时间余量或者在 FPGA 内部对输出数据做一级 IOB 寄存器寄存让数据变化与 WR 边沿对齐。更严谨的做法是在数据总线上插入延迟链动态调整 DQS 与 DQ 的相位关系不过这在小项目里通常用不上。如果你的工程里恰好有reg32.v.bak不妨看看它是否被用作数据输出锁存这是很多参考设计预留的接口。本文还有配套的精品资源点击获取
延伸阅读

更多相关文章

2026/9/15 14:07:38

大文件上传、断点续传、秒传

#如何系统性地设计一个支持大文件上传和断点续传的方案面试答案核心架构:“三驾马车”一个成熟的方案通常是三大核心技术的组合:分片上传 (Chunked Upload)、断点续传 (Resumable Upload) 和秒传 (Instant Upload)。分片上传:为传输大文件“搭…

2026/9/15 14:32:41

sqlmap深度实战:从SQL注入检测到企业级安全巡检

1. 这不是黑客工具,而是一把数据库安全的听诊器sqlmap——这三个字母在渗透测试工程师的日常里,几乎等同于“SQL注入检测”的代名词。它不是用来搞破坏的黑产脚本,而是一个高度工程化的、开源的自动化SQL注入与数据库接管工具。我第一次在客户…

2026/9/15 4:54:30

拯救者Y7000黑屏故障排查与维修实战指南

1. 项目概述:一台黑屏的拯救者Y7000,到底卡在哪一步? 联想拯救者Y7000系列笔记本,从2018年第一代搭载i5-8300H开始,到后来的i7-9750H、i7-10750H、i5-11400H,再到2023年款的R7-7840HS,它始终是学…

2026/9/15 0:01:16

AI英语单词APP开发:自适应学习算法与移动端优化实践

1. 项目概述 作为一名在移动应用开发领域摸爬滚打多年的老手,我最近完成了一个AI英语单词APP的开发项目。这个项目将传统单词记忆方法与现代AI技术相结合,打造了一款能够智能适应不同用户学习习惯的英语学习工具。 市面上大多数单词APP都存在一个通病&a…

2026/9/15 0:01:16

Flutter与OpenHarmony结合开发手语学习APP实战

1. 项目背景与核心价值作为一名同时接触过Flutter和OpenHarmony的开发者,最近我完成了一个基于Flutter for OpenHarmony的手语学习APP实战项目。这个项目最大的特点在于实现了跨平台框架与国产操作系统深度结合的创新实践——用Flutter开发的应用能完美运行在OpenHa…

2026/9/15 0:01:16

六个月成为机器人工程师:从ROS2到SLAM的实战路径

1. 六个月的紧迫感从哪来:先搞清楚你要成为哪种机器人工程师说实话,六个月的期限并不是一个宽松的时间线。市面上任何一本正经的机器人学教材都超过五百页,ROS2的官方文档可以翻到你怀疑人生,再加上ABB、KUKA这些工业机器人厂家动…

2026/9/15 14:22:53

USB Type-C PCB布局分区设计:电源、高速信号与PD协议全攻略

做硬件这行,Type-C接口算是典型的“看着简单,做起来全坑”的东西。光引脚就24个,高低速信号、电源、控制线全部塞在一个小小的连接器里,如果PCB布局不做规划,打样回来基本就是“插上没反应”、“高速掉线”、“静电一打…

2026/9/14 13:53:59

系统编程学习原型如何补齐稳定性边界

系统编程学习原型如何补齐稳定性边界预算有限时&#xff0c;我先优化明显多余的复制&#xff0c;而不是猜测性地换容器。用借用传递只读数据通常就能减少分配&#xff1a; fn parse(line: &str) -> Result<Item, Error> { /* ... */ }用基准确认热点确实在分配&am…

2026/9/15 11:42:23

雨花区哪家财务公司代理记账比较好?

在雨花区&#xff0c;企业处理财税事务常常面临诸多挑战&#xff0c;选择一家靠谱的财务公司至关重要。湖南巨勤财务管理咨询有限公司就是本地正规实体财税服务机构&#xff0c;深耕本地工商财税行业多年&#xff0c;熟悉当地工商局、税务局最新政策与申报流程。主营公司注册、…

还想了解更多?直接咨询顾问

免费诊断 + 免费方案 + 透明报价。

全国咨询热线400-8866-253
免费获取方案
咨询二维码