🏠

hls_x5_multi_instance_system_config 模块深度解析

一句话概述

x5.cfg 是一个 Vitis 编译器配置文件,它定义了 5 个并行的 2D-FFT 计算流水线实例 的系统拓扑结构。想象一下,这就像在一个工厂里部署了 5 条完全相同的生产线,每条线独立处理自己的数据流,从而将整体吞吐量提升 5 倍。这个配置文件的职责是告诉 Vitis 链接器:实例化哪些内核、如何命名它们、以及如何用 AXI4-Stream 通道将它们连接起来。


问题空间:为什么需要多实例配置?

单实例的瓶颈

hls_x1_single_instance_system_config 中,系统只有一条 FFT 处理流水线:

DDR → dma_hls_0 → fft_2d_0 (行FFT → 转置 → 列FFT) → dma_hls_0 → DDR

这条流水线的吞吐量受限于:

  1. 单个 FFT 内核的计算能力 —— HLS 综合后的 fft_2d 内核有其固定的时钟频率和处理延迟
  2. DMA 搬运带宽 —— dma_hls 内核的数据吞吐能力
  3. NoC/DDR 带宽 —— 与外部存储器的交互速度

当单条流水线的处理能力无法满足应用需求时(例如实时视频处理、大规模雷达信号处理),我们需要横向扩展。

多实例并行化的设计洞察

Versal ACAP 架构的优势在于其高度可编程的逻辑资源(PL)和 AI 引擎阵列。与其试图优化单条流水线到极致(可能遇到架构瓶颈),不如复制多条独立流水线并行工作。这类似于 CPU 从单核向多核演进的思想——通过增加处理单元数量来线性提升吞吐量。

x5.cfg 正是实现这一策略的配置文件,它创建了 5 条独立的处理流水线,每条流水线包含一个 fft_2d 内核和一个配套的 dma_hls 数据搬运内核。


核心抽象与心智模型

类比:快递分拣中心的并行流水线

想象一个大型快递分拣中心:

  • 单实例 (x1.cfg):只有一个分拣台,所有包裹都经过同一个入口→分拣→出口的流程。如果包裹量激增,这个分拣台会成为瓶颈。

  • 五实例 (x5.cfg):建造了 5 个完全相同的分拣台,每个分拣台有自己的入口传送带和出口传送带。包裹可以被分配到任意一个空闲的分拣台处理,整体处理能力提升 5 倍。

在这个类比中:

  • dma_hls_N = 第 N 个分拣台的入口/出口传送带(负责把包裹运进来和运出去)
  • fft_2d_N = 第 N 个分拣台的核心分拣机械(执行实际的 2D-FFT 计算)
  • stream_connect = 连接传送带和分拣机械的物理管道

关键抽象概念

概念 含义 在本模块中的体现
Kernel Instance (内核实例) 一个内核的特定副本,有唯一名称 fft_2d_0fft_2d_4dma_hls_0dma_hls_4
Compute Unit (CU) 硬件上的一个内核实例化 每个 fft_2d_N 对应一个独立的硬件 CU
Stream Connection AXI4-Stream 点对点连接 stream_connect=dma_hls_0.strmOut_to_rowiseFFT:fft_2d_0.strmFFTrows_inp
Dataflow Isolation 各实例间的数据流完全隔离 每个实例的 DMA 只连接到自己的 FFT,不与其他实例共享

架构与数据流

系统拓扑图

