🏠

fft2d_aie_vs_hls_scaling_and_system_configs 模块技术深度解析

概述:这个模块解决什么问题?

想象你正在设计一个高性能计算系统,需要在 AMD Versal 自适应 SoC 上实现二维快速傅里叶变换(2D FFT)。你有两个主要选择:AI Engine(AIE)——专为 DSP 优化的矢量处理器阵列,或者 HLS(高层次综合)——将 C/C++ 代码转换为硬件逻辑。这个模块正是为了回答一个关键问题而存在的:"在相同的 2D FFT 工作负载下,AIE 和 HLS 两种实现方式如何扩展?它们的系统配置有何不同?"

这不是一个简单的性能对比测试。这是一个系统级集成框架,它展示了如何在同一硬件平台上构建、配置和扩展两种截然不同的计算范式。对于刚加入团队的工程师来说,理解这个模块意味着理解 Versal 架构的精髓——如何在可编程逻辑(PL)和 AI 引擎之间 orchestrate 数据流。

核心使命

  1. 提供可扩展的系统配置模板:从单实例(x1)到五实例(x5)再到十实例(x10),展示如何水平扩展 2D FFT 处理能力
  2. 统一 DMA 数据搬运层:无论底层是 AIE 还是 HLS 实现的 FFT,都使用相同的 dma_hls 内核进行数据传输
  3. 抽象 FFT 配置类型:通过模板化的配置结构体支持不同的数据类型(cint16/cfloat)和变换维度(行/列)

心智模型:像搭积木一样思考

把这个模块想象成一个模块化音频混音台

  • DMA 内核就像是输入/输出接口面板——它们负责把外部音频信号(DDR 内存中的数据)转换成调音台内部的线路电平(AXI4-Stream)
  • FFT 内核(无论是 AIE 还是 HLS 版本)就像是效果器单元——每个单元独立处理一路信号,执行行方向的 FFT 和列方向的 FFT
  • 配置文件.cfg)就像是跳线盘——它定义了哪个输入连接到哪个效果器,以及效果器之间如何串联

关键洞察:DMA 层是通用的,FFT 层是可替换的。这种设计允许你在不改变数据搬运基础设施的情况下,切换或比较不同的 FFT 实现。

数据流的双向对称性

每个 FFT 实例都有四条数据路径,形成一个完整的"请求-响应"循环:

DDR → DMA → 行FFT输入 → 行FFT输出 → DMA → DDR
                ↓
            [转置/重排]
                ↓
DDR → DMA → 列FFT输入 → 列FFT输出 → DMA → DDR

这就像是工厂的流水线:原材料(DDR 数据)进入第一道工序(行 FFT),中间产品经过内部转运(转置),进入第二道工序(列 FFT),最终成品返回仓库(DDR)。


架构详解与数据流追踪

整体架构图

graph TB subgraph "Host / DDR Memory" HOST[Host Application] DDR[DDR Memory Buffer] end subgraph "PL - Programmable Logic" subgraph "DMA Layer (Common)" DMA0[dma_hls_0
MM2S/S2MM] DMA1[dma_hls_1] DMA9[... dma_hls_N] end subgraph "HLS FFT Layer (Alternative 1)" HLS0[fft_2d_0
Row + Col FFT] HLS1[fft_2d_1] HLS9[... fft_2d_N] end end subgraph "AIE - AI Engine Array" AIE0[ai_engine_0
Graph Instance] end %% Data Flow for HLS Path HOST -->|Configure| DMA0 HOST -->|Configure| HLS0 DDR -->|MM2S| DMA0 DMA0 -->|strmOut_to_rowiseFFT| HLS0 HLS0 -->|strmFFTrows_out| DMA0 DMA0 -->|S2MM| DDR DMA0 -->|strmOut_to_colwiseFFT| HLS0 HLS0 -->|strmFFTcols_out| DMA0 %% Data Flow for AIE Path DMA0 -.->|strmOut_to_rowiseFFT| AIE0 AIE0 -.->|DataOut0| DMA0 DMA0 -.->|strmOut_to_colwiseFFT| AIE0 AIE0 -.->|DataOut1| DMA0 style DMA0 fill:#e1f5ff style HLS0 fill:#fff4e1 style AIE0 fill:#f0ffe1

子模块职责划分

子模块 职责 关键技术
aie_dma_scaling_system_configs AIE 版本的系统连接配置(x1/x5/x10) .cfg 文件中的 stream_connect 指令
hls_fft2d_configuration_types HLS FFT 核的配置结构体和类型定义 hls::ip_fft::params_t 模板特化
hls_x1_single_instance_system_config HLS 单实例系统配置 单个 fft_2d + dma_hls 配对
hls_x5_multi_instance_system_config HLS 五实例并行配置 五个独立的 FFT-DMA 通道对
hls_x10_multi_instance_system_config HLS 十实例并行配置 十个独立的 FFT-DMA 通道对,最大规模测试

