FPGA教程系列-Vivado中串行FIR设计(非FIR核)
什么是FIR滤波器
Finite Impulse Response Filter, 有限冲激响应滤波器,是数字信号处理里最常用、最基础、也最稳定 的一类滤波器。"只有零点,没有极点 "的滤波器:输出只依赖有限个过去和当前的输入样本 ,不依赖以前的输出,因此系统总是稳定的。FIR 滤波器是有限长单位冲击响应滤波器。直接型结构如下:

FIR 滤波器本质上就是输入信号与单位冲击响应函数的卷积,表达式如下:

冲激响应(滤波器系数)
h[n] 是FIR滤波器的冲激响应 ,即滤波器对单位冲激信号的响应序列。h[n]直接对应系数b_n (如h[0]=b₀, h[1]=b₁, ...),用于加权输入信号及其延迟值。
通俗理解:h是滤波器的"权重",决定了滤波器的特性(如低通、高通等)。
延迟算子
z⁻¹ 是Z变换中的延迟算子 ,表示信号延迟一个采样周期。通俗理解:z⁻¹是"记忆单元",用于存储输入信号的过去值,供后续加权计算。
Matlab仿真滤波器
FIR滤波器有好几种形式,简单的用matlab程序,仿真出一组系数:
matlab
clc;
clear;
close all;
% 滤波器参数
order = 10; % 滤波器阶数 (系数个数 = order + 1)
cutoff = 0.4; % 截止频率 (归一化)
% 1. 使用汉明窗设计
h_hamming = fir1(order, cutoff, 'low', hamming(order+1));
% 2. 使用汉宁窗设计
h_hanning = fir1(order, cutoff, 'low', hanning(order+1));
% 3. 使用布莱克曼窗设计 (旁瓣衰减更好,但过渡带更宽)
h_blackman = fir1(order, cutoff, 'low', blackman(order+1));
% 绘制频率响应进行比较
figure;
freqz(h_hamming, 1, 1024);
hold on;
freqz(h_hanning, 1, 1024);
freqz(h_blackman, 1, 1024);
title('不同窗函数设计的低通滤波器频率响应');
legend('Hamming', 'Hanning', 'Blackman');
grid on;
%量化参数
h_x = round(1023*h_hanning) %

运行以后可以生成一组参数:

这个就是可以在vivado中使用的参数。
Vivado新建工程
有了参数以后,就可以在vivado中建立工程,用单纯的Verilog来编写程序的话,会有很多重复的部分,而System Verilog可以实现参数化的操作,因此这部分选用System Verilog来进行编写。
建立Top文件,fir_top.sv:
verilog
`timescale 1ns / 1ps
module fir_top #(
// --- 参数定义 ---
parameter TAPS = 11, // 滤波器阶数
parameter DATA_IN_W = 2, // 输入数据位宽
parameter COEF_W = 14, // 系数位宽 (与原IP核匹配)
parameter DATA_OUT_W = 20 // 输出数据位宽 (16+ceil(log2(11))=20)
)(
input i_clk,
input i_rst,
input signed [DATA_IN_W-1:0] i_din,
output signed [DATA_OUT_W-1:0] o_dout
);
// --- 11阶滤波器系数定义 ---
// 0, -19, -32, 71, 288, 407, 288, 71, -32, -19, 0
localparam signed [COEF_W-1:0] COEF [0:TAPS-1] = '{
14'd0, 14'sd19, 14'sd32, 14'd71, 14'd288, 14'd407,
14'd288, 14'd71, 14'sd32, 14'sd19, 14'd0
};
// 注意:Verilog中负数需要用'sd声明,或者在位宽前加s,如14'sd19
// --- 延迟链 (移位寄存器) ---
reg signed [DATA_IN_W-1:0] x_reg [0:TAPS-1];
integer i;
always @(posedge i_clk or posedge i_rst) begin
if (i_rst) begin
for (i = 0; i < TAPS; i = i + 1) begin
x_reg[i] <= {DATA_IN_W{1'b0}};
end
end else begin
x_reg[0] <= i_din;
for (i = 1; i < TAPS; i = i + 1) begin
x_reg[i] <= x_reg[i-1];
end
end
end
// --- 乘法器输出线网数组 ---
wire signed [15:0] r [0:TAPS-1]; // 假设乘法器IP核输出为16位
// --- 使用 generate 循环实例化乘法器 ---
genvar g;
generate
for (g = 0; g < TAPS; g = g + 1) begin : mul_gen
multer multer_inst (
.CLK(i_clk),
.A(x_reg[g]), // 连接到对应的延迟寄存器
.B(COEF[g]), // 连接到对应的系数
.SCLR(i_rst),
.P(r[g]) // 输出到对应的线网
);
end
endgenerate
// --- 求和 ---
assign o_dout = r[0] + r[1] + r[2] + r[3] + r[4] + r[5] + r[6] + r[7] + r[8] + r[9] + r[10];
endmodule
乘法器采用的是vivado的IP核,可以参考以前的设计,进行设计。注意是带复位信号的乘法器。
top写完以后可以编写testbench文件:
verilog
`timescale 1ns / 1ps
module test_fir;
reg i_clk;
reg i_rst;
reg signed[1:0]i_din;
wire signed[15:0]o_dout;
fir_tops fir_tops_u(
.i_clk (i_clk),
.i_rst (i_rst),
.i_din (i_din),
.o_dout (o_dout)
);
// 时钟生成:周期10ns,频率100MHz
always #5 i_clk = ~i_clk;
initial
begin
// 初始化
i_clk = 1'b1;
i_rst = 1'b1;
i_din = 2'b00;
// 复位持续100ns
#100 i_rst = 1'b0;
// ==================== 测试序列设计 ====================
// 1. 阶跃响应测试 (0 -> +1)
$display("=== 阶跃响应测试开始 ===");
#50 i_din = 2'b01; // 输入+1,观察滤波器如何从0上升到稳态值
#200; // 持续200ns,观察完整的上升过程
// 2. 负阶跃响应测试 (+1 -> -1)
$display("=== 负阶跃响应测试开始 ===");
i_din = 2'b11; // 输入-1,观察滤波器的负向响应
#200; // 持续200ns
// 3. 回零测试 (-1 -> 0)
$display("=== 回零测试开始 ===");
i_din = 2'b00; // 输入0,观察滤波器回到零点
#200; // 持续200ns
// 4. 单脉冲测试 (0 -> +1 -> 0)
$display("=== 单脉冲测试开始 ===");
i_din = 2'b01; // 输入单个正脉冲
#20; // 脉冲宽度20ns
i_din = 2'b00; // 回到0
#200; // 观察脉冲响应
// 5. 双脉冲测试
$display("=== 双脉冲测试开始 ===");
i_din = 2'b01; // 第一个正脉冲
#20;
i_din = 2'b00;
#30;
i_din = 2'b01; // 第二个正脉冲
#20;
i_din = 2'b00;
#200;
// 6. 负脉冲测试
$display("=== 负脉冲测试开始 ===");
i_din = 2'b11; // 负脉冲
#20;
i_din = 2'b00;
#200;
// 7. 交替脉冲序列测试
$display("=== 交替脉冲序列测试开始 ===");
repeat(5) begin
i_din = 2'b01; // 正脉冲
#20;
i_din = 2'b00;
#30;
i_din = 2'b11; // 负脉冲
#20;
i_din = 2'b00;
#30;
end
// 8. 最后回到零点
$display("=== 测试结束,回到零点 ===");
i_din = 2'b00;
#200;
$display("=== 仿真结束 ===");
$finish;
end
// 监控输出变化
always @(posedge i_clk) begin
$display("Time=%0t ns, Input=%d, Output=%d", $time, $signed(i_din), $signed(o_dout));
end
endmodule
进行仿真即可,不会仿真的可以问AI

可以看到仿真的结果。
工程文件:https://download.csdn.net/download/fantasygwh2015/92261208