🏠

fft_dma_data_movers 模块技术深度解析

概述

fft_dma_data_movers 模块是 Prime Factor FFT-1008 设计中的数据搬运门户,负责在 DDR4 内存与 AIE-ML 阵列之间建立高速数据通道。你可以把它想象成一个精心设计的"物流中转站"——它不负责计算(那是 AIE 内核的工作),也不负责数据重排(那是 permutation 内核的工作),它的唯一使命就是以正确的时序、正确的速率,将原始数据从内存搬入处理流水线,再将处理结果搬回内存

这个模块解决了异构计算系统中最基础却最关键的问题:如何弥合片外 DRAM 的延迟与片上 AI Engine 的计算吞吐量之间的差距。没有高效的数据搬运,再强大的计算单元也会因"断粮"而闲置。


问题空间与设计动机

为什么需要专门的 DMA 数据搬运器?

在 Versal AIE-ML 架构中,我们面临一个典型的"存储墙"问题:

  1. DDR4 内存带宽有限:外部 DRAM 访问延迟高(数百纳秒),突发传输虽能摊销延迟,但无法匹配 AIE 内核的单周期处理能力
  2. AIE 阵列需要持续数据流:DFT-7/9/16 内核以 312.5 MHz 时钟每周期处理 4 个样本(128-bit 宽接口),要求输入输出保持稳定的吞吐率
  3. 系统级同步需求:整个 PFA-1008 流水线包含多个阶段(输入置换 → DFT-7 → Transpose-0 → DFT-9 → Transpose-1 → DFT-16 → 输出置换),DMA 端点必须与这些阶段精确对齐

替代方案的权衡

方案 优点 缺点 为何未被选择
直接使用 AIE Memory Tile DMA 零 PL 资源占用 不支持复杂的模运算寻址模式 PFA 置换需要 mod(C×D×R + ...) 寻址,超出 Memory Tile BD 能力
纯 PL 实现所有数据移动 完全可定制 功耗高、开发复杂 违背了利用 AIE 进行密集计算的初衷
通用 Vitis 数据搬运器 即拿即用 无法针对 PFA-1008 的特定时序优化 需要精确的 latency padding 和 loop control

最终选择:使用 HLS 编写专用的轻量级 DMA Source/Sink 内核,专门针对 PFA-1008 的 1008 点变换尺寸、128-bit 数据宽度和循环控制需求进行优化。


核心抽象与心智模型

类比:剧院的票务与入场系统

想象 fft_dma_data_movers 是一个剧院的票务系统:

  • DDR4 内存 = 剧院外的停车场(大量存储,但进出缓慢)
  • PL BRAM 缓冲区 = 剧院大厅的等候区(容量有限,但快速 accessible)
  • AXI Stream 接口 = 通往观众席的入口闸机(稳定流速,一次一人/一组)
  • AIE 阵列 = 剧院内的表演舞台(需要观众按正确顺序入座)

pfa1008_dma_src(Source/DMA MM2S)= 票务发放员

  • 提前从停车场取出一批观众(load_buffer:DDR4 → BRAM)
  • 按照演出节奏,一批批放行观众入场(transmit:BRAM → AXI Stream)
  • 考虑演员化妆时间,故意延迟开场(LATENCY padding:等待 permutation 内核准备就绪)

pfa1008_dma_snk(Sink/DMA S2MM)= 散场引导员

  • 接收结束观看的观众(capture_streams:AXI Stream → BRAM)
  • 只记录指定场次的数据(loop_sel 过滤)
  • 最后将所有观众送回停车场(read_buffer:BRAM → DDR4)

关键抽象概念

┌─────────────────────────────────────────────────────────────────┐
│                    DATA SOURCE (DMA MM2S)                        │
│  ┌──────────┐    ┌─────────────┐    ┌──────────────────────┐   │
│  │ DDR4     │───→│ PL BRAM     │───→│ AXI Stream (sig_o)   │───┼──→ To Permute_I
│  │ (mem[])  │    │ (buff[DEPTH])│    │ 128-bit @ II=1       │   │
│  └──────────┘    └─────────────┘    └──────────────────────┘   │
│        ↑              ↑                      ↑                  │
│    m_axi port    local array           axis port                │
│   (burst read)   (ping-pong)          (streaming)               │
└─────────────────────────────────────────────────────────────────┘

