vivado FFT IP Core

news2024/9/20 10:57:35

文章目录

  • 前言
  • FFT IP 接口介绍
    • 接口简介
    • tdata 格式说明
  • 其他细节
    • 关于计算精度及缩放系数
    • 计算溢出
    • 架构选择
    • 数据顺序
    • 实时/非实时模式
    • 数据输入输出时序
    • 关于配置信息的应用时间节点
  • FFT IP 例化介绍
  • 控制代码实现 & 测试
  • 速度测试
  • 参考文献

前言

  由于计算资源受限,准备将上位机 FFT 的计算移动到 FPGA 侧,通过 FFT IP 进行并行计算加速。本文对 Xilinx FFT IP Core 进行介绍。

FFT IP 接口介绍

接口简介

  Fast Fourier Transform IP 核,接口如下

在这里插入图片描述

在这里插入图片描述

采用 AXI4-Stream 协议(与标准的 AXI4 协议略有不同),包含 S_AXI_CONFIG,S_AXI_DATA,M_AXI_DATA,M_AXI_STATUS 四个通道,每个通道均采用 valid/ready 握手机制。

在这里插入图片描述

  • S_AXI_CONFIG

  配置通道,可以对工作模式(FFT/IFFT)、缩放因子、变换点数等进行配置。vaild、ready 信号不再赘述,下同;tdata 携带循环前缀长度 CP_LEN、工作模式 FWD/INV、变换点数 NFFT 和缩放因子 SCALE_SCH 等信息。

  • S_AXI_DATA

  数据输入通道,tdata 携带待变换数据的实部 XN_RE 和虚部 XN_IM,tlast 指示是否是最后一个数据。

  • M_AXI_DATA

  变换结果输出通道,tdata 输出结果的实部 XK_RE 和虚部 XK_IM,tlast 指示是否是最后一个数据,tuser 携带额外的样本信息,如输出数据索引 XK_INDEX、算术溢出指示器 OVFLO 和块指数 BLK_EXP。

  • M_AXI_STATUS

  状态通道,tdata 传递块指数 BLK_EXP 或算术溢出指示器 OVFLO 两个状态。

  • aclk, aresetn, aclken

  略;若 aclken 功能被启用,会降低 aclk 的最大运行频率;arestn 需保持至少 2 clk_cycle 以确保正确复位。

  • event
    • event_frame_started,O,指示开始处理新帧的断言;
    • event_tlast_unexpected,O,当在非最后一个数据时看到 tlast 为 High 的断言;
    • event_tlast_missing,O,在随后一个数据看到 tlast 为 Low 的断言;
    • event_fft_overflow,O,数据输出通道出现数据溢出的断言;
    • event_data_in_channel_halt,O,数据输入通道需要数据而没有可用数据时的断言;
    • event_data_out_channel_halt,O,数据输出通道准备输出数据而无法进行时的断言;
    • event_status_channel_halt,O,状态输出通道准备写入状态信息而无法进行时的断言。

tdata 格式说明

  • 配置通道 tdata

  s_axis_config_tdata,具有 FWD/INV、CP_LEN、NFFT、SCALE_SCH 等几个字段,8bit 对齐(所有的 tdata 和 tuser 都是 8bit 对齐的),组织格式如下(虚线字段为可选字段,下同;若在 IP 配置时没有选用这些可选字段,则在 tdata/tuser 中相应字段不存在)

在这里插入图片描述

  FWD/INV 配置 IP 核的工作模式,为 1 为正常的 FFT,为 0 表示进行 IFFT,每个 bit 对应一个 FFT 通道,LSB(bit0) 代表通道 0,可逐帧设置

  NFFT,位宽 5 bits,设置运行时的 FFT 点数(需小于最大点数,最大点数在生成 IP 时设置,最大 65536(对应 5’h10)),值为 log ⁡ 2 ( N F F T ) \log_2(NFFT) log2(NFFT),比如要设置为 1024 点,则值应设置为 5‘d10。

  CP_LEN,位宽为 log ⁡ 2 ( m a x m u m   p o i n t   s i z e ) \log_2(maxmum\ point\ size) log2(maxmum point size),循环前缀长度(Cyclic Prefix Length):在输出整个变换结果之前,最初作为循环前缀输出的变换末端的样本数。可以设置为 0 到小于 NFFT 的任意数。

  SCALE_SCH,设置输出结果的缩放比例,可逐帧设置。对于 Pipelined Streaming I/O 和 Radix-4 burst I/O 架构,位宽为 2 × c e i l ( N F F T 2 ) 2\times ceil(\frac{NFFT}{2}) 2×ceil(2NFFT);对于 Radix-2 Burst I/O 和 Radix-2 Lite Burst I/O 架构,位宽为 2 × N F F T 2\times NFFT 2×NFFT;这里的 NFFT 指 log ⁡ 2 ( m a x m u m   p o i n t   s i z e ) \log_2(maxmum\ point\ size) log2(maxmum point size),即 FFT/IFFT 的蝶形算法阶段 (stage) 数,Radix-2 的每个蝶形单元拾取 2 个复数,而 Radix-4 的蝶形单元会拾取 4 个复数。每个 FFT 通道对应一个 SCALE_SCH,通道间的 SCALE_SCH 无 Padding,而整个字段需要向 8bit 对齐。对于 Burst I/O 架构,每个 stage 对应 2bit 的放缩系数,3/2/1/0,对应右移移位数;例如,1024 点 Radix-4,可设置为 [1 0 2 3 2](LSB 对应第一个 stage),表示第一个 stage 移动 2 位,第二个 stage 移动 3 位,…,一共移动了 8 位,对应缩放系数 1 / 256 1/256 1/256;又如,128 点 Radix-2,可设置为 [1 1 1 1 0 1 2],对应总缩放系数 1 / 128 1/128 1/128对于 Pipelined Streaming I/O 架构,将每两个相邻的 Radix-2 stage 看做一组,每组对应 2bit,3/2/1/0,对应右移移位数,当 FFT 点数不是 4 的幂时,最后一个 stage 将仅对应 1bit;例如 对于 256 点,可设置为 [2 2 2 3],对应缩放系数 1 / 512 1/512 1/512;512 点的 [2 2 2 2 2] 将是非法值,而 [0 2 2 2 2] 和 [1 2 2 2 2] 则是合法的。一般而言,对于标准的 FFT,总放缩系数为 1 / N 1/N 1/N,即对于 Radix-2 的每个 stage 应移位 1bit,而 Radix-4 每个 stage 应移位 2bit,这也正是 FFT IP 的 SCALE_SCH 默认配置;通过设置 SCALE_SCH 可对输出结果进行缩放。

  • 数据输入通道 tdata

  s_axis_data_tdata,传递待变换数据的实部 XN_RE 和虚部 XN_IM,最大支持 12 路 FFT 通道。XN_RE/XN_IM 位宽 8~34 可选,可以是定点数,也支持单精度浮点(32bit,IEEE-754 Single-precision Floating Point,包括 1bit 符号位、8bit 指数以及 23bit 底数);浮点的输入输出数据类型将导致 IP 消耗更多的资源,以进行浮点-定点类型的转换。s_axis_data_tdata 的组织格式如下

