当前位置:   article > 正文

【FPGA】FFT测量信号频率(Quartus IP核)_fft函数检测频率

fft函数检测频率

​​​​​​​文章目录

注意:笔者使用的是Quartus Standard 17.1版本,高版本的Quartus需要先破解IP核才能调用FFT,不然在编译仿真时会在EDA Netlist Writer报错说没有相应的license,同时打开simulation tool也会报错。板子用的是小梅哥的AC620V2开发板,型号是cyclone IV E : EP4CE10F17C8。


二、FFT是什么(原理)?

        FFT(快速傅里叶变换)是数字信号处理中一种重要的算法,用于将一个信号从时域转化为频域。其原理基于傅里叶变换,但相比传统的傅里叶变换,FFT算法具有更高的计算效率。

        FFT算法的原理是将一个N点的离散信号通过递归分治的方式分解成多个小规模的离散信号,然后通过频域旋转因子的运算将其合并成最终的频域结果。具体而言,FFT算法通过将信号分解为偶数点和奇数点,然后再对这些小规模的子问题进行傅里叶变换,最终将结果合并得到信号的频域表示。

        FFT算法在数字信号处理中具有重要的意义。首先,FFT算法可以将信号从时域转换为频域,这使得我们可以了解信号的频谱特征,例如信号的频率成分、幅度和相位信息等。这对于信号处理、频谱分析和滤波等应用非常重要。

其次,FFT算法具有高效的计算速度。传统的傅里叶变换算法的计算复杂度为O(N^2),而FFT算法的计算复杂度为O(NlogN)。这意味着FFT算法能够在较短的时间内进行大规模信号的频域变换,对于实时信号处理和大数据处理具有重要意义。

        此外,FFT算法还有其他一些重要的应用,例如信号滤波、频谱估计、相关性分析和频谱乘法等。这些应用使得FFT成为数字信号处理中必不可少的工具。

        快速傅里叶变换的基本思想是将时域序列逐次分解为一组子序列,利用旋转因子的特性,由子序列的DFT来实现整个序列的DFT。


三、FFT IP核参数介绍

按以下步骤可以打开官方对于FFT IP核的解释文档

双击FFT可以进入IP核设置界面

Transform:Length是指转换序列长度或者在可变数据流模式下的最大转换长度;Direction可选FFT或者逆快速傅里叶变换IFFT。同时可以在端口信号inverse处设置变换方向,0为FFT,1为IFFT。

.inverse      (inverse),      //       .inverse

I/O:Data Flow可以选择四种模式Variable Streaming、Streaming、Buffered Burst、Burst。可以根据自己的需求选择具体的模式。Streaming模式允许对于输入数据的持续处理并且持续输出处理后的数据流,不需要暂停输入输出数据流。Variable Streaming模式和Streaming模式很相似,但Variable Streaming在FFT运行过程中可以处理不同长度的序列。Buffered Burst模式进行FFT的处理时间会相对久一点,但对于内存资源的需求会比Streaming模式少。Burst模式和Buffered Burst模式也很相似,但Burst会消耗更少的内存资源,同时平均吞吐量会更低。

Data and Twiddle:设置数据类型和旋转因子。和之前设置的IO模式有对应关系,否则会显示报错。可以再具体翻一翻手册。

Basic Parameters

ParametersValueDescription
Transform Length
 
64, 128, 256, 512, 1024,
2048, 4096, 8192,
16384, 32768, or 65536.
Variable streaming also
allows 8, 16, 32,
131072, and 262144
The transform length. For variable streaming, this value is the
maximum FFT length.
Transform Direction

Forward,reverse,

bidirectional

The transform direction.
I/O Data Flow

Streaming

Variable Streaming

Buffered Burst

Burst

