3. FIR带通滤波器设计
任务目标
设计一个中心频率可配置的带通FIR滤波器,在FPGA上实现滤波逻辑,并通过仿真验证带外信号被有效抑制。滤波器阶数、采样率、中心频率与带宽作为参数输入,由模块内部自动计算系数。
实现方案
采用窗函数法(Hamming窗)设计带通FIR滤波器,系数在FPGA内部实时计算,避免外部导入系数文件。滤波采用直接I型对称结构,利用系数对称性将乘法器数量减半。中心频率通过寄存器配置,支持运行时调整。
核心代码
module fir_bandpass #(
parameter DATA_WIDTH = 16,
parameter COEF_WIDTH = 16,
parameter TAPS = 32, // 滤波器阶数(需为偶数)
parameter ACC_WIDTH = 32
)(
input wire clk,
input wire rst_n,
input wire signed [DATA_WIDTH-1:0] data_in,
input wire data_valid,
// 配置接口
input wire [31:0] cfg_fc, // 中心频率(归一化:fc/fs * 2^32)
input wire [31:0] cfg_bw, // 带宽(归一化:bw/fs * 2^32)
input wire cfg_update, // 系数更新使能
// 输出
output reg signed [DATA_WIDTH-1:0] data_out,
output reg data_out_valid
);
// 系数存储(对称,只需TAPS/2个)
reg signed [COEF_WIDTH-1:0] coef [0:TAPS/2-1];
reg signed [DATA_WIDTH-1:0] shift_reg [0:TAPS-1];
// 系数计算(Hamming窗带通)
integer i;
reg [31:0] phase_acc;
reg [31:0] wc_low, wc_high;
reg [31:0] hamming_val;
reg [31:0] sinc_val;
reg [31:0] coef_tmp;
// 系数更新逻辑
always @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
for (i = 0; i < TAPS/2; i++) begin
coef[i] <= 0;
end
end else if (cfg_update) begin
wc_low <= (cfg_fc - cfg_bw/2) * 2; // 归一化角频率
wc_high <= (cfg_fc + cfg_bw/2) * 2;
for (i = 0; i < TAPS/2; i++) begin
// 计算带通冲激响应 h[n] = (sin(wc2*n) - sin(wc1*n))/(pi*n)
if (i == TAPS/2 - 1) begin
// 中心点(n=0)
coef_tmp = (wc_high - wc_low) / 3.14159;
end else begin
phase_acc = (i - (TAPS/2 - 1)) * 2;
// 简化sinc计算(实际工程建议使用CORDIC或查找表)
sinc_val = ((wc_high * phase_acc) >> 16) - ((wc_low * phase_acc) >> 16);
coef_tmp = (sinc_val * 32768) / (phase_acc * 3.14159);
end
// Hamming窗
hamming_val = 0.54 - 0.46 * 3.14159 * (i * 2) / (TAPS - 1);
coef[i] <= coef_tmp * hamming_val;
end
end
end
// 滤波主逻辑(对称结构)
reg signed [ACC_WIDTH-1:0] acc;
reg [7:0] tap_idx;
reg [1:0] state;
always @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
data_out <= 0;
data_out_valid <= 0;
acc <= 0;
state <= 0;
end else begin
case (state)
2'd0: begin
if (data_valid) begin
// 移位寄存器更新
for (i = TAPS-1; i > 0; i--) begin
shift_reg[i] <= shift_reg[i-1];
end
shift_reg[0] <= data_in;
acc <= 0;
tap_idx <= 0;
state <= 2'd1;
end
end
2'd1: begin
if (tap_idx < TAPS/2) begin
// 对称系数累加
acc <= acc + coef[tap_idx] *
(shift_reg[tap_idx] + shift_reg[TAPS-1-tap_idx]);
tap_idx <= tap_idx + 1;
end else begin
data_out <= acc[ACC_WIDTH-1:DATA_WIDTH-1];
data_out_valid <= 1;
state <= 2'd0;
end
end
endcase
end
end
endmodule
仿真验证
创建testbench,输入两个不同频率的正弦波叠加信号,验证滤波器输出仅保留通带内频率分量。
module tb_fir_bandpass;
reg clk = 0;
reg rst_n = 0;
reg signed [15:0] din = 0;
reg din_valid = 0;
reg [31:0] fc = 32'd536870912; // fc/fs = 0.125 (归一化)
reg [31:0] bw = 32'd107374182; // bw/fs = 0.025
reg update = 0;
wire signed [15:0] dout;
wire dout_valid;
// 输入信号:1kHz + 5kHz混合(假设fs=40kHz)
real t = 0;
real freq1 = 1000.0, freq2 = 5000.0;
real fs = 40000.0;
fir_bandpass dut(
.clk(clk), .rst_n(rst_n),
.data_in(din), .data_valid(din_valid),
.cfg_fc(fc), .cfg_bw(bw), .cfg_update(update),
.data_out(dout), .data_out_valid(dout_valid)
);
always #12.5 clk = ~clk; // 40kHz采样时钟
initial begin
#100 rst_n = 1;
#50 update = 1;
#25 update = 0;
// 生成混合信号
forever begin
#25;
t = t + 1.0/fs;
din = $rtoi(10000 * (1.0 + $sin(2*3.14159*freq1*t) +
0.5*$sin(2*3.14159*freq2*t)));
din_valid = 1;
end
end
// 输出监测
integer fd;
initial begin
fd = $fopen("filter_out.txt");
#100000;
$fclose(fd);
$finish;
end
always @(posedge clk) begin
if (dout_valid) begin
$fwrite(fd, "%d\n", dout);
end
end
endmodule
运行与验证
- 仿真运行:使用Vivado或ModelSim运行上述testbench,仿真时间建议≥10ms(400个采样周期以上)
- 结果分析:将
filter_out.txt导入MATLAB或Python,执行FFT分析输出频谱 - 通过标准:
- 输出频谱中1kHz分量幅度衰减≤3dB
- 5kHz分量幅度衰减≥20dB(带外抑制)
- 滤波器输出无直流偏置,波形稳定
关键说明
- 系数计算采用简化sinc近似,实际工程中建议使用CORDIC算法或预计算查找表提高精度
- 中心频率配置范围:0.05~0.45(归一化频率),超出此范围需调整滤波器阶数
- 输出位宽截取时采用高16位,可根据实际动态范围调整
- 系数更新需在
cfg_update高电平期间完成,更新期间输出无效