当前位置:   article > 正文

FPGA设计实现FIR滤波器仿真验证_fpga实现fft如何仿真验证正确性

fpga实现fft如何仿真验证正确性

要求:设计一个15阶(长度为16)的低通线性相位FIR滤波器,采用blackman窗,截止频率500Hz,采样频率2KHz,采用FPGA全串行结构滤波器实现,滤波器系数量化位宽12bit,输入数据位宽12bit,输出数据位宽29bit,系统时钟16KHz。(数据速率刚好2KHz)

目录

1.首先采用Matlab求出符合要求的滤波器的系数,并观察12bit量化后的滤波器幅频响应曲线(代码如下)。

2.采用Matlab仿真产生待测试数据

3.编写Verilog代码实现FPGA设计滤波器结构

4.编写仿真测试文件

5.将仿真滤波器滤波后输出的数据写入txt文件中。

6.采用Matlab分析经过滤波后的数据,分析滤波性能。

7.总结

1.首先采用Matlab求出符合要求的滤波器的系数,并观察12bit量化后的滤波器幅频响应曲线(代码如下)。

  1. %fir8serial.m
  2. function hn = E4_7_Fir8Serial
  3. N = 16;%滤波器长度
  4. fs = 2000;%滤波器采样频率
  5. fc = 500;%低通滤波器的截止频率
  6. B = 12;%量化位数
  7. %生成窗函数
  8. w_black = blackman(N)';
  9. %采用fir1函数设计FIR滤波器
  10. b_black = fir1(N-1,fc*2/fs,w_black);
  11. %量化滤波器系数
  12. Q_black = round(b_black/max(abs(b_black))*(2^(B-1)-1));
  13. hn = Q_black;
  14. %转换成16进制数补码
  15. Q_h = dec2hex(Q_black+2^B*(Q_black<0));
  16. %求滤波器的幅频响应
  17. m_black = 20*log10(abs(fft(b_black,1024)));m_black = m_black - max(m_black);%将最大值归位0
  18. Ql_black = 20*log10(abs(fft(Q_black,1024)));Ql_black = Ql_black - max(Ql_black);
  19. %设置幅频响应横坐标单位为Hz
  20. x_f = [0:(fs/length(m_black)):fs/2];
  21. m1 = m_black(1:length(x_f));
  22. m2 = Ql_black(1:length(x_f));
  23. %绘制幅频响应曲线
  24. plot(x_f,m1,'-',x_f,m2,'-.');
  25. xlabel('频率(Hz)');ylabel('幅度(dB)');
  26. legend('未量化','12bit量化');
  27. grid;

运行结果为

可以发现12bit量化符合要求

滤波器系数如下

2.采用Matlab仿真产生待测试数据

并通过设计的滤波器验证理论上的滤波效果同时将生成的测试数据写入txt文件中供后面Modelsim仿真时读取