┌─────────────────────────────────────────────────────────────────┐
│                    DATA SINK (DMA S2MM)                          │
│  ┌──────────────────────┐    ┌─────────────┐    ┌──────────┐   │
│  │ AXI Stream (sig_i)   │───→│ PL BRAM     │───→│ DDR4     │   │
│  │ 128-bit @ II=1       │    │ (buff[DEPTH])│    │ (mem[])  │   │
│  └──────────────────────┘    └─────────────┘    └──────────┘   │
│         ↑                           ↑                ↓          │
│     axis port                  local array      m_axi port      │
│    (streaming)                 (capture)       (burst write)    │
└─────────────────────────────────────────────────────────────────┘

架构详解与数据流

整体架构图

graph LR subgraph "Host/PS" HOST[Host Application
XRT Control] end subgraph "PL Region (Programmable Logic)" subgraph "DMA Data Movers" SRC[pfa1008_dma_src
Wrapper + load_buffer + transmit] SNK[pfa1008_dma_snk
Wrapper + capture_streams + read_buffer] end subgraph "Permutation Kernels" PERM_I[pfa1008_permute_i
Input Permutation] PERM_O[pfa1008_permute_o
Output Permutation] end end subgraph "AIE-ML Array" DFT7[DFT-7 Kernel] T0[Transpose-0
Memory Tile] DFT9[DFT-9 Kernel] T1[Transpose-1
Memory Tile] DFT16[DFT-16 Kernel] end DDR[(DDR4 Memory)] HOST -->|s_axilite config| SRC HOST -->|s_axilite config| SNK DDR <-->|m_axi burst| SRC DDR <-->|m_axi burst| SNK SRC -->|axis stream| PERM_I PERM_I -->|axis stream| DFT7 DFT7 --> T0 --> DFT9 --> T1 --> DFT16 DFT16 -->|axis stream| PERM_O PERM_O -->|axis stream| SNK

数据流详细追踪

1. 输入数据流(Source Path):DDR4 → AIE

// Step 1: Host 配置 loop_cnt 通过 s_axilite 接口
// Step 2: Wrapper 启动 DATAFLOW 区域
void pfa1008_dma_src_wrapper(mem, loop_cnt, sig_o) {
    // DATAFLOW pragma 启用任务级并行
    #pragma HLS DATAFLOW
    
    TT_DATA buff[DEPTH];  // 内部 BRAM 缓冲区
    
    // Stage 1: Burst read from DDR4 to BRAM
    load_buffer(mem, buff);  // 1008 cycles @ II=1
    
    // Stage 2: Stream from BRAM to AXI-Stream
    transmit(buff, sig_o, loop_cnt);  // loop_cnt * DEPTH + LATENCY cycles
}

时序分析

  • load_buffer:顺序读取 DDR4 的 1008 个 128-bit 字,II=1,耗时 ~3.2 μs @ 312.5 MHz
  • transmit:每个循环输出 1008 个样本,外加 LATENCY = NFFT/2 = 504 个零填充周期
  • 为什么需要 LATENCY padding? 下游的 permute_i 内核采用 ping-pong 缓冲,第一块数据的输出需要等待一个完整块的填充时间(1008/4 = 252 周期),加上 permute_o 的类似延迟,总共约 504 周期。DMA Source 必须预先推送 dummy 数据来"预热"管道,否则下游会出现气泡(bubble)。

2. 输出数据流(Sink Path):AIE → DDR4