graph TB subgraph "Instance 0" D0[dma_hls_0] -->|strmOut_to_rowiseFFT| F0R[fft_2d_0
Row FFT] F0R -->|strmFFTrows_out| D0R[strmInp_from_rowiseFFT] D0R -.->|Internal
Transpose| D0C[strmOut_to_colwiseFFT] D0C -->|strmFFTcols_inp| F0C[fft_2d_0
Col FFT] F0C -->|strmFFTcols_out| D0 end subgraph "Instance 1" D1[dma_hls_1] -->|strmOut_to_rowiseFFT| F1R[fft_2d_1
Row FFT] F1R -->|strmFFTrows_out| D1R[strmInp_from_rowiseFFT] D1R -.->|Internal
Transpose| D1C[strmOut_to_colwiseFFT] D1C -->|strmFFTcols_inp| F1C[fft_2d_1
Col FFT] F1C -->|strmFFTcols_out| D1 end subgraph "Instance 2" D2[dma_hls_2] -->|strmOut_to_rowiseFFT| F2R[fft_2d_2
Row FFT] F2R -->|strmFFTrows_out| D2R[strmInp_from_rowiseFFT] D2R -.->|Internal
Transpose| D2C[strmOut_to_colwiseFFT] D2C -->|strmFFTcols_inp| F2C[fft_2d_2
Col FFT] F2C -->|strmFFTcols_out| D2 end subgraph "Instance 3" D3[dma_hls_3] -->|strmOut_to_rowiseFFT| F3R[fft_2d_3
Row FFT] F3R -->|strmFFTrows_out| D3R[strmInp_from_rowiseFFT] D3R -.->|Internal
Transpose| D3C[strmOut_to_colwiseFFT] D3C -->|strmFFTcols_inp| F3C[fft_2d_3
Col FFT] F3C -->|strmFFTcols_out| D3 end subgraph "Instance 4" D4[dma_hls_4] -->|strmOut_to_rowiseFFT| F4R[fft_2d_4
Row FFT] F4R -->|strmFFTrows_out| D4R[strmInp_from_rowiseFFT] D4R -.->|Internal
Transpose| D4C[strmOut_to_colwiseFFT] D4C -->|strmFFTcols_inp| F4C[fft_2d_4
Col FFT] F4C -->|strmFFTcols_out| D4 end

配置文件逐段解析

1. 内核实例化声明

nk=fft_2d:5:fft_2d_0.fft_2d_1.fft_2d_2.fft_2d_3.fft_2d_4
nk=dma_hls:5:dma_hls_0.dma_hls_1.dma_hls_2.dma_hls_3.dma_hls_4

语法解析nk=<kernel_name>:<count>:<instance_names>

  • fft_2d:5 —— 从 fft_2d 内核生成 5 个实例
  • fft_2d_0.fft_2d_1... —— 这 5 个实例的具体命名,用点号分隔

设计意图:Vitis 链接器会为每个实例创建独立的硬件 Compute Unit (CU),每个 CU 有自己的地址空间和接口。

2. 流式连接配置

对于每个实例 N(0-4),配置定义了 4 条 AXI4-Stream 连接:

# 行方向 FFT 数据流
stream_connect=dma_hls_N.strmOut_to_rowiseFFT:fft_2d_N.strmFFTrows_inp
stream_connect=fft_2d_N.strmFFTrows_out:dma_hls_N.strmInp_from_rowiseFFT

# 列方向 FFT 数据流  
stream_connect=dma_hls_N.strmOut_to_colwiseFFT:fft_2d_N.strmFFTcols_inp
stream_connect=fft_2d_N.strmFFTcols_out:dma_hls_N.strmInp_from_colwiseFFT

连接语义

  • source_port:destination_port 格式
  • 源端口必须是一个输出流(由内核写入)
  • 目标端口必须是一个输入流(被内核读取)
  • Vitis 链接器会在两者间插入 AXI4-Stream 互联逻辑

数据流路径

┌─────────────────────────────────────────────────────────────┐
│                     单个实例的数据流循环                       │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│   ┌──────────┐    strmOut_to_rowiseFFT    ┌──────────┐     │
│   │          │ ─────────────────────────> │          │     │
│   │ dma_hls_N│                            │ fft_2d_N │     │
│   │          │ <───────────────────────── │          │     │
│   │          │    strmFFTrows_out         │  行FFT   │     │
│   │          │                            └────┬─────┘     │
│   │  (内部    │                                 │           │
│   │  转置操作)│    strmOut_to_colwiseFFT    ┌───┴─────┐     │
│   │          │ ─────────────────────────> │         │     │
│   │          │                            │ fft_2d_N │     │
│   │          │ <───────────────────────── │          │     │
│   │          │    strmFFTcols_out         │  列FFT   │     │
│   └──────────┘                            └──────────┘     │
│                                                             │
└─────────────────────────────────────────────────────────────┘