在这里插入图片描述

当输入输出数据为定点数格式时,为二进制补码形式。

  • 数据输出通道 tdata

  m_axis_data_tdata,传递变换结果的实部 XK_RE 和虚部 XK_IM,格式同 s_axis_data_tdata,如下

在这里插入图片描述

  • 数据输出通道 tuser

  m_axis_data_tuser,具有 XK_INDEX、BLK_EXP 和 OVFLO 三个字段,结构如下

在这里插入图片描述

  XK_INDEX,位宽 log ⁡ 2 ( m a x m u m   p o i n t   s i z e ) \log_2(maxmum\ point\ size) log2(maxmum point size),对应输出数据的索引。

  BLK_EXP,位宽 8bit,缩放系数,即右移的位数,每个通道均对应一个。仅在 Block Floating Point 模式下存在。

  OVFLO,位宽 1bit,断言本数据帧中是否出现了数据溢出异常,在新的数据帧被重置,每个通道均对应一个。

  • 状态输出通道 tdata

  m_axis_config_tdata,具有 BLK_EXP 和 OVFLO 两个字段,结构如下

在这里插入图片描述

BLK_EXP 和 OVFLO 同 m_axis_data_tuser 的,不再赘述。

其他细节

关于计算精度及缩放系数

  在 Radix-4 架构下,每经过一个蝶形 stage,值会出现 1 + 3 2 ≈ 5.242 1+3\sqrt{2}\approx5.242 1+32 5.242 的增长(对应 3bit 的增长),而 Radix-2 则会出现 1 + 2 ≈ 2.414 1+\sqrt{2}\approx2.414 1+2 2.414 的增长 (对应 2bit 的增长)。为了 处理 bit 的增长可能带来的问题(计算溢出 or 精度损失):

  1. 采用不缩放的全精度定点计算,以携带所有重要的整数位到计算结束;
  2. 每个 stage 采用固定缩放因子的定点计算;
  3. 采用浮点运算(Block Floating Point)。

  若采用全精度不缩放定点计算,则每个蝶形 stage 的乘法计算结果会截断(Truncation)或四舍五入(Convergent Rounding),最终输出宽度 I n p u t W i d t h + log ⁡ 2 N F F T + 1 InputWidth+\log_2{NFFT}+1 InputWidth+log2NFFT+1,以适应比特增长的最坏情况。如果采用定点缩放,则需要合理设置 SCALE_SCH,以避免溢出和精度损失。采用 Block Floating Point 浮点运算模式时则将自动确定缩放倍数,缩放倍数可通过 BLK_EXP 字段进行追踪。

  蝶形单元输出数据的舍入模式可在 IP 界面配置。其中截断(Truncation)可能引入直流偏置,而收敛舍入(Convergent Rounding)可以避免这一问题,但消耗更多的资源,并略微增加转换延迟。

  是否启用缩放和是否采用浮点运算可以在例化生成 IP 核时设置,是否采用浮点计算与输入输出数据的定点/浮点格式可独立设置。在定点缩放和浮点运算的模式下,输入输出数据位宽相一致;全精度不缩放定点模式下输出数据位宽如前所述。

  在 FPGA 端实现完全的浮点运算是非常昂贵的,因此 FFT IP 核在 Block Floating Point 选项采用的实际是更高精度的定点运算,通过相位因子宽度 Phase Factor Width 的选择控制计算的精度。当然,浮点运算将消耗更多的资源,因此如果输入数据的状况是被充分了解的、可以通过固定的缩放因子保证数据不出现溢出问题或严重的精度损失时,建议采用固定缩放因子的定点运算,以节约 FPGA 资源。

  由于舍入误差的存在,实值 FFT (输入值均为实数,即虚部均置零)的输出结果不完全对称,同时考虑到低频部分的噪声水平更加明显,因此 Xilinx 建议在执行实值 FFT 时使用输出结果的上半部分 N / 2 + 1   ∼   N N/2+1\ \sim\ N N/2+1  N)。