代码如下:

  1. %E4_7_NoiseAndCarrier.m
  2. f1 = 200 ;%信号1频率200Hz
  3. f2 = 800 ;%信号2频率800Hz
  4. Fs = 2000;%采样频率2KHz
  5. N = 12 ;%量化数据位宽12bit 输入数据12bit
  6. %产生信号
  7. t = 0:1/Fs:1;
  8. c1 = 2*pi*f1*t;
  9. c2 = 2*pi*f2*t;
  10. s1 = sin(c1);%产生正弦信号
  11. s2 = sin(c2);%产生正弦信号
  12. s = s1 + s2;%产生两个单载波合成后的信号
  13. %产生随机序列信号
  14. noise=randn(1,length(t));%产生高斯白噪声序列
  15. %归一化处理
  16. s = s / max(abs(s));
  17. noise = noise / max(abs(noise));
  18. %12bit量化
  19. Q_noise = round(noise*(2^(N-1)-1));
  20. Q_s = round(s*(2^(N-1)-1));
  21. %调用自己设计的滤波器函数对信号进行滤波
  22. hn = fir8serial;
  23. Filter_noise = filter(hn,1,Q_noise);
  24. Filter_s = filter(hn,1,Q_s);
  25. %求信号的幅频响应
  26. %滤波之前
  27. m_noise = 20*log10(abs(fft(Q_noise,1024))); m_noise = m_noise - max(m_noise);
  28. m_s = 20*log10(abs(fft(Q_s,1024))); m_s = m_s - max(m_s);
  29. %滤波之后
  30. Fm_noise = 20*log10(abs(fft(Filter_noise,1024)));Fm_noise = Fm_noise - max(Fm_noise);
  31. Fm_s = 20*log10(abs(fft(Filter_s,1024)));Fm_s = Fm_s - max(Fm_s);
  32. %滤波器本身的幅频响应
  33. m_hn = 20*log10(abs(fft(hn,1024)));m_hn = m_hn - max(m_hn);
  34. %设置幅频响应的横坐标为Hz
  35. x_f = [0:(Fs/length(m_s)):Fs/2];
  36. %只显示正频率部分
  37. mf_noise = m_noise(1:length(x_f));
  38. mf_s = m_s(1:length(x_f));
  39. Fmf_noise = Fm_noise(1:length(x_f));
  40. Fmf_s = Fm_s(1:length(x_f));
  41. Fm_hn = m_hn(1:length(x_f));
  42. %绘制幅频响应曲线
  43. subplot(211)
  44. plot(x_f,mf_noise,'-.',x_f,Fmf_noise,'-',x_f,Fm_hn,'--');
  45. xlabel('频率(Hz)');ylabel('幅度(dB)');title('Matlab仿真白噪声滤波前后的频谱');
  46. legend('输入信号频谱','输出信号频谱','滤波器响应');
  47. grid;
  48. subplot(212);
  49. plot(x_f,mf_s,'-.',x_f,Fmf_s,'-',x_f,Fm_hn,'--');
  50. xlabel('频率(Hz)');ylabel('幅度(dB)');title('Matlab仿真合成单频信号滤波前后频谱');
  51. legend('输入信号','输出信号','滤波器响应');
  52. grid;
  53. %将生成的数据以10进制格式写入txt文档
  54. fid = fopen('D:\test\fir\4_7\E4_7_Int_noise.txt','w');
  55. fprintf(fid,'%8d\r\n',Q_noise);
  56. fprintf(fid,';');
  57. fclose(fid);
  58. fid = fopen('D:\test\fir\4_7\E4_7_Int_s.txt','w');
  59. fprintf(fid,'%8d\r\n',Q_s);
  60. fprintf(fid,';');
  61. fclose(fid);
  62. %将生成的数据以二进制形式写入txt文档
  63. fid = fopen('D:\test\fir\4_7\E4_7_Bin_noise.txt','w');
  64. for i = 1:length(Q_noise)
  65. B_noise = dec2bin(Q_noise(i)+(Q_noise(i)<0)*2^N,N);%N为位数
  66. for j=1:N
  67. if B_noise(j)=='1'
  68. tb = 1;
  69. else
  70. tb = 0;
  71. end
  72. fprintf(fid,'%d',tb);
  73. end
  74. fprintf(fid,'\r\n');
  75. end
  76. fprintf(fid,';');
  77. fclose(fid);
  78. fid = fopen('D:\test\fir\4_7\E4_7_Bin_s.txt','w');
  79. for i=1:length(Q_s)
  80. B_s = dec2bin(Q_s(i)+(Q_s(i)<0)*2^N,N);%如果为负数则转换为它所对应的正数
  81. for j=1:N
  82. if B_s(j)=='1'
  83. tb = 1;
  84. else
  85. tb = 0;
  86. end
  87. fprintf(fid,'%d',tb);
  88. end
  89. fprintf(fid,'\r\n');
  90. end
  91. fprintf(fid,';');
  92. fclose(fid);

运行结果:

我们可以发现当输入信号为白噪声时输出800Hz以后的频率衰减已经达到-40dB了

当输入信号为200Hz和800Hz的叠加信号时,滤波后的输出只剩200Hz的信号了,800Hz被滤除了。

