ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

基于Verilog与Vivado的FIR低通滤波器硬件实现全流程解析

基于Verilog与Vivado的FIR低通滤波器硬件实现全流程解析 1. 项目概述从需求到实现的数字信号处理之旅在数字信号处理DSP的广阔世界里滤波器扮演着“守门人”的角色负责筛选出我们需要的信号滤除那些不想要的噪声或干扰。而有限脉冲响应FIR滤波器以其绝对稳定的特性和易于实现线性相位的优势成为了硬件实现中的常客。今天要聊的这个项目就是一次典型的硬件DSP实践使用Verilog硬件描述语言在Xilinx的Vivado开发平台上实现一个FIR低通滤波器并且附上完整的测试平台Testbench。这不仅仅是写几行代码它涉及从算法设计、定点量化、硬件架构选择到RTL编码、仿真验证乃至最终上板调试的完整链条。无论你是正在学习数字电路设计的学生还是需要为某个嵌入式系统快速部署一个定制滤波器的工程师这个从理论到硬件的完整流程都具有很高的参考价值。它的核心在于如何将数学上的滤波器系数高效、准确地映射到并行处理的硬件逻辑中并确保其行为符合预期。2. FIR滤波器核心原理与设计选型2.1 为什么选择FIR滤波器在动手写代码之前我们必须清楚选择FIR而非IIR无限脉冲响应滤波器的理由。FIR滤波器的系统函数只有零点没有极点除了原点这意味着它在Z平面上全部极点都在原点系统永远是稳定的。这对于硬件实现来说是个巨大的优点我们无需担心运算过程中因舍入误差导致系统不稳定。其次FIR滤波器可以很容易地设计成具有严格线性相位的特性这意味着信号通过滤波器后所有频率分量的延迟时间相同不会产生相位失真这对于通信、音频等对波形保真度要求高的应用至关重要。当然代价是达到相同的滤波性能FIR通常需要比IIR更高的阶数这意味着更多的乘法器和寄存器消耗更多的硬件资源。但在FPGA中丰富的DSP Slice和逻辑资源使得实现高阶FIR滤波器变得可行且高效。2.2 从理论系数到硬件架构FIR滤波器的核心是卷积运算输出y[n]是输入x[n]与滤波器系数h[k]的卷积和。在硬件中我们无法进行连续的卷积而是将其转化为一个乘累加MAC操作。常见的硬件架构有三种直接型Direct Form、转置型Transposed Form和对称型Symmetric Form。对于线性相位的FIR滤波器系数具有对称性对称型架构可以节省近一半的乘法器是资源优化的首选。我们的设计将基于此展开。首先我们需要确定滤波器的规格截止频率、采样率、通带波纹、阻带衰减。这些参数可以通过MATLAB的fdesign或fir1函数来设计出浮点系数。例如设计一个采样率Fs为100MHz截止频率Fc为10MHz的100阶低通滤波器。得到浮点系数后必须进行定点量化才能被FPGA的定点运算单元处理。量化位宽的选择至关重要位宽太小滤波性能下降甚至不稳定位宽太大浪费DSP资源并增加功耗。通常系数量化为16位有符号整数Q15格式是一个平衡性能和资源的常见起点。输入数据也需要根据ADC的位宽进行相应量化。3. Vivado开发环境与工程创建3.1 Vivado项目初始化与IP核的权衡启动Vivado创建一个新的RTL项目。在添加源文件的阶段我们暂时不添加任何文件。这里有一个关键决策点是纯手工编写所有Verilog代码还是使用Vivado提供的FIR Compiler IP核对于学习、定制化需求高或需要完全掌控流水线结构的情况手工编写是更好的选择这也是本项目采用的方式。如果追求快速部署和最优化的资源利用FIR Compiler IP核是更专业的工具它可以根据参数自动生成高度优化的网表。选择手工编写意味着我们需要自己构建滤波器内核、数据缓冲区、控制逻辑和测试平台。这能让我们深入理解每一个时钟周期里数据是如何流动和计算的。3.2 设计约束与时钟策略在开始编码前思考时钟和时序约束。假设我们的系统时钟clk为100MHz复位rst_n低有效。我们需要在xdc约束文件中定义时钟端口和时序特性。更重要的是要估算关键路径。FIR滤波器的关键路径通常位于乘累加链中。一个100阶的滤波器如果采用一个乘法器循环累加100次的结构那么关键路径延迟必须小于10ns100MHz时钟周期这对组合逻辑的延迟提出了严峻挑战。因此我们必须采用流水线技术来切割这条长路径这也是硬件设计提升时序性能的核心手段。4. Verilog实现滤波器内核的详细构建4.1 顶层模块设计与接口定义首先定义顶层模块fir_lpf的接口。这包括时钟复位、数据输入输出、以及可能的控制信号如数据有效标志。module fir_lpf #( parameter COEFF_WIDTH 16, // 系数位宽 parameter DATA_WIDTH 16, // 数据位宽 parameter TAP_NUM 101 // 滤波器阶数通常为奇数以保证对称 )( input wire clk, // 系统时钟 input wire rst_n, // 异步复位低有效 input wire data_valid_i, // 输入数据有效标志 input wire signed [DATA_WIDTH-1:0] data_i, // 输入数据 output reg data_valid_o, // 输出数据有效标志 output reg signed [DATA_WIDTHCOEFF_WIDTH-1:0] data_o // 输出数据 );注意输出数据data_o的位宽是DATA_WIDTH COEFF_WIDTH - 1。这是因为两个N位有符号数相乘会产生一个2N-1位宽的有符号数最高位为符号位扩展。在后续累加过程中位宽还可能增长需要仔细处理防止溢出。4.2 对称结构系数存储与数据移位寄存器对于线性相位FIR系数是对称的h[n] h[N-1-n]。我们可以利用这一特性将对称的数据先相加再与同一个系数相乘从而将乘法器数量减半。首先我们需要一个深度为TAP_NUM的移位寄存器来缓存输入数据流。在Verilog中可以用寄存器数组实现。reg signed [DATA_WIDTH-1:0] data_delay [0:TAP_NUM-1]; always (posedge clk or negedge rst_n) begin if (!rst_n) begin for (integer i0; iTAP_NUM; ii1) data_delay[i] 0; end else if (data_valid_i) begin // 数据移位 for (integer iTAP_NUM-1; i0; ii-1) begin data_delay[i] data_delay[i-1]; end data_delay[0] data_i; end end接下来定义系数。系数应事先用MATLAB等工具计算并量化然后以常数的形式存储在模块中。为了利用对称性我们只存储一半的系数向上取整。localparam HALF_TAP (TAP_NUM 1) / 2; // 对称系数个数 wire signed [COEFF_WIDTH-1:0] coeff [0:HALF_TAP-1]; assign coeff[0] 16sd327; // 举例中心系数 assign coeff[1] 16sd845; // ... 赋值所有一半的系数 assign coeff[HALF_TAP-1] 16sd52;4.3 预相加单元与乘法器阵列这是设计的核心。我们需要将对称位置的数据相加。对于奇数阶滤波器中心点data_delay[(TAP_NUM-1)/2]不需要相加直接参与后续运算。wire signed [DATA_WIDTH:0] pre_add [0:HALF_TAP-2]; // 预加结果位宽扩展1位防溢出 generate genvar i; for (i0; iHALF_TAP-1; ii1) begin: PRE_ADD_GEN assign pre_add[i] data_delay[i] data_delay[TAP_NUM-1-i]; end endgenerate wire signed [DATA_WIDTH-1:0] center_data data_delay[(TAP_NUM-1)/2];然后构建乘法器阵列。每个预加结果以及中心数据与对应的系数相乘。在FPGA中我们直接使用*操作符综合工具会自动推断出DSP48E1/2 Slice这是最优化实现。reg signed [DATA_WIDTHCOEFF_WIDTH:0] product [0:HALF_TAP-1]; // 乘积寄存器位宽进一步扩展 always (posedge clk or negedge rst_n) begin if (!rst_n) begin for (integer j0; jHALF_TAP; jj1) product[j] 0; end else if (data_valid_i) begin // 对称对的乘积 for (integer j0; jHALF_TAP-1; jj1) begin product[j] $signed(pre_add[j]) * $signed(coeff[j]); end // 中心系数的乘积 product[HALF_TAP-1] $signed(center_data) * $signed(coeff[HALF_TAP-1]); end end注意这里为乘积结果product寄存器增加了额外的位宽DATA_WIDTHCOEFF_WIDTH:0这是为了安全地容纳乘法结果。实际上两个有符号数Aa位和Bb位相乘结果位宽为ab位。我们之前的pre_add是DATA_WIDTH1位系数是COEFF_WIDTH位乘积位宽应为(DATA_WIDTH1) COEFF_WIDTH。为了简化并留有余量这里统一做了扩展。在实际工程中需要根据量化后的系数范围进行精确的位宽分析以避免不必要的资源浪费。4.4 多级流水线累加树直接用一个大的加法器链将所有乘积结果相加会形成很长的组合逻辑路径严重限制系统时钟频率。解决方案是使用流水线累加树将加法操作分成多级并在每一级之间插入寄存器。假设我们有HALF_TAP个乘积需要相加。我们可以将它们两两分组相加结果打拍再继续两两相加直到得到一个最终的和。这个过程像一棵二叉树。// 第一级流水两两相加 reg signed [DATA_WIDTHCOEFF_WIDTH1:0] sum_stage1 [0:(HALF_TAP/2)-1]; always (posedge clk or negedge rst_n) begin if (!rst_n) begin for (integer k0; kHALF_TAP/2; kk1) sum_stage1[k] 0; end else if (data_valid_i) begin for (integer k0; kHALF_TAP/2; kk1) begin sum_stage1[k] product[2*k] product[2*k1]; end // 如果HALF_TAP是奇数最后一个product需要单独处理传递到下一级 end end // 第二级、第三级... 依此类推直到合并为一个值。 // 最终输出 reg signed [DATA_WIDTHCOEFF_WIDTH4:0] accumulator; // 累加器位宽需足够大 always (posedge clk or negedge rst_n) begin if (!rst_n) begin accumulator 0; data_valid_o 1b0; data_o 0; end else begin // 这里应该是累加树最后一级的结果赋值给accumulator // 为了示例假设最终和是final_sum // accumulator final_sum; data_valid_o data_valid_i; // 数据有效标志跟随流水线延迟传递 // 输出可能需要截位或饱和处理 data_o accumulator[DATA_WIDTHCOEFF_WIDTH-1:0]; // 简单截取低有效位 end end流水线的级数取决于HALF_TAP的数量每增加一级流水输出相对于输入的有效标志data_valid_o就会延迟相应的时钟周期。这个延迟Latency是固定的需要在模块说明中明确。5. Testbench的编写与仿真验证策略5.1 测试激励生成模拟真实信号场景一个完备的TestbenchTB是验证设计正确性的生命线。我们的TB需要生成包含目标频率和噪声的混合信号作为滤波器的输入。timescale 1ns / 1ps module tb_fir_lpf(); reg clk; reg rst_n; reg data_valid_i; reg signed [15:0] data_i; wire data_valid_o; wire signed [31:0] data_o; // 实例化被测设计 fir_lpf #( .COEFF_WIDTH(16), .DATA_WIDTH(16), .TAP_NUM(101) ) u_fir_lpf ( .clk(clk), .rst_n(rst_n), .data_valid_i(data_valid_i), .data_i(data_i), .data_valid_o(data_valid_o), .data_o(data_o) ); // 时钟生成周期10ns (100MHz) initial clk 0; always #5 clk ~clk; // 复位与激励生成 initial begin rst_n 0; data_valid_i 0; data_i 0; #100; // 释放复位 rst_n 1; #20; data_valid_i 1; // 开始持续输入数据 // 生成测试信号1MHz正弦波 20MHz高频噪声 fork generate_sine_wave(1_000_000); // 1MHz正弦波 generate_noise(20_000_000); // 20MHz噪声 join end // 任务生成正弦波 task generate_sine_wave(input integer freq_hz); integer t; real pi 3.1415926535; real fs 100_000_000.0; // 采样率100MHz real phase 0; real phase_inc 2 * pi * freq_hz / fs; forever begin (posedge clk); if (data_valid_i) begin data_i $rtoi(32767.0 * $sin(phase)); // 幅度缩放至16位有符号范围 phase phase phase_inc; if (phase 2 * pi) phase phase - 2 * pi; end end endtask // 任务生成高频噪声简单用伪随机数模拟 task generate_noise(input integer dummy); forever begin (posedge clk); if (data_valid_i) begin // 将正弦波信号叠加一个高频随机扰动 data_i data_i ($random % 1024); // 增加一个较小的随机噪声 end end endtask这个TB生成了一个1MHz的正弦波在通带内叠加了高频随机噪声的信号。理想的低通滤波器截止频率10MHz应该能很好地保留1MHz信号同时极大衰减噪声成分。5.2 结果捕获与自动化验证我们需要将输出数据记录下来并与理论值或MATLAB仿真结果进行对比。可以将数据写入文件。integer fp_out; initial begin fp_out $fopen(fir_output.txt, w); forever begin (posedge clk); if (data_valid_o) begin $fdisplay(fp_out, %d, data_o); end end end // 仿真控制 initial begin #500000; // 仿真足够长时间例如采集5000个点 $fclose(fp_out); $finish; end仿真结束后我们可以用MATLAB或Python读取fir_output.txt绘制时域波形和频谱并与输入信号对比直观地评估滤波效果。5.3 关键检查点与断言在TB中加入断言Assertion可以自动检查一些关键属性例如复位后输出是否为零数据有效标志的延迟是否符合设计例如从data_valid_i有效到data_valid_o有效应该正好等于滤波器的流水线延迟周期数。// 检查流水线延迟 integer latency_counter; always (posedge clk) begin if (!rst_n) latency_counter 0; else if (data_valid_i) latency_counter latency_counter 1; end property prop_latency; (posedge clk) disable iff (!rst_n) (data_valid_i) |- ##(LATENCY) data_valid_o; // LATENCY为设计值如50 endproperty assert_latency: assert property (prop_latency) else $error(Latency mismatch!);6. 综合、实现与板级调试要点6.1 Vivado中的综合与时序分析在Vivado中完成行为仿真Behavioral Simulation验证功能正确后需要进行综合Synthesis和实现Implementation。综合会将我们的RTL代码映射为FPGA内部的基本元件LUT、寄存器、DSP等。综合后必须查看综合报告资源利用率报告关注DSP48E1的使用数量、LUT和FF的使用量。我们的对称结构应该比直接型节省近一半的DSP。时序报告检查是否满足时钟约束。重点关注建立时间Setup Time和保持时间Hold Time是否违例。FIR滤波器的关键路径在累加树中如果出现违例可能需要增加流水线级数或者优化加法器结构例如使用进位保留加法器CSA。实操心得Vivado的时序报告非常详细。对于违例路径可以查看“Path Details”了解信号经过了哪些逻辑单元延迟分别有多大。有时违例不是因为逻辑深度而是因为布线延迟过大。可以尝试使用register_duplication或max_fanout等综合属性来优化高扇出网络或者使用pipeline指令更精细地控制流水线。6.2 上板调试与ILA的使用将生成好的比特流文件下载到FPGA开发板后真正的挑战才开始。我们需要验证滤波器在真实硬件上的行为。Xilinx的集成逻辑分析仪ILAIP核是必不可少的调试工具。在Vivado中通过“IP Integrator”或直接例化ILA IP核将滤波器的关键信号clk,rst_n,data_valid_i,data_i,data_valid_o,data_o连接到探针。设置触发条件例如当data_valid_i上升沿时触发。然后运行硬件管理Hardware Manager即可在电脑上实时捕获FPGA内部这些信号的电平变化波形与仿真工具中类似。常见问题与排查问题1输出全为零或不变。排查首先检查ILA中data_valid_i和data_valid_o信号。如果data_valid_i从未有效检查前级数据源。如果data_valid_i有效但data_valid_o无效检查复位逻辑和流水线延迟计数器。如果data_valid_o有效但data_o不变检查输入数据data_i是否在变化以及系数加载是否正确。问题2输出数据明显错误幅度或频率不对。排查用ILA捕获一段连续的输入和输出数据导出为CSV文件。在MATLAB中重新绘制波形和频谱。对比仿真结果。重点检查系数在Verilog代码中的赋值是否与MATLAB设计的量化系数完全一致包括符号、顺序。检查数据位宽和乘法、加法过程中的符号位处理是否有误。问题3系统运行一段时间后出错。排查这可能与时序违例导致的亚稳态有关。尽管静态时序分析STA通过了但在高温、低压等极端条件下临界路径可能失效。尝试降低系统时钟频率看问题是否消失。如果消失说明是时序问题需要回头优化代码或增加约束。6.3 性能评估与优化方向一个成功的实现不仅要求功能正确还要评估其性能。最高工作频率Fmax通过时序报告获得。可以通过降低逻辑层级、增加流水线、使用寄存器平衡register balancing等技术来提升Fmax。资源消耗与理论估算对比。如果DSP使用过多可以考虑使用时分复用Time-Division Multiplexing技术让一个DSP核在多个时钟周期内计算多个乘积累加但这会降低吞吐量。功耗估算Vivado可以提供设计后的功耗分析报告。动态功耗与时钟频率、信号翻转率、资源使用量成正比。在满足性能要求下尽可能降低时钟频率和使用更少的逻辑资源有助于降低功耗。最后这个FIR滤波器模块可以作为一个独立的IP集成到更大的系统中例如用于软件无线电SDR的数字下变频DDC链中或者用于实时音频处理。它的接口清晰延迟固定非常适合在流水线化的数字系统中使用。整个从算法到硬件的实现过程是一次对数字信号处理理论、硬件描述语言和FPGA开发工具的深度整合与实践。
返回列表