🏠

system.cfg 技术深度解析

概述:模块存在的意义

想象你正在设计一个交响乐团——乐手(AI Engine 核)已经就位,乐器(计算逻辑)也已准备就绪,但如果没有指挥家的总谱来规定谁何时演奏、如何配合,整个乐团只会发出嘈杂的噪音。system.cfg 就是这个"数字交响乐团"的总谱:它不是一个可执行程序,而是一份系统级连接配置契约,定义了 AI Engine 阵列与可编程逻辑(PL)之间的数据通路、时钟域和调试接口。

这个配置文件解决的核心问题是:在异构计算架构中,如何让多个独立编译的子系统(AI Engine Graph、HLS PL 核、Host 控制程序)在链接阶段正确地"握手"。没有它,Vitis 编译器无法知道 datagen 核的哪个输出应该连接到 AI Engine 的哪个输入端口,也无法确定系统中应该实例化多少个 s2ss 核副本。


架构角色与数据流

心智模型:机场航站楼的中转系统

system.cfg 想象成机场航站楼的航班调度系统:

  • 航空公司(nk 指令):定义有哪些航班公司运营,以及每家公司有多少架班机。例如 nk=s2ss:3 表示 "s2ss 航空" 有 3 架班机,分别命名为 s2ss_1s2ss_2s2ss_3
  • 航线(stream_connect):定义航班的起降路线,比如 datagen_1.out → ai_engine_0.Datain0 表示 datagen_1 的输出航班降落在 AI Engine 的 Datain0 登机口。
  • 地面设施(clock/debug):定义航站楼的基础设施参数,如默认时钟频率和安检监控点。
flowchart LR subgraph PL["可编程逻辑 (PL)"] DG1["datagen_1"] DG2["datagen_2"] DG3["datagen_3"] S1["s2ss_1"] S2["s2ss_2"] S3["s2ss_3"] end subgraph AIE["AI Engine 阵列"] IN0["Datain0"] IN1["Datain1"] IN2["Datain2"] OUT0["Dataout0"] OUT1["Dataout1"] OUT2["Dataout2"] end DG1 -->|"stream_connect"| IN0 DG2 -->|"stream_connect"| IN1 DG3 -->|"stream_connect"| IN2 OUT0 -->|"stream_connect"| S1 OUT1 -->|"stream_connect"| S2 OUT2 -->|"stream_connect"| S3

完整数据流路径