3. 高级参数配置

[advanced]
param=hw_emu.enableProfiling=false
param=compiler.addOutputTypes=hw_export
  • hw_emu.enableProfiling=false —— 在硬件仿真中禁用性能分析,加速仿真运行
  • compiler.addOutputTypes=hw_export —— 额外导出 XSA 文件,用于后续创建固定平台

组件依赖关系

上游依赖(谁使用此配置)

调用者 关系类型 说明
HLS/Makefile 构建时引用 FFT_2D_INSTS=5 时,Makefile 选择 x5.cfg 作为 --config 参数
Vitis Compiler (v++ -l) 链接阶段消费 链接器解析此文件,生成包含 5 个 CU 的 XSA

下游依赖(此配置引用的组件)

被引用组件 关系类型 说明
fft_2d 内核定义 fft_2d 内核的源代码,定义了端口 strmFFTrows_inp/out, strmFFTcols_inp/out
dma_hls 内核定义 dma_hls 内核的源代码,定义了端口 strmOut_to_rowiseFFT, strmInp_from_rowiseFFT

兄弟配置(同族其他实例数配置)

配置文件 实例数 适用场景
x1.cfg 1 功能验证、资源受限、低吞吐量需求
x5.cfg 5 中等并行度,平衡吞吐量与资源消耗
x10.cfg 10 最大吞吐量,最高资源占用

设计决策与权衡

1. 实例数选择:为什么是 5?

背景:该教程支持 1、5、10 三种实例数配置。

设计考量

  • 5 是一个"甜点"值:相比单实例提供 5 倍吞吐量,但资源占用仅为 10 实例的一半
  • Versal 器件的资源约束:VCK190 平台的 PL 资源和 NoC 带宽有限,5 实例能在大多数矩阵尺寸下成功布局布线
  • 对比基准:5 实例提供了介于单实例和最大配置之间的中间数据点,便于分析扩展效率

替代方案:理论上可以支持 2、3、4、6 等其他实例数,但教程选择了 1/5/10 这三个具有代表性的配置来展示扩展趋势。

2. 完全隔离 vs 资源共享

当前设计(完全隔离)

  • 每个 fft_2d_N 只连接到自己的 dma_hls_N
  • 各实例间没有数据交换或共享资源

优势

  • 确定性时序:一个实例的拥塞不会影响其他实例
  • 简化调试:可以独立验证每个实例
  • 线性扩展:理想情况下吞吐量与实例数成正比

代价

  • 更高的资源占用:每个实例都有自己的 DMA 逻辑
  • 无法利用实例间的负载均衡(如果一个实例空闲,不能帮助处理其他实例的数据)

未选择的替代方案

  • 共享 DMA:多个 FFT 实例共享一个高带宽 DMA,通过仲裁访问。这会降低资源占用,但可能引入竞争和复杂的时序分析。

3. Stream vs Memory 接口

当前设计使用纯 AXI4-Stream 连接(axis 接口):

// fft_2d.cpp
#pragma HLS interface axis port=strmFFTrows_inp
#pragma HLS interface axis port=strmFFTrows_out

选择理由

  • 流式接口天然适合流水线处理,无需地址管理开销
  • dma_hls 内核内部实现了数据重组(行→列转置),不需要随机内存访问
  • 避免了 AXI4-Full 接口的地址生成逻辑,节省资源

权衡

  • 数据必须在 PL 内完成全部重组,不能利用 DDR 作为中间缓冲
  • 不适合需要随机访问或缓存大数据集的场景

4. ap_ctrl_none 控制模式

#pragma HLS interface ap_ctrl_none port=return

