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
这条流水线的吞吐量受限于:
- 单个 FFT 内核的计算能力 —— HLS 综合后的
fft_2d内核有其固定的时钟频率和处理延迟 - DMA 搬运带宽 ——
dma_hls内核的数据吞吐能力 - 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_0 到 fft_2d_4,dma_hls_0 到 dma_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,不与其他实例共享 |
架构与数据流
系统拓扑图
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_2d 和 dma_hls 运行在不同频率:
fft_2d: 500 MHzdma_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 thestrmInp_from_colwiseFFTport is profiled to avoid too much data.
这意味着在实际分析中,你可能需要选择性监控关键端口,而非全部 20 个流接口(5 实例 × 4 端口)。
相关文档
- HLS 实现总览 —— 完整的构建和使用指南
- fft_2d 内核源码 —— 2D-FFT 计算内核的 HLS 实现
- dma_hls 内核源码 —— 数据搬运和转置内核的实现
- 单实例配置 —— 基础配置的详细解析
- 十实例配置 —— 最大并行度配置