当前位置:   article > 正文

vivado之FFT ip核的入门学习(已补充调用模块)_vivado fft ip核

vivado fft ip核

一、什么是FFT

1.1      简介与我的理解

        FFT是离散傅立叶变换的快速算法,可以将一个信号变换到频域。有些信号在时域上是很难看出什么特征的,但是如果变换到频域之后,就很容易看出特征了。这就是很多信号分析采用FFT变换的原因。另外,FFT可以将一个信号的频谱提取出来,这在频谱分析方面也是经常用的。

        下面说说具体物理意义。一个模拟信号,经过ADC采样之后,就变成了数字信号。采样定理告诉我们,采样频率要大于信号频率的两倍,这些我就不在此罗嗦了。采样得到的数字信号,就可以做FFT变换了。N个采样点,经过FFT之后,就可以得到N个点的FFT结果。为了方便进行FFT运算,通常N取2的整数次方。假设采样频率为Fs,信号频率F,采样点数为N。那么FFT之后结果就是一个为N点的复数。每一个点就对应着一个频率点。这个点的模值,就是该频率值下的幅度特性。

        假设FFT之后某点n用复数a+bi表示,那么这个复数的模就是An=根号a*a+b*b,相位就是Pn=atan2(b,a)。根据以上的结果,就可以计算出n点(n≠1,且n<=N/2)对应的信号的表达式为:An/(N/2)*cos(2*pi*Fn*t+Pn),即2*An/N*cos(2*pi*Fn*t+Pn)。对于n=1点的信号,是直流分量,幅度即为A1/N。    由于FFT结果的对称性,通常我们只使用前半部分的结果,即小于采样频率一半的结果。atan2(a,b)返回以弧度表示的 y/x 的反正切.相位的计算可用函数atan2(b,a)计算。atan2(b,a)是求坐标为(a,b)点的角度值,范围从-pi到pi。

1.2        实际例子介绍(网络照搬)

        以一个实际信号为例,求取其FFT。

信号为:S=2+3*cos(2*pi*50*t-pi*30/180)+1.5*cos(2*pi*75*t+pi*90/180)

        式中cos参数为弧度,所以-30度和90度要分别换算成弧度。

        我们以256Hz的采样率对这个信号进行采样,总共采样256点。按照我们上面的分析,Fn=(n-1)*Fs/N,我们可以知道,每两个点之间的间距就是1Hz,第n个点的频率就是n-1

        我们的信号有3个频率:0Hz(直流2v)、50Hz、75Hz,应该分别在第1个点、第51个点、第76个点上出现峰值,其它各点应该接近0

1点: 512+0i

2点: -2.6195E-14 - 1.4162E-13i
3点: -2.8586E-14 - 1.1898E-13i

50点:-6.2076E-13 - 2.1713E-12i
51点:332.55 - 192i
52点:-1.6707E-12 - 1.5241E-12i

75点:-2.2199E-13 -1.0076E-12i
76点:3.4315E-12 + 192i
77点:-3.0263E-14 +7.5609E-13i

         很明显,1点、51点、76点的值都比较大,它附近的点值都很小,可以认为是0,即在那些频率点上的信号幅度为0。接着,我们来计算各点的幅度值。分别计算这三个点的模值,结果如下:
1点  :  512
51点:384
76点:192

          按照公式,可以计算出直流分量为:512/N=512/256=2(第一个点是除以N其他的除2);50Hz信号的幅度为:384/(N/2)=384/(256/2)=3;75Hz信号的幅度为192/(N/2)=192/(256/2)=1.5。可见,从频谱分析出来的幅度是正确的。

         然后再来计算相位信息。直流信号没有相位可言,不用管它。先计算50Hz信号的相位,atan2(-192, 332.55)=-0.5236,结果是弧度,换算为角度就是180*-0.5236)/pi=-30.0001。再计算75Hz信号的相位,atan2(192, 3.4315E-12)=1.5708弧度,换算成角度就是180*1.5708/pi=90.0002。可见,相位也是对的。根据FFT结果以及上面的分析计算,我们就可以写出信号的表达式了,它就是我们开始提供的信号。