这意味着 fft_2d 内核启动后持续运行,不需要 PS 的显式启动/停止控制。

优势

  • 零控制开销:数据到达即处理,最小化延迟
  • 适合流式数据处理场景

风险

  • 无法通过标准 XRT API 查询内核状态
  • 如果上游断流,内核会阻塞等待;如果下游阻塞,内核会反压上游

使用指南

构建命令

cd AI_Engine_Development/AIE/Design_Tutorials/06-fft2d_AIEvsHLS/HLS

# 使用 5 实例配置构建(默认是 hw_emu 目标)
make run FFT_2D_INSTS=5

# 指定具体参数
make run TARGET=hw FFT_2D_DT=0 FFT_2D_INSTS=5 ITER_CNT=16 FFT_2D_PT=2048

配置参数说明

参数 可选值 说明
FFT_2D_INSTS 1, 5, 10 选择对应的 .cfg 文件
FFT_2D_DT 0 (cint16), 1 (cfloat) 数据类型,影响位宽和精度
FFT_2D_PT 64, 128, 256, 512, 2048 FFT 点数,决定矩阵维度
MAT_ROWS x MAT_COLS 32x64 到 1024x2048 自动根据 FFT_2D_PT 计算

链接阶段的配置使用

在 Makefile 中,x5.cfg 的使用方式:

make xsa:
    v++ -l \
        --platform xilinx_vck190_base_202420_1 \
        --config $(SYSTEM_CONFIGS_REPO)/x5.cfg \
        -t $(TARGET) \
        -o \((BUILD_TARGET_DIR)/vck190_hls_fft_2d.\)(TARGET).xsa \
        \((BUILD_TARGET_DIR)/fft_2d.\)(TARGET).xo \
        \((BUILD_TARGET_DIR)/dma_hls.\)(TARGET).xo

潜在陷阱与注意事项

1. 时钟域交叉 (CDC)

fft_2ddma_hls 运行在不同频率:

  • fft_2d: 500 MHz
  • dma_hls: 250 MHz

配置文件中虽然没有直接体现,但 Makefile 的链接命令设置了时钟:

--clock.freqHz 500000000:fft_2d_0 --clock.freqHz 250000000:dma_hls_0

注意:当实例数增加到 5 时,需要确保所有 5 个 fft_2d_N 实例和 5 个 dma_hls_N 实例都有正确的时钟分配。链接脚本使用通配符或循环来处理这一点。

2. 资源利用率上限

5 实例配置的资源占用约为单实例的 5 倍(加上少量互联开销)。在较大的矩阵尺寸(如 1024x2048)下,这可能接近 VCK190 的 LUT/BRAM/DSP 上限。

症状:布局布线失败,或时序收敛困难。

缓解措施

  • 使用 Vivado 策略调整(如 README 中提到的 Performance_HighUtilSLRs
  • 降级到更小的矩阵尺寸
  • 减少实例数

3. 主机程序适配

当实例数从 1 变为 5 时,主机程序需要相应调整:

  • 为每个实例分配独立的输入/输出缓冲区
  • 使用 xrt::run 对象分别启动每个 CU,或使用 xrt::run::set_arg 设置 CU 掩码
  • 处理 5 倍的并发数据传输

参考实现:查看 host_app_src 目录下的主机代码,了解如何遍历多个 CU。

4. 调试复杂性

5 实例系统的调试比单实例复杂得多:

  • 波形文件更大,需要关注 5 组独立的信号
  • 死锁可能发生在任意一个实例的任意一个流接口上
  • 推荐使用 --save-temps 保留中间文件,便于分析问题

5. 性能分析的采样点选择

启用性能分析 (EN_TRACE=1) 时,5 实例会产生大量分析数据。README 建议:

For higher values of FFT_2D_INSTS, only the strmInp_from_colwiseFFT port is profiled to avoid too much data.

这意味着在实际分析中,你可能需要选择性监控关键端口,而非全部 20 个流接口(5 实例 × 4 端口)。


相关文档

On this page