4. FIR带阻滤波器设计

任务目标

设计一个系数可动态配置的FIR带阻滤波器,在FPGA上实现对特定频率分量的抑制。通过串口或按键调整阻带中心频率,并用仿真验证滤波器对目标频率的衰减效果。

实现方案

采用直接型FIR结构,滤波器阶数固定为32阶(33个系数)。阻带中心频率通过外部输入参数freq_ctrl[4:0]控制(0~31对应不同归一化频率),内部通过查找表(LUT)切换预计算的带阻系数。系数使用16位有符号定点数(Q1.15格式),输入输出均为16位。

核心代码

module fir_bandstop #(
    parameter DATA_WIDTH = 16,
    parameter COEF_WIDTH = 16,
    parameter TAPS = 33
)(
    input  wire clk,
    input  wire rst_n,
    input  wire [4:0] freq_ctrl,   // 阻带中心频率选择 0~31
    input  wire [DATA_WIDTH-1:0] din,
    input  wire din_valid,
    output reg  [DATA_WIDTH-1:0] dout,
    output reg  dout_valid
);

    // 系数ROM:32组预计算带阻系数,每组33个
    // 系数由MATLAB/Python生成后固化,此处展示前3组示例
    reg [COEF_WIDTH-1:0] coef_rom [0:31][0:TAPS-1];
    
    // 示例系数(实际工程中由脚本生成完整32组)
    initial begin
        // 阻带中心 = 0.1*fs(归一化0.1)
        coef_rom[0][0]  = 16'h0032; coef_rom[0][1]  = 16'h0015;
        coef_rom[0][2]  = 16'hFFE8; coef_rom[0][3]  = 16'hFFC8;
        // ... 完整33个系数
        // 阻带中心 = 0.2*fs
        coef_rom[1][0]  = 16'h0028; coef_rom[1][1]  = 16'h0010;
        coef_rom[1][2]  = 16'hFFD0; coef_rom[1][3]  = 16'hFFA0;
        // ... 其余30组
    end

    // 延迟线
    reg [DATA_WIDTH-1:0] delay_line [0:TAPS-1];
    integer i;
    
    // 乘累加
    reg signed [COEF_WIDTH-1:0] coef;
    reg signed [DATA_WIDTH-1:0] data;
    reg signed [COEF_WIDTH+DATA_WIDTH-1:0] mac;
    
    always @(posedge clk or negedge rst_n) begin
        if (!rst_n) begin
            for (i = 0; i < TAPS; i = i + 1) delay_line[i] <= 0;
            dout <= 0;
            dout_valid <= 0;
        end else if (din_valid) begin
            // 移位
            for (i = TAPS-1; i > 0; i = i - 1) 
                delay_line[i] <= delay_line[i-1];
            delay_line[0] <= din;
            
            // FIR计算
            mac = 0;
            for (i = 0; i < TAPS; i = i + 1) begin
                coef = coef_rom[freq_ctrl][i];
                data = delay_line[i];
                mac = mac + coef * data;
            end
            
            dout <= mac[COEF_WIDTH+DATA_WIDTH-2 -: DATA_WIDTH];
            dout_valid <= 1;
        end else begin
            dout_valid <= 0;
        end
    end
endmodule

系数生成与配置

使用Python的scipy.signal生成32组带阻系数,每组对应不同阻带中心频率。系数生成后以Verilog初始化格式写入文件,替换代码中的initial块。

import numpy as np
from scipy.signal import firwin

for idx in range(32):
    f_center = 0.05 + idx * 0.025  # 归一化频率0.05~0.825
    f_low = f_center - 0.02
    f_high = f_center + 0.02
    taps = firwin(33, [f_low, f_high], pass_zero=False)
    coefs = np.round(taps * 32768).astype(int)
    print(f"// f_center={f_center:.3f}")
    for c in coefs:
        print(f"16'h{c & 0xFFFF:04X}")

仿真验证

编写testbench,输入一个包含两个频率分量(如0.1fs和0.3fs)的混合信号,切换freq_ctrl分别抑制不同频率,观察输出频谱。

// testbench关键部分
reg [15:0] stimulus [0:1023];
initial begin
    // 生成混合信号:0.1fs正弦 + 0.3fs正弦
    for (i = 0; i < 1024; i = i + 1) begin
        stimulus[i] = $rtoi(32767 * (0.5*$sin(2*3.14159*0.1*i) + 
                                     0.5*$sin(2*3.14159*0.3*i)));
    end
end

always @(posedge clk) begin
    if (i < 1024) begin
        din <= stimulus[i];
        din_valid <= 1;
    end
end

验证方法与判定标准

  1. 功能仿真:在Vivado/ModelSim中运行testbench,设置freq_ctrl=0(阻带中心0.1fs),观察输出波形中0.1fs分量幅度明显衰减,0.3fs分量基本保留。
  2. 频谱分析:将仿真输出数据导入MATLAB/Python做FFT,对比输入输出频谱。判定标准:阻带中心频率处衰减≥20dB,通带波动≤1dB。
  3. 动态切换:仿真中在t=500时切换freq_ctrl=8(阻带中心0.25fs),验证滤波器实时切换阻带位置。

常见问题

  • 系数溢出:乘累加结果位宽需为COEF_WIDTH+DATA_WIDTH,截取时保留符号位,否则直流偏移会导致输出失真。
  • 时序收敛:33阶乘累加在单时钟内完成,若时钟频率超过100MHz需插入流水线寄存器。
  • 系数精度:16位量化后阻带衰减可能从理想60dB降至40dB左右,如需更高精度可扩展系数位宽。