附上MATLAB仿真代码

  1. close all; %先关闭所有图片
  2. Adc=2; %直流分量幅度
  3. A1=3; %频率F1信号的幅度
  4. A2=1.5; %频率F2信号的幅度
  5. F1=50; %信号1频率(Hz)
  6. F2=75; %信号2频率(Hz)
  7. Fs=256; %采样频率(Hz)
  8. P1=-30; %信号1相位(度)
  9. P2=90; %信号相位(度)
  10. N=256; %采样点数
  11. t=(0:1/Fs:N/Fs); %采样时刻
  12. %信号
  13. S=Adc+A1*cos(2*pi*F1*t+pi*P1/180)+A2*cos(2*pi*F2*t+pi*P2/180);
  14. %显示原始信号
  15. plot(S);
  16. title('原始信号');
  17. figure;
  18. Y = fft(S,N); %做FFT变换
  19. Ayy = (abs(Y)); %取模
  20. plot(Ayy(1:N)); %显示原始的FFT模值结果
  21. title('FFT 模值');
  22. figure;
  23. Ayy=Ayy/(N/2); %换算成实际的幅度
  24. Ayy(1)=Ayy(1)/2;
  25. F=([1:N]-1)*Fs/N; %换算成实际的频率值
  26. plot(F(1:N/2),Ayy(1:N/2)); %显示换算后的FFT模值结果
  27. title('幅度-频率曲线图');

1.3总结

        假设采样频率为Fs,采样点数为N,做FFT之后,某一点n(n从1开始)表示的频率为:Fn=(n-1)*Fs/N;该点的模值除以N/2就是对应该频率下的信号的幅度(对于直流信号是除以N);该点的相位即是对应该频率下的信号的相位。相位的计算可用函数atan2(b,a)计算。atan2(b,a)是求坐标为(a,b)点的角度值,范围从-pi到pi。要精确到xHz,则需要采样长度为1/x秒的信号,并做FFT。要提高频率分辨率,就需要增加采样点数,这在一些实际的应用中是不现实的,需要在较短的时间内完成分析。解决这个问题的方法有频率细分法,比较简单的方法是采样比较短时间的信号,然后在后面补充一定数量的0,使其长度达到需要的点数,再做FFT,这在一定程度上能够提高频率分辨力。

二、VIVADO之FFT   ip核使用

         fft的ip核使用对新手来说显得十分复杂。这里直接从ip核设置每个地方开始介绍。

2.1 调出ip核设置

 

接下来是具体参数介绍

 (1)number  of  channels :变换通道,可以选择多通道,实现多帧数据同时进行FFT运算

 (2)transform  lenfgth   : FFT变换长度,如果选择了最下面的‘run time configurable transdorm legth’,则该参数是FFT变化的最大长度,一般不选。

 (3)采样时钟核数据时钟:这个设置越大速度越快,且可以fft测得clk/2大小的频率,如100mhz时钟可以算出50M以内得频率。

 (4)architecure  choice:FFT架构选择,消耗资源依次递减。第一个是自动选择。选完架构可以从左边框中的latency(隐藏起来了)看一次FFT需要的时间,即求完这N个点(FFT变换长度)的时间。资源消耗越高耗时越短,根据工程需求选择。一般选中间的。

 

 (5)data format:1. 定点全精度  2. 定点缩减位宽    一般就默认Fixed Point

   (6)   scaling optios :缩放选项 :

            1、 block floating point :不管输入的格式如何,FFT变化内部都采用浮点,会根据每一级的的数据情况自动缩放。 这个模式的输入输出位宽一致,便于调用没有特殊需求就用这个

            2、scaled :在m_axis_data_tuser中会有5BIT表示每一级的缩放情况,在s_axis_config_data中会有相应的字段配置配置缩放因子.每一级别包含2个stage ,2个bit 表示一级缩放,一般0-3可选,如果log(NFFT)不是2的倍数,则最高一级的缩放只能在0-1之间选取。 

           3、unscaled :不用担心变化过程中会出现溢出,但是输出位宽会非常大,一般情况为了后续的信号处理,不建议用。


   (7)  Aresten : 复位信号要勾选,至少保持两个时钟的低电平。FFT IP核的复位引脚(ARESETn)最好使用,怎么使用?–把时钟IP核输出的locked引脚赋给FFT的复位引脚,并且在locked引脚为低时给FFT IP核一系列输入引脚均赋予初值。时钟IP核locked引脚代表意义:时钟IP核输出的时钟是否稳定有效,低时不稳定,高时表明时钟IP核输出时钟稳定


  (8)  output odering options:默认是倒序但是我们一般选自然输出

(9)optional output fileds :选项输出字段:

          1、xk_index:FFT 变幻的结果索引,在m_axis_data_user中有相应的字段。

          2、OVFLO是变换中溢出的指示信号,对应event_fft_overflow.
 

