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
验证方法与判定标准
- 功能仿真:在Vivado/ModelSim中运行testbench,设置
freq_ctrl=0(阻带中心0.1fs),观察输出波形中0.1fs分量幅度明显衰减,0.3fs分量基本保留。 - 频谱分析:将仿真输出数据导入MATLAB/Python做FFT,对比输入输出频谱。判定标准:阻带中心频率处衰减≥20dB,通带波动≤1dB。
- 动态切换:仿真中在t=500时切换
freq_ctrl=8(阻带中心0.25fs),验证滤波器实时切换阻带位置。
常见问题
- 系数溢出:乘累加结果位宽需为
COEF_WIDTH+DATA_WIDTH,截取时保留符号位,否则直流偏移会导致输出失真。 - 时序收敛:33阶乘累加在单时钟内完成,若时钟频率超过100MHz需插入流水线寄存器。
- 系数精度:16位量化后阻带衰减可能从理想60dB降至40dB左右,如需更高精度可扩展系数位宽。