3.编写Verilog代码实现FPGA设计滤波器结构

  1. /*
  2. *
  3. *@Author: X-Z
  4. *@Date:2023-09-17 00:45:22
  5. *@Function:全串行FIR数字滤波器 加法器位宽13bit,量化12bit扩展一位 乘法器输入位宽12bit和13bit输出数据位宽25bit
  6. */
  7. /*
  8. 设计一个15阶(长度216)的低通线性相位的FIR滤波器,采用布莱克曼窗,截止频率500Hz,采样频率2KHZ,
  9. 采用FPGA实现全串行结构的滤波器,系数量化位数12Bit,输入数据位宽12bit,输出数据位宽29bit
  10. 系统时钟16KHz
  11. */
  12. module FirFullSerial(
  13. input wire clk ,//FPGA系统时钟频率16KHz
  14. input wire rst_n ,
  15. input wire signed [11:0] Xin ,//数据输入频率2KHz
  16. output wire signed [28:0] Yout //滤波器输出数据
  17. );
  18. //例化有符号数乘法器IP核
  19. reg signed [11:0] coe ;//滤波器为12bit量化数据
  20. wire signed [12:0] add_s;//输入为12bit量化滤波器系数,两个对称项系数相加需要13bit位宽
  21. wire signed [24:0] Mout ;
  22. mul u_mult(
  23. .aclr (~rst_n),
  24. .clock (clk ),
  25. .dataa (coe ),
  26. .datab (add_s ),
  27. .result (Mout )
  28. );
  29. //例化有符号数加法器IP核,对输入数据进行1位数据位的扩展,输出结果为13bit数据
  30. reg signed [12:0] add_a;
  31. reg signed [12:0] add_b;
  32. add u_adder(
  33. .dataa (add_a),
  34. .datab (add_b),
  35. .result (add_s)
  36. );
  37. //3位计数器,计数周期为8,刚好为数据输入的速率2KHZ
  38. reg [2:0] cnt;
  39. always @(posedge clk or negedge rst_n)begin
  40. if(!rst_n)
  41. cnt <= 3'd0;
  42. else
  43. cnt <= cnt + 1'd1;
  44. end
  45. //将数据移入移位寄存器Xin_Reg中
  46. reg [11:0] Xin_Reg [15:0];//1612bit位宽的存储器,用来存储滤波器系数
  47. reg [3:0] i,j;
  48. always @(posedge clk or negedge rst_n)begin
  49. if(!rst_n)begin//初始化寄存器为0
  50. for(i=0;i<15;i=i+1)
  51. Xin_Reg[i] <= 12'd0;
  52. end
  53. else begin
  54. if(cnt==3'd7)begin
  55. for(j=0;j<15;j=j+1)
  56. Xin_Reg[j+1] <= Xin_Reg[j];
  57. Xin_Reg[0] <= Xin;
  58. end
  59. end
  60. end
  61. //将对称系数的输入数据相加,同时将对应滤波器的系数送人乘法器
  62. //需要注意的是以下程序只是用了一个加法器和一个乘法器资源
  63. //以8倍速率调用乘法器IP核,由于滤波器长度为16,系数具有对称性,所以
  64. //可以在一个时钟周期内完成所有8个滤波器系数与对应数据的乘法运算
  65. //为保证加法运算不溢出,输入输出数据均扩展为13bit
  66. //乘法器使用了1级流水线,加法器没有使用流水线会立即输出相加结果
  67. /*
  68. 12bit量化滤波器系数
  69. 0 -3 15 46 -117 -263 590 2047
  70. 2047 590 -263 -117 46 15 -3 0
  71. */
  72. always @(posedge clk or negedge rst_n)begin
  73. if(!rst_n)begin
  74. add_a <= 13'd0;
  75. add_b <= 13'd0;
  76. coe <= 12'd0;//滤波器系数
  77. end
  78. else begin
  79. case(cnt)
  80. 3'd0 : begin
  81. add_a <= {Xin_Reg[0][11],Xin_Reg[0]};//将数据扩展一位符号位
  82. add_b <= {Xin_Reg[15][11],Xin_Reg[15]};
  83. coe <= 12'd0;//c0
  84. end
  85. 3'd1 : begin
  86. add_a <= {Xin_Reg[1][11],Xin_Reg[1]};
  87. add_b <= {Xin_Reg[14][11],Xin_Reg[14]};
  88. coe <= -12'd3;//c1
  89. end
  90. 3'd2 : begin
  91. add_a <= {Xin_Reg[2][11],Xin_Reg[2]};
  92. add_b <= {Xin_Reg[13][11],Xin_Reg[13]};
  93. coe <= 12'd15;//c2
  94. end
  95. 3'd3 : begin
  96. add_a <= {Xin_Reg[3][11],Xin_Reg[3]};
  97. add_b <= {Xin_Reg[12][11],Xin_Reg[12]};
  98. coe <= 12'd46;//c3
  99. end
  100. 3'd4 : begin
  101. add_a <= {Xin_Reg[4][11],Xin_Reg[4]};
  102. add_b <= {Xin_Reg[11][11],Xin_Reg[11]};
  103. coe <= -12'd177;//c4
  104. end
  105. 3'd5 : begin
  106. add_a <= {Xin_Reg[5][11],Xin_Reg[5]};
  107. add_b <= {Xin_Reg[10][11],Xin_Reg[10]};
  108. coe <= -12'd263;//c5
  109. end
  110. 3'd6 : begin
  111. add_a <= {Xin_Reg[6][11],Xin_Reg[6]};
  112. add_b <= {Xin_Reg[9][11],Xin_Reg[9]};
  113. coe <= 12'd590;//c6
  114. end
  115. 3'd7 : begin
  116. add_a <= {Xin_Reg[7][11],Xin_Reg[7]};
  117. add_b <= {Xin_Reg[8][11],Xin_Reg[8]};
  118. coe <= 12'd2047;//c7
  119. end
  120. default : begin
  121. add_a <= 13'd0;
  122. add_b <= 13'd0;
  123. coe <= 12'd0;//滤波器系数
  124. end
  125. endcase
  126. end
  127. end
  128. //对滤波器系数与输入数据的乘法结果进行累加,并输出滤波后的数据
  129. //考虑到乘法器及累加器的延时,需要计数器为2时对累加器清零,同时输出
  130. //滤波器结果数据,类似的时延长度一方面可以通过精确计算获得,但更好的方法是
  131. //通过行为仿真查看
  132. reg signed [28:0] sum ;
  133. reg signed [28:0] yout;
  134. always @(posedge clk or negedge rst_n)begin
  135. if(!rst_n)begin
  136. sum <= 29'd0;
  137. yout <= 29'd0;
  138. end
  139. else begin
  140. if(cnt==3'd2)begin
  141. yout <= sum ;//同时cnt==1时最后一个数运算完成但cnt==2时才有效;输出yout有效时cnt==3
  142. sum <= Mout;//ct==2时输出第一个乘法器运算结果
  143. end
  144. else
  145. sum <= sum + Mout; //经过1级流水线,cnt==1时全部加完cnt==2时输出累加结果有效
  146. end
  147. end
  148. assign Yout = yout;
  149. endmodule

4.编写仿真测试文件

  1. `timescale 1ns/1ps
  2. module FirFullSerial_tb();
  3. reg clk ;
  4. reg rst_n ;
  5. reg [11:0] Xin ;
  6. wire clk_data;//数据时钟,速率为系统时钟clk速率的1/8
  7. wire [28:0] Yout ;
  8. //例化滤波器模块
  9. FirFullSerial u_FirFullSerial(
  10. /*input wire */ .clk (clk ) ,//FPGA系统时钟频率16KHz
  11. /*input wire */ .rst_n(rst_n) ,
  12. /*input wire signed [11:0] */ .Xin (Xin ) ,//数据输入频率2KHz
  13. /*output wire signed [28:0] */ .Yout (Yout ) //滤波器输出数据
  14. );
  15. parameter clk_period = 62500;//设置时钟信号周期(频率):16KHz
  16. parameter clk_period_data = clk_period * 8;
  17. parameter clk_half_period = clk_period / 2;
  18. parameter clk_half_period_data = clk_period_data / 2;
  19. parameter data_num = 2000;//仿真数据长度
  20. parameter time_sim = data_num * clk_period_data;//仿真时间
  21. initial begin
  22. clk <= 1'b1;
  23. rst_n = 1'b0;
  24. #(5*clk_period)
  25. rst_n = 1'b1;
  26. //设置仿真时间
  27. #time_sim $finish;
  28. Xin = 12'd10;//输入信号赋初值
  29. end
  30. //产生时钟信号
  31. always #clk_half_period clk = ~clk;//16KHz
  32. reg [2:0] cn_clk = 0;
  33. always @(posedge clk) cn_clk <= cn_clk + 3'd1;
  34. assign clk_data = cn_clk[2];//时钟周期与系统时钟对齐;2KHz
  35. //从外部txt文件(SinIn.txt)中读入数据作为测试数据
  36. integer pattern;
  37. reg [11:0] stimuls [1:data_num];
  38. initial begin
  39. //文件必须放在"工程目录\simulation\modelsim"路径下
  40. $readmemb("E4_7_Bin_noise.txt",stimuls);
  41. //$readmemb("E4_7_Bin_s.txt",stimuls);
  42. pattern = 0;
  43. repeat(data_num)begin
  44. @(posedge clk_data);
  45. pattern = pattern + 1;
  46. Xin = stimuls[pattern];
  47. end
  48. end
  49. //将仿真数据dout写入外部txt文件中(out.txt)
  50. integer file_out;
  51. initial begin
  52. //放在"工程目录\simulation\modelsim"路径下
  53. file_out = $fopen("E4_7_Noiseout.txt");
  54. //file_out = $fopen("E4_7_sout.txt");
  55. if(!file_out)begin
  56. $display("could not open file!");
  57. $finish;
  58. end
  59. end
  60. wire rst_write;
  61. wire signed [28:0] dout_s;
  62. assign dout_s = Yout;//将dout转换成有符号数据
  63. assign rst_write = clk_data & rst_n;//产生写入时钟信号,复位状态下不写入数据
  64. always @(posedge rst_write)
  65. $fdisplay(file_out,"%d",dout_s);
  66. endmodule

5.将仿真滤波器滤波后输出的数据写入txt文件中。

Modelsim仿真结果:

通过仿真我们可以发现大致实现了滤波性能,但还是不够直观,因此我们采用Matlab进行分析滤波性能。

6.采用Matlab分析经过滤波后的数据,分析滤波性能。

代码如下:

  1. %E4_7_NoiseAndCarrierOut.m
  2. f1 = 200 ;%信号1频率200Hz
  3. f2 = 800 ;%信号2频率800Hz
  4. Fs = 2000;%采样频率2KHz
  5. N = 12 ;%量化位数
  6. %从文本文件中读取数据
  7. %测试输入数据分别放在Noise_in和S_in变量中
  8. fid = fopen('D:\test\fir\4_7\E4_7_Int_Noise.txt','r');
  9. [Noise_in,N_n] = fscanf(fid,'%lg',inf);
  10. fclose(fid);
  11. fid = fopen('D:\test\fir\4_7\E4_7_Int_s.txt','r');
  12. [S_in,N_n] = fscanf(fid,'%lg',inf);
  13. fclose(fid);
  14. %滤波后的输出结果数据放在Noise_out和S_out变量中
  15. fid = fopen('D:\test\fir\4_7\FirFullSerial\prj\simulation\modelsim\E4_7_Noiseout.txt','r');
  16. [Noise_out,N_count] = fscanf(fid,'%lg',inf);
  17. fclose(fid);
  18. fid = fopen('D:\test\fir\4_7\FirFullSerial\prj\simulation\modelsim\E4_7_sout.txt','r');
  19. [S_out,N_count] = fscanf(fid,'%lg',inf);%以浮点数形式全部读取文件中的数据
  20. fclose(fid);
  21. %归一化处理
  22. Noise_out = Noise_out / max(abs(Noise_out));
  23. S_out = S_out / max(abs(S_out));
  24. Noise_in = Noise_in / max(abs(Noise_in));
  25. S_in = S_in /max(abs(S_in));
  26. %求信号的幅频响应
  27. out_noise = 20*log10(abs(fft(Noise_out,1024)));out_noise = out_noise - max(out_noise);
  28. out_s = 20*log10(abs(fft(S_out,1024)));out_s = out_s - max(out_s);
  29. in_noise = 20*log10(abs(fft(Noise_in,1024)));in_noise = in_noise - max(in_noise);
  30. in_s = 20*log10(abs(fft(S_in,1024)));in_s = in_s - max(in_s);
  31. %滤波器本身的幅频响应
  32. hn = fir8serial;
  33. m_hn = 20*log10(abs(fft(hn,1024)));m_hn = m_hn - max(m_hn);
  34. %设置幅频响应横坐标单位为Hz
  35. x_f = [1:(Fs/length(out_noise)):Fs/2];
  36. %只显示正频率部分的幅频响应
  37. mf_out_noise = out_noise(1:length(x_f));
  38. mf_out_s = out_s(1:length(x_f));
  39. mf_in_noise = in_noise(1:length(x_f));
  40. mf_in_s = in_s(1:length(x_f));
  41. mf_hn = m_hn(1:length(x_f));
  42. %绘制幅频响应特性曲线
  43. figure(1);
  44. subplot(211);
  45. plot(x_f,mf_in_noise,'--',x_f,mf_out_noise,'-',x_f,mf_hn,'-.');
  46. xlabel('频率(Hz)');ylabel('幅度(dB)');
  47. title('FPGA仿真白噪声滤波前后幅频响应曲线');
  48. legend('输入信号频谱','输出信号频谱','滤波器响应');
  49. grid;
  50. subplot(212);
  51. plot(x_f,mf_in_s,'--',x_f,mf_out_s,'-',x_f,mf_hn,'-.');
  52. xlabel('频率(Hz)');ylabel('幅度(dB)');title('FPGA仿真单频叠加信号滤波前后幅频响应曲线');
  53. axis([0 1000 -100 0]);
  54. legend('输入信号幅频响应','输出信号频谱','滤波器响应');
  55. grid;
  56. %绘制时域波形
  57. %设置显示数据的范围
  58. t = 0:1/Fs:50/Fs;t=t*1000;
  59. %length(t)
  60. t_in_noise = Noise_in(1:length(t));
  61. t_in_s = S_in(1:length(t));
  62. t_out_s = S_out(1:length(t));
  63. t_out_noise = Noise_out(1:length(t));
  64. figure(2);
  65. subplot(211);
  66. %length(hn);
  67. plot(t,t_in_noise,'--',t,t_out_noise,'-');
  68. xlabel('时间(ms)');ylabel('幅值');title('FPGA仿真白噪声滤波前后的时域波形');
  69. legend('输入信号波形','输出信号波形');
  70. grid;
  71. subplot(212);
  72. plot(t,t_in_s,'--',t,t_out_s,'-');
  73. xlabel('时间(ms)');ylabel('幅值');
  74. title('FPGA仿真单频叠加信号滤波前后时域波形');
  75. legend('输入信号波形','输出信号波形');
  76. grid;

运行结果为:


我们发现采用FPGA滤波前后的波形和Matlab仿真波形图基本一致,唯一不同的是Matlab的滤波效果更好一些,FPGA衰减没有Matlab仿真的大,这是因为FPGA中存在字长效应以及数据处理时存在截尾效应。因此可以发现理论与实际的差距,但最终设计满足要求。

注:自学完杜勇的FIR滤波器所写,能力有限,不喜勿喷。

本文内容由网友自发贡献,转载请注明出处:https://www.wpsshop.cn/w/人工智能uu/article/detail/945939
推荐阅读
相关标签
  

闽ICP备14008679号