当 Host 程序启动一次归一化计算时,数据沿着以下路径流动:

  1. 数据生成阶段:三个 datagen HLS 核并行产生测试数据(bfloat16 格式的 0-7 序列),通过 AXI Stream 接口输出
  2. 入站路由stream_connectdatagen_X.out 映射到 ai_engine_0.DatainX,数据进入 AI Engine 阵列
  3. AI Engine 处理:6 个 AIE 核组成的流水线执行 mean-deviation-normalization 算法(详见 aie_kernels
  4. 出站路由:处理结果从 ai_engine_0.DataoutX 通过 stream_connect 流向 s2ss_X.s
  5. 数据接收阶段s2ss 核作为数据汇点(sink),消费并丢弃数据(本例用于性能测试)

配置项深度解析

[connectivity] 段:系统的"神经系统"

这是配置文件中最重要的部分,定义了所有动态连接关系。

nk(Number of Kernels)指令

nk=s2ss:3:s2ss_1.s2ss_2.s2ss_3
nk=datagen:3:datagen_1.datagen_2.datagen_3

设计意图:Vitis 工具链采用"单核多实例"的设计哲学。s2ss.cpp 只编译一次,但通过 nk 指令可以创建多个独立实例,每个实例有自己的地址空间和接口。这类似于 Docker 镜像与容器的关系——一份镜像,多个独立运行的容器。

命名规范中的 . 分隔符(如 s2ss_1.s2ss_2.s2ss_3)明确指定了实例名称,这些名称必须与 Host 代码中的 kernel 标识符严格匹配:

// host.cpp 中的对应引用
auto s2ss1 = xrt::kernel(device, id, "s2ss:{s2ss_1}");
auto mm2s1 = xrt::kernel(device, id, "datagen:{datagen_1}");

隐式契约:如果 nk 定义的实例名与 Host 代码中的 {instance_name} 不匹配,XRT 运行时会抛出 "kernel not found" 错误。这是一个常见的集成陷阱。

stream_connect 指令

stream_connect=ai_engine_0.Dataout0:s2ss_1.s
stream_connect=datagen_1.out:ai_engine_0.Datain0

语法解析source:destination 格式,其中:

  • ai_engine_0 是系统自动生成的 AI Engine 图实例名(由 graph.cpp 中的 SimpleGraph gr; 导出)
  • Dataout0Datain0 是在 graph.h 中通过 output_plio/input_plio 声明的 PLIO 端口名
  • s2ss_1.s 表示 s2ss_1 实例的 s 接口(对应 s2ss.cpp 中的 hls::stream<ap_axis<128,...>> & s 参数)
  • datagen_1.out 表示 datagen_1 实例的 out 接口

为什么需要显式连接?

在 Versal 架构中,AI Engine 和 PL 位于不同的硅片区域,它们之间的物理连接需要通过 NoC(Network on Chip)和 AXI Stream Switch 进行路由。stream_connect 就是告诉 Vitis 链接器如何配置这些硬件路由资源。没有这些指令,编译器无法知道哪些端口应该在物理上相连。

[debug] 段:可观测性基础设施

aie.chipscope=Dataout0
aie.chipscope=Datain0

作用:为指定的 PLIO 端口插入 ChipScope 调试探针,允许开发者在硬件运行时捕获和分析 AXI Stream 上的实际数据传输。

权衡考量:ChipScope 探针会消耗额外的 LUT 和 BRAM 资源,并且可能引入轻微的时序延迟。因此只在关键调试端口启用,而非全量开启。本例中选择 Dataout0Datain0 作为代表,覆盖了输入和输出两条主要通路。

[clock] 段:时域定义

defaultFreqHz=300000000

含义:设置 PL 侧 kernels 的默认时钟频率为 300MHz。

与 AIE 时钟的关系:注意到 Makefile 中 AIE 编译使用了 --aie.pl-freq=312.5,这意味着 AI Engine 侧的 PLIO 接口运行在 312.5MHz。这种有意的不匹配是一个重要的设计决策——它迫使设计者在异步时钟域 crossing 上保持警惕,确保 FIFO 深度足够缓冲跨时钟域的数据突发。


设计决策与权衡

1. 三通道并行 vs 单通道高带宽

本配置选择了 PLIO_NUM=3 的三通道设计,而非单通道更高位宽的设计。

选择理由

  • 负载均衡:256×384 的矩阵被均匀划分为 3 个 256×128 的子矩阵,每个通道处理一块
  • 资源分散:三个独立的 AXI Stream 通路可以利用不同的物理路由资源,避免单一通路的拥塞
  • 模块化测试:每个通道可以独立验证,便于问题定位

代价:更多的 PL 资源消耗(3 个 datagen + 3 个 s2ss),以及更复杂的 Host 控制逻辑。

2. 同步启动模式

观察 Host 代码的执行顺序:

auto s2ss1_run = s2ss1(nullptr, OUTPUT_SIZE);  // 先启动 sink
gr.run(iterations);                              // 再启动 AIE graph
auto mm2s1_run = mm2s1(nullptr, OUTPUT_SIZE);   // 最后启动 source

这种"先 sink、后 graph、最后 source"的顺序看似违反直觉(通常认为应该先生产后消费),实则是防止数据丢失的关键设计。AXI Stream 接口没有内置背压缓存,如果 source 先启动而 sink 未准备好,数据会被直接丢弃。通过先启动 sink 等待,可以确保数据通路两端的 FIFO 都处于可接收状态。

3. 配置与代码的分离

system.cfg 独立于 C++/HLS 源代码存在,这种分离带来了:

优势

  • 可以在不重新编译 kernels 的情况下调整连接拓扑(如改变通道数、重命名实例)
  • 同一套 kernel 二进制可以通过不同 cfg 文件适配不同板卡或应用场景

风险

  • 配置与代码的同步成为维护负担
  • 重构 kernel 接口名后必须同步更新 cfg,否则链接阶段才会暴露错误

依赖关系与集成边界

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

组件 依赖方式 说明
Makefile VPP_SPEC=system.cfg v++ 链接阶段的 --config 参数
v++ 链接器 命令行传入 解析并生成最终的 xclbin 比特流

下游依赖(本配置依赖谁)

组件 契约内容 不匹配的后果
graph.h Datain0-2, Dataout0-2 端口名 链接错误:port not found
pl_kernels/s2ss.cpp s 接口名 链接错误:interface mismatch
pl_kernels/datagen.cpp out 接口名 链接错误:interface mismatch
sw/host.cpp kernel 实例名 {s2ss_1}, {datagen_1} 运行时错误:kernel not found

版本演化注意

本配置属于 normalization_v4,相比 v1/v2/v3 的主要演进在于:

  • 支持多流(multistream)并行处理
  • 引入了 pl_kernels 目录下的独立 HLS kernels
  • 使用 shared_buffer 替代了早期的简单 buffer 连接

如果从早期版本迁移,需要特别注意 nk 实例数量的变化以及新增的 PL kernel 连接。


常见陷阱与调试技巧

陷阱 1:实例名拼写不一致

# system.cfg
nk=s2ss:3:s2ss_1.s2ss_2.s2ss_3
// host.cpp - 错误的写法
auto s2ss1 = xrt::kernel(device, id, "s2ss:{s2ss1}");  // 缺少下划线

症状:运行时抛出 xrt::kernel not found 异常。

调试:使用 xbutil examine 查看已加载 xclbin 中的 kernel 列表,确认实际实例名。

陷阱 2:stream_connect 方向颠倒

# 错误:尝试让数据从 sink 流向 source
stream_connect=s2ss_1.s:ai_engine_0.Dataout0

症状:链接阶段报错,或硬件挂死。

记忆口诀:stream_connect 的方向是"数据流动的方向",即 producer:consumer

陷阱 3:时钟域不匹配导致的数据损坏

虽然 cfg 中设置了 300MHz,但如果某个 PL kernel 内部逻辑期望 400MHz(如 config.cfg 中的 freqhz=400000000),跨时钟域的数据可能出现采样错误。

建议:保持 system.cfg 中的 [clock] 与 HLS config 文件中的频率一致,或者显式设计异步 FIFO。


扩展与定制指南

增加第四通道

  1. 修改 graph.h:将 PLIO_NUM 改为 4,添加 in[3]out[3]
  2. 更新 system.cfg
    nk=s2ss:4:s2ss_1.s2ss_2.s2ss_3.s2ss_4
    nk=datagen:4:datagen_1.datagen_2.datagen_3.datagen_4
    stream_connect=ai_engine_0.Dataout3:s2ss_4.s
    stream_connect=datagen_4.out:ai_engine_0.Datain3
    
  3. 更新 Host 代码:添加 s2ss4mm2s4 的初始化和运行调用

替换 datagen 为真实数据源

如果需要从 DDR 内存读取真实数据:

  1. 创建新的 HLS kernel(如 mm2s_ddr.cpp),使用 m_axi 接口访问 DDR
  2. system.cfg 中添加对应的 nkstream_connect
  3. 在 Host 代码中使用 xrt::bo 分配 buffer 并写入数据

总结

system.cfg 是 Versal ACAP 异构系统集成中的"粘合剂"——它不实现任何算法逻辑,却决定了所有逻辑单元能否协同工作。理解它的关键在于认识到:这不是一份普通的配置文件,而是对硬件物理连接的抽象描述。每一个 stream_connect 都对应着芯片上实际的 AXI Stream Switch 配置,每一个 nk 都对应着实际的 kernel 实例化。

对于新加入团队的开发者,建议按照以下顺序建立认知:

  1. 首先理解 graph.h 中定义的 AIE 数据流图
  2. 然后阅读本配置文件,建立 PL-AIE 边界的连接映射
  3. 最后结合 Host 代码,理解软件如何驱动这个硬件系统

三者共同构成了 normalization_v4 设计的完整图景。

On this page