If you select Variable Streaming and Floating Point, the precision is automatically set to 32, and the reverse I/O order options are Digit Reverse Order.
I/O OrderBit Reverse Order, Digit
Reverse Order, Natural
Order, N/2 to N/2
The input and output order for data entering and leaving the FFT (variable streaming FFT only). The Digit Reverse Order option replaces the Bit Reverse Order in variable streaming floating point variations.
Data RepresentationFixed point or single
floating point, or block
floating point
The internal data representation type (variable streaming FFT
only), either fixed point with natural bit-growth or single precision floating point. Floating-point bidirectional IP cores expect input inThe internal data representation type (variable streaming FFT only), either fixed point with natural bit-growth or single precision floating point. Floating-point bidirectional IP cores expect input in
Data Width8, 10, 12, 14, 16, 18, 20,24, 28, 32The data precision. The values 28 and 32 are available for
variable streaming only.
Twiddle Width8, 10, 12, 14, 16, 18, 20,24, 28, 32The twiddle precision. The values 28 and 32 are available for
variable streaming only. Twiddle factor precision must be less
than or equal to data precision.

Basic Parameters

在Burst模式下还需要配置Advanced Parameters,但只需要配置FFT Engine Architecture和Number of Parallel FFT Engines。

Advanced Parameters

ParametersValueDescription
FFT Engine ArchitectureQuad Output, Single
Output
Choose between one, two, and four quad-output FFT engines
working in parallel. Alternatively, if you have selected a singleoutput FFT engine architecture, you may choose to implement one or two engines in parallel. Multiple parallel engines reduce transform time at the expense of device resources, which allows you to select the desired area and throughput trade-off point. Not available for variable streaming or streaming FFTs.
Number of Parallel FFT Engines1, 2, 4
DSP Block Resource OptimizationOn or OffTurn on for multiplier structure optimizations. These optimizations
use different DSP block configurations to pack multiply operations
and reduce DSP resource requirements. This optimization may
reduce FMAX because of the structure of the specific configurations of the DSP blocks when compared to the basic operation. Specifically, on Stratix V devices, this optimization may also come at the expense of accuracy. You can evaluate it using the MATLAB model provided and bit wise accurate simulation models. If you turn on DSP Block Resource Optimization and your variation has data precision between 18 and 25 bits, inclusive, and twiddle precision less than or equal to 18 bits, the FFT MegaCore function configures the DSP blocks in complex 18 x 25 multiplication mode.
Enable Hard Floating Point BlocksOn or offFor Arria 10 devices and single-floating-point FFTs only.

在配置完IP后,可以看到生成的Block Symbol 。

 具体信号含义可以看下表,帮助理解。该表来源于FPGA学习专题-FFT IP核的使用_quartus实现fft全流程-CSDN博客

        在配置IP核时可以考虑fft的延时问题,burst模式比stream模式稍慢,而基4模式比基2模式(在advanced parameters中设置)快,可以搭配使用。


四、仿真

matlab中生成正弦波,并将数据导入FPGA进行仿真,验证FFT核的正确性

0、文件完整结构

在后续建文件和修改绝对路径时尽量参考这个文件结构。

1、设置IP核

这是Quartus中FFT IP核的编辑界面。要generate两个地方,不然在仿真的时候会报错

2、例化FFT,并完善顶层文件

  1. //File name:fft_demo
  2. //Complete date:23/03/24
  3. /
  4. module fft_demo(
  5. input wire clk,
  6. input wire rst_n,
  7. input wire sink_valid,
  8. input wire sink_sop,
  9. input wire sink_eop,
  10. input signed [15:0] data_in,
  11. output wire source_valid,
  12. output wire source_sop,
  13. output wire source_eop,
  14. output signed [31:0] data_out
  15. );
  16. wire sink_ready;
  17. wire [1:0] sink_error;
  18. wire signed [15:0] sink_imag;
  19. wire inverse;
  20. wire source_ready;
  21. wire [1:0] source_error;
  22. wire signed [15:0] source_real;
  23. wire signed [15:0] source_imag;
  24. wire [5:0] source_exp;
  25. assign sink_error=2'b00;
  26. assign sink_imag=16'd0;
  27. assign inverse=1'b0;
  28. assign source_ready=1'b1;
  29. fft u0 (
  30. .clk (clk), // clk.clk
  31. .reset_n (rst_n), // rst.reset_n
  32. .sink_valid (sink_valid), // sink.sink_valid
  33. .sink_ready (sink_ready), // .sink_ready
  34. .sink_error (sink_error), // .sink_error
  35. .sink_sop (sink_sop), // .sink_sop
  36. .sink_eop (sink_eop), // .sink_eop
  37. .sink_real (data_in), // .sink_real
  38. .sink_imag (sink_imag), // .sink_imag
  39. .inverse (inverse), // .inverse
  40. .source_valid (source_valid), // source.source_valid
  41. .source_ready (source_ready), // .source_ready
  42. .source_error (source_error), // .source_error
  43. .source_sop (source_sop), // .source_sop
  44. .source_eop (source_eop), // .source_eop
  45. .source_real (source_real), // .source_real
  46. .source_imag (source_imag), // .source_imag
  47. .source_exp (source_exp) // .source_exp
  48. );
  49. wire signed [31:0] dout_re,dout_im;
  50. assign dout_re=source_real*source_real;
  51. assign dout_im=source_imag*source_imag;
  52. assign data_out=dout_re+dout_im;
  53. endmodule

