VIVADO中FFT核的使用(FPGA计算FFT和IFFT)

         关于这方面的内容,有些文章已经写的很好很详细了。不过我在使用的过程中,还是踩了一些坑,我在这里详细的介绍了IP核每一个设置的作用,然后写了个fft计算和ifft计算的环路的测试程序。应该可以帮大家学会使用fft的同时,也对它有个较为全面的理解。

FPGA计算FFT和MATLAB计算FFT

        利用FPGA计算FFT和MATLAB的结果是一样的,可以获得同样的实部和虚部,还可以获得相应的频率坐标,虽然由于字节有限长的影响,精度会差些,但可以设置32位,一般也够用了。

        下面是我用matlab和fpga分布做fft和ifft得到的一些结果,原始信号是一个正弦一个余弦的单频信号,时域如下图

        下面是分别利用matlab和fpga做fft的结果,可以看到形状是一致的,然后最大值的数值也基本一致。具体哪个点对应哪个频率可以通过,点数(m_axis_data_tuser)/N*fs来计算,和matlab是一致的,不过fpga中没有fftshift函数可以用。

        

对其做ifft,结果如下:

         这是我画这些图对应的matlab程序,大家后面做测试的时候可以直接用,fpga部分在讲完IP核后再给出。

N=128;    %FFT的点数
f1=30;
f2=70;
fs=600;
t=(1:N)/fs;
s=cos(2*pi*f2*t)+sin(2*pi*f1*t);
% s=sin(2*pi*f1*t);
data_fft_before=floor(s*100);
figure;plot(t,data_fft_before);
title('时域原始信号')
f=linspace(0,fs,N);
fft_s=fft(data_fft_before);

figure;plot(f,real(fft_s),'r');hold;plot(f,imag(fft_s),'b')
title('频域');
legend('实部','虚部');

figure;plot(f,abs(fft_s));
title('频域幅值');
ifft_s=ifft(fft_s);

absffts=real(ifft_s);
figure;plot(absffts)
title('反傅里叶变换还原的信号');

%下面是把数据转换为16位的2进制数,如果后面选择不同位宽,把这里的16改成相应位宽就行

fp=fopen('D:\Matlab2016\bin\fft\data_fft_before.txt','w');%这里改成你自己需要的文件路径
for i=1:N
    if(data_fft_before(i)>=0)
        temp=dec2bin(data_fft_before(i),16);  %这里的16是FPGA输入数据位宽
    else
        temp=dec2bin(data_fft_before(i)+2^16,16);
    end
    for j=1:16
        fprintf(fp,'%s',temp(j));
    end
    fprintf(fp,'\r\n');
end
fclose(fp);

VIVADO的FFT核详解

        如果不想看说明,可以直接按我图上的进行配置就行。

        ip核的调出,选FFT就行。

        下面这个是LTE(Long-Term Evolution)无线通信技术的标准而设计。该IP核是为了满足LTE系统中特殊的FFT大小(如LTE信道带宽所需的FFT点数),以及URE(资源元素)或子载波间隔等LTE参数优化的,一般不需要用这个。

 1.第一页设置

(1)Number of Channels:这个参数可以选择做FFT的通道数,也就是这个IP核同时对几组数据做FFT/IFFT。我在其他地方用的时候需要做两组FFT,然后把它们相乘后做IFFT,设置通道为2,这还方便我解决数据同步的问题。

(2)Transform Length:FFT的点数,一般输入的信号长度要和这个数值相同,不足的情况下,IP核会在末尾自动补零.

(3)Target Clock Frequency (目标时钟频率):按我的理解这是IP核的工作频率,它得和下面的数据吞吐量相适应。

(4)Target Data Throughput (目标数据吞吐量):这个参数指的是FFT IP核期望处理的数据吞吐量,表明IP核应该能够每秒处理多少样本数,从而保证FFT变换的输出与输入能够按照一定速率进行,以匹配系统其他部分的处理速率,如ADC(Digital-to-Analog Converter)数据捕捉率或后续数据处理模块。(3)和(4)是给自动选择架构时计算时延做参考的,IP核不能保证达到相应的吞吐率。

