低通FIR滤波器Verilog实现与MATLAB结果不符问题求助
FIR滤波器实现问题排查
背景信息
通过MATLAB命令h = fir1(99, 0.2, 'low')生成低通FIR滤波器系数,随后生成三个正弦波信号:$f(t)=\sin(2 \cdot \pi \cdot f \cdot t)$,频率分别取950Hz、1100Hz、2000Hz,以10000Hz采样率采集2000个样本。将样本与滤波器系数转换为Q(2,14)定点格式后,MATLAB输出结果中$y[0] = 0x0000$(十六进制),该结果符合预期,因为三种频率正弦波的首个输入样本均为0。
遇到的问题
- 三种Verilog实现均未输出$y[0] = 0x0000$:按设计逻辑,第7个时钟上升沿应输出$yout = y[0] = 0$,实际却得到$yout = y[1] = FFFE$(十六进制),$y[0]$始终未生成。
- 三种Verilog滤波器的输出结果不一致。
直接型FIR滤波器Verilog代码
模块代码
module direct_fir #(parameter integer N = 100) ( input wire clk, input wire rst, // 同步高有效复位 input wire vin, input wire signed [15:0] xin, // Q_2_14 output reg vout, output reg signed [15:0] yout // Q_2_14 ); integer i; reg signed [15:0] xdel [0:N-1]; reg signed [15:0] h [0:N-1]; reg signed [63:0] acc; reg signed [31:0] prod; reg vin_d; reg signed [63:0] acc_abs; reg signed [63:0] acc_scaled; localparam signed [63:0] HALF_LSB = 64'sd8192; // 2^13 localparam integer SHIFT = 14; initial begin $readmemh("coef_q214_hex.txt", h); end function automatic signed [15:0] sat16(input signed [63:0] x); begin if (x > 64'sd32767) sat16 = 16'sd32767; else if (x < -64'sd32768) sat16 = -16'sd32768; else sat16 = x[15:0]; end endfunction always @(posedge clk) begin if (rst) begin for (i=0; i<N; i=i+1) xdel[i] <= 16'sd0; yout <= 16'sd0; vout <= 1'b0; vin_d <= 1'b0; end else begin if (vin) begin for (i=N-1; i>0; i=i-1) xdel[i] <= xdel[i-1]; xdel[0] <= xin; end vin_d <= vin; vout <= 1'b0; if (vin_d) begin acc = 64'sd0; acc_abs = 64'sd0; acc_scaled = 64'sd0; for (i=0; i<N; i=i+1) begin prod = xdel[i] * h[i]; // Q_4_28 acc = acc + {{32{prod[31]}}, prod}; // 符号扩展到64位 end if (acc >= 0) begin acc_scaled = (acc + HALF_LSB) >>> SHIFT; end else begin acc_abs = -acc; acc_scaled = -((acc_abs + HALF_LSB) >>> SHIFT); end yout <= sat16(acc_scaled); vout <= 1'b1; end end end endmodule
测试平台代码
`timescale 1ns/1ps module tb_direct_fir; // 参数定义 parameter integer N_TAPS = 100; parameter integer NSAMPLES = 2000; parameter integer TCLK = 10; // DUT信号 reg clk; reg rst; reg vin; reg signed [15:0] xin; wire vout; wire signed [15:0] yout; // 输入存储器 reg [15:0] xmem [0:NSAMPLES-1]; integer n; integer fout; // 例化DUT direct_fir #(.N(N_TAPS)) dut ( .clk (clk), .rst (rst), .vin (vin), .xin (xin), .vout(vout), .yout(yout) ); // 时钟生成 initial begin clk = 1'b0; forever #(TCLK/2) clk = ~clk; end //__________________________________________________________ initial begin $dumpfile("direct_fir.vcd"); $dumpvars(0, tb_direct_fir); end //______________________________________________________________ // 同步复位任务 task apply_reset; begin rst = 1'b1; vin = 1'b0; xin = 16'sd0; @(posedge clk); @(posedge clk); @(posedge clk); rst = 1'b0; @(posedge clk); end endtask // 输出日志记录 always @(negedge clk) begin if (!rst && vout) begin $fwrite(fout, "%04h\n", yout); end end // 运行测试任务 task run_file; input [8*64-1:0] in_filename; input [8*64-1:0] out_filename; begin $readmemh(in_filename, xmem); // 加载输入样本到xmem fout = $fopen(out_filename, "w"); // 打开输出文件 if (fout == 0) begin $display("ERROR: 无法打开输出文件 %s", out_filename); $finish; end apply_reset; // 复位DUT // 每时钟喂入一个样本:在时钟下降沿驱动,上升沿采样 vin = 1'b1; for (n = 0; n < NSAMPLES; n = n + 1) begin @(negedge clk); xin = $signed(xmem[n]); // 确保下一个上升沿时信号稳定 end // 额外一个上升沿,确保最后一个样本被捕获 @(posedge clk); // 停止喂入样本 @(negedge clk); xin = 16'sd0; vin = 1'b0; for (n = 0; n < 10; n = n + 1) begin // 额外等待10个周期确保输出完全刷新 @(posedge clk); end @(negedge clk); $fclose(fout); end endtask // 主测试流程 initial begin rst = 1'b1; vin = 1'b0; xin = 16'sd0; run_file("x950_q214_hex.txt", "y950_direct_hex.txt"); run_file("x1100_q214_hex.txt", "y1100_direct_hex.txt"); run_file("x2000_q214_hex.txt", "y2000_direct_hex.txt"); $display("DONE: 输出已写入文件。"); $finish; end endmodule
内容的提问来源于stack exchange,提问作者Shashank Ranjan
相关产品推荐
相关产品推荐

