简介:基于高云FPGA的IP设计FIR低通滤波器工程包,面向毕业设计、课程设计、工程实训与学科竞赛等场景,完整覆盖从源码编写、IP核配置、功能仿真到板级验证的闭环流程。项目已在真实工程环境中测试运行成功,可直接复现,也可作为数字信号处理相关题目或大作业的扩展起点。资源共124个文件,压缩包约33.77MB,其中23个Verilog源文件构成滤波器核心逻辑,工程配置与约束文件帮助快速恢复开发环境,多个数据文件提供正弦波、余弦查找表及滤波器系数等仿真激励,文档包含设计报告与使用说明。已有154人学习,适合需要快速上手高云FPGA滤波器设计并追求可运行方案的学生或工程师。下载后先打开说明文档和工程目录,按步骤复刻工程,还可结合仿真波形与测试脚本理解系数生成、时序约束和频谱验证细节。
1. 高云FPGA上的FIR低通滤波器IP设计,难点不在Verilog而在参数
做课程设计或竞赛项目时,很多人听到“基于高云FPGA的IP设计FIR低通滤波器”,第一反应是去网上找现成的Verilog代码,然后把滤波器核心粘贴进工程。实际上FIR的差分方程本身很简单,真正决定设计能不能出彩的是另外三件事:滤波器系数怎么算、系数和数据的位宽怎么量化、IP核的资源和时钟怎么配。高云FPGA的云源软件相比Xilinx/Altera工具链更轻量,IP核配置也直接,适合在毕设、课设或实训里完整走通“系数设计→IP例化→仿真验证→上板调试”这条链路。这篇按这个顺序推进,最后落一个用FFT自查频率响应的技巧,让你在答辩或竞赛演示时有实测数据可讲,而不是只给一张时域波形图。
2. FIR低通滤波器系数设计:窗口法、阶数和16 bit量化
2.1 线性相位条件:系数对称是IP核资源减半的前提
FIR滤波器的时域表达式为 y[n] = Σ h[k]·x[n-k],k 从 0 到 N-1,其中 N 是抽头数。低通滤波器要求 h[k] 在通带内近似为 1、在阻带内近似为 0,并且用有限长度的窗函数截断理想低通响应。FPGA 实现时通常利用线性相位对称性 h[k] = h[N-1-k],把乘法器数量从 N 降为 N/2(奇数抽头时是 (N+1)/2)。高云IP核一般会自动识别系数文件的对称性,前提是你在系数文件里严格按左右对称排列。如果从MATLAB导出的系数没有排序,或者自己手工输入时错了一位,IP核会回退成全抽头计算,DSP资源占用直接翻倍,这在资源紧张的器件上可能直接导致布局布线失败。
“阶数”这个概念在IP核配置里容易混淆。有人口头上说“63阶滤波器”,但不同资料里“阶”可能指抽头数,也可能指系统函数的最高次数。抽头数 N 和数学阶数 N-1 相差 1。答辩时如果被问到“你的滤波器是几阶”,建议统一回答“65个抽头,数学阶次64”,并和IP核界面上的 Taps 参数保持一致,不要给评审留下模棱两可的印象。
2.2 用Python快速算一组可用的低通系数
课程设计阶段的FIR系数设计基本用窗口法,不用等纹波法。窗口法设计过程不迭代,直接做理想低通的逆傅里叶变换再乘窗函数,答辩时理论推导好写。用 scipy.signal 的 firwin 一行就能拿到系数:
import numpy as np from scipy.signal import firwin Fs = 48_000 # 采样率,48 kHz cutoff = 10_000 # 截止频率,10 kHz N = 65 # 抽头数,低通用奇数 taps = firwin(N, cutoff, fs=Fs, window='hamming') print(len(taps), taps[0], taps[N // 2], taps[-1])firwin 默认返回归一化浮点系数,直流增益约为 1,并且自动保证线性相位。运行后应该看到第一个和最后一个系数相等,中间抽头值最大,这是低通滤波器系数分布的特征。抽头数 N 决定过渡带宽度:海明窗的过渡带约为 3.3 × Fs / N。按 48 kHz 采样率计算,N=31 时过渡带宽约 5.1 kHz,N=65 时约 2.4 kHz,阻带起始频率从 10 kHz 分别推开到 12.5 kHz 和 11.2 kHz 左右。先想清楚这个换算关系,再去改 N,不要盲目加大抽头数。
浮点系数不能直接给到IP核,寄存器里只能放定点数,常见位宽是 16 bit。直接截断会让滤波器的阻带特性在高频段明显变差,正确的做法是先缩放再取整:
scale = (1 << 15) - 1 # 32767,16bit 有符号数的最大值 q = np.round(taps * scale).astype(np.int64) # 修正直流增益,让所有系数之和等于 32768 delta = (1 << 15) - q.sum() idx = N // 2 # 中心抽头对通带增益影响最大 q[idx] += delta print(q.sum(), q.min(), q.max())修正直流增益时必须注意:N 为奇数时中心抽头只有一个,修正它不会破坏对称性;N 为偶数时中心是两个抽头,需要两个同时修正。量化完成后把 q 保存成文本文件,每行一个十进制系数,后面在云源软件里导入。不要存成浮点格式,IP核通常要求整数格式,具体是十进制还是十六进制,以你用的IP核用户指南为准。
2.3 窗函数选型和系数量化带来的边界效应
窗函数的选择决定了阻带衰减和过渡带宽的一组权衡。下表是常用窗的典型指标,N 为抽头数:
| 窗函数 | 过渡带宽度(× Fs/N) | 阻带衰减 |
|---|---|---|
| 矩形窗 | 0.9 | -21 dB |
| 汉宁窗 | 3.1 | -44 dB |
| 海明窗 | 3.3 | -53 dB |
| 布莱克曼窗 | 5.5 | -74 dB |
项目里没有特殊要求时用海明窗,过渡带和阻带衰减比较均衡。竞赛题目如果明确要求阻带衰减低于 -70 dB,再考虑布莱克曼窗,但过渡带会变宽,需要同步增加抽头数。
16 bit 量化会引入约 1/2^15 ≈ 0.003% 的系数误差,换算成阻带衰减约 -96 dB,本身不会明显劣化海明窗的 -53 dB 特性。真正的坑在小系数被截断:抽头数超过 128 后,两端幅度很小的旁瓣系数可能在量化后变成 0,实际参与运算的有效抽头数减小,阻带衰减上限反而变差。遇到这种情况,先把系数归一化到满量程再量化,或者把系数位宽提到 24 bit。后者会显著增加DSP乘法器的输入位宽,资源代价要单独评估。
3. 高云云源软件里配置FIR IP核并完成Verilog例化
3.1 打开IP核生成器并核对关键参数
高云云源软件的IP核生成路径一般是 Tool 菜单下的 IP Core Generator,然后在 DSP 类目下找到 FIR 相关的核。不同版本核的名字略有差异,可能是 FIR Filter 或 FIR Compiler,判断标准是看配置页面里有没有 Lowpass 类型和 Coefficient File 加载入口,两者都有基本就是对的。创建IP前先确认工程选中的芯片型号,高云 GW2A 系列带专用 DSP 宏单元,适合全并行 FIR;部分 GW1N 系列逻辑资源有限,更适合复用型乘加结构。
IP核配置界面里需要重点检查的参数如下表:
| 配置项 | 建议值 | 作用 |
|---|---|---|
| Filter Type | Lowpass | 低通类型,避免与系数文件信息冲突 |
| Taps | 65 | 与系数文件行数严格一致 |
| Data Width | 16 | 输入数据位宽,有符号补码 |
| Coefficient Width | 16 | 系数位宽,与量化位宽一致 |
| Output Width | 32 | 全精度输出通常需要更大位宽,按实际需求取 |
| Implementation | DSP / LUT | 优先 DSP,资源不够时切 LUT |
Taps 和系数文件行数不一致是课上项目最常见的配置错误。有些IP核会静默按配置的 Taps 数截断或补零系数文件,仿真不报错,上板后频谱却不对。生成IP后,在生成的仿真模型或系数文件里数一遍非零系数数量,花一分钟能省半天调试。
3.2 例化IP核并处理复位极性和数据位序
高云生成的FIR IP核端口在不同版本有差异,典型包括时钟 clk、复位、输入数据 din、输出数据 dout,有些核带时钟使能 clken 或数据有效 valid。下面例化代码里端口名和位宽以实际生成模板为准,这里展示的是通用接入方式:
module fir_top ( input wire clk, input wire rst_n, input wire [15:0] din, output wire [15:0] dout ); wire [31:0] dout_full; gowin_fir_filt u_fir ( .clk (clk), .rst (~rst_n), // 核对核的复位极性,必要时反相 .din (din), .dout (dout_full) ); // 取高16位只是示意,高位对齐方式必须看IP核文档 assign dout = dout_full[31:16]; endmodule这里有两个特别容易出问题的点。第一,复位极性。高云部分IP核默认高电平复位,而工程顶层习惯用低电平复位 rst_n,两者不同需要反相。IP核内部通常是同步复位,复位释放后还要等若干时钟周期才能输出有效数据。第二,全精度输出取哪几位。输入16 bit、系数16 bit、65抽头时,全精度输出理论位宽可到39 bit,许多IP核把输出限制在32 bit,内部已经做了截位处理。取高16位前要先确认对齐方式,不能想当然截最高位。一个保险的做法是在仿真里输入直流信号,观察输出数据的哪几位反映了正确的增益,再确定截位方案。
3.3 在Testbench里验证滤波效果
FIR滤波器的仿真验证用例,最直观的是拼接两个正弦波,一个在通带内,一个在阻带内。下面这个Testbench用相位累加器生成 48 kHz 采样率下的 3 kHz 和 15 kHz 混合信号:
`timescale 1ns / 1ps module tb_fir; reg clk = 0; reg rst_n = 0; reg clken = 1; reg [15:0] din = 0; wire [15:0] dout; always #10 clk = ~clk; // 50 MHz 时钟 real phase_d; localparam real PHASE_STEP = 2.0 * 3.1415926 * 3000.0 / 48_000.0; always @(posedge clk) begin if (!rst_n) begin phase_d <= 0.0; din <= 0; end else begin phase_d <= phase_d + PHASE_STEP; din <= $rtoi(10000.0 * $sin(phase_d)); end end initial begin #100 rst_n = 1; #10000 $finish; end fir_top dut ( .clk (clk), .rst_n (rst_n), .din (din), .dout (dout) ); endmodule相位步长的计算公式为目标频率除以采样率再乘以 2π。上面这个 PHASE_STEP 对应 3 kHz 正弦,如果要拼接15 kHz,建议换用块语句或查找表方式切换频率,而不是实时改变步长,否则频谱里会混入切换瞬间的宽带跳变。$rtoi 把实数量化为整数,输入数据的幅度 10000 是给后面截位留的余量,避免滤波后相加饱和。
仿真库编译是新手卡住最多的地方。云源软件生成的IP核仿真模型,需要手动加进仿真工程的文件列表,或者用工具里的“生成仿真文件”功能自动加入。只把 RTL 文件加进 ModelSim 再启动仿真通常会报找不到模块,这不是代码问题,是IP核仿真模型没编译进去。仿真通过后再进入上板阶段,不要拿着没仿真过的IP直接烧录调试。
4. FIR滤波器IP核的资源占用、时序分析和上板排错
4.1 抽头数、位宽与DSP资源的关系
FIR滤波器的乘法器资源从根本上由抽头数、输入位宽和系数位宽决定。全并行结构每个有效抽头一个乘法器,利用系数对称后,65抽头只需33个乘法器。串行结构只用一个乘加器,运行 N 个时钟周期才输出一个样本,适合抽头数大但采样率低的场景。高云IP核一般会在配置里给出并行度或DSP复用选项,常见取舍如下:
| 结构 | DSP占用 | 吞吐率 | 适合场景 |
|---|---|---|---|
| 全并行 | 约 N/2 个乘法器 | 每周期1个样本 | 高采样率,GW2A系列 |
| 半并行 | 2~4个乘法器 | 每 N/k 周期1个样本 | 资源和性能折中 |
| 串行 | 1个乘法器 | 每 N 周期1个样本 | 抽头多但采样率低 |
综合后如果DSP超量,先检查是不是选了“全并行”而不是“自动”模式。高云部分IP核允许指定目标工作频率,不满足时序时自动插入流水寄存器,这会增加LUT和寄存器资源,但能提升最高运行频率。毕设阶段不需要手工定位到具体哪一级流水缺失,让工具自动选择,综合通过后再看资源报告即可。
4.2 输出恒为0、毛刺频繁和valid信号不拉高
FIR滤波器上板后的常见问题可以归成三类。第一,输出恒为0。先看 clk 和复位是否正常,再看有没有时钟使能信号没拉高,最后检查输入数据 din 是否一直为低。很多时候问题不在滤波核本身,而是上级模块的输出没连上。第二,输出产生不规则毛刺。优先怀疑数据总线多位同时翻转导致的采样亚稳态,而不是滤波器算法出错,解决方法是在 FIR 输入级先打一拍再送入。第三,valid 信号一直不拉高。检查复位保持时间,某些高云IP核要求复位保持至少一个完整数据时钟周期,复位释放过快内部状态机可能停在初始状态。
4.3 输出截位错误:看起来正常但增益不对
全精度输出位宽超过32 bit后,IP核会在内部完成截位。很多项目直接截掉低16位来得到16 bit输出,但FIR有增益累积,直流增益为1倍时,输出幅度与输入幅度相当,直接取高16位可能丢掉有效数据。常见表现是输出幅值只是输入的一半或四分之一,且波形形状正常。验证方法是给IP输入一个直流常数,观察输出位宽中哪些位反映了真实增益,再决定右移位数。更稳的做法是让IP输出全精度,在外部用有符号饱和截位模块处理,避免溢出导致的大幅跳变。
4.4 用在线逻辑分析仪抓总线,而不是示波器
数字滤波器内部总线用示波器很难观测,高云云源软件自带在线逻辑分析工具,把 din 和 dout 挂进去,设置触发条件,上板跑起来后把数据导出成 csv 或 txt。导出时采样深度至少设置 4096 个点,否则后面做FFT时频率分辨率太粗,看不到阻带的真实形态。这一步导出的数据就是下一章频响验证的输入,所有滤波效果的分析都在PC端用 Python 完成。
5. 用FFT快速验证FIR低通滤波器频率响应的自查技巧
滤波器设计最怕时域波形看着对、频谱其实不对。比如阻带衰减只有 -20 dB 时,时域上高频分量被削掉大半,肉眼看起来边缘变圆了,容易误判成已经达标。但上频谱图时低频成分泄漏到阻带,一下就能暴露问题。一个高效率的自查流程是:先把理论频响算出来,再把FPGA仿真或上板导出的数据做FFT,两张图画在一起对比。
先对量化后的系数算理论频率响应:
from scipy.signal import freqz # q 是量化系数,缩放回浮点再用 taps = q.astype(float) / (1 << 15) w, h = freqz(taps, worN=4096) f = w * Fs / (2 * np.pi) # 数字角频率转物理频率freqz 返回的 h 是复数频率响应,取绝对值再换算成 dB,就能得到理论低通曲线。截止频率处应约为 -6 dB,这是 FIR 频响检查的第一道指标。
再对FPGA导出或仿真保存的 din、dout 序列画实测频谱:
import numpy as np def spectrum(data, Fs): Nfft = 8192 win = np.hanning(len(data)) D = np.fft.rfft(data * win, Nfft) freqs = np.fft.rfftfreq(Nfft, 1.0 / Fs) return freqs, np.abs(D) freqs_in, spec_in = spectrum(din_seq, Fs) freqs_out, spec_out = spectrum(dout_seq, Fs)注意先乘窗函数再做FFT,否则信号截断带来的旁瓣会掩盖真实阻带形态。对比理论曲线和实测输出曲线时,统一用通带中心频率的幅度归一化,不要用直流点。直流分量在FPGA数据里常叠加了固定偏移,用它做基准会导致整条曲线偏移。
自查时记住三个边界条件。第一,取信号长度至少为抽头数的4倍,65抽头对应至少260个采样点。FIR滤波的瞬态还没结束前,输出是“卷积未完成”的状态,FFT看到的阻带会明显偏高。第二,FFT点数不是越大越好,补零不能提高真实频率分辨率,阻带内的毛刺要用实际信号长度对应的频率分辨率判断是否合理。第三,阻带实测包络高于理论值10 dB以内,通常不是滤波器系数问题,而是输入信号本身带来的谐波或量化噪声,先把输入信号在阻带频段的频谱拉出来看。
这个技巧在答辩时非常实用。理论频响曲线和实测频谱叠在一张图里,通带重合、阻带包络低于 -40 dB,比任何文字说明都有说服力。真实项目中建议在顶层留一个调试用数据选择器,把 din 和 dout 转发到空闲IO方便示波器快速确认有无信号,再用在线逻辑分析仪抓总线做FFT。调试完成后把这部分逻辑删掉即可,不要占用最终作品的管脚资源。
本文还有配套的精品资源,点击获取