计算溢出

  当启用 OVFLO 功能时,若数据帧转换结果中出现溢出,则 OVFLO 会在数据卸载期间保持为高;如果有多个 FFT 通道,则每个通道各自对应一个。在发生溢出时,转换结果发生绕回(wrapped)而不是饱和(saturated),这会导致却大多数应用下数据不可用。

架构选择

  FFT IP 提供了四种架构,以在核的大小和转换速度方面进行权衡。Pipelined Streaming I/O 模式允许连续进行数据处理,规模最大,速度最快;Radix-4 Burst I/O 分别加载数据和处理数据,他的规模比 Pipelined 更小,但转换时间也更长;Radix-2 Burst I/O 使用比 Radix-4 Burst I/O 更小的蝶形单元,规模更小,转换时间更长;Radix-2 Lite Burst I/O 在 Radix-2 Burst I/O 的基础上进一步采用时分复用,规模更小,转换时间更长。在核心规模和数据吞吐方面,每一种架构几乎都是后一个架构的两倍,需根据项目需求决定采用何种架构。

  除 Radix-4 Burst I/O 仅支持 64 到 65536 点的变换外,其余架构均支持 8 到 65536 点变换。

  Pipeline 的每个 Radix-2 蝶形单元可以同时加载新帧的数据、处理当前帧的计算、卸载上一帧的计算结果,因此支持连续的数据输入,并支持一定延迟后的连续数据卸载。(不过数据流连续并不代表可以忽略 AXI 的流等待状态,实际上 FFT 核可能会插入等待状态来中断样本输入)。其架构如下

在这里插入图片描述

  Radix-4 Burst I/O 的数据处理与加卸载分开进行,首先进行数据加载,待数据帧完整后,开始进行转换,转换完成后再进行数据卸载。若卸载采用位翻转,则数据加卸载可以同时进行(即加载新帧的同时,卸载当前帧的转换结果);若输出采取自然序,则无法同时加载、卸载数据。关于转换数据的组织顺序,详见下一小节。Radix-4 Burst I/O 架构如下

在这里插入图片描述

  Radix-2 Burst I/O 与 Radix-4 类似,数据加卸载与数据处理不能同时进行,而如果采用位翻转,则数据加卸载可以重叠进行。

在这里插入图片描述

  Radix-2 Lite Burst I/O 采用共享的加减法器,时分复用,以时间性能的损失换取更小的资源消耗。数据加卸载和转换不可同时进行,如果采用位翻转,则数据加载、卸载可以重叠进行。Radix-2 Lite 的架构如下

在这里插入图片描述

数据顺序

  待转换数据以自然序输入(就是正常的二进制数顺序),而数据输出可以选择自然序(Natrual Order)还是反序(Bit/Digit Reversed Order)。默认设置下,输出数据采用反序。采用自然序数据输出会导致性能损失,在 Burst I/O 架构下,会导致时间性能损失,而对于 Pipelined Streaming I/O 架构,需要额外的 RAM 资源进行数据重排,导致消耗更多的面积。

  反序输出时,对于 Radix-2 和 Pipelined 架构,输出结果存在位翻转,最高位实际是计算结果的 LSB,如 4’b0001 实际表示 4’d8(4’b1000);对于 Radix-4 架构,则翻转是以 2bit 为单元(官方称之为 Digit)进行的,如 4’b0001 实际表示 4’d4(4’b0100),如果结果位宽是奇数,则最低 1bit 代表最高位,其余以 Digit 为单位翻转,如 5’b00101 实际表示 5’d24(5’b11000)。

  输出序号 XK_INDEX 值为 0 ∼ N − 1 0\sim N-1 0N1,对应 N 个转换结果输出。在反序输出模式下,这个序号也进行了翻转。

  当输出顺序为自然序时,可使用循环前缀插入。循环前缀:接受 FFT 输出的一部分,前缀到开头,随后再输出转换结果。CP_LEN 可修改循环前缀的长度,取值 0 ∼ N − 1 0\sim N-1 0N1,默认 0。

实时/非实时模式

  默认采用的是非实时模式(Non Real Time),AXI 接口会用到 tdata、tready、tvaild 三组信号,从而使 Master 和 Slave 端均具有随时暂停数据传输的能力。在实时模式下(Real Time),IP 会生成一个更小、更快的设计,以牺牲数据加载、卸载的灵活性为代价。

  在 Real Time 模式下,数据输出通道、状态输出通道的 tready 信号被删除,同时数据输入通道的 tvaild 信号在数据帧开始以后也将被忽略(也就是用户可以在数据帧尚未开始加载时阻止数据传输,但一旦开始加载数据,用户将丧失中止数据传输的能力)。在这种设计下,数据加载过程中若出现需要暂停数据传输的情况,将会导致 IP 加载到错误的数据;在卸载数据时,若收端不能及时响应,也将造成数据的丢失。

  Real Time 设计下,若在开始数据加载后,用户没有提供数据(s_axis_data_tvalid = L),则 IP 将重复上一个数据,并拉高 event_data_in_channel_halt 以指示出现数据加载异常,如下图所示

在这里插入图片描述

这种异常同时会进一步导致后续帧的数据错位(比如图中当前帧的最后一个数据 D7 将被 IP 认为是下一帧的第一个数据),因此发生这一问题后就必须重新进行数据同步。