其他的一般不用设置。

    我们可以看到假设我们输入的数据位宽是10,则FFT之后,还有虚数部分,[9:0]是实数,[25:16]是虚数。一共有2048个点,一次FFT。

2.2  IP核参数的意义

        

 这里面引脚很多,最重要的当然就是输入输出了。

.aclk(aclk),   输入时钟                                             
.aresetn(aresetn),   复位 ,接PLL的locked

Input[N:0]:  s_axis_config_tdata:控制输入模式,最低位控制。1fft   0ifft。如果我们只需要FFT就直接设置N'b1就ok,这里在配置scale模式的时候需要使用,之后再专门介绍。

Input:          s_axis_config_tvalid: 设置为1就行

Output:       s_axis_config_tready:不接不管他,总之如果你的IP核没启动这个就为0.

Output:       s_axis_data_tready:aresetn拉高两个时钟周期后,该口输出1;此时ip核初始化完成,可进行数据输入。 

Input:          s_axis_data_tvalid数据有效  当s_axis_data_tready高电平后,将s_axis_data_tvalid拉高L个周期,输入L个数据进行fft;L是FFT的点数。

Input[M:0]:  s_axis_data_tdata:看配置界面会提示如下图低16就是实部高16虚部,我们ADC的数据是实信号放实部就好,新手注意这里必须是有符号数。

Input:          s_axis_data_tlast:输入L个数据后拉高,停止数据输入。TLAST是起到一个输入数据末端指示作用,比如你作8点FFT运算,你要给8个数据给IP核吧,在最后一个数据期间你把TLAST拉高就行了,意思是告诉IP核,这一组的8个数据都给你带过来了,这是最后一个。IP核一验算,整整齐齐8个,就不会找你麻烦了。

做fft需要耗费的时钟周期计算如下s_axis_data_tlast- s_axis_data_tvalid

Output[M1:0]:m_axis_data_tdata:高位为虚部,低位为实部。  在FPGA里不好开方所以我们一般求他的功率谱即实部和虚部平方相加。

Output:          m_axis_data_tvalid:当fft结果输出时拉高,输出2最后一个数据时拉低,这个是对齐最后一个数据的。

Output[M2:0]:m_axis_data_tuser:这里的OVFLO是溢出指示位,这里的XK_INDEX就是索引了,是对齐谱线的。XK_INDEX*fs/N=频率(fs就是你的采样率,N就是FFT点数)

Input:             m_axis_data_tready:需保持高电平,保证FFT单元处在计算模式,并且能够输出结算结果;接   1 .m_axis_data_tready(1)

Output:          m_axis_data_tlast:当fft结果输出到最后一个结果时拉高,紧接着下一个时钟就拉低。

主要就是时钟、复位、配置、数据、和事件报告。s是从机,m是主机,也好理解,对于FPGA来说是要接受主句的输出来作为输入数据,去FFT。

转自 数字信号处理(三):Xilinx FFT IP核详解(二) - 知乎 (zhihu.com)

20230109补充代码和解释

本代码是把FFTIP例化在模块内方便调用。