核心组件深度分析

1. HLS FFT 配置类型系统 (configRow / configCol)

位于 fft_2d.h 中的这两个结构体是整个 HLS FFT 实现的类型基石。它们通过模板特化 hls::ip_fft::params_t 来配置 Xilinx FFT IP 核的行为。

struct configRow : hls::ip_fft::params_t {
    static const unsigned ordering_opt = hls::ip_fft::natural_order;
    static const unsigned config_width = FFT_ROWS_CONFIG_WIDTH;
    static const unsigned max_nfft = FFT_ROWS_NFFT_MAX;
    static const unsigned stages_block_ram = FFT_ROWS_STAGES_BLK_RAM;
    static const unsigned input_width = FFT_INPUT_WIDTH;
    static const unsigned output_width = FFT_OUTPUT_WIDTH;
};

设计意图解读

  • ordering_opt = natural_order:输出保持自然顺序而非位反转顺序。这简化了后续处理,但可能增加一点延迟。
  • max_nfft:定义 FFT 的最大长度(以 2 的幂次表示)。这是编译时常量,决定了旋转因子表的大小和蝶形运算级的数量。
  • stages_block_ram:控制哪些阶段使用 Block RAM 存储中间结果。这是典型的面积 vs 性能权衡——更多 BRAM 意味着更少的数据竞争和更高的时钟频率。

条件编译的智慧

代码通过 FFT_2D_DT 宏支持两种数据类型:

  • FFT_2D_DT == 0(cint16):定点复数,16 位实部 + 16 位虚部,适合资源受限场景
  • FFT_2D_DT != 0(cfloat):浮点复数,32 位 IEEE-754,适合精度敏感场景

浮点版本额外配置了 scaling_opt = block_floating_pointphase_factor_width = 24,这是为了防止动态范围溢出同时保持较高的相位精度。

2. 系统配置文件(.cfg)的连接语义

配置文件使用 Vitis 的 connectivity 语法定义硬件连接。让我们解剖 x5.cfg 中的一个典型连接:

stream_connect=dma_hls_0.strmOut_to_rowiseFFT:fft_2d_0.strmFFTrows_inp

这行代码建立了从 DMA 到 FFT 的单向 AXI4-Stream 连接

  • 源端dma_hls_0 内核的 strmOut_to_rowiseFFT 端口(MM2S 方向,Memory-to-Stream)
  • 目的端fft_2d_0 内核的 strmFFTrows_inp 端口(接收行数据输入)

四个连接构成一个完整的事务周期

连接 方向 数据内容
dma_hls_X.strmOut_to_rowiseFFTfft_2d_X.strmFFTrows_inp DDR → FFT 原始图像/矩阵的行数据
fft_2d_X.strmFFTrows_outdma_hls_X.strmInp_from_rowiseFFT FFT → DDR 行 FFT 后的频域数据
dma_hls_X.strmOut_to_colwiseFFTfft_2d_X.strmFFTcols_inp DDR → FFT 转置后的列数据
fft_2d_X.strmFFTcols_outdma_hls_X.strmInp_from_colwiseFFT FFT → DDR 最终的 2D FFT 结果

3. 扩展模式:从 x1 到 x10

配置文件展示了三种扩展策略:

x1(基准测试)

  • 用途:功能验证、时序收敛测试、资源基线测量
  • 特点:最简单的连接关系,最容易调试

x5(中等并行度)

  • 用途:探索多实例的资源开销和带宽瓶颈
  • 特点:五个完全独立的 FFT 通道,可以处理五个独立的图像帧

x10(最大压力测试)

  • 用途:验证平台的极限吞吐量、测试 NoC(Network-on-Chip)的仲裁能力
  • 特点:需要仔细规划 DDR 访问模式以避免 bank 冲突

关键观察:所有配置都设置了 param=hw_emu.enableProfiling=false。这是因为硬件仿真(hw_emu)模式下,profiling 会显著降低仿真速度。只有在实际硬件运行时才启用 profiling。


设计决策与权衡分析

决策 1:共享 DMA 内核 vs 每 FFT 实例专用 DMA

选择的方案:每个 FFT 实例拥有专用的 DMA 内核对(一个 MM2S,一个 S2MM,但在 dma_hls 中合并为一个内核实例)

为什么这样设计

  • 隔离性:一个 FFT 实例的内存访问不会阻塞其他实例
  • 可预测性:每个通道的延迟和带宽是独立的,便于实时系统分析
  • 简化连接配置:不需要复杂的仲裁逻辑或多路复用器

放弃的替代方案:共享 DMA 控制器通过时分复用服务多个 FFT 实例

  • 优点:更少的 DMA 内核实例,节省 LUT/FF 资源
  • 缺点:需要复杂的调度逻辑,可能引入不可预测的延迟抖动