void pfa1008_dma_snk_wrapper(mem, loop_sel, loop_cnt, sig_i) {
    #pragma HLS DATAFLOW
    
    TT_DATA buff[DEPTH];
    
    // Stage 1: Capture from AXI-Stream to BRAM
    capture_streams(buff, sig_i, loop_sel, loop_cnt);
    
    // Stage 2: Burst write from BRAM to DDR4
    read_buffer(mem, buff);
}

关键特性

  • loop_sel 参数允许选择性捕获:在多次迭代(loop_cnt)中,只保存指定那一次的结果到 DDR4
  • 这是验证和调试的关键功能——可以观察中间迭代的输出而不必存储所有数据

设计决策与权衡分析

1. 双缓冲(Ping-Pong)vs 单缓冲

代码体现

// permute_i 内核中的 ping-pong 实现
static TT_DATA buff[2][4][4][NFFT/4];  // 第一维是 ping/pong
static bool ping = 0;
// ...
ping = (last_wr == 1) ? !ping : ping;  // 自动切换

权衡

  • Ping-Pong(已选):读写可完全并行,无停顿,面积开销 ×2
  • 单缓冲:面积减半,但读写必须串行,吞吐量下降 50%

为何选择 Ping-Pong:PFA-1008 目标是 1 Gsps 吞吐率,任何停顿都会破坏实时性约束。额外的 BRAM 成本(50 个 BRAM)是可接受的。

2. DATAFLOW vs PIPELINE

Source/Sink 使用 DATAFLOW

#pragma HLS DATAFLOW
// load_buffer 和 transmit 作为独立任务并行执行

Permutation 内核使用 PIPELINE

#pragma HLS pipeline II=1
// 单个循环体内每周期处理一个样本

区分原因

  • DMA 数据搬运器有明确的生产者-消费者边界(DDR4→BRAM→Stream),适合任务级并行的 DATAFLOW
  • Permutation 内核是单一复杂操作(读→查表→多路选择→写),需要细粒度的指令级并行 PIPELINE

3. 128-bit 数据宽度选择

static constexpr unsigned NBITS = 128;  // 不是 32, 64, 或 256
typedef ap_uint<NBITS> TT_DATA;

数学依据

  • AIE-ML 时钟:1250 MHz
  • PL HLS 时钟:312.5 MHz(恰好是 1/4)
  • 要在 PL 侧维持与 AIE 侧相同的数据吞吐率,位宽必须是 4 倍:32-bit × 4 = 128-bit

这确保了跨时钟域传输时的带宽匹配,避免 FIFO 上溢或下溢。

4. Latency Padding 的精确计算

static constexpr int LATENCY = NFFT/2;  // 504 cycles
// 注释说明:
// pfa1008_permute_i:   NFFT/4+0 = 252 cycles
// pfa1008_permute_o:   NFFT/4+0 = 252 cycles
// Total:               504 cycles

设计洞察:这不是随意选择的数字,而是基于下游内核的确定性延迟计算得出。这种"预填充"策略比使用反压(back-pressure)机制更简单可靠,因为:

  • 避免了复杂的 ready/valid 握手逻辑
  • 确保了初始阶段的连续数据流
  • 代价是固定的 504 周期启动延迟(对于连续流式处理可忽略)

接口定义与协议

DMA Source (pfa1008_dma_src_wrapper)

端口 协议 方向 用途
mem[DEPTH] m_axi (bundle=gmem) 输入 DDR4 数据源,突发读取
loop_cnt s_axilite (bundle=control) 输入 重复传输次数,Host 配置
sig_o axis 输出 流向 permute_i 的 AXI-Stream
return s_axilite - 函数返回状态

DMA Sink (pfa1008_dma_snk_wrapper)

端口 协议 方向 用途
mem[DEPTH] m_axi (bundle=gmem) 输出 DDR4 数据目的地,突发写入
loop_sel s_axilite 输入 要保存的迭代索引(0-based)
loop_cnt s_axilite 输入 总迭代次数
sig_i axis 输入 来自 permute_o 的 AXI-Stream

新贡献者注意事项

1. 隐式契约与前置条件

load_buffer 的内存假设