要注意的是,我输入datach1是无符号补码,都是正数。在送给fft ip时,实际上要补上一个符号位0,s_axis_data_tdata <= {22'd0,input_data_ch1};这里面就是我补了0,实际使用根据你的输入来自己设置。总之,ip输入带符号。而输出肯定也是带符号的。用ILA看的时候设置为有符号数看。

我下面还例化了别的IP,自行去除。本模块只需要设置好你的输入,即可跑出fft 后的实部和虚部。

  1. module fft_fix_4096(
  2. input aclk ,
  3. input aresetn ,
  4. input [9:0] input_data_ch1 , //adc数据输入
  5. output signed [15:0] fft_real ,
  6. output signed [15:0] fft_imag ,
  7. output signed [32:0] amp , //输出平方和,可以不要
  8. output reg [11:0] freq , //由于multisine fs/N=1,谱线间隔为1HZ
  9. output fft_out_valid , //fft输出有效。可以通过这个接口取4096个值。
  10. output [23:0] abs_fft , //开方
  11. output abs_tvalid //开方数据输出有效
  12. );
  13. //配置数据
  14. reg [10:0] input_data_ch1_reg; //和ADC输出格式有关,总之ip core 需要带符号位
  15. wire [7:0] s_axis_config_tdata;
  16. reg s_axis_config_tvalid;
  17. //输入数据
  18. wire s_axis_data_tready;
  19. reg [31:0] s_axis_data_tdata;
  20. reg s_axis_data_tvalid;
  21. reg s_axis_data_tlast;
  22. //输出数据
  23. wire [31:0] m_axis_data_tdata;
  24. wire [23:0] m_axis_data_tuser;
  25. wire m_axis_data_tvalid;
  26. wire m_axis_data_tlast;
  27. reg [7:0] cfg_cnt;
  28. reg [13:0] sink_ctl_cnt;
  29. reg [15:0] fft_real,fft_imag;
  30. reg fft_out_valid;
  31. wire [7:0] m_axis_status_tdata;
  32. wire m_axis_status_tvalid;
  33. wire event_frame_started;
  34. wire event_tlast_unexpected;
  35. wire event_tlast_missing;
  36. wire event_data_in_channel_halt;
  37. wire s_axis_config_tready;
  38. xfft_0 fixed_fft (
  39. .aclk(aclk), // input wire aclk
  40. .aresetn(aresetn), // input wire aresetn
  41. .s_axis_config_tdata(s_axis_config_tdata), // input wire [7 : 0] s_axis_config_tdata
  42. .s_axis_config_tvalid(s_axis_config_tvalid), // input wire s_axis_config_tvalid
  43. .s_axis_config_tready(s_axis_config_tready), // output wire s_axis_config_tready
  44. .s_axis_data_tdata(s_axis_data_tdata), // input wire [31 : 0] s_axis_data_tdata
  45. .s_axis_data_tvalid(s_axis_data_tvalid), // input wire s_axis_data_tvalid
  46. .s_axis_data_tready(s_axis_data_tready), // output wire s_axis_data_tready
  47. .s_axis_data_tlast(s_axis_data_tlast), // input wire s_axis_data_tlast
  48. .m_axis_data_tdata(m_axis_data_tdata), // output wire [31 : 0] m_axis_data_tdata
  49. .m_axis_data_tuser(m_axis_data_tuser), // output wire [23 : 0] m_axis_data_tuser
  50. .m_axis_data_tvalid(m_axis_data_tvalid), // output wire m_axis_data_tvalid
  51. .m_axis_data_tlast(m_axis_data_tlast), // output wire m_axis_data_tlast
  52. .m_axis_status_tdata(m_axis_status_tdata), // output wire [7 : 0] m_axis_status_tdata
  53. .m_axis_status_tvalid(m_axis_status_tvalid), // output wire m_axis_status_tvalid
  54. .event_frame_started(event_frame_started), // output wire event_frame_started
  55. .event_tlast_unexpected(event_tlast_unexpected), // output wire event_tlast_unexpected
  56. .event_tlast_missing(event_tlast_missing), // output wire event_tlast_missing
  57. .event_data_in_channel_halt(event_data_in_channel_halt) // output wire event_data_in_channel_halt
  58. );
  59. //fft core config
  60. always@(posedge aclk or negedge aresetn)
  61. begin
  62. if(!aresetn)
  63. cfg_cnt <= 8'd0;
  64. else
  65. begin
  66. if(cfg_cnt < 8'd200)
  67. cfg_cnt <= cfg_cnt + 1'b1;
  68. else
  69. cfg_cnt <= cfg_cnt;
  70. end
  71. end
  72. always@(posedge aclk or negedge aresetn)
  73. begin
  74. if(!aresetn)
  75. s_axis_config_tvalid <= 1'b0;
  76. else
  77. begin
  78. if(cfg_cnt < 8'd200)
  79. s_axis_config_tvalid <= 1'b1;
  80. else
  81. s_axis_config_tvalid <= 1'b0;
  82. end
  83. end
  84. assign s_axis_config_tdata = 8'd1;
  85. //fft sink_in ctl//
  86. always@(posedge aclk or negedge aresetn)
  87. begin
  88. if(!aresetn)
  89. s_axis_data_tdata <= 32'b0;
  90. else
  91. s_axis_data_tdata <= {22'd0,input_data_ch1}; //本人adc 10位无符号补码,补一位符号位0,ip设置11位输入
  92. end
  93. always@(posedge aclk or negedge aresetn)
  94. begin
  95. if(!aresetn)
  96. sink_ctl_cnt <= 14'd8194;
  97. else if(s_axis_config_tvalid)
  98. sink_ctl_cnt <= 14'd0;
  99. else if(sink_ctl_cnt == 14'd8192)
  100. sink_ctl_cnt <= 14'd1;
  101. else
  102. sink_ctl_cnt <= sink_ctl_cnt + 1'b1;
  103. end
  104. //s_axis_data_tvalid
  105. always@(posedge aclk or negedge aresetn)
  106. begin
  107. if(!aresetn)
  108. s_axis_data_tvalid <= 1'b0;
  109. else if(sink_ctl_cnt < 14'd1)
  110. s_axis_data_tvalid <= 1'b0;
  111. else if(sink_ctl_cnt < 14'd4097)
  112. s_axis_data_tvalid <= 1'b1;
  113. else
  114. s_axis_data_tvalid <= 1'b0;
  115. end
  116. //s_axis_data_tlast
  117. always@(posedge aclk or negedge aresetn)
  118. begin
  119. if(!aresetn)
  120. s_axis_data_tlast <= 1'b0;
  121. else
  122. begin
  123. if(sink_ctl_cnt == 14'd4096)
  124. s_axis_data_tlast <= 1'b1;
  125. else
  126. s_axis_data_tlast <= 1'b0;
  127. end
  128. end
  129. //fft sink_in ctl/ / 对齐
  130. always@(posedge aclk)
  131. begin
  132. fft_real <= m_axis_data_tdata[15:0];
  133. end
  134. always@(posedge aclk)
  135. begin
  136. fft_imag <= m_axis_data_tdata[31:16];
  137. end
  138. always@(posedge aclk)
  139. begin
  140. fft_out_valid <= m_axis_data_tvalid;
  141. end
  142. always@(posedge aclk)
  143. begin
  144. freq <= m_axis_data_tuser[11:0]; //这个位宽取n-1个,2^n=N,由于MATLAB里FS=N,所以谱线间隔是1HZ
  145. end
  146. /********** 计算频谱的幅值信号 **********/
  147. wire [31:0] xkre_square, xkim_square;
  148. mult_gen_0 inst1 (
  149. .CLK(aclk), // input wire CLK
  150. .A(fft_real), // input wire [15 : 0] A
  151. .B(fft_real), // input wire [15 : 0] B
  152. .P(xkre_square) // output wire [31 : 0] P
  153. );
  154. mult_gen_0 inst2 (
  155. .CLK(aclk), // input wire CLK
  156. .A(fft_imag), // input wire [15 : 0] A
  157. .B(fft_imag), // input wire [15 : 0] B
  158. .P(xkim_square) // output wire [31 : 0] P
  159. );
  160. assign amp = xkre_square+ xkim_square;
  161. wire abs_tvalid;
  162. cordic_0 your_instance_name (
  163. .aclk(aclk), // input wire aclk
  164. .aresetn(aresetn), // input wire aresetn
  165. .s_axis_cartesian_tvalid(m_axis_data_tvalid), // input wire s_axis_cartesian_tvalid
  166. .s_axis_cartesian_tdata(amp[31:0]), // input wire [31 : 0] s_axis_cartesian_tdata
  167. .m_axis_dout_tvalid(abs_tvalid), // output wire m_axis_dout_tvalid
  168. .m_axis_dout_tdata(abs_fft) // output wire [23 : 0] m_axis_dout_tdata
  169. );
  170. endmodule

注意事项补充:

1.始终均可拉高的引脚:S_AXIS_CONFIG_TVALID,S_AXIS_DATA_TREADY。

2.我们要注意采样频率和FFT采样点数,这两个关系到分辨率,而且采样频率还得满足奈奎斯特定理。FFT采样点数越大越好,即分辨率会高。但是太大了运算时间就会长,分辨率分辨的就是频率,第m个点对应频率m*FS/N。

3.当FFT计算结果输出完成后,信号fft_m_data_tlast变为高电平,代表数据输出结束,并在延时一小段时间后,fft_s_data_tready重新变为低电平,代表IP核重新进入到空闲状态。可以进行对IP核下一组数据的输入。

4.输入输出如果不到8bit会补上,例如12bit的实数输入,输出会有32位,[11:0]是实数部分[25:16]是虚数,实数虚数前各补4个0,输出也是一样的

2024.03.22补充:

这个地方有坑。一般我们只希望我们拉高多少个电平时读多少数据来FFT,在有一次设计中,我把FIFO空信号设置为读使能来将数据读到FFTip中,但是发现不对!!!我以为是FFT ip只支持连续送数据进去,结果是我不小心设置成实时的了,而空信号是断断续续的………………我超服了。

过几天把缩放的介绍一下再把多通道的介绍一下。

声明:本文内容由网友自发贡献,转载请注明出处:【wpsshop博客】
推荐阅读
相关标签
  

闽ICP备14008679号