(5)Architecture Choice(FPAG架构的选择):从上面到下面的架构,速度依次降低,复杂度也依次降低。选好后可以在左上角的选项卡里看到一次FFT的计算时间。

(6)Run time configurable Transform length(FPGA长度实时改变):选这个意味着可以在运行过程中改变FFT的计算长度。例如,你可能有一个无线通信系统,在其中需要针对不同的信道带宽进行FFT操作,这会要求FFT变换的长度根据情况进行调整。若FFT IP核支持运行时配置,那么可以在软件控制下更改其变换长度,如从1024点切换到2048点或其他任何支持的长度。

2.第二页设置

(1)Data Format:选择输入输出数据采样是采用定点格式(Fixed point),还是采用IEEE-754单精度(Floating point)(32位)浮点格式。当内核处于多通道配置中时,浮点格式不可用。

下面两个选项是用来解决FPGA计算的有限字符问题,指数字系统中数值的表示、操作和运算由于位宽限制导致的问题。由于FPGA中的资源有限,对于数字电路设计而言,我们不能使用无限位宽来表示一个数,而是需要根据应用需求和资源限制使用固定的位数。

(2)Scaling Opitons:

       Unscaled: 所有的整数位增长都携带到输出中,会使用更多的FPGA资源。这里我踩过一个坑:因为我的测试程序是把fft得到的数据直接给到ifft的输入端,因为选择了这个选项,数据位扩展了,所以输入的数据有问题,但这种问题vivado是不会报错的。

        Scaled:用户定义的缩放计划决定数据如何在FFT阶段之间缩放,通过寄存机s_axis_config_data进行配置。

        Block Floating-Point:内核确定需要多少缩放才能充分利用可用的动态范围,并将缩放因子报告为块指数。

(3)Rounding Model(一般如果你使用的位宽足够时也不用纠结这个)

        截断(Truncation):这是最简单的舍入模式,直接舍弃掉所有多余的LSBs,不添加任何额外的逻辑来处理这些位。这意味着数据始终向下舍入至最接近的值,可能会导致系统性偏差或误差累积,特别是在处理大量数据时这种误差的效果可能会被放大。

        收敛舍入(Convergent Rounding):收敛舍入也被称之为向偶数舍入(round-to-even)或银行家舍入法(Bankers Rounding)。这种舍入方法相对公平,并且无偏。当一个数字的小数部分正好等于0.5(即一半)时,根据数字的奇偶性来决定是向上还是向下舍入。如果该数字是奇数,那么结果将向上舍入到下一个偶数;如果是偶数,则舍入到最近的偶数。这种方法可以消除随机舍入误差累积的偏差,更平均地分布上舍和下舍的情况,并且在统计上是无偏的。

(4) Precision Options:输入数据和相位因子可以独立配置宽度从8到34位,包括。当数据格式为浮点时,输入数据宽度固定为32位,相位因子宽度可设置为24或25位,具体取决于所需的噪声性能和可用资源。

(5)Control Signals:时钟使能(aclken)和同步清除(aresetn)是可选引脚。如果两者都被选中,同步清除将覆盖时钟启用。如果不选择某个选项,则可以节省一些逻辑资源,并且可以实现更高的时钟频率。

(6)Output Ordering:这个决定数据是按什么顺序输出的,一般选natural order就行。

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

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

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

(8)Throttle Schemes: 在性能和数据定时需求之间进行权衡。实时模式通常提供更小、更快的设计,但对必须提供和使用数据的时间有严格的限制。非实时模式没有这样的限制,但设计可能更大更慢。

3.第三页设置

这一页是关于数据存储的设置,一般默认就行。

IP核实例化

        IP核使用的是AXI协议,它的主要特点是不管是发配置还是数据都会有个握手的过程,通过valid和ready这两个信号,其中发送方控制valid信号,接收方控制ready信号,这两个信号同时拉高时,也就是双方都准备好了,传输的内容才有效。

        实例化之前,先对IP核使用的通信协议的引脚进行介绍 ,主要是配置通道,数据输入通道,数据输出通道

.aclk(aclk),   输入时钟                                             
.aresetn(aresetn),   复位 

配置通道

Input[N:0]:  s_axis_config_tdata:

一般用到最低位控制。1fft   0ifft。如果我们只需要FFT就直接设置N'b1就ok。前面几位的配置可以去参考手册,是关于缩放和循环前缀的配置。

Input:  s_axis_config_tvalid:配置数据有效。这是是我们给IP核输入配置数据,没有特殊要求一直拉高就行拉高两个时钟周期之后,将端口s_axis_data_tvalid和s_axis_data_tready拉高。 
Output:  s_axis_config_tready:可以接收配置数据了。复位两个时钟周期后,该口给1输出;若干个时钟周期后,自动归零。没有特殊需要直接出去wire就好。

数据输入通道

Input:  s_axis_data_tvalid:输入数据有效。当IP核准备好接收数据,也就是s_axis_data_tready高电平后,将s_axis_data_tvalid拉高N个周期,输入N个数据进行fft;N是FFT的点数。

Output:   s_axis_data_tready:复位两个时钟周期后,该引脚;此时ip核初始化完成,可进行数据输入。 我们可以通过这个信号来拉高valid信号。

Input[M:0]:  s_axis_data_tdata:将数据输入进行FFT运算。高一半的位是虚部,低一半的位是实部。输入实数的时候可以把虚部全部置零,注意这里输入的是有符合的二进制数。

Input:  s_axis_data_tlast:输入最后一个数据时拉高,停止数据输入。TLAST是起到一个输入数据末端指示作用。但根据我的测试,就算不配置这个引脚也不影响fft的计算,看手册上说它会影响后面的event信号,也都是一些指示信号。
 

数据输出通道

Output[M1:0]:m_axis_data_tdata:高位为虚部,低位为实部。想看幅值的话,在FPGA里不好开方,我们一般看的是幅值的平方,也就是功率谱。

Output:  m_axis_data_tvalid:当ip核计算完以后会拉高,输出N个点的数据后拉低。

Output[M2:0]:m_axis_data_tuser:输出值*fs/L为对应频点。这里和matlab是一样的,第一个点也是直流的值。

Input:    m_axis_data_tready:这是告诉ip核我们准备好接收数据了,我们当然时刻准备着,一直拉高就行。

还有一些引脚相对来说没那么重要,这里就不一一介绍了。

测试程序如下,结合上面的matlab程序是可以直接用的。

