FPGA教程系列-Vivado中串行FIR设计(非FIR核)

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

相关推荐
国际学术会议-杨老师4 天前
2026年仿真与数据分析国际会议 (ICSDA 2026)
数据挖掘·数据分析·仿真
湖南精循科技6 天前
Ansys 案例研究 | O型圈密封分析
仿真·ansys·有限元分析·o型圈
ddsoft12311 天前
仿真应用的六大误区
仿真·solidworks
余衫马12 天前
Ubuntu 24.04 环境实战:ROS 2 Kilted 实现 SLAM 建图与 Nav2 导航
ubuntu·仿真·ros2·导航
xyzhan12 天前
使用Delphi开发混凝土生产车间自动化过程控制系统的仿真系统
运维·物联网·自动化·delphi·仿真·混凝土车间
材料科学研究17 天前
如何下手!深度学习有限元仿真!
深度学习·仿真·有限元
不吃橘子的橘猫2 个月前
《集成电路设计》复习资料2(设计基础与方法)
学习·算法·fpga开发·集成电路·仿真·半导体
不吃橘子的橘猫2 个月前
《集成电路设计》复习资料4(Verilog HDL概述)
学习·算法·fpga开发·集成电路·仿真·半导体
不吃橘子的橘猫2 个月前
《集成电路设计》复习资料3(电路模拟与SPICE)
学习·算法·集成电路·仿真·半导体
Ulyanov2 个月前
基于Python的单脉冲雷达导引头回波生成技术
python·算法·仿真·单脉冲雷达、