数据输入输出时序

  从数据输入到数据输出的延迟由所选架构和循环前缀长度共同决定。

  • Pipelined Stream I/O & CP_LEN=0

在这里插入图片描述

  在流水架构下,数据加载、处理、卸载同时进行,如上图所示。

  • Burst I/O

在这里插入图片描述

  在 Burst I/O 架构下,在处理新帧前,必须先卸载当前帧的转换结果;若采用自然序,数据加卸载不能同时进行,如图 3-44 所示,而如果采用输出反序,则数据加卸载可以同时进行,如图 3-45 所示。注意到在加载完一帧数据后,IP 仍加载了一小段下一帧的数据,这是由于 AXI 包含了一个大小为 16 的输入缓冲区,缓冲区的数据在 IP 加载数据时才会被加载到处理核心的寄存器中进行转换计算。

关于配置信息的应用时间节点

  在核心开始转换新数据帧时,会检测是否有新的配置信息,若在加载新帧之前或在加载新帧时的同一上升沿看到新的配置信息,则会使用该配置信息处理该帧数据,否则使用最后看到的配置信息(如果从没有过配置,则采用默认配置)处理该帧。为确保新帧采用新的配置信息进行转换,应在数据输入通道的第一个数据前至少一个 clk_cycle 写入配置信息。

  在 Pipeline Stream 架构下,若核心非空闲,则难以确定新的配置信息被应用于了哪个帧,因为数据很可能已经进入了处理流程。官方推荐使用帧启动信号 event_frame_started 进行配置信息到帧的同步,当该信号为高时,是为下一帧更新配置的安全时间点,在这个时间点后的配置则可能无法确定被用于哪一帧。

FFT IP 例化介绍

  在 Vivado IP 界面可以找到 Fast Fourier Transform 的 IP 核

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

  在配置完成后,可以看左侧的 Implementation Details,里面给出了接口里各个字段的定义,这个很方便,不需要根据配置自己去推算各个字段是什么含义了:

在这里插入图片描述

控制代码实现 & 测试

  作为示例,这里例化一个 Radix-2 Burst I/O 架构、单 FFT 通道、1024 点变换、16bit 定点、定点缩放、截断舍入、自然序输出、Non Real Time 的 IP,同时启用 XK_INDEX 以方便我们观察数据结果的顺序。

  用 matlab 生成数据文件,该信号包含 3,37,200 三个频点:

clc,clear,close all

%% 生成 testbench $readmemh 需要读取的 FFT IP 测试数据
N = 1024;
DataWidth = 16;

t = 0:N-1;
data = 2*cos(2*pi/N*3*t) + sin(2*pi/N*37*t) + cos(2*pi/N*200*t+pi/3);

data = data./max(abs(data))*0.99;   %归一化
data = round(data*2^(DataWidth-1)); %量化

fp = fopen('./fft_test_data.txt','w');
for i=1:N
    if data(i)>=0
        s = dec2hex(data(i),ceil(DataWidth/4));
    else
        s = dec2hex(data(i)+2^DataWidth,ceil(DataWidth/4));
        %这个函数太蠢了,如果数比较小,则不会拓展高位的符号位,所以要自己计算一下
    end
    
    fprintf(fp,s);
    fprintf(fp,'\n');
end
clear i s

fclose(fp);

%% fft
data_fft = fft(data)./N;

figure('color','w')
subplot(3,1,1)
plot(data)
subplot(3,1,2)
plot(real(data_fft))
subplot(3,1,3)
plot(imag(data_fft))

  然后编写 Testbench 如下:

//测试FFT IP核
`timescale 1ns/100ps
`default_nettype none
module FFT_IP_tb();

reg		clk_100M	= 1'b1;
reg		rst_n		= 1'b1;

always #5 begin
	clk_100M	<= ~clk_100M;
end