`timescale 1ns / 1ps
//
// Company: 
// Engineer: 
// 
// Create Date: 2023/12/26 14:41:06
// Design Name: 
// Module Name: fft_test
// Project Name: 
// Target Devices: 
// Tool Versions: 
// Description: 
// 
// Dependencies: 
// 
// Revision:
// Revision 0.01 - File Created
// Additional Comments:
// 
//


module fft_test(

    );
    reg clk,rstn;
    reg signed [15:0]TIME_DATA[127:0];                            //存放输入数据
    wire fft_s_config_tready;

    reg signed[31:0]fft_s_data_tdata;
    reg fft_s_data_tvalid;
    wire fft_s_data_tready;
    reg fft_s_data_tlast;
    
    wire signed[31:0]fft_m_data_tdata;
    wire signed[31:0]ifft_m_data_tdata;
    wire signed[7:0]fft_m_data_tuser;
    wire fft_m_data_tvalid;
    reg fft_m_data_tready;
    wire fft_m_data_tlast;
    
    reg[10:0]  count;
    reg signed [15:0]fft_i_out;
    reg signed [15:0]fft_r_out;
    reg signed [15:0]ifft_i_out;
    reg signed [15:0]ifft_r_out;
    reg signed [31:0]fft_abs;
    reg signed [31:0]ifft_abs;


    initial begin
         clk=1'b1;
         rstn=1'b0;
         fft_m_data_tready=1'b1;
         #100;
         rstn<=1'b1;  
         $readmemb("D:/Matlab2016/bin/fft/data_fft_before.txt",TIME_DATA);

    //这里注意要改成matlab生成数据时的路径,而且注意不是\,而是/。
    end
    
    always #5 clk=~clk;

    
 //循环给fft核输入128位数据
    always @(posedge clk or negedge rstn)begin
    if(!rstn)begin
        fft_s_data_tvalid<=1'b0;
        count<=0;
       fft_s_data_tlast<=1'b0;
    end
    else if(fft_s_data_tready )begin
        if(count<8'd128)
        begin
            fft_s_data_tvalid<=1;
            fft_s_data_tdata<={16'b0,TIME_DATA[count]};      //这里虚部直接给的0
            count<=count+1;
         fft_s_data_tlast<=1'b0;

        end
        else begin
            fft_s_data_tvalid<=0;
            fft_s_data_tlast<=1'b1;
            #3000;                                          
            count<=0;
        end    
    end
    else begin
        fft_s_data_tvalid<=0;
    end
    end
    
    //高位赋值给虚部,低位赋值给实部
    always @(posedge clk)begin
        if(fft_m_data_tvalid)
        begin
            fft_i_out=fft_m_data_tdata[31:16];
            fft_r_out=fft_m_data_tdata[15:0];
        end
    end
    
   
    //求幅值,这里的值是没有开方的
    always @(posedge clk)begin
        fft_abs<=$signed(fft_i_out)*$signed(fft_i_out)+$signed(fft_r_out)*$signed(fft_r_out);
    end
   
    //实例化fft核
    xfft_0 fft0(
      .aclk                        (clk), 
      .aresetn                     (rstn),
      .s_axis_config_tdata         (8'b1),
      .s_axis_config_tvalid        (1'b1),
      .s_axis_config_tready        (fft_s_config_tready),
      
      .s_axis_data_tdata           (fft_s_data_tdata),
      .s_axis_data_tvalid          (fft_s_data_tvalid),
      .s_axis_data_tready          (fft_s_data_tready),
      .s_axis_data_tlast           (fft_s_data_tlast),
      
      .m_axis_data_tdata           (fft_m_data_tdata),
      .m_axis_data_tuser           (fft_m_data_tuser),
      .m_axis_data_tvalid          (fft_m_data_tvalid),
      .m_axis_data_tready          (fft_m_data_tready),
      .m_axis_data_tlast           (fft_m_data_tlast)
    );


    wire signed[31:0] ifft_s_data_tdata;
    assign ifft_s_data_tdata=fft_m_data_tdata;
    wire ifft_s_data_tready;
    wire ifft_s_data_tvalid;
    assign ifft_s_data_tvalid=fft_m_data_tvalid;
    wire ifft_s_data_tlast;
    assign ifft_s_data_tlast=fft_m_data_tlast;
    wire signed[7:0]ifft_m_data_tuser;
    wire ifft_m_data_tlast;
    wire ifft_m_data_tvalid;
    wire ifft_s_config_tready;
    
    //实例化ifft核,把fft核输出的数据直接作为它的输入
    xfft_0 ifft0(
      .aclk                        (clk), 
      .aresetn                     (rstn),
      .s_axis_config_tdata         (8'b0),  //因为是做iit,所以这里配置为0
      .s_axis_config_tvalid        (1'b1),  
      .s_axis_config_tready        (ifft_s_config_tready),
       .s_axis_data_tdata          (ifft_s_data_tdata),
      .s_axis_data_tvalid          (ifft_s_data_tvalid),
      .s_axis_data_tready          (ifft_s_data_tready),
      .s_axis_data_tlast           (ifft_s_data_tlast),
      
      .m_axis_data_tdata           (ifft_m_data_tdata),
      .m_axis_data_tuser           (ifft_m_data_tuser),
      .m_axis_data_tvalid          (ifft_m_data_tvalid),
      .m_axis_data_tready          (fft_m_data_tready),
      .m_axis_data_tlast           (ifft_m_data_tlast)
      
    );
    
    
     always @(posedge clk)begin
        if(ifft_m_data_tvalid)
        begin
            ifft_i_out=ifft_m_data_tdata[31:16];
            ifft_r_out=ifft_m_data_tdata[15:0];
        end
    end
    
    always @(posedge clk)begin
        ifft_abs<=$signed(ifft_i_out)*$signed(ifft_i_out)+$signed(ifft_r_out)*$signed(ifft_r_out);
    end
    
endmodule
 

写在最后

        写这篇文章还是花了挺多的时间的,希望能对大家有所帮助,有什么问题欢迎一起讨论~

奥利给

        

        

  • 84
    点赞
  • 140
    收藏
    觉得还不错? 一键收藏
  • 24
    评论
评论 24
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值