项目目标在FPGA上实现一个使用汉明窗设计的FIR低通滤波器并通过仿真验证其滤波效果。一、FIR 原理1. FIR 加权移动平均升级版 想象在听一串连续的声音采样值x[n], x[n-1], x[n-2], ...。FIR 滤波器计算当前输出 y[n] 的方式是把当前的和过去若干个输入样本各自乘上一个特定的“权重”系数 h[k]然后把所有这些乘积加起来。2. “有限”的含义 它只使用有限个 (N1 个) 过去的样本x[n] 到 x[n-N]。N1 就是滤波器的抽头数TapsN 是阶数。抽头数越多滤波器能实现的频率响应特性比如截止陡峭度、阻带衰减通常越好但计算量也越大。3. 公式 y[n] h[0]*x[n] h[1]*x[n-1] h[2]*x[n-2] ... h[N]*x[n-N]4. 关键点 这些系数 h[0], h[1], ..., h[N] 决定了滤波器的特性低通、高通、带通等。系数不同滤波效果就完全不同。5. 窗函数汉明窗的作用 直接计算理想滤波器的系数无限长不可行。窗函数如汉明窗像一把平滑的“剪刀”把理想的、无限长的系数序列“截短”成我们需要的有限长 (N1个)同时尽量减小截短带来的不良影响如吉布斯效应让滤波器的实际频率响应更接近理想状态。二、MATLAB 部分目标 根据想要的滤波器特性低通Fs采样频率100kHz, Fc截止频率10kHz计算出 32 个 (N132) 最优的量化系数 h_quant[0:31]。步骤1. 计算归一化截止频率 Wn Fc / (Fs/2) 10000 / 50000 0.22.调用 fir1 函数设置滤波器 h fir1(31, 0.2, low, hamming(32));31 滤波器阶数 N (抽头数 Taps N1 32)。0.2 归一化截止频率 Wn。low 低通滤波器。hamming(32) 使用 32 点的汉明窗。% 滤波器参数 Fs 100000; % 采样频率 (Hz) Fc 10000; % 截止频率 (Hz) N 31; % 滤波器阶数 (抽头数 N 1 32) % 计算归一化截止频率 (范围 0-1, 1对应 Fs/2) Wn Fc / (Fs/2); % Wn 10000 / 50000 0.2 % 使用 fir1 函数设计汉明窗低通FIR滤波器 % fir1(阶数, 归一化截止频率, 滤波器类型, 窗函数) h fir1(N, Wn, low, hamming(N1)); % hamming(N1) 生成 N1 点的汉明窗同时需要检查图形通带是否在0-10kHz相对平坦阻带衰减是否足够汉明窗典型约-53dB过渡带是否在预期位置% 绘制频率响应 freqz(h, 1, 1024, Fs); % freqz(系数, 分母(1表示FIR), FFT点数, 采样频率) title(FIR Lowpass Filter Frequency Response (Hamming Window, N31)); grid on;3. 量化系数 (关键FPGA 需要整数、定点数)h 是 MATLAB 算出的浮点数系数 (范围大约 -1 到 1)。确定系数位宽 COEFF_WIDTH (例如 16 bit)。确定量化格式 Q 格式 (常用 Q1.15即 1 位符号位 15 位小数位)。COEFF_WIDTH 16; FRAC_BITS COEFF_WIDTH - 1; % 15 for Q1.15 h_quant round(h * (2^FRAC_BITS)); % 放大 2^15 倍后四舍五入取整 % 饱和处理 (防止溢出) max_val 2^(COEFF_WIDTH-1) - 1; % 32767 for 16-bit min_val -2^(COEFF_WIDTH-1); % -32768 for 16-bit h_quant(h_quant max_val) max_val; h_quant(h_quant min_val) min_val;4. 生成 .coe 文件 (供 Vivado ROM IP 使用)% 打开文件用于写入 fid fopen(fir_coefficients.coe, w); % 写入.coe文件头 (指定Radix和系数值) fprintf(fid, memory_initialization_radix 10;\n); % 或 2, 16 fprintf(fid, memory_initialization_vector \n); % 写入系数 (十进制整数形式最后一个系数后面不加逗号) for i 1:length(h_quant) if i length(h_quant) fprintf(fid, %d,\n, h_quant(i)); else fprintf(fid, %d;\n, h_quant(i)); % 最后一个系数以分号结尾 end end fclose(fid); disp(COE file fir_coefficients.coe generated.);三、vivado 工程整体框图如下:1. 创建用于存储系数的ROM (Block Memory Generator IP)2. 实现串行FIR滤波器结构核心思想为使用单个乘法器依次计算每个抽头的乘积通过状态机精确控制计算流程移位寄存器存储历史样本ROM存储滤波器系数完整工作流程为复位阶段所有寄存器清零状态机归零新样本到达进入状态0移位寄存器更新累加器和地址初始化乘累加阶段状态1到TAPS-1依次计算每个抽头的乘积累加前一个抽头的计算结果输出阶段状态TAPS输出累加结果返回状态0等待新样本timescale 1ns / 1ps module serial_fir#( parameter DATA_WIDTH 12, parameter OUTPUT_WIDTH 28, parameter COEFF_WIDTH 16, parameter TAPS 32 )( input wire clk, input wire rst, input wire signed [DATA_WIDTH - 1 : 0] data_in, output reg signed [OUTPUT_WIDTH - 1 : 0] data_out ); //移位寄存器链存储当前与过去的输入样本 reg signed [DATA_WIDTH-1:0] shift_reg [0:TAPS-1];//前面是每个元素的位宽后面是一个数组及索引范围 integer i; //FIR系数接收 wire signed[COEFF_WIDTH-1:0] coeff_out; reg [$clog2(TAPS)-1:0] addr ; fir_coeff_rom your_instance_name ( .clka(clk), // input wire clka .addra(addr), // input wire [4 : 0] addra .douta(coeff_out) // output wire [15 : 0] douta ); //乘法器(为了高时钟F加流水线级)累加器,状态寄存器 reg signed[OUTPUT_WIDTH-1:0] accumulator; reg signed[$clog2(TAPS):0] state; reg signed[OUTPUT_WIDTH-1:0]product_reg; reg signed[OUTPUT_WIDTH-1:0]mult_result; always(posedge clk or posedge rst) if(rst)begin for(i0; i0; ii-1)begin shift_reg[i] shift_reg[i-1]; end shift_reg[0] data_in; state 1; end default begin if(state TAPS) begin mult_result shift_reg[addr] * coeff_out; product_reg mult_result; accumulator accumulator product_reg; addr addr 1; state state 1; end else begin data_out accumulator; state 0; end end endcase end endmodule四、使用乘法器IP核进行硬件加速虽然Verilog的 * 操作符在小位宽时综合工具可能能推断出乘法器但对于16位或更大位宽使用Xilinx专用的DSP Slice IP核 (Multiplier) 通常性能更好、资源利用更优、时序更容易满足。修改 serial_fir.v 代码1. 找到原来的乘法操作 shift_reg[addr] * coeff_out;2. 同时将原来的代码进行时序状态机优化2. 替换为乘法器IP核实例化timescale 1ns / 1ps module serial_fir#( parameter DATA_WIDTH 12, parameter OUTPUT_WIDTH 28, parameter COEFF_WIDTH 16, parameter TAPS 32 )( input wire clk, input wire rst, input wire signed [DATA_WIDTH - 1 : 0] data_in, output reg signed [OUTPUT_WIDTH - 1 : 0] data_out ); // 移位寄存器链 reg signed [DATA_WIDTH-1:0] shift_reg [0:TAPS-1]; integer i; // FIR系数接收 wire signed [COEFF_WIDTH-1:0] coeff_out; reg [$clog2(TAPS)-1:0] addr; // 状态寄存器 - 需要覆盖0到TAPS1状态 reg [$clog2(TAPS2):0] state; // 修正位宽 // 乘法器、累加器相关 reg signed [OUTPUT_WIDTH-1:0] accumulator; reg signed [OUTPUT_WIDTH-1:0] product_reg; wire signed [OUTPUT_WIDTH-1:0] mult_result; // 改为wire类型 // 系数ROM fir_coeff_rom coeff_rom_inst ( .clka(clk), .addra(addr), .douta(coeff_out) ); // 乘法器IP核实例化 signed_multiplier mult_inst ( .CLK(clk), .A(shift_reg[addr]), .B(coeff_out), .P(mult_result) ); // 状态定义 localparam S_LOAD 0; // 加载新样本 localparam S_MULT 1; // 启动乘法 localparam S_ACC 2; // 累加结果 localparam S_OUTPUT 3; // 输出结果 always (posedge clk or posedge rst) begin if (rst) begin // 复位所有寄存器 for (i 0; i 0; i i - 1) begin shift_reg[i] shift_reg[i-1]; end shift_reg[0] data_in; // 初始化累加器和地址 accumulator 0; addr 0; // 进入乘法状态 state S_MULT; end S_MULT: begin // 启动乘法结果将在下一周期出现在mult_result // 不需要直接赋值乘法器IP会自动计算 // 存储上一个乘法结果如果有 // 对于第一个抽头product_reg为0 // 进入累加状态 state S_ACC; end S_ACC: begin // 累加前一个乘法结果 accumulator accumulator product_reg; // 存储当前乘法结果用于下一个抽头的累加 product_reg mult_result; // 更新地址 addr addr 1; // 判断是否完成所有抽头 if (addr TAPS - 1) begin state S_MULT; // 继续下一个抽头 end else begin state S_OUTPUT; // 所有抽头处理完成 end end S_OUTPUT: begin // 累加最后一个抽头的结果 accumulator accumulator product_reg; // 输出最终结果 data_out accumulator; // 返回加载状态 state S_LOAD; end endcase end end endmodule五、编写仿真文件查看波形timescale 1ns/1ps module serial_fir_tb(); localparam DATA_WIDTH 12; localparam OUTPUT_WIDTH 28; localparam COEFF_WIDTH 16; localparam TAPS 32; localparam CLK_PERIOD 10; reg clk; reg rst; reg signed [DATA_WIDTH - 1 : 0] data_in; wire signed [OUTPUT_WIDTH - 1 : 0] data_out ; parameter FS_REAL 100000; parameter REAL_PERIOD 1.0e9/FS_REAL; integer t; real time_val; parameter freq1 5000; parameter freq2 30000; parameter pi 3.1415926; parameter amplitude (1 (DATA_WIDTH-1))-1; //时钟生成 always #(CLK_PERIOD/2) clk ~clk; //实例化模块 serial_fir#( .DATA_WIDTH(DATA_WIDTH), .OUTPUT_WIDTH(OUTPUT_WIDTH), .COEFF_WIDTH(COEFF_WIDTH), .TAPS(TAPS) ) uut( .clk(clk), .rst(rst), .data_in(data_in), .data_out(data_out) ); //初始化 initial begin clk 0; rst 1; data_in 0; #(CLK_PERIOD*2) rst 0; //测试1单位冲击响应验证滤波系数 data_in (1 (DATA_WIDTH-2)); #(CLK_PERIOD); data_in 0; #(CLK_PERIOD*(TAPS*10)); //测试2混合频率信号验证滤波效果 //localparam FS_REAL 100000.0; //localparam REAL_PERIOD 1.0e9 / FS_REAL; //integer t; //real time_val; //real freq1 5000; //real freq2 30000; //real pi 3.1415926; //real amplitude (1 (DATA_WIDTH-1))-1; for(t0; t500; tt1) begin time_val t*(REAL_PERIOD/1.0e9);//第t个样本对应的实际时间s data_in $rtoi(amplitude * (0.6 * $sin(2 * pi * freq1 * time_val) 0.4 * $sin(2 * pi * freq2 * time_val))); $display(t %d, time_val %f, data_in %d data_out %d, t, time_val, data_in, data_out); #(REAL_PERIOD); end //结束仿真 #(CLK_PERIOD*100); $finish; end //波形记录 initial begin $dumpfile(waveform.vcd); $dumpvars(0,uut); end endmodule仿真结果如下图所示六、MATLAB进行FFT分析将生成的dumpfile文件复制到txt文档然后导入MATLAB进行FFT分析一部分如下MATLAB代码如下% FIR滤波器频谱分析完整代码 % 功能读取数据文件计算并绘制data_in和data_out的频谱验证滤波效果 % -------------------------- % 1. 读取数据文件 % -------------------------- % 确保数据文件与当前MATLAB脚本在同一目录或使用绝对路径 data_filename fir_data_clean.txt; % 数据文件名 data load(data_filename); % 读取空格分隔的纯数值文件 % 提取各列数据 t data(:, 1); % 时间索引 data_in data(:, 2); % 输入信号 data_out data(:, 3); % 输出信号 % 验证数据读取结果 fprintf(成功读取数据共 %d 个样本\n, length(t)); fprintf(时间范围: t %d 至 %d\n, t(1), t(end)); % -------------------------- % 2. 配置信号参数 % -------------------------- FS 100000; % 采样率 (Hz)与仿真中的FS_REAL一致 N length(data_in); % 数据点数 Nyquist FS / 2; % 奈奎斯特频率 (Hz) % -------------------------- % 3. 计算FFT并处理频谱 % -------------------------- % 对输入信号计算FFT dc_in mean(data_in); % 计算直流分量 fft_in fft(data_in - dc_in); % 去除直流分量后计算FFT fft_mag_in abs(fft_in) / N * 2; % 幅度归一化双边谱转单边谱 fft_mag_in(1) fft_mag_in(1) / 2; % 修正直流分量幅度 % 对输出信号计算FFT dc_out mean(data_out); fft_out fft(data_out - dc_out); fft_mag_out abs(fft_out) / N * 2; fft_mag_out(1) fft_mag_out(1) / 2; % 生成频率轴单位Hz freq (0:N-1) * FS / N; % -------------------------- % 4. 绘制时域波形 % -------------------------- figure(Name, 时域波形, Position, [100, 100, 1000, 600]); subplot(2, 1, 1); plot(t, data_in, b-, LineWidth, 1); xlabel(时间索引 t); ylabel(幅度); title(输入信号 data_in 时域波形); grid on; subplot(2, 1, 2); plot(t, data_out, r-, LineWidth, 1); xlabel(时间索引 t); ylabel(幅度); title(输出信号 data_out 时域波形); grid on; sgtitle(时域波形对比); % -------------------------- % 5. 绘制频谱图0至奈奎斯特频率 % -------------------------- figure(Name, 频谱对比, Position, [200, 200, 1000, 600]); % 输入信号频谱 subplot(2, 1, 1); plot(freq(1:N/2), fft_mag_in(1:N/2), b-, LineWidth, 1.2); xlabel(频率 (Hz)); ylabel(幅度); title(data_in 频谱5kHz 30kHz 混合信号); grid on; xlim([0, Nyquist]); % 显示0至50kHz hold on; % 标记关键频率 plot([5000, 5000], ylim, r--, LineWidth, 1); % 5kHz目标保留频率 plot([30000, 30000], ylim, g--, LineWidth, 1); % 30kHz目标衰减频率 legend(信号幅度, 5kHz通带, 30kHz阻带, Location, best); % 输出信号频谱 subplot(2, 1, 2); plot(freq(1:N/2), fft_mag_out(1:N/2), r-, LineWidth, 1.2); xlabel(频率 (Hz)); ylabel(幅度); title(data_out 频谱滤波后); grid on; xlim([0, Nyquist]); hold on; plot([5000, 5000], ylim, r--, LineWidth, 1); plot([30000, 30000], ylim, g--, LineWidth, 1); legend(信号幅度, 5kHz通带, 30kHz阻带, Location, best); sgtitle(输入输出信号频谱对比); % -------------------------- % 6. 定量分析关键频率成分 % -------------------------- % 查找5kHz附近的频率索引 [idx_5k, ~] min(abs(freq - 5000)); mag_in_5k fft_mag_in(idx_5k); % 输入信号5kHz幅度 mag_out_5k fft_mag_out(idx_5k); % 输出信号5kHz幅度 % 查找30kHz附近的频率索引 [idx_30k, ~] min(abs(freq - 30000)); mag_in_30k fft_mag_in(idx_30k); % 输入信号30kHz幅度 mag_out_30k fft_mag_out(idx_30k);% 输出信号30kHz幅度 % 计算衰减比和增益 attenuation_ratio mag_in_30k / mag_out_30k; % 30kHz衰减倍数越大越好 gain_5k mag_out_5k / mag_in_5k; % 5kHz增益应接近1 % 打印分析结果 fprintf(\n 频谱定量分析结果 \n); fprintf(5kHz信号幅度: 输入%.2f, 输出%.2f\n, mag_in_5k, mag_out_5k); fprintf(30kHz信号幅度: 输入%.2f, 输出%.2f\n, mag_in_30k, mag_out_30k); fprintf(30kHz衰减比: %.2f 倍理想低通滤波器应远大于1\n, attenuation_ratio); fprintf(5kHz增益: %.2f理想应接近1\n, gain_5k); % 判断滤波效果 if attenuation_ratio 5 gain_5k 0.5 fprintf(\n结论: 滤波效果符合预期30kHz高频被有效衰减5kHz低频保留良好。\n); else fprintf(\n结论: 滤波效果不理想需检查FIR滤波器系数或内部逻辑。\n); end从结果来看滤波效果符合预期原因如下1. 频谱分析输入频谱data_in在 5kHz 和 30kHz 处均有明显峰值符合 “5kHz 30kHz 混合信号” 的设计预期。输出频谱data_out仅保留了 5kHz 处的峰值30kHz 处的峰值几乎完全被衰减说明 FIR 滤波器成功实现了 “保留 5kHz 低频、衰减 30kHz 高频” 的低通滤波功能。2. 结论当前频谱结果表明FIR 滤波器的滤波效果符合设计目标30kHz 高频被有效抑制5kHz 低频被完整保留。后续改进方向可向并行设计靠拢