在Vivado的IP中搜索FFT,会显示出FFT和LTE FFT,如下图所示。FFT就是我们一般使用的FFT IP核,而LTE FFT是什?它和FFT有什么区别?什么时候使用它?为消除这些疑问,下面简单介绍下LTE FFT。
LTE(Long Term Evolution,长期演进)项目是3G向4G演进的过渡技术(具体可参考3G、3GPP、LTE、4G解释),此IP是为了满足LTE技术特殊的FFT要求而专门开发的,它与常规的FFT IP核有两个区别:
1。LTE FFT有一个Enable 15MHz Bandwidth Support的可选项,选中后Maximum Transform Length可选384、768、1536,3072这几个非2n的点数,这应该是LTE技术特有的FFT处理点数要求。
2。LTE FFT的输入数据宽度为14~17。
暂时就发现这两个区别。显然只有在开发LTE相关技术时我们才会使用LTE FFT IP核。
一般来说,我们使用FFT IP核。下面介绍FFT IP核。
可参考Xilinx官网Fast Fourier Transform (FFT)概述,
Fast Fourier Transform (FFT) 是 DSP 系统内使用的基本构建模块,其应用范围从基于 OFDM 的数字调制解调器到超声波、RADAR 和 CT 图像重建算法。尽管它的算法很容易理解,但实现架构和特性的变体很重要,而且对于今天的硬件工程师来说,非常耗时。
FFT LogiCORE™ IP 核不仅提供 4 种不同的架构,而且还提供系统级定点 C 模型,可将 3-6 个月的典型实现时间锐减至按下按钮所需的时间。此外,它还可帮助用户针对所有必要的算法和实现方案做出 DSP 算法及硬件工程师所要求的权衡。这些容易做出的权衡有助于用户针对其应用所需的特定点大小及转换时间选择最多资源的低功耗解决方案。
FFT LogiCORE 不仅可增加数据和高达 34 位的相位因数宽度支持,而且还可支持 IEEE 单精度浮点数据类型,从而可扩展对更高动态范围的关注。浮点选项是通过内部利用精度更高的定点 FFT 实现的,可通过数量显著减少的资源,实现与完整浮点实现方案类似的噪声性能。
与 AXI4-Stream 兼容的接口。
正反向的复杂 FFT,运行时可配置
转换尺寸 n = 2m、m = 3 – 16
数据采样精度 bx = 8 – 34
相位因数精度 bw = 8 – 34
运算类型:
定点或浮点接口
蝶后取舍
用于数据和相位因数存储的分布式 RAM 的块 RAM
运行时可配置的可选转换点大小
运行时可配置的扩展计划支持定标定点内核
位/数颠倒(或自然)输出顺序
数字通信系统的可选循环前缀插入
4 种架构可提供内核大小和转换时间之间的平衡
比特位精确的 C 模型和 MEX 可供下载,满足系统建模需求
可参考Xilinx官网Fast Fourier Transform (FFT)文档
下载PG109 - Fast Fourier Transform v9.1 Product Guide (v9.1)。
分类 | 名称 | 方向 | 必须/可选 | 位宽 | 说明 |
---|---|---|---|---|---|
时钟与复位 | aclk | I | 必须 | 1 | 模块工作时钟,上升沿有效 |
aclken | I | 可选 | 1 | 时钟使能信号,高电平有效,低电平时内核暂停工作,可通过此信号降低内核运行的最大时钟频率 | |
aresetn | I | 可选 | 1 | 低电平同步复位信号,优先级高于aclken,低电平至少持续两个时钟周期 | |
配置 | s_axis_config_tvalid | I | 必须 | 1 | 配置通道的 tvalid,由外部主机断言以表示它能够提供数据 |
s_axis_config_tready | O | 必须 | 1 | 配置通道的 tready,由内核断言以表示它已准备好接收数据 | |
s_axis_config_tdata | I | 必须 | 可配置,8的倍数 | 配置通道的tdata,携带配置信息,NFFT、FWD/INV、CP_LEN和SCALE_SCH | |
输入数据 | s_axis_data_tvalid | I | 必须 | 1 | 输入数据通道tvalid,由外部主机断言以表示它能够提供数据 |
s_axis_data_tready | O | 必须 | 1 | 输入数据通道tready,由内核断言以表示它能够接收数据 | |
s_axis_data_tdata | I | 必须 | 可配置,16的倍数 | 输入数据通道tdara,由外部主机提供待处理的数据 | |
s_axis_data_tlast | I | 必须 | 1 | 输入数据通道tdara,由外部主机断言表示最后一组数据。 只在生成事件event_tlast_unexpected 和 event_tlast_missing时,内核才使用它 |
|
输出数据 | m_axis_data_tvalid | O | 必须 | 1 | 输出数据通道tvalid,由内核断言表示它能够提供数据 |
m_axis_data_tready | I | 必须 | 1 | 输出数据通道tready,由外部丛机断言表示它能够接收数据 | |
m_axis_data_tdata | O | 必须 | 可配置,16的倍数 | 输出数据通道tdata,由内核提供FFT后的数据 | |
m_axis_data_tuser | O | 可选 | 可配置,8的倍数 | 输出数据通道tuser,由内核提供XK_INDEX,OVFLO,BLK_EXP信息 | |
m_axis_data_tlast | O | 必须 | 1 | 数据数据通道tlast,由内核断言表示最后一个输出数据 | |
状态(可选) | m_axis_status_tvalid | O | 可选 | 1 | 状态通道tvalid,由内核断言表示它能够提供状态数据 |
m_axis_status_tready | I | 可选 | 1 | 状态通道tready,由外部丛机断言表示它能够接收状态数据 | |
m_axis_status_tdata | O | 可选 | 16 | 状态通道tdata,由内核提供的状态数据,包含BLK_EXP 或 OVFLO | |
事务 | event_frame_started | O | 必须 | 1 | 当内核开始处理新帧时,该事件信号在一个时钟周期内被置位。提供此信号是为了允许您对帧进行计数,并在需要时将内核的配置同步到特定的帧。 |
event_tlast_unexpected | O | 必须 | 1 | 当内核在不是帧中最后一个的任何传入数据样本上看到 s_axis_data_tlast High 时,该事件信号被置位为一个时钟周期。这表明核心和上游数据源在帧大小方面的配置不匹配,并表明上游数据源配置为比核心更小的点大小。这仅在内核开始处理帧时计算,因此事件可能会滞后 s_axis_data_tlast 上的意外高点大量时钟周期 如果一帧的 s_axis_data_tlast 上有多个意外的高点,则对它们中的每一个都进行断言。 |
|
event_tlast_missing | O | 必须 | 1 | 当 s_axis_data_tlast 在帧最后传入的数据样本上为低时,该事件信号在单个时钟周期内被置位。这表明内核和上游数据源在帧大小方面的配置不匹配,并表明上游数据源配置为比核心更大的点大小。 这仅在内核开始处理帧时计算,因此事件可能会滞后丢失的 s_axis_data_tlast 大量时钟周期 |
|
event_fft_overflow | O | 可选 | 1 | 当在 m_axis_data_tdata 上传输的数据样本中出现溢出时,该事件信号在每个时钟周期置位。 只有在使用缩放算术或单精度浮点 I/O 时才会出现 FFT 溢出。在所有其他配置中,此引脚被移除 |
|
event_data_in_channel_halt | O | 必须 | 1 | 该事件在内核需要来自数据输入通道的数据且没有数据可用的每个周期被置位。 在实时模式下,即使帧已不可恢复地损坏,内核仍会继续处理该帧。 在非实时模式下,核心处理停止并仅在数据写入数据输入通道时继续。帧没有损坏。 在这两种模式下,事件保持有效,直到数据输入通道中的数据可用 |
|
event_data_out_channel_halt | O | 必须 | 1 | 该事件在内核需要将数据写入数据输出通道,但因为通道中的缓冲区已满而无法写入的每个周期中被置位。发生这种情况时,核心处理会停止,所有活动都会停止,直到通道缓冲区中有可用空间。帧没有损坏。 此事件引脚仅在非实时模式下可用。 |
|
event_status_channel_halt | O | 必须 | 1 | 该事件在内核需要将数据写入状态通道但不能因为通道上的缓冲区已满而无法写入的每个周期中被置位。发生这种情况时,核心处理会停止,所有活动都会停止,直到通道缓冲区中有可用空间。帧没有损坏。 此事件引脚仅在非实时模式下可用 |
如果内核上存在 aresetn 引脚,则将引脚驱动为低电平会导致所有输出引脚、内部计数器和状态变量重置为其初始值。表 3-1 中描述的初始值也是电路上电时采用的默认值,无论内核是否配置为 aresetn。所有挂起的加载过程、转换计算和卸载过程都停止并重新初始化。
NFFT 设置为允许的最大 FFT 点大小(在 Vivado 集成设计环境 (IDE) 中设置的变换长度值)。
扩展计划设置为 1/N。对于具有非四次幂点大小的 Radix-4 Burst I/O 和 Pipelined Streaming I/O 架构,最后一个阶段的缩放比例为 1,其余阶段的缩放比例为 2。见表 3- 1。
aresetn 引脚优先于 aclken。如果 aresetn 被置位,则无论 aclken 的值如何,都会发生复位。需要两个周期的最小aresetn 活动脉冲。
s_axis_config_tdata字段里面包含多个小字段,如下表。
小字段名 | 必须/可选 | 宽度 | 填充 | 描述 |
---|---|---|---|---|
NFFT | 可选 | 5 | 000/111 | 实际变换的点数 = 2NFFT,IDE中设置的是最大点数,NFFT的值可以比最大点数要小。例如,一个1024点的FFT可以计算的点数为1024,512,256或者更小。注意: 仅选中了run time configurable transform length(运行期间可配置变换长度)时,此字段才可用 |
CP_LEN | 可选 | log2(maxinum point size) | 是 | 循环前缀长度: 从转换结束起,在输出整个转换之前,最初作为循环前缀输出的样本数。CP_LEN可以是零 ~ 实际点大小(不包括) 之间的任何数字。 CP_LEN的前NFFT位是有效的,后面补0。 该字段仅在循环前缀插入时出现 |
FWD_INV | 必须 | FFT通道数 | – | 此字段指定FFT IP核是进行FFT变换还是IFFT变换。当FWD_INV = 1时,FFT变换;当FWD_INV = 0时,IFFT变换。此字段宽度等于通道数。bit0(LSB)表示通道0,bit1表示通道1,以此类推。 |
SCALE_SCH | 可选 | 见下方详解 | – | 见下发详解 |
这些字段构成的tdata的位宽必须是8的倍数,如果不是8的倍数,应该填充位到8的倍数。
IP核在读取字段值时会忽略填充位的值,所以填充位可以填任意值,但应该设为常量(通常是全0),这样可以减小资源消耗。
SACLE_SCH是可选的,只有当缩放选项选为Scaled时,SCALE_SCH才被需要,表示将输出数据缩放到与输入数据相同的位宽。在进行FFT运算时,有多次加法或乘法运算,运算结果的位宽会逐渐增加,最终导致最终输出结果的位宽增加。为了防止输出结果出现位宽溢出,需要对每一级的输入数据进行缩放,SACLE_SCH就表示每一级的缩放倍数,比如 SCALE_SCH = [01 10 00 11 10],以2位为隔断,从左向右数,10表示第一级缩放倍数=22=4,即右移两位。11表示第二级缩放倍数=23=8,即右移3位,以此类推。最终输出数据数据右移(1+2+0+3+2)=8位,也就是缩小了256倍,最终输出的结果要放大256倍才是正确的。
SACLE_SCH的位宽与运算架构和运算点数相关,对于流水线IO与蝶4突发IO,SACLE_SCH位宽=2×ceil(NFFT/2),ceil表示进一取整。例如NFFT = 11(对应FFT点数=2048),SACLE_SCH位宽=2×ceil(11/2)=2×6=12。对于蝶2突发IO与蝶2Lite突发IO,SACLE_SCH位宽=2×NFFT,例如NFFT = 11,SACLE_SCH位宽=22。
SACLE_SCH是指定的每一级的缩放倍数,当FFT点数不是4的n次幂时,最后一级只有2位,对应的缩放倍数最多为2,即此时SACLE_SCH的最高两位只能是00或者01,表示不缩放或缩放2倍。例如FFT点数 = 512,512 = 44×2,所以最后一级只有2位数,此时SACLE_SCH的最高两位只能是00或者01。
为完全防止数据溢出,应保证缩放倍数=2×FFT点数,一种可用的SACLE_SCH的取值方式为:
当FFT点数=1024,架构为蝶4突发IO时,SCALE_SCH = [10 10 10 10 11],对应总缩放倍数=2^(2+2+2+2+3)=2048,它完全避免了输出数据溢出。
同理,当当FFT点数=2048,架构为蝶4突发IO时,SCALE_SCH = [01 10 10 10 10 11],对应总缩放倍数=4096。
当FFT点数=1024,架构为蝶2突发IO时,SCALE_SCH = [01 01 01 01 01 01 01 01 01 10],对应总缩放倍数=2^(1×9+2)=2048,同样完全避免的输出数据溢出。
这些字段如何构成s_axis_config_tdata字段如下图所示,其中,PAD表示填充位,可选字段用虚线表示。
配置数据示例:
字段解读:
小字段名 | 填充 | 值 | 描述 |
---|---|---|---|
NFFT | 000 | 00011 | 实际变换的点数 = 2NFFT = 23 = 8 |
CP_LEN | 0 | 1000000 | CP_LEN位宽 = log2(maxinum point size),所以IDE中设置的变换长度N = 27 = 128。 因NFFT = 3,所以CP_LEN只有高3位有效,所以这里CP_LEN的值为100 = 4,后面4位是补的0。 |
FWD_INV | 无 | 100 | 通道0反变换,通道1反变换,通道2正变换 |
小字段 | 位宽 | 填充 | 描述 |
---|---|---|---|
XN_RE 输入数据实部 |
bxn | 0或1任选 (一般为全0) |
二进制补码或单精度浮点格式的实数分量,bxn = 8 ~ 34 |
XN_IM 输入数据虚部 |
bxn | 0或1任选 (一般为全0) |
二进制补码或单精度浮点格式的虚数分量,bxn = 8 ~ 34 |
多通道的s_axis_data_tdata字段如下图所示,其中虚线框表示可选,PAD表示填充。
输入数据示例:
小字段 | 位宽 | 填充 | 描述 |
---|---|---|---|
XK_RE 输出数据实部 |
bxk | 符号位扩展 | 二进制补码或浮点格式的实数分量。 当数据格式为定点数时: 1. 对于缩放算术和块浮点运算,输出数据位宽bxk = 输入数据位宽bxn 2. 对于未缩放算术,bxk = bxn + log2(最大变换点数) + 1 当数据格式为单精度浮点数时:bxk = 32 |
XK_IM 输出数据虚部 |
bxk | 符号位扩展 | 二进制补码或浮点格式的虚数分量。 其余同上 |
bxk必须是8的倍数,不是则符号位扩展为8的倍数。
多通道的m_axis_data_tdata字段如下图所示,其中虚线框表示可选,PAD表示符号位填充。
输出数据示例:
小字段 | 必须/可选 | 位宽 | 填充 | 描述 |
---|---|---|---|---|
XK_INDEX | 可选 | log2(maxinum point size) | 全0 | 输出数据的索引(无符号 2 的补码)。 此字段是可选的,仅在 IDE 中启用 XK_INDEX 时才包含 |
BLK_EXP | 可选 | 8 | 全0 | 块指数(无符号 2 的补码):应用的缩放量(即未缩放的输出值向下移动的位数)。 每个 FFT 通道都包含一个单独的 BLK_EXP 字段。 仅当使用块浮点时可用 |
OVFLO | 可选 | 1 | – | 算术溢出指示器(高电平有效):如果数据帧中的任何值溢出,则在结果卸载期间 OVFLO 为高电平。 OVFLO 信号在新数据帧开始时复位。 每个 FFT 通道都包含一个单独的 OVFLO 字段。 此字段是可选的,仅可用于缩放算术或单精度浮点 I/O |
所有构成tuser的字段都是可选的, 当一个也没有被选择时, tuser将自动从输出数据通道中去除。
多通道的m_axis_data_tuser字段如下图所示,其中虚线框表示可选,PAD表示0填充。
输出用户字段示例1:
输出用户字段示例2:
小字段 | 必须/可选 | 位宽 | 填充 | 描述 |
---|---|---|---|---|
BLK_EXP | 可选 | 5 | 全0 | 块指数(无符号 2 的补码):应用的缩放量(即未缩放的输出值向下移动的位数)。 每个 FFT 通道都包含一个单独的 BLK_EXP 字段。 仅当使用块浮点时可用 |
OVFLO | 可选 | 1 | – | 算术溢出指示器(高电平有效):如果数据帧中的任何值溢出,则在结果卸载期间 OVFLO 为高电平。 OVFLO 信号在新数据帧开始时复位。 每个 FFT 通道都包含一个单独的 OVFLO 字段。 此字段是可选的,仅可用于缩放算术或单精度浮点 I/O |
相比较输出用户字段,输出状态字段省略了XK_INDEX,它适用于不直接对数据进行操作但可能需要知道信息来控制系统另一部分的下游从站。
多通道的m_axis_status_tdata字段如下图所示,其中虚线框表示可选,PAD表示0填充。
输出状态示例:
配置选项 | 说明 |
---|---|
Number of Channels | 通道数,取值范围:1~12 |
Transform Length | 变换点数,取值范围:23 ~ 216 (8 ~ 65536) |
Target Clock Frequency | 目标时钟频率(MHz),取值范围:1 ~ 1000 |
Target Data Throughput | 目标数据比特率(MSPS),取值范围:1 ~ 1000 目标时钟频率和目标数据吞吐量仅用于自动选择的执行情况,并计算延迟。 不保证内核以指定的目标时钟频率或目标数据吞吐量运行 |
Automatically | 自动选择,由IP根据上面给定的时钟频率与比特率自行选择架构 |
Pipelined, Streaming I/O | 流水线IO,允许连续数据处理,仅当使用单通道时,流水线IO可选 |
Radix-4, Burst I/O | 蝶4突发IO,使用迭代方法分别加载和处理数据。它的资源消耗比流水线架构小,但转换时间更长 一点不同:蝶4架构的变换点数为64 ~ 65536,其它架构点数为 8 ~ 65536 |
Radix-2, Burst I/O | 蝶2突发IO,使用与 Radix-4 相同的迭代方法,但蝶形运算单元更小。 它的资源消耗比 Radix-4 架构的尺寸更小,但转换时间更长 |
Radix-2 Lite, Burst I/O | 蝶2-Lite突发IO,基于 Radix-2 架构,此变体使用分时复用方法实现蝶形运算单元, 以实现更少的资源消耗,但代价是转换时间更长 |
Run Time Configurable Transform Length | 选择运行时是否可配置转换长度。 当不可配置时,内核使用更少的逻辑资源并具有更快的最大时钟速度; 当可配置时,配置数据s_axis_config_tdata中的NFFT字段被激活, s_axis_config_tdata的位宽会增加8位 |
四种FFT架构的资源消耗与吞吐量的关系如下图所示。
配置选项 | 子选项 | 说明 |
---|---|---|
Data Format(数据格式) | Fixed Point | 定点数 |
Floating Point | IEEE-754 单精度(32 位)浮点格式。 仅当使用单通道时,浮点数可用; 使用多通道时,仅可选择定点数。 |
|
Scaling Options(缩放选项) (仅当数据格式选为定点数时可用) |
Block Floating Point | 块浮点,内核确定需要多少缩放才能充分利用可用动态范围,并将缩放因子报告为块指数 |
Scaled | 缩放,用户定义的缩放计划决定了数据在 FFT 阶段之间的缩放方式 | |
Unscaled | 不缩放,运算过程中的所有位增长都会叠加到输出。 输出实部位宽 = 输入实部位宽 + log2(变换点数) + 1。 虚部位宽 = 实部位宽 |
|
Rounding Modes(省入模式) (仅当数据格式选为定点数时可用) |
Truncation | 截断,小数部分直接丢弃 |
Convergent Rounding | 收敛省入,四省五入,特殊的: 当小数部分 = 0.5时,如果整数部分是奇数,则收敛舍入向上舍入, 如果整数部分为偶数,则向下舍入。 收敛舍入可用于避免 DC 偏置,但会增加资源使用量和延迟而导致转换时间略有增加 |
|
Precision Options(精度选项) | Input Date Width | 输入数据宽度,8 ~ 32;当数据格式为浮点数时固定为32 |
Phase Factor Width | 相位因子宽度,8 ~ 32;当数据格式为浮点数时仅可选24/25 | |
Control Signals(控制信号) | ACLKEN | 时钟使能 |
ARESETn | 同步复位,低电平有效且低电平至少要持续两个时钟周期 | |
Output Ordering(输出位序) | Bit/Digit Reversed Order | 位/数反转排序 |
Normal Order | 正常顺序 | |
Cycle Prefix Insertion | 循环前缀插入 | |
Optional Output Fields(可选输出字段) | XK_INDEX | 输出序号,输出数据通道m_axis_data_tuser中的可选字段 |
OVFLO | 数据溢出标志,输出数据通道m_axis_data_tuser中的可选字段, 也是输出状态通道m_axis_status_tdata中的可选字段 |
|
Throttle Scheme(节流方案) | Non Real Time | 非实时 |
Real Time | 实时 |
四种FFT架构都提供输出数据的 正常顺序 或 位/数反转排序 选项。
默认为位/数反转排序,此时数据以正常顺序输入时,但FFT 算法在处理过程中会对输入数据重新排序,所以输出数据会以位/数反转的顺序输出。
当选择位/数字反转顺序时,基于 Radix-2 的架构(流水线流 I/O、Radix-2 Burst I/O 和 Radix-2 Lite Burst I/O)提供位反转排序,而基于 Radix-4 的架构(Radix-4 Burst I/O) O) 提供数字反转排序。
当选中正常顺序时,对于流水线 I/O 架构,内核使用的内存会增加。对于突发 I/O 架构,因为需要单独的卸载阶段,整体转换时间会增加。
如果输出顺序是自然顺序,则可以选择循环前缀插入。循环前缀插入适用于所有架构,通常用于 OFDM 无线通信系统。
选择性能和数据定时需求之间的平衡。Real time模式通常提供更小、更快的设计,但是在必须提供和使用数据时有严格的限制。非实时模式没有这样的约束,但是设计可能会更大、更慢。
TODO,实时模式与非实时模式的区别
配置选项 | 子选项 | 说明 |
---|---|---|
Data | Block RAM | 使用块RAM存储数据,性能较好 |
Distributed RAM | 使用分布式RAM存储数据,资源消耗少 | |
Phase Factors (相位因子) |
Block RAM | 使用块RAM存储相位因子 |
Distributed RAM | 使用分布式RAM存储相位因子 | |
Number of stages using Block RAM for Data and Phase Factors (数据和相位因子使用块RAM的级数) |
– | 范围:1 ~ 5,流水线级数,越大运算速度越快,消耗资源越多 |
Reorder Buffer 重排序缓冲 |
Block RAM | 使用块RAM存储重排序的数据 |
Distributed RAM | 使用分布式RAM存储重排序的数据 | |
Complex Multipliers (复数乘法器) |
Use 3-multiplier structure(resource optimization) | 使用3乘法结构(资源优化) 所有复数乘法器都使用三个实数乘法、五个加/减结构,其中乘法器使用 DSP Slice。这减少了 DSP 片数,但使用了一些片逻辑。这种结构可以利用DSP Slice 预加器来减少或消除对额外Slice 逻辑的需求,并提高性能 |
Use 4-multiplier structure(performance optimization) | 使用4乘法结构(性能优化) 所有复数乘法器都使用四实数乘法、两个加/减结构,利用 DSP Slice。这种结构以牺牲更多专用乘法器为代价获得了最高的时钟性能。在带有 DSP Slice 的设备中,加/减操作在 DSP Slice 内实现 |
|
Use CLB Logic | 所有复数乘法器都是使用切片逻辑构造的。这适用于具有低性能要求的目标应用程序,或具有很少 DSP Slice 的目标设备。 | |
Butterfly Arithmetic (蝶形运算单元) |
Use CLB Logic | 所有蝶形运算单元都使用切片逻辑构建 |
Use Xtremedsp Slices | 此选项强制使用 DSP Slice 中的加法器/减法器实现所有蝶形运算单元 |
同上图。
可在IP配置界面的左侧mplementation Details子页查看到实现架构类型,变换点数,资源消耗情况,AXI Stram端口数据构成情况的信息。
可在Latency子页查看运算延迟,延迟指的是从上游主机提供第一个输入数据到最后一个输出数据输出完成的时间,它取决于IP核工作时钟频率,所以以时钟数的形式给出。
仿真FFT IP,掌握输入输出时序。IP配置如下图所示。
流水线架构,单通道,1024个定点数,不缩放,小数截断,输入数据位宽24,相位因子位宽16,正常顺序输出,非实时模式。
将FFT IP核的输入/输出端口信号引出,在testbench中进行赋值/观测。
说明:
根据傅里叶变换的公式,FFT的计算结果关于采样频率的一般对称。但在实际计算中,这种对称是不完全的。
计算结果的两半之间的不对称性在较大的变换点大小上更为明显。此外,噪声在较低频率段中更为突出。因此,赛灵思建议在执行实值 FFT 时使用输出数据的上半部分(N/2 + 1 ~ N 点,N表示变换点数)。(此处参考pg109-xfft第33页)
所以,总是使用N/2 + 1 ~ N之间的数据作为傅里叶变换的结果是有好处的。
以下matlab程序可以根据设置,生成各种单频率或是混合频率的波形,并转为二进制补码,注意实际值与二进制值的关系。
%{
* @Author : Xu Dakang
* @Email : XudaKang_up@qq.com
* @Date : 2021-07-05 15:18:49
* @LastEditors : Xu Dakang
* @LastEditTime : 2021-07-05 17:38:18
* @Filename :
* @Description :
%}
N = 256; % 采样频率
T_NUM = 10; % 波形周期数
BIT_WIDTH = 24; % 二进制位数
t = linspace(0, 1, N);
% y0 = 0.7 * sin(2 * pi * 1 * t);
y0 = 0.5 + 0.7 * sin(2 * pi * 1 * t) + 0.8 * sin(2 * pi * 2 * t);
% y0 = 0.7 * sin(2 * pi * 1 * t) + sin(2 * pi * 2 * t);
WAVE_MAX = 2; % 波形可能的最大值
y1 = round((2^(BIT_WIDTH - 1) - 1) * y0 / WAVE_MAX);
y = [];
for i = 0:T_NUM
y = [y, y1];
end
plot(y);
A = myDec2Bin(y, BIT_WIDTH);
filepath = './sin.txt';
myWriteTxt(A, filepath);
function yout_signed_bin_str = myDec2Bin(xin_int, BIT_WIDTH)
%{
* description:输入十进制整数矩阵,得到指定位数的二进制补码字符矩阵, 整数数值满足范围:-(2^(BIT_WIDTH - 1))~2^(BIT_WIDTH - 1) - 1
* @param xin_int 十进制数矩阵
* @param BIT_WIDTH 二进制位数
* return yout_signed_bin_str 有符号的指定位数的二进制字符矩阵
%}
% 判断输入参数是否合法
for i = 1:numel(xin_int)
if fix(xin_int(i)) ~= xin_int(i) % 输入应是整数
error('Error!, xin_int %d should be int, but there is %s', i, class(xin_int(i)));
elseif xin_int(i) < -(2^(BIT_WIDTH - 1)) || xin_int(i) > 2^(BIT_WIDTH -1) - 1 % 输入不能超过有符号BIT_WIDTH的范围
error('Error!, xin_int %d should be in %d ~ %d, but it is %d', ...
i, -(2^(BIT_WIDTH - 1)), 2^(BIT_WIDTH -1) - 1, xin_int(i));
end
end
yout_signed_bin_str = strings(size(xin_int)); % 创造一个与输入格式相同的字符串数组
for i = 1:numel(xin_int) % numel(A)表示矩阵A元素个数
yout_signed_bin_str(i) = dec2bin(xin_int(i) + (xin_int(i) < 0) * 2^BIT_WIDTH, BIT_WIDTH);
if xin_int(i) < 0
yout_signed_bin_str(i) = dec2bin((2^BIT_WIDTH + xin_int(i)), BIT_WIDTH);
else
yout_signed_bin_str(i) = dec2bin(xin_int(i), BIT_WIDTH);
end
end
end
%{
* @description:输入字符串矩阵, 每一行写入对应路径的文件中
* @param xin_str_matrix 字符串矩阵
* @return {*}
%}
function myWriteTxt(xin_str_matrix, filepath)
% 检查输入是否合法
if ~ischar(filepath) % filepath必须为字符向量
error('Error!, filepath should be string, but it is %s', class(filepath))
end
for i = 1:numel(xin_str_matrix)
if ~isstring(xin_str_matrix(i))
error('Error!, xin_str_matrix %d should be string matrix, but it is %s', i, class(xin_str_matrix(i)))
end
end
fid = fopen(filepath, 'w');
for i = 1:numel(xin_str_matrix)
fprintf(fid, xin_str_matrix(i));
if i ~= numel(xin_str_matrix) % 除最后一行外, 每行加换行符
fprintf(fid, '\r\n');
end
end
fclose(fid);
end
/*
* @Author : Xu Dakang
* @Email : [email protected]
* @Date : 2021-07-01 16:04:26
* @LastEditors : Xu Dakang
* @LastEditTime : 2021-07-05 16:53:26
* @Filename : myFFT_tb.sv
* @Description : testbench of myFFT
*/
module myFFT_tb ();
timeunit 1ns;
timeprecision 1ps;
logic aclk;
logic [47:0] din_tdata;
logic din_tlast;
logic din_tready;
logic din_tvalid;
logic [79:0] dout_tdata;
logic dout_tlast;
logic dout_tready;
logic dout_tvalid;
logic event_data_in_channel_halt;
logic event_data_out_channel_halt;
logic event_frame_started;
logic event_status_channel_halt;
logic event_tlast_missing;
logic event_tlast_unexpected;
logic [7:0] fft_config_tdata;
logic fft_config_tready;
logic fft_config_tvalid;
myFFT_wrapper myFFT_wrapper_u0(.*);
// 生成时钟
localparam CLKT = 4;
initial begin
aclk = 0;
forever #(CLKT / 2) aclk = ~aclk;
end
// 导入输入波形文件
// * 注意修改路径
string work_path = "D:/Onedrive/VivadoPrj/FFT/myFFT/myFFT.srcs/sim_1/new/";
string din_path = {work_path, "sin.txt"};
localparam DIN_WIDTH = 24;
localparam DATA_NUM = 2816; // 数据量, 也就是txt文件的行数, 如果此参数大于数据行数, 读取到的内容为不定态
logic signed [DIN_WIDTH-1 : 0] din_wave_data [DATA_NUM]; // 读取输入波形数据
initial begin
$readmemb(din_path, din_wave_data, 0, DATA_NUM-1); // vivado读取txt文件
end
initial begin
// 输入数据端口初始化
din_tdata = '0;
din_tlast = 1'b0;
din_tvalid = 1'b0;
// 配置端口初始化
fft_config_tvalid = 1'b0;
fft_config_tdata = '0;
// 输出数据端口初始化
dout_tready = 1'b1;
end
int file_din_re;
initial begin
file_din_re = $fopen({work_path, "fft_din_re.txt"});
#CLKT;
for (int i = 0; i < 1024; i++) begin
if (i == 1023)
din_tlast = 1'b1;
din_tvalid = 1'b1;
din_tdata = {24'b0, din_wave_data[i]};
$fdisplay(file_din_re, "%d", din_wave_data[i]);
wait(din_tready == 1'b1) #CLKT;
end
din_tvalid = 1'b0;
din_tlast = 1'b0;
$fclose(file_din_re);
#(CLKT * 3000) $stop;
end
//< 将输出实部虚部分别写入两个文件 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
// 得到输出结果,再写入到txt
logic signed [39 : 0] dout_re;
assign dout_re = dout_tdata[39 : 0];
logic signed [39 : 0] dout_im;
assign dout_im = dout_tdata[79 : 40];
int file_dout_re;
int file_dout_im;
initial begin
file_dout_re = $fopen({work_path, "fft_re.txt"});
file_dout_im = $fopen({work_path, "fft_im.txt"});
wait(dout_tlast) #(CLKT * 2);
$fclose(file_dout_re);
$fclose(file_dout_im);
end
always @(posedge aclk) begin
if (dout_tready && dout_tvalid) begin
$fdisplay(file_dout_re, "%d", dout_re);
$fdisplay(file_dout_im, "%d", dout_im);
end
end
//< 将输出实部虚部分别写入两个文件 ------------------------------------------------------------
endmodule
%{
* @Author:Xu Dakang
* @Email:XudaKang_up@qq.com
* @Date:2021 - 05 - 17 21:56:19
* @LastEditors : Xu Dakang
* @LastEditTime : 2021-07-05 17:41:41
* @Filename:
* @Description:
%}
Fs = 256; % 采样频率
T = 1 / Fs;
%% matlab根据输入数据进行fft并绘制频谱
file_din_re = './fft_din_re.txt';
din_re_id = fopen(file_din_re, 'r');
din_re = fscanf(din_re_id, '%d\n');
L = length(din_re);
f = Fs * (0:(L / 2)) / L;
din_X = din_re + 1i * 0;
din_Y = fft(din_X);
din_P1 = abs(din_Y) / L;
din_P2 = din_P1(1:L / 2 + 1);
din_P2(2:end - 1) = 2 * din_P2(2:end - 1); % 除了直流,其余频率幅值×2
figure;
set(gcf, 'position', [300 200 1200 600]);
subplot 121;
plot(f, din_P2);
title('matlab fft 频谱');
%% 接收Vivado的fft输出,绘制频谱
file_fft_im = './fft_im.txt';
file_fft_re = './fft_re.txt';
file_re_id = fopen(file_fft_re, 'r');
file_im_id = fopen(file_fft_im, 'r');
re = fscanf(file_re_id, '%d\n');
im = fscanf(file_im_id, '%d\n');
% 绘制单边频谱
Y = re + 1i * im;
P1 = abs(Y) / L;
P2 = [P1(1)', P1(end:-1:(L / 2 + 1))']; % 使用直流幅值与后半部分频谱
P2(2:end - 1) = 2 * P2(2:end - 1); % 除了直流,其余频率幅值×2
subplot 122;
plot(f, P2);
title('Vivado fft 频谱');
幅值0.7(对应24位2进制补码整数 2936012),频率1Hz,采样频率256,fft点数1024包含4个周期
对应频谱:
可见结果还是比较准确的,
幅值0.7(2936012)频率1Hz正弦 + 幅值0.9(3774873)频率2Hz正弦
对应频谱:
幅值0.5(2097152)的直流 + 幅值0.7(2936012)频率1Hz正弦 + 幅值0.8(3355443)频率10Hz正弦
对应频谱:
FFT v9.1 Xilinx IP解析 Vivado 2020.2工程.7z
欢迎大家关注我的公众号:徐晓康的博客,回复以下代码获取。
7463
建议复制过去不会码错字!
FFT IP的使用比较复杂,而且IP手册很长且繁琐,本次提炼了手册中关于IP使用的一些基本信息,省略了原理性介绍。最后通过仿真理清了此IP的输入输出数据格式与时序。
对于位/数反转排序以及循环前缀插入还没有深入理解,也没有使用到,有懂的欢迎评论交流。
徐晓康的博客持续分享高质量硬件、FPGA与嵌入式知识,软件,工具等内容,欢迎大家关注。