3、利用matlab生成正弦波信号

这段代码首先定义了采样频率fs为100000Hz,信号频率f1为1000Hz,然后生成了一个长度为2048的时间序列t,采样频率为fs。接着生成了一个长度为2048的信号x,信号是由0.5倍幅度的1000Hz正弦波和0.5的直流分量组成,然后将其量化为8位,即取值范围为0-255。

接下来使用fft函数对信号x进行1024点的快速傅里叶变换,得到频谱y。然后使用plot函数绘制了频谱y的幅度平方的图像,即频谱的能量谱图。

  1. clc;
  2. clear;
  3. fs=100000;
  4. f1=1000;
  5. t=0:1/fs:2047/fs;
  6. x=floor((0.5*cos(2*pi*f1*t)+0.5)*255);
  7. plot(x);
  8. y=fft(x,1024);
  9. plot(abs(y).*abs(y));

4、导出变量x生成的正弦波数据

新建一个trans.cpp文件,将datain.txt中的数据导入dataset.vh文件。

  1. #include<bits/stdc++.h>
  2. using namespace std;
  3. int main()
  4. {
  5. freopen("datain.txt","r",stdin);
  6. freopen("dataset.vh","w",stdout);
  7. signed short val;
  8. for(int i=0;i<2048;i++)
  9. {
  10. cin>>val;
  11. printf("%x\n",val);
  12. }
  13. fclose(stdin);
  14. fclose(stdout);
  15. return 0;
  16. }

5、编写testbench