//--------------------------FFT IP------------------------------------
//config
reg		[23:0]	s_axis_config_tdata		= {3'b0, 20'h55555, 1'b1};
reg				s_axis_config_tvalid	= 1'b0;
wire			s_axis_config_tready;

//data in
reg		[31:0]	s_axis_data_tdata		= 32'd0;
reg				s_axis_data_tvalid		= 1'b0;
wire			s_axis_data_tready;
reg				s_axis_data_tlast		= 1'b0;

//data out
wire	[31:0]	m_axis_data_tdata;
wire	[9:0]	m_axis_data_tuser;
wire			m_axis_data_tvalid;
reg				m_axis_data_tready		= 1'b0;
wire			m_axis_data_tlast;

//events
wire			event_frame_started;
wire			event_tlast_unexpected;
wire			event_tlast_missing;
wire			event_status_channel_halt;
wire			event_data_in_channel_halt;
wire			event_data_out_channel_halt;

//FFT IP
FFT_0 FFT_inst(
	.aclk							(clk_100M),
	.aresetn						(rst_n),

	//Config
	.s_axis_config_tdata			(s_axis_config_tdata),
	.s_axis_config_tvalid			(s_axis_config_tvalid),
	.s_axis_config_tready			(s_axis_config_tready),

	//DATA INPUT
	.s_axis_data_tdata				(s_axis_data_tdata),
	.s_axis_data_tvalid				(s_axis_data_tvalid),
	.s_axis_data_tready				(s_axis_data_tready),
	.s_axis_data_tlast				(s_axis_data_tlast),

	//DATA OUTPUT
	.m_axis_data_tdata				(m_axis_data_tdata),
	.m_axis_data_tuser				(m_axis_data_tuser),
	.m_axis_data_tvalid				(m_axis_data_tvalid),
	.m_axis_data_tready				(m_axis_data_tready),
	.m_axis_data_tlast				(m_axis_data_tlast),

	//event signal
	.event_frame_started			(event_frame_started),
	.event_tlast_unexpected			(event_tlast_unexpected),
	.event_tlast_missing			(event_tlast_missing),
	.event_status_channel_halt		(event_status_channel_halt),
	.event_data_in_channel_halt		(event_data_in_channel_halt),
	.event_data_out_channel_halt	(event_data_out_channel_halt)
);

//----------------------------Ctrl-------------------------------------
reg		signed	[15:0]	fft_test_data	[0:1023];

reg		[9:0]	cnt		= 10'd0;
always @(posedge clk_100M or negedge rst_n) begin
	if(~rst_n) begin
		cnt		<= 10'd0;
	end
	else begin
		if(s_axis_data_tvalid & s_axis_data_tready) begin
			cnt		<= cnt + 1'b1;
		end
		else begin
			cnt		<= cnt;
		end
	end
end

//KN_real/img
reg		signed	[15:0]	KN_Real;
reg		signed	[15:0]	KN_Img;
always @(*) begin
	KN_Real		<= fft_test_data[cnt];
	KN_Img		<= 16'sd0;
end

//s_axis_data_tdata
always @(*) begin
	s_axis_data_tdata	<= {KN_Img, KN_Real};
end

//s_axis_data_tvalid
always @(posedge clk_100M or negedge rst_n) begin
	if(~rst_n) begin
		s_axis_data_tvalid	<= 1'b0;
	end
	else begin
		s_axis_data_tvalid	<= 1'b1;
	end
end

//s_axis_data_tlast
always @(*) begin
	s_axis_data_tlast = (cnt==10'd1023)? 1'b1 : 1'b0;
end

//m_axis_data_tready
always @(posedge clk_100M or negedge rst_n) begin
	if(~rst_n) begin
		m_axis_data_tready	<= 1'b0;
	end
	else begin
		m_axis_data_tready	<= 1'b1;
	end
end

//XK_Real
reg		signed	[15:0]	XK_Real;
integer i;
always @(*) begin
    //如用反序输出,则需要调整XK_Real、XK_Img、XK_INDEX三个信号的顺序
    //自然序的结果与matlab仅相差+-3以内;但倒序时出的结果太奇怪了,序号和数据都对不上
	for(i=0; i<=15; i=i+1) begin
		XK_Real[i]		<= m_axis_data_tdata[i];//[15-i];//
	end
end

//XK_Img
reg		signed	[15:0]	XK_Img;
always @(*) begin
	for(i=0; i<=15; i=i+1) begin
		XK_Img[i]		<= m_axis_data_tdata[16+i];//[31-i];//
	end
end

//XK_INDEX
reg				[9:0]	XK_INDEX;
always @(*) begin
	for(i=0; i<=9; i=i+1) begin
        XK_INDEX[i]		<= m_axis_data_tuser[i];//[9-i];//
	end
end

initial begin
    $readmemh("./fft_test_data.txt", fft_test_data);
	rst_n	= 1'b0;
	#200;
	rst_n	= 1'b1;

	#100000;
	$stop;
end

endmodule

  仿真结果如下,对比 matlab 结果,误差在 ± 3 \pm3 ±3 以内

在这里插入图片描述

在这里插入图片描述

  由于这个自然序不能同时加卸载数据,所以后面又尝试了反序输出,然而 XK_REAL 和 XK_IMG 的结果非常奇怪,不论是计算结果还是应当出现的频点,都没法被理解。有懂的兄弟可以说一下这个反序输出到底是怎么回事 。

速度测试

  MATLAB 下进行了 FFT 的计算速度测试,涉及1024,4096,16384,65536等几个常见点数:

%测试 matlab FFT 的速度,以与 FPGA 的 FFT IP 速度做对照
clc,clear,close all

s=rand(1,65536);

tic;
Num=1000000;
for i=1:Num
    fft(s,1024);
end
t=toc;
fprintf('N=1024,mean speed=%fus\n',t/Num*1e6)

tic
Num=1000000;
for i=1:Num
    fft(s,4096);
end
t=toc;
fprintf('N=4096,mean speed=%fus\n',t/Num*1e6)

tic
Num=100000;
for i=1:Num
    fft(s,16384);
end
t=toc;
fprintf('N=16384,mean speed=%fus\n',t/Num*1e6)

tic
Num=10000;
for i=1:Num
    fft(s,65536);
end
t=toc;
fprintf('N=65536,mean speed=%fus\n',t/Num*1e6)

测试到的速度如下

在这里插入图片描述

  例化一个 Pipelined Stream I/O 、65536 可配置长度的 FFT IP,在上述几个NFFT条件下进行测试,

// 测试 FFT 速度
`default_nettype none
module main27(
input	wire			clk_sys,		//OCXO_10M

input	wire	[3:0]	Key,
output	wire	[3:0]	LED
);

wire	clk_100M;
wire	clk_1Hz;
reg		rst_n	= 1'b1;

//PLL 10M -> 100M
clk_wiz_0 clk_wiz(
	.clk_in1   (clk_sys),
	.clk_out1  (clk_100M),
	.reset     (1'b0),
	.locked    ()
);

//clk 1Hz
clkdiv #(.N(100_000_000))
clkdiv_1Hz(
	.clk_in		(clk_100M),
	.clk_out	(clk_1Hz)
);

reg		[15:0]	cnt				= 16'd0;		//FFT输入计数
reg		[15:0]	N_last			= 16'd1023;		//对应NFFT的最后一个元素的计数值

reg		[31:0]	FFT_finish_cnt	= 32'd0;		//计数FFT完成次数(即输出tlast的个数)

//--------------------------FFT IP------------------------------------
//config
reg				[15:0]	SCALE_SCH				= 16'haaaa_aaaa;
reg						FWD_INV					= 1'b1;
reg				[4:0]	NFFT					= 5'd10;
wire			[31:0]	s_axis_config_tdata;
reg						s_axis_config_tvalid	= 1'b0;
wire					s_axis_config_tready;

assign	s_axis_config_tdata	= {7'b0, SCALE_SCH, FWD_INV, 3'b0,NFFT};

//data in
reg		signed	[15:0]	XN_Real					= 16'sd0;
reg		signed	[15:0]	XN_Img					= 16'sd0;
wire			[31:0]	s_axis_data_tdata;
reg						s_axis_data_tvalid		= 1'b0;
wire					s_axis_data_tready;
reg						s_axis_data_tlast		= 1'b0;

assign	s_axis_data_tdata	= {XN_Img, XN_Real};

//data out
wire	signed	[15:0]	XK_Real;
wire	signed	[15:0]	XK_Img;
wire			[15:0]	XK_INDEX;
wire			[31:0]	m_axis_data_tdata;
wire			[15:0]	m_axis_data_tuser;
wire					m_axis_data_tvalid;
reg						m_axis_data_tready		= 1'b0;
wire					m_axis_data_tlast;

assign	XK_Real		= m_axis_data_tdata[15:0];
assign	XK_Img		= m_axis_data_tdata[31:16];
assign	XK_INDEX	= m_axis_data_tuser[15:0];

//events
wire					event_frame_started;
wire					event_tlast_unexpected;
wire					event_tlast_missing;
wire					event_status_channel_halt;
wire					event_data_in_channel_halt;
wire					event_data_out_channel_halt;

//FFT IP,65536 point,启用Config Transform Length功能,数据位宽16bit,Pipelined Stream I/O,Natural Order,固定缩放
FFT_1 FFT_inst(
	.aclk							(clk_100M),
	.aresetn						(rst_n),

	//Config
	.s_axis_config_tdata			(s_axis_config_tdata),
	.s_axis_config_tvalid			(s_axis_config_tvalid),
	.s_axis_config_tready			(s_axis_config_tready),

	//DATA INPUT
	.s_axis_data_tdata				(s_axis_data_tdata),
	.s_axis_data_tvalid				(s_axis_data_tvalid),
	.s_axis_data_tready				(s_axis_data_tready),
	.s_axis_data_tlast				(s_axis_data_tlast),

	//DATA OUTPUT
	.m_axis_data_tdata				(m_axis_data_tdata),
	.m_axis_data_tuser				(m_axis_data_tuser),
	.m_axis_data_tvalid				(m_axis_data_tvalid),
	.m_axis_data_tready				(m_axis_data_tready),
	.m_axis_data_tlast				(m_axis_data_tlast),

	//event signal
	.event_frame_started			(event_frame_started),
	.event_tlast_unexpected			(event_tlast_unexpected),
	.event_tlast_missing			(event_tlast_missing),
	.event_status_channel_halt		(event_status_channel_halt),
	.event_data_in_channel_halt		(event_data_in_channel_halt),
	.event_data_out_channel_halt	(event_data_out_channel_halt)
);

//----------------------------Ctrl-------------------------------------
//KN_Real/KN_Img
always @(posedge clk_100M) begin
	if(~rst_n) begin
		XN_Real		<= 16'sd0;
		XN_Img		<= 16'sd0;
	end
	else begin
		if(s_axis_data_tvalid & s_axis_data_tready) begin
			XN_Real		<= XN_Real + 16'd7;
			XN_Img		<= XN_Img + 16'd3;
		end
		else begin
			XN_Real		<= XN_Real;
			XN_Img		<= XN_Img;
		end
	end
end

//s_axis_data_tvalid
always @(posedge clk_100M) begin
	if(~rst_n) begin
		s_axis_data_tvalid	<= 1'b0;
	end
	else begin
		s_axis_data_tvalid	<= 1'b1;
	end
end

//s_axis_data_tlast
always @(*) begin
	s_axis_data_tlast = (cnt==N_last)? 1'b1 : 1'b0;
end

//N_last & NFFT & s_axis_config_tvalid
always @(posedge clk_100M) begin
	if(~rst_n) begin
		NFFT					<= 5'd10;
		N_last					<= 16'd1023;
		s_axis_config_tvalid	<= 1'b1;
	end
	else begin
		if(event_frame_started) begin
			case(~Key)					//按键修改FFT点数
			4'b0001: begin
				NFFT					<= 5'd10;
				N_last					<= 16'd1023;
				s_axis_config_tvalid	<= 1'b1;
			end
			4'b0010: begin
				NFFT					<= 5'd12;
				N_last					<= 16'd4095;
				s_axis_config_tvalid	<= 1'b1;
			end
			4'b0100: begin
				NFFT					<= 5'd14;
				N_last					<= 16'd16383;
				s_axis_config_tvalid	<= 1'b1;
			end
			4'b1000: begin
				NFFT					<= 5'd16;
				N_last					<= 16'd65535;
				s_axis_config_tvalid	<= 1'b1;
			end
			default: begin
				NFFT					<= NFFT;
				N_last					<= N_last;
				s_axis_config_tvalid	<= 1'b0;
			end
			endcase
		end
		else begin
			NFFT					<= NFFT;
			N_last					<= N_last;
			s_axis_config_tvalid	<= 1'b0;
		end
	end
end

//cnt
always @(posedge clk_100M) begin
	if(~rst_n) begin
		cnt	<= 16'd0;
	end
	else begin
		if(s_axis_data_tvalid & s_axis_data_tready) begin
			if(cnt < N_last) begin
				cnt		<= cnt + 1'b1;
			end
			else begin
				cnt		<= 16'd0;
			end
		end
		else begin
			cnt		<= cnt;
		end
	end
end

//m_axis_data_tready
always @(posedge clk_100M) begin
	if(~rst_n) begin
		m_axis_data_tready	<= 1'b0;
	end
	else begin
		m_axis_data_tready	<= 1'b1;
	end
end

//FFT_finish_cnt
always @(posedge clk_100M) begin
	if(~rst_n) begin
		FFT_finish_cnt	<= 32'd0;
	end
	else begin
		FFT_finish_cnt	<= m_axis_data_tlast? FFT_finish_cnt + 1'b1 : FFT_finish_cnt;
	end
end

// ILA
ila_main27 ila(
	.clk		(clk_100M),

	.probe0		(clk_1Hz),
	.probe1		(NFFT),
	.probe2		(FFT_finish_cnt),

	.probe3		(event_frame_started),
    .probe4		(m_axis_data_tlast),
    
    .probe5		(XN_Real),
	.probe6		(XN_Img),
	.probe7		(cnt),

	.probe8		(XK_Real),
	.probe9		(XK_Img),
	.probe10	(XK_INDEX)
);

endmodule

对已完成的 FFT 次数进行计数,每秒获取一次,如下

在这里插入图片描述

计算得知 1024/4096/16384/65536 点数下的平均转换时间为 34.33us、144.59us、615.76us、2.622ms,相较于 matlab 的 1.727us、7.11us、38.01us、318.15us 要慢不少,这是由于时钟频率(100M)较低,以 65536 点为例,仅加载数据就需要 655us。但这仍不足以解释为何 Pipelined 流水情况下如此慢,进一步调试发现,其数据加载、卸载、转换是完全分开的,表明实际生成了 Burst I/O 架构的核心(后面查看 IP 核,发现是不知道为啥变成 Radix-4 Burst 架构了(•́へ•́╬)):

在这里插入图片描述

  重新配置为 Pipelined 架构,结果如下

在这里插入图片描述

计算得知 1024/4096/16384/65536 点数下的平均转换时间为 10.24us、40.96us、163.84us、655.31us,与对应的数据加载时间大抵相当,达到最优时间(唔!!!流水永远滴神!!)。

参考文献

  • pg109-xfft.pdf

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1939341.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【Matlab】RBF径向基神经网络回归预测算法(附代码)

资源下载&#xff1a; 资源合集&#xff1a; 目录 一&#xff0c;概述 RBF 神经网络&#xff08;Radial Basis Function Neural Network&#xff09;是一种基于径向基函数的前向型神经网络。它的特点是具有快速的训练速度和良好的泛化性能。 RBF 神经网络的基本结构包括输入层…

探索 Electron:如何利用Electron和Vite打造高效桌面应用

Electron是一个开源的桌面应用程序开发框架&#xff0c;它允许开发者使用Web技术&#xff08;如 HTML、CSS 和 JavaScript&#xff09;构建跨平台的桌面应用程序&#xff0c;它的出现极大地简化了桌面应用程序的开发流程&#xff0c;让更多的开发者能够利用已有的 Web 开发技能…

三、建造者模式

文章目录 1 基本介绍2 案例2.1 Car 类2.2 CarBuilder 抽象类2.3 EconomyCarBuilder 类2.4 LuxuryCarBuilder 类2.5 CarDirector 类2.6 测试程序2.7 测试结果2.8 总结 3 各角色之间的关系3.1 角色3.1.1 Product ( 产品 )3.1.2 Builder ( 抽象建造者 )3.1.3 ConcreteBuilder ( 具…

电阻有哪些参数呢

电阻是电路中最常见的元件之一&#xff0c;它在控制电流、分压和保护电路等方面发挥着重要作用。了解电阻的主要参数对于选择和使用电阻至关重要。本文将详细介绍电阻的主要参数&#xff0c;包括电阻值、功率额定值、温度系数、容差、噪声、频率特性、体积和封装等。 1. 电阻值…

长模式下的分页

前提 如果开启了长模式&#xff0c;则必须同时开启分页模式&#xff0c;因为长模式弱化了分段模型而分段模型也确实有很多不足&#xff0c;不适应现在操作系统和应用软件的发展长模式也扩展了 CPU 的位宽&#xff0c;使得 CPU 能使用 64 位的超大内存地址空间所以&#xff0c;…

当一个程序员的博客突然变少

shigen坚持更新文章的博客写手&#xff0c;擅长Java、python、vue、shell等编程语言和各种应用程序、脚本的开发。记录成长&#xff0c;分享认知&#xff0c;留住感动。 个人IP&#xff1a;shigen hello&#xff0c;伙伴们好久不见。马上到了八月&#xff0c;也是我在个人公众号…

JMeter介绍、安装配置以及快速入门

文章目录 1. JMeter简介2. JMeter安装配置3. JMeter快速入门 1. JMeter简介 Apache JMeter是一款开源的压力测试工具&#xff0c;主要用于测试静态和动态资源&#xff08;如静态文件、服务器、数据库、FTP服务器等&#xff09;的性能。它最初是为测试Web应用而设计的&#xff…

ETAS StackM配置及使用-stack监控

文章目录 前言Stack基本介绍StackM配置StackMTargetStackMGeneral Linker配置EcuM配置RTE配置集成与测试总结 前言 嵌入式C语言执行的软件中&#xff0c;stack溢出会导致程序执行异常&#xff0c;严重可能导致直接进硬件异常中断(hardfault)。软件执行过程中的stack监控是非常…

【JAVA多线程】Future,专为异步编程而生

目录 1.Future 2.CompletableFuture 2.1.为什么会有CompletableFuture&#xff1f; 2.2.使用 2.2.1.提交任务获取结果 2.2.2.回调函数 2.2.3.CompletableFuture嵌套问题 1.Future Java中的Future接口代表一个异步计算。其提供了一组规范用来对异步计算任务进行管理控制…

java项目(knife4j使用,静态资源未放在static资源包下,公共字段自动填充,Spring Cache与Spring Task)

Knife4j&#xff08;生成接口文档&#xff09; 使用swagger你只需要按照它的规范去定义接口及接口相关的信息&#xff0c;就可以做到生成接口文档&#xff0c;以及在线接口调试页面。官网:https://swagger.io/ Knife4j是为Java MVC框架集成Swagger生成Api文档的增强解决方案。…

uni-app 影视类小程序开发从零到一 | 开源项目推荐

引言 在数字娱乐时代&#xff0c;对于电影爱好者而言&#xff0c;随时随地享受精彩影片成为一种日常需求。分享一款基于 uni-app 开发的影视类小程序。它不仅提供了丰富的影视资源推荐&#xff0c;还融入了个性化知乎日报等内容&#xff0c;是不错的素材&#xff0c;同时对电影…

Springboot同时支持http和https访问

springboot默认是http的 一、支持https访问 需要生成证书&#xff0c;并配置到项目中。 1、证书 如果公司提供&#xff0c;则直接使用公司提供的证书&#xff1b; 如果公司没有提供&#xff0c;也可自己使用Java自带的命令keytool来生成&#xff1a; &#xff08;1&#x…

JavaWeb笔记_Cookie

一.会话技术概述 在日常生活中,A和B之间在打电话过程中一连串的你问我答就是一个会话 在BS模型中,会话可以理解为通过浏览器访问服务端的资源,点击超链接可以进行资源的跳转,直到浏览器关闭过程叫做会话 我们使用会话技术可以解决的是整个会话过程中(通过浏览器浏览服务…

【Linux】一文向您详细介绍 Vim编辑器 显示行号的方法

【Linux】一文向您详细介绍 Vim编辑器 显示行号的方法 下滑即可查看博客内容 &#x1f308; 欢迎莅临我的个人主页 &#x1f448;这里是我静心耕耘深度学习领域、真诚分享知识与智慧的小天地&#xff01;&#x1f387; &#x1f393; 博主简介&#xff1a;985高校的普通本…

【Matlab】PLS偏最小二乘法回归预测算法(附代码)

资源下载&#xff1a; 资源合集&#xff1a; 目录 一&#xff0c;概述 偏最小二乘法是一种新型的多元统计数据分析方法&#xff0c;于1983年由S.Wold和C.Albano等人首次提出。偏最小二乘法实现了&#xff0c;在一个算法下&#xff0c;可以同时实现回归建模&#xff08;多元线…

类和对象:赋值函数

1.运算符重载 • 当运算符被⽤于类类型的对象时&#xff0c;C语⾔允许我们通过运算符重载的形式指定新的含义。C规定类类型对象使⽤运算符时&#xff0c;必须转换成调⽤对应运算符重载&#xff0c;若没有对应的运算符重载&#xff0c;则会编译报错&#xff1b;&#xff08;运算…

SwiftUI 5.0(iOS 17)滚动视图的滚动目标行为(Target Behavior)解惑和实战

概览 在 SwiftUI 的开发过程中我们常说&#xff1a;“屏幕不够&#xff0c;滚动来凑”。可见滚动视图对于超长内容的呈现有着多么秉轴持钧的重要作用。 这不&#xff0c;从 SwiftUI 5.0&#xff08;iOS 17&#xff09;开始苹果又为滚动视图增加了全新的功能。但是官方的示例可…

【LeetCode】80.删除有序数组中的重复项II

1. 题目 2. 分析 3. 代码 class Solution:def removeDuplicates(self, nums: List[int]) -> int:if len(nums) < 3:return len(nums)i 0j 1k 2while(k < len(nums)):if (nums[i] nums[j]):while(k < len(nums) and nums[j] nums[k] ):k1if (k < len(nums…

C语言指针超详解——最终篇一

C语言指针系列文章目录 入门篇 强化篇 进阶篇 最终篇一 文章目录 C语言指针系列文章目录1. 回调函数是什么2. qsort 函数2.1 概念2.2 qsort 排序 int 类型数据2.3 使用 qsort 排序结构体数据 3. 模拟实现 qsort 函数4. sizeof 与 strlen 的对比4.1 sizeof4.2 strlen4.3 sizeof…

ctf中php反序列化汇总

序列化与反序列化的概念 序列化就是将对象转换成字符串。字符串包括 属性名 属性值 属性类型和该对象对应的类名。 反序列化则相反将字符串重新恢复成对象。 对象的序列化利于对象的保存和传输,也可以让多个文件共享对象。 序列化举例&#xff1a;一般ctf题目中我们就是要将对…