决策 2:HLS 行/列分离 vs 一体化 2D FFT

选择的方案:在 HLS 层面分别实现 fft_rowsfft_cols,由外部调用者(或 DMA 回环)处理转置

为什么这样设计

  • 模块化:行 FFT 和列 FFT 可以独立优化和测试
  • 内存效率:不需要在 FPGA 上存储整个矩阵来进行内部转置
  • 灵活性:允许不同的行/列长度配置(非方阵 FFT)

权衡代价

  • 需要额外的 DDR 往返来存储中间结果(行 FFT 输出)
  • 增加了总线带宽压力

决策 3:AIE 与 HLS 使用相同的 DMA 抽象

选择的方案dma_hls 内核同时服务于 AIE 和 HLS FFT 路径

架构意义

  • 这是真正的 apples-to-apples 比较的基础——性能差异仅来自 FFT 实现本身,而非数据搬运
  • 允许主机软件使用统一的 API 来驱动两种后端

实现细节:注意 AIE 配置中使用 ai_engine_0.DataIn0 这样的端口名,而 HLS 配置中使用 fft_2d_0.strmFFTrows_inp。这反映了底层硬件的不同(AIE 的 graph 端口 vs HLS 内核的 stream 端口),但 DMA 端的接口保持一致。

决策 4:编译时常量 vs 运行时参数

观察 fft_2d.h 中的设计:FFT 长度、数据宽度等都是 #definestatic const,而非函数参数。

为什么

  • HLS 需要这些常量在编译时确定以生成优化的硬件结构
  • 运行时变化的参数会导致更保守(面积更大、速度更慢)的实现

代价

  • 需要为不同的 FFT 尺寸重新编译
  • 无法在同一比特流中支持多种 FFT 配置

新贡献者必读:陷阱与注意事项

1. 数据类型一致性陷阱

FFT_2D_DT 宏必须在整个编译单元中保持一致。如果在 fft_2d.h 和调用它的代码中定义不同,会导致未定义行为——通常是数据解释错误(把 float 当成 int16 读取)。

建议:在项目构建系统中明确定义此宏,不要在单个文件中覆盖。

2. Stream 深度与死锁

虽然配置文件中没有显式设置 FIFO 深度,但 Vitis 会为每个 stream_connect 插入默认深度的 AXI4-Stream FIFO。如果 FFT 内核的处理速度低于 DMA 的突发传输速率,FIFO 可能溢出。

调试技巧:如果在 hw_emu 中看到挂起,检查是否有 TREADY 持续为低的情况。

3. 地址对齐要求

dma_hls 内核通常对 DDR 访问有对齐要求(如 64 字节或 128 字节边界)。未对齐的缓冲区可能导致:

  • 性能下降(非突发传输)
  • 或直接 DMA 错误

最佳实践:使用 posix_memalign 或类似的页对齐分配器。

4. AIE 与 HLS 的端口命名差异

概念 AIE 配置 HLS 配置
行输入 ai_engine_0.DataIn0 fft_2d_0.strmFFTrows_inp
行输出 ai_engine_0.DataOut0 fft_2d_0.strmFFTrows_out
列输入 ai_engine_0.DataIn1 fft_2d_0.strmFFTcols_inp
列输出 ai_engine_0.DataOut1 fft_2d_0.strmFFTcols_out

常见错误:复制粘贴配置时忘记修改端口名前缀。

5. 多实例配置的编号连续性

在 x5 和 x10 配置中,实例编号必须是连续的(0,1,2,3,4...)。Vitis 链接器会验证这一点,但错误信息可能不够直观。

6. restrict 关键字的重要性

在 HLS 代码中(虽然没有在提供的片段中显示,但在完整的 fft_2d.cpp 中),指针参数应该使用 __restrict__restrict 修饰。这告诉 HLS 工具这些指针不会别名,允许更激进的流水线优化。


与其他模块的关系

本模块是 AIE_Design_System_Integration 教程的一部分,专注于 2D FFT 这一特定工作负载的系统集成。它与以下模块有密切联系:


总结

fft2d_aie_vs_hls_scaling_and_system_configs 模块是一个精心设计的系统集成教学框架。它不仅仅展示如何运行 2D FFT,更重要的是展示了如何在 Versal 平台上进行有意义的架构决策

  1. 抽象的力量:通过统一的 DMA 层屏蔽底层实现差异
  2. 扩展的艺术:从 x1 到 x10 的渐进式复杂度增长
  3. 比较的公平性:确保性能对比只反映 FFT 实现本身的差异

对于新加入的工程师,理解这个模块意味着掌握了 Versal 系统设计的核心思维模式:分解问题、抽象接口、渐进验证、量化比较

On this page