注意要修改其中的路径,不然在仿真时会报错。 

  1. /
  2. `timescale 1ns/1ns
  3. module fft_demo_tb;
  4. reg clk;
  5. reg rst_n;
  6. wire sink_valid;
  7. wire sink_sop;
  8. wire sink_eop;
  9. wire signed [15:0]data_in;
  10. wire source_valid;
  11. wire source_sop;
  12. wire source_eop;
  13. wire [1:0]source_error;
  14. wire signed [31:0]data_out;
  15. parameter FILE_PATH="E:/Quartus-standard-17.1/Documents/fft/simulation/modelsim/dataset.vh";
  16. reg [15:0] data[2048:0];
  17. initial begin
  18. clk=0;
  19. $readmemh(FILE_PATH,data);
  20. #0 rst_n=0;
  21. #110 rst_n=1;
  22. end
  23. always #5 clk=~clk;
  24. reg [10:0] cnt;
  25. always@(posedge clk or negedge rst_n)
  26. begin
  27. if(!rst_n)begin cnt<=0; end
  28. else begin cnt<=cnt+1; end
  29. end
  30. assign data_in=data[cnt];
  31. reg [10:0] cnt1;
  32. always@(posedge clk or negedge rst_n)
  33. begin
  34. if(!rst_n)begin cnt1<=0; end
  35. else begin cnt1<=cnt1+1; end
  36. end
  37. assign sink_sop=(cnt1==1 && rst_n==1)?1:0;
  38. assign sink_eop=(cnt1==1024 && rst_n==1)?1:0;
  39. assign sink_valid=(cnt1>=1 && cnt1<=1024 && rst_n==1)?1:0;
  40. fft_demo u_test(
  41. .clk(clk),
  42. .rst_n(rst_n),
  43. .sink_valid(sink_valid),
  44. .sink_sop(sink_sop),
  45. .sink_eop(sink_eop),
  46. .data_in(data_in),
  47. .source_valid(source_valid),
  48. .source_sop(source_sop),
  49. .source_eop(source_eop),
  50. .source_error(source_error),
  51. .data_out(data_out)
  52. );
  53. integer vec_file1;
  54. initial
  55. begin
  56. wait(rst_n==1'b1);
  57. #10;
  58. vec_file1=$fopen("E:/Quartus-standard-17.1/Documents/fft/data_out.dat","w");
  59. forever
  60. begin
  61. @(posedge clk);
  62. #1;
  63. if(source_valid)
  64. $fwrite(vec_file1,"%d\n",data_out);
  65. end
  66. $fclose(vec_file1);
  67. end
  68. endmodule

6、RTL Simulation

打开Quartus中的RTL Simulation。

可以从仿真图上看到source_valid为1期间,data_out结果即为fft之后的频谱结果。可以看到结果和matlab生成的效果相差无几。


五、上板

将matlab中生成的信号数据导成mif文件,FPGA 使用ram ip核读取mif文件,再调用fft ip核计算频谱。

1、matlab生成正弦波信号并导出mif文件

  1. clc;
  2. clear;
  3. fs=800;
  4. f1=50;
  5. N=1024;
  6. x=0.5*sin(2*pi*f1/fs*(0:N-1));
  7. f_axis=[0:N-1]*fs/N;
  8. figure;
  9. plot(x); %原信号
  10. y=fft(x,1024);
  11. figure;
  12. plot(f_axis,abs(y)); %频谱
  13. % 将信号量化为12
  14. x_quantized = round(x * (2^11-1));
  15. % 生成mif文件
  16. fid = fopen('single_freq_signal.mif', 'w');
  17. fprintf(fid, 'DEPTH = 1024;\n');
  18. fprintf(fid, 'WIDTH = 12;\n');
  19. fprintf(fid, 'ADDRESS_RADIX = HEX;\n');
  20. fprintf(fid, 'DATA_RADIX = HEX;\n');
  21. fprintf(fid, 'CONTENT\n');
  22. fprintf(fid, 'BEGIN\n');
  23. for i = 0:1023
  24. fprintf(fid, '%03X : %03X;\n', i, x_quantized(i+1));
  25. end
  26. fprintf(fid, 'END;\n');
  27. fclose(fid);

2、ram ip核设置及相关模块

ram_ip例化(简单驱动)

  1. //顶层模块
  2. module ram_ip(
  3. input sys_clk, //系统时钟
  4. input sys_rst_n, //系统复位,低电平有效
  5. output [7:0] ram_rd_data
  6. );
  7. //wire define
  8. wire ram_wr_en ; //ram写使能
  9. wire ram_rd_en ; //ram读使能
  10. wire [9:0] ram_addr ; //ram读写地址
  11. wire [7:0] ram_wr_data ; //ram写数据
  12. //wire [7:0] ram_rd_data ; //ram读数据
  13. ram_rw ram_rw_inst( //例化底层模块
  14. .clk (sys_clk), //系统时钟
  15. .rst_n (sys_rst_n), //系统复位,低电平有效
  16. .ram_wr_en (ram_wr_en ), //ram写使能
  17. .ram_rd_en (ram_rd_en ), //ram读使能
  18. .ram_addr (ram_addr ), //ram读写地址
  19. .ram_wr_data (ram_wr_data), //ram写数据
  20. .ram_rd_data (ram_rd_data) //ram读数据
  21. );
  22. ram_config ram_config_inst ( //实例化底层IP核
  23. .address (ram_addr), //ram读写地址
  24. .inclock (sys_clk), //系统时钟
  25. .outclock (sys_clk),
  26. .data (ram_wr_data), //ram写数据
  27. .rden (ram_rd_en), //ram读使能
  28. .wren (ram_wr_en), //ram写使能
  29. .q (ram_rd_data) //ram读数据
  30. );
  31. endmodule

ram_rw(读写控制)

  1. //RAM读写驱动
  2. module ram_rw(
  3. input clk , //时钟信号
  4. input rst_n , //复位信号,低电平有效
  5. output ram_wr_en , //ram写使能
  6. output ram_rd_en , //ram读使能
  7. output reg [9:0] ram_addr , //ram读写地址
  8. output reg [7:0] ram_wr_data, //ram写数据
  9. output reg read_flag,
  10. input [7:0] ram_rd_data //ram读数据
  11. );
  12. reg [10:0] rw_cnt ; //读写控制计数器
  13. //rw_cnt计数范围在0~31,ram_wr_en为高电平;32~63时,ram_wr_en为低电平
  14. //assign ram_wr_en = ((rw_cnt >= 6'd0) && (rw_cnt <= 6'd31)) ? 1'b1 : 1'b0;
  15. assign ram_wr_en = 1'b0;
  16. //rw_cnt计数范围在32~63,ram_rd_en为高电平;0~31时,ram_rd_en为低电平
  17. assign ram_rd_en = ((rw_cnt >= 11'd0) && (rw_cnt <= 11'd1023)) ? 1'b1 : 1'b0; //assign赋值功能
  18. //读写控制计数器,计数器范围0~63
  19. always @(posedge clk or negedge rst_n) begin
  20. if(rst_n == 1'b0)
  21. rw_cnt <= 11'd0;
  22. else if(rw_cnt == 11'd1023) //计数到63清零
  23. begin
  24. rw_cnt <= 11'd0;
  25. read_flag<=1;
  26. end
  27. else //if(rw_cnt < 11'd1023)
  28. begin
  29. rw_cnt <= rw_cnt + 11'd1;
  30. read_flag<=0;
  31. end
  32. end
  33. //读写控制器计数范围:0~31 产生ram写使能信号和写数据信号
  34. always @(posedge clk or negedge rst_n) begin
  35. if(rst_n == 1'b0)
  36. ram_wr_data <= 8'd0;
  37. else if(rw_cnt >= 6'd0 && rw_cnt <= 6'd31)
  38. ram_wr_data <= ram_wr_data + 8'd1;
  39. else
  40. ram_wr_data <= 8'd0;
  41. end
  42. //读写地址信号 范围:0~31
  43. always @(posedge clk or negedge rst_n) begin
  44. if(rst_n == 1'b0)
  45. ram_addr <= 10'd0;
  46. else if(ram_addr == 10'd1023)
  47. ram_addr <= 10'd0;
  48. else
  49. ram_addr <= ram_addr + 1'b1;
  50. end
  51. endmodule

fft_ram(连接fft和ram)

  1. module fft_ram(
  2. input clk,
  3. input rst_n,
  4. output [15:0] data_out,
  5. output source_valid
  6. );
  7. wire [7:0] ram_rd_data;
  8. ram_ip ram_ip(
  9. .sys_clk(clk),
  10. .sys_rst_n(rst_n),
  11. .ram_rd_data(ram_rd_data)
  12. );
  13. fft_demo fft_demo(
  14. .clk(clk),
  15. .rst_n(rst_n),
  16. .data_in(ram_rd_data),
  17. .data_out(data_out),
  18. .source_valid(source_valid)
  19. );
  20. endmodule

3、fft ip核

ip核设置

  1. //File name:fft_demo
  2. //Complete date:23/03/24
  3. /
  4. module fft_demo(
  5. input wire clk,
  6. input wire rst_n,
  7. input signed [7:0] data_in,
  8. output signed [15:0] data_out,
  9. output wire source_valid
  10. );
  11. wire sink_valid;
  12. wire sink_sop;
  13. wire sink_eop;
  14. //wire source_valid;
  15. wire source_sop;
  16. wire source_eop;
  17. wire [1:0] source_error;
  18. wire sink_ready;
  19. wire [1:0] sink_error;
  20. wire signed [7:0] sink_imag;
  21. wire inverse;
  22. wire source_ready;
  23. //wire [1:0] source_error;
  24. wire signed [7:0] source_real;
  25. wire signed [7:0] source_imag;
  26. wire [5:0] source_exp;
  27. assign sink_error=2'b00;
  28. assign sink_imag=8'd0;
  29. assign inverse=1'b0;
  30. assign source_ready=1'b1;
  31. reg [10:0] cnt;
  32. always@(posedge clk or negedge rst_n)
  33. begin
  34. if(!rst_n)
  35. cnt<=0;
  36. else
  37. cnt<=cnt+1;
  38. end
  39. assign sink_sop=(cnt==1)?1:0;
  40. assign sink_eop=(cnt==1024)?1:0;
  41. assign sink_valid=(cnt>=1 && cnt<=1024)?1:0;
  42. fft u0 (
  43. .clk (clk), // clk.clk
  44. .reset_n (rst_n), // rst.reset_n
  45. .sink_valid (sink_valid), // sink.sink_valid
  46. .sink_ready (sink_ready), // .sink_ready
  47. .sink_error (sink_error), // .sink_error
  48. .sink_sop (sink_sop), // .sink_sop
  49. .sink_eop (sink_eop), // .sink_eop
  50. .sink_real (data_in), // .sink_real
  51. .sink_imag (sink_imag), // .sink_imag
  52. .inverse (inverse), // .inverse
  53. .source_valid (source_valid), // source.source_valid
  54. .source_ready (source_ready), // .source_ready
  55. .source_error (source_error), // .source_error
  56. .source_sop (source_sop), // .source_sop
  57. .source_eop (source_eop), // .source_eop
  58. .source_real (source_real), // .source_real
  59. .source_imag (source_imag), // .source_imag
  60. .source_exp (source_exp) // .source_exp
  61. );
  62. wire signed [15:0] dout_re,dout_im;
  63. assign dout_re=source_real*source_real;
  64. assign dout_im=source_imag*source_imag;
  65. assign data_out=dout_re+dout_im;
  66. endmodule

4、计算频谱下标

输出的fft结果只有单个单个的频点,但有时需要信号频率信息,即需要根据公式f=(k*fs)/N

  1. //通过横坐标算出对应频率,其实也就是找峰值
  2. module f_calculate(
  3. input clk,
  4. input rst_n,
  5. output reg [15:0] peak_value,
  6. output reg [9:0] peak_index,//未经过换算
  7. output reg [9:0] peak_index2,//经过换算
  8. output reg [9:0] index
  9. );
  10. reg [15:0] data_array [0:1023];
  11. reg [15:0] fs=16000;
  12. reg [15:0] threshold1=500;//设置上下阈值
  13. reg [15:0] threshold2=2000;
  14. wire [15:0] data_out;
  15. wire source_valid;
  16. fft_ram fft_ram_inst(
  17. .clk(clk),
  18. .rst_n(rst_n),
  19. .data_out(data_out),
  20. .source_valid(source_valid)
  21. );
  22. always@(posedge clk or negedge rst_n)
  23. begin
  24. if(!rst_n)
  25. begin
  26. index<=0;
  27. peak_value<=0;
  28. peak_index<=0;
  29. end
  30. else begin
  31. if(source_valid)
  32. begin
  33. data_array[index]<=data_out;
  34. index<=index+1;
  35. if(data_out>threshold1 && data_out<threshold2)
  36. begin
  37. peak_value<=data_out;
  38. peak_index<=index<512 ? index : 1024-index;
  39. peak_index2<=peak_index*fs/1024;
  40. end
  41. end
  42. end
  43. end
  44. endmodule

5、上板验证(signaltap)

可以看到能正确显示对应频率。


六、IP核破解

        如果使用的是高版本的Quartus需要先破解IP核才能调用FFT,不然在编译仿真时会在EDA Netlist Writer报错说没有相应的license。


参考文献及资料

1、丝滑仿真quartus fft ip_哔哩哔哩_bilibili   (这个up主的视频里有仿真部分的完整操作,保姆级,强烈推荐)

2、FPGA学习专题-FFT IP核的使用_quartus实现fft全流程-CSDN博客

3、这篇博客介绍了使用modelsim的一些常见问题和解决办法

modelsim遇到的问题_illegal base specifier in numeric constant-CSDN博客 


其他

以上就是完整使用fft ip核进行仿真和实际上板的过程。笔者学识尚浅,文章内容可能有些许纰漏。如有问题可指出,共同讨论学习。

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/笔触狂放9/article/detail/724149
推荐阅读
相关标签
  

闽ICP备14008679号