void load_buffer(TT_DATA mem[DEPTH], TT_DATA (&buff)[DEPTH])
// 调用者必须保证:
// - mem 指向有效的 DDR4 映射区域
// - mem[0..DEPTH-1] 已初始化有效数据
// - 物理连续性确保突发传输效率

transmit 的时序假设

void transmit(..., const int& loop_cnt)
// 调用者必须保证:
// - loop_cnt > 0,否则无数据输出
// - 下游消费者(permute_i)已准备好接收,或在 504 周期内准备好

2. 潜在陷阱

陷阱 1:修改 LATENCY 值

static constexpr int LATENCY = NFFT/2;  // 不要擅自修改!

如果下游 permutation 内核的实现改变(如增加流水级数),这个值必须同步更新,否则会导致数据错位。

陷阱 2:忽略 restrict 关键字 虽然当前代码未显式使用 restrict,但如果未来优化添加指针别名注解,必须确保 membuff 确实不重叠。

陷阱 3:loop_sel 越界

// capture_streams 中的条件判断
if (ll == loop_sel) {  // 如果 loop_sel >= loop_cnt,永远不会命中
    buff[dd] = val;
}

没有运行时检查,loop_sel 必须由 Host 代码正确配置。

3. 调试技巧

C-Simulation 验证

cd hls/pfa1008_dma_src
make csim  # 验证基本功能

查看生成的 RTL 接口: 在 solution/syn/verilog/ 目录下检查:

  • ap_start/ap_done/ap_idle 信号(DATAFLOW 控制)
  • m_axi_gmem_* 信号(DDR 访问时序)
  • sig_o_T* 信号(AXIS 握手)

子模块详细文档

本模块由两个互补的 HLS Kernel 组成,各自有独立的详细文档:

子模块 功能 文档链接
DMA Source Kernel (pfa1008_dma_src) 从 DDR4 读取数据并通过 AXI Stream 发送到 AIE 阵列 dma_source_kernel.md
DMA Sink Kernel (pfa1008_dma_snk) 从 AIE 阵列接收 AXI Stream 数据并写回 DDR4 dma_sink_kernel.md

相关模块与依赖关系

上游依赖(本模块依赖)

模块 关系 说明
Host/XRT 控制 通过 s_axilite 配置 loop_cnt, loop_sel
DDR4 Controller 数据 通过 m_axi 进行突发传输

下游依赖(依赖本模块)

模块 关系 说明
pfa1008_permute_i 数据 接收 DMA Source 的输出流
pfa1008_permute_o 数据 输出到 DMA Sink 的输入流

同层级协作模块

  • fft_input_permutation_kernel / fft_output_permutation_kernel:相关的置换内核变体
  • prime_factor_fft_vitis_system_integration:系统集成层,将这些内核连接成完整设计

性能特征

指标 数值 备注
时钟频率 312.5 MHz hls.cfg 中的 clock=3.2ns 设定
数据位宽 128 bit 4 个 32-bit 样本/周期
峰值吞吐 400 MB/s 128-bit × 312.5 MHz
启动延迟 ~504 周期 LATENCY padding
BRAM 用量 ~25 per kernel 估算值,取决于 DEPTH
II (Initiation Interval) 1 理想情况下每周期一个样本

总结

fft_dma_data_movers 模块是整个 PFA-1008 系统的数据门户,其设计哲学是**"简单、确定、可预测"**:

  • 简单:只做一件事——搬运数据,不做计算、不做重排
  • 确定:latency padding 提供可预测的时序行为,便于系统级同步
  • 可预测:固定的吞吐率、固定的延迟、无动态内存分配

对于新加入团队的工程师,理解这个模块的关键在于认识到:它不是孤立的组件,而是更大管弦乐队中的一个声部。它的节奏必须与 permutation 内核的 ping-pong 切换、AIE 内核的计算节拍精确对齐。当你修改这里的任何参数(特别是 LATENCYDEPTH)时,务必检查整个信号链路的兼容性。

On this page