1. 为什么要在 Versal 上折腾 AXI NoC
Versal 这代 ACAP 平台跟之前的 Zynq MPSoC 完全不是一个玩法。Zynq 时代大家习惯了 AXI HP/HPC 口直连 DDR,脑子里就一张简单的点对点连接图;到了 Versal,AMD 把整个片上互联换成了AXI NoC(Network on Chip),它本质上是一张可编程的片上网络,把 PL、PS、DDR、AI Engine、PCIe、以太网这些主从设备全部挂在一张网里,通过 NoC Master/Slave Unit 和 NoC 内部的交换矩阵来路由事务。
我第一次接触 Versal 的 NoC 配置时,最直观的感受就是:它像给 FPGA 内部装了一个小型交换机。以前 PL 要访问 DDR,你得手动例化 AXI Interconnect,自己算位宽转换、时钟域跨越、仲裁优先级;现在这些活儿大部分交给 NoC 了,你只需要在 Vivado 里把 NoC 连接搭好,配置带宽和 QoS,剩下的交给工具去生成。
但问题也来了——NoC 配置错了,仿真能过,上板直接挂。我踩过最典型的一个坑:PL 侧通过 NoC 读 DDR,仿真里数据全对,上板跑起来读回来的数据每隔几百个 beat 就错一次。查了整整两天,最后发现是 NoC Slave Unit 的 outstanding 事务数配得太激进,超过了 DDR 控制器实际能承受的并发,导致返回数据乱序后没有被正确重组。这种问题在纯 RTL 仿真里根本复现不出来,因为仿真模型不会模拟真实的 DDR 调度延迟。
所以这篇东西,我想把AXI NoC 从配置、仿真到 QoS 调优这一整条链路完整走一遍。适合谁看?如果你手上有 Versal 板子(VCK190、VHK158、VPK120 这些都行),正在做 PL 到 DDR 的高带宽数据通路,或者要跑 AI Engine 和 PL 之间的数据流,那这篇内容应该能帮你少走不少弯路。纯新手也能看,我会把 NoC 的基本概念用生活化的方式讲清楚,但前提是你得会用 Vivado,至少知道怎么建 Block Design。
2. AXI NoC 的整体设计与思路拆解
2.1 NoC 到底解决了什么问题
在 Zynq MPSoC 上,PL 访问 DDR 走的是 AXI HP 口,每个 HP 口位宽固定、时钟固定,带宽上限摆在那里。你要想提高带宽,只能多开几个 HP 口,然后在 PL 里做 AXI Interconnect 把多个 master 汇聚起来。这种做法有几个硬伤:一是 AXI Interconnect 本身消耗大量 LUT 和 BRAM;二是仲裁策略固定,没法针对不同 master 做差异化 QoS;三是时钟域跨越要自己处理,稍不注意就出亚稳态。
Versal 的 NoC 把这些事情全部硬件化了。NoC 内部有专门的NoC Master Unit(NMU)和NoC Slave Unit(NSU),NMU 负责把 AXI 事务转换成 NoC 内部的流式包,NSU 负责把包还原成 AXI 事务。中间经过 NoC 的交换矩阵,支持多路径路由、动态带宽分配和 QoS 优先级调度。
用个类比:Zynq 的 AXI HP 口像是一条固定车道的公路,车多了就堵;Versal 的 NoC 像是一个智能交通系统,可以根据车流量动态调整车道,还能给救护车(高优先级事务)开绿灯。
2.2 什么场景下必须用 NoC
不是所有设计都需要 NoC。如果你只是做个低速外设控制,PL 里几个寄存器读写,用 AXI-Lite 直连 PS 的 M_AXI_LPD 就够了,没必要上 NoC。但以下几种场景,NoC 基本是唯一选择:
- PL 到 DDR 的高带宽数据流:比如视频采集、雷达信号处理,数据率动辄几个 GB/s,只有 NoC 能提供足够的带宽和灵活的位宽配置。
- AI Engine 与 PL 之间的数据交换:AI Engine 阵列本身通过 AXI Stream 和 NoC 连接,PL 要跟 AI Engine 通信,必须经过 NoC。
- 多 Master 共享 DDR 且需要差异化 QoS:比如一个系统里同时有视频流、控制流和调试流,视频流要求高带宽,控制流要求低延迟,NoC 的 QoS 机制可以给它们分配不同的优先级。
- 需要动态重构 NoC 连接:Versal 支持部分重配置,NoC 的配置也可以在运行时修改,这在多任务切换场景下很有用。
2.3 NoC 配置的核心参数有哪些
在 Vivado 里配置 NoC,主要涉及以下几个关键参数,我逐个解释它们的作用和选型逻辑:
位宽(Data Width):NMU 和 NSU 都支持 32/64/128/256/512 位。位宽越大,单次事务传输的数据量越大,但消耗的 NoC 内部资源也越多。一般原则是:如果数据流是连续的,尽量用 128 或 256 位;如果是随机小事务,64 位就够了。
时钟频率:NoC 的时钟可以独立于 PL 和 PS 的时钟。频率越高,带宽越大,但功耗也越高。VCK190 上 NoC 最高可以跑到 1GHz 左右,但实际设计中我一般跑 500-800MHz,留点余量。
Outstanding 事务数:这是最容易踩坑的参数。它决定了 NMU 可以同时发出多少个未完成的事务。设得太小,带宽上不去;设得太大,DDR 控制器可能处理不过来,导致数据错乱。一般建议从 8 开始试,逐步往上加,同时用性能计数器观察 DDR 的利用率。
QoS 优先级:NoC 支持 0-15 共 16 个优先级。数值越大优先级越高。高优先级事务会抢占低优先级事务的带宽。但注意,优先级不是越高越好,如果所有 master 都设成最高优先级,等于没有优先级。
路由策略:NoC 支持多种路由算法,包括最短路径、负载均衡等。默认的最短路径在大多数场景下够用,但如果 NoC 内部出现拥塞,可以考虑切换到负载均衡模式。
2.4 仿真策略的选择
NoC 的仿真分两个层次:功能仿真和性能仿真。
功能仿真用 Vivado 自带的 AXI VIP(Verification IP),主要验证 AXI 事务的正确性,比如读写数据是否一致、握手信号是否合规。这种仿真速度快,但不会模拟 NoC 内部的拥塞和延迟。
性能仿真需要用到 NoC 的性能模型,AMD 提供了 NoC Performance Model,可以模拟不同配置下的带宽和延迟。这种仿真跑得慢,但能提前发现 QoS 配置不合理的问题。
我的建议是:功能仿真必做,性能仿真选做。如果设计里 NoC 的负载不高,性能仿真可以跳过;但如果多个 master 同时抢带宽,性能仿真能帮你省下大量上板调试的时间。
3. 核心细节解析与实操要点
3.1 在 Vivado 中搭建 NoC 连接
打开 Vivado 2022.2 或更新版本,新建工程,选择对应的 Versal 器件。在 Block Design 里,你会看到 IP 目录中有AXI NoC IP。但更常用的方式是通过Versal ACAP 的 CIPS(Control, Interface, and Processing System)IP来配置 NoC,因为 CIPS 里已经集成了 PS 和 NoC 的接口。
具体步骤:
- 在 Block Design 中添加 CIPS IP,双击配置。在PS-PL Interfaces选项卡里,使能AXI NoC接口。
- 根据需要选择 NoC 的 master 和 slave 端口数量。比如你要从 PL 访问 DDR,就需要一个 NoC Master 端口(PL 侧)和一个 NoC Slave 端口(DDR 侧)。
- 配置每个端口的位宽和时钟。PL 侧的时钟一般跟 PL 逻辑时钟一致,DDR 侧的时钟跟 DDR 控制器时钟一致。
- 在NoC QoS选项卡里,为每个 master 端口设置优先级和带宽限制。
这里有个细节:NoC 的时钟必须由 Clocking Wizard 或外部时钟源提供,不能直接用 PS 的时钟。因为 NoC 的时钟域是独立的,直接连 PS 时钟会导致时序问题。
3.2 NoC 连接的正确性检查
配置完 NoC 后,Vivado 会自动生成 NoC 的连接逻辑。但别急着生成比特流,先做几项检查:
- 地址映射:在 Address Editor 里确认每个 master 能访问的地址范围是否正确。NoC 的地址映射跟普通 AXI 不一样,它有一个专门的地址转换表。
- 位宽匹配:NMU 和 NSU 的位宽必须匹配,或者通过 NoC 内部的位宽转换器自动转换。如果手动配置,要确保转换逻辑正确。
- 时钟域跨越:如果 NMU 和 NSU 的时钟不同,NoC 会自动插入异步 FIFO。但要确认 FIFO 的深度是否足够,太浅会导致吞吐量下降。
注意:NoC 的地址映射一旦配错,仿真可能不报错,但上板后访问会直接挂死。建议在仿真阶段就用 AXI VIP 做一次全地址范围的读写测试。
3.3 QoS 参数的配置逻辑
QoS 是 NoC 最核心的功能之一,但也是最容易被滥用的。我见过不少设计把所有 master 的优先级都设成最高,结果 NoC 内部的仲裁器直接懵了,带宽反而下降。
正确的做法是按业务重要性分级:
| 业务类型 | 优先级 | 带宽限制 | 说明 |
|---|---|---|---|
| 实时控制流 | 12-15 | 不限制 | 延迟敏感,数据量小 |
| 视频/雷达数据流 | 8-11 | 设上限 | 带宽敏感,可容忍一定延迟 |
| 调试/日志流 | 0-3 | 严格限制 | 非关键业务,不能抢带宽 |
| 普通数据流 | 4-7 | 设上限 | 一般业务 |
带宽限制的单位是 MB/s,设置时要参考 NoC 的总带宽。比如 NoC 总带宽是 10GB/s,视频流占 6GB/s,控制流占 1GB/s,剩下的留给其他业务。
3.4 仿真环境的搭建
NoC 的功能仿真需要以下组件:
- AXI VIP:作为 master 和 slave,模拟 AXI 事务。
- NoC 仿真模型:Vivado 会自动生成,不需要手动例化。
- 测试激励:用 SystemVerilog 或 Python 写测试序列。
一个典型的仿真流程:
// 伪代码示例 initial begin // 初始化 AXI VIP axi_master.init_master(); axi_slave.init_slave(); // 配置 NoC 寄存器 noc_config.set_qos(0, 15); // master 0 优先级 15 noc_config.set_bandwidth(0, 0); // 不限制带宽 // 发起读写事务 axi_master.write(32'h0000_1000, 32'hDEAD_BEEF); axi_master.read(32'h0000_1000, rdata); // 检查结果 if (rdata !== 32'hDEAD_BEEF) begin $error("Read data mismatch!"); end end仿真跑起来后,重点观察以下几个信号:
- AWVALID/AWREADY:写地址握手是否正常。
- WVALID/WREADY:写数据握手是否正常。
- BVALID/BREADY:写响应是否正常。
- ARVALID/ARREADY:读地址握手是否正常。
- RVALID/RREADY:读数据是否正常。
如果某个握手信号长时间不拉高,说明 NoC 内部出现了反压,需要检查 QoS 配置或 outstanding 事务数。
4. 实操过程与核心环节实现
4.1 从零搭建一个 PL 到 DDR 的 NoC 通路
假设我们要做一个视频采集系统,PL 侧从摄像头接收数据,通过 NoC 写入 DDR,然后 PS 侧读取 DDR 做后续处理。以下是完整步骤:
第一步:创建 Vivado 工程
选择器件xcvc1902-vsva2197-2MP-e-S(VCK190 的器件型号)。工程建好后,创建一个 Block Design。
第二步:添加并配置 CIPS
在 Block Design 中添加 CIPS IP。双击配置:
- 在PS-PL Interfaces里,使能PL to DDR的 NoC 接口。
- 配置 NoC Master 端口数量为 1,Slave 端口数量为 1。
- Master 端口位宽设为 128 位,Slave 端口位宽设为 256 位(DDR 侧位宽可以更大)。
- Master 端口时钟设为 300MHz,Slave 端口时钟设为 500MHz。
第三步:添加 NoC IP 并连接
在 IP 目录里搜索AXI NoC,添加到 Block Design。将 CIPS 的 NoC Master 端口和 NoC IP 的 Slave 端口连接,NoC IP 的 Master 端口连接到 DDR 控制器。
这里要注意:NoC IP 的时钟输入必须来自独立的 Clocking Wizard,不能直接连 CIPS 的时钟输出。我一般会加一个 Clocking Wizard,输出 300MHz 和 500MHz 两路时钟,分别给 NoC 的 Master 和 Slave 侧。
第四步:配置 QoS
双击 NoC IP,在QoS选项卡里:
- 将 Master 0 的优先级设为 10(视频流,较高优先级)。
- 带宽限制设为 4000 MB/s(假设 NoC 总带宽 10GB/s)。
- Outstanding 事务数设为 16。
第五步:地址映射
在 Address Editor 里,将 DDR 的地址范围映射到 NoC Master 0。假设 DDR 起始地址是0x0000_0000,大小 2GB,那么 Master 0 的地址范围就是0x0000_0000到0x7FFF_FFFF。
第六步:生成比特流并导出 XSA
完成 Block Design 后,生成 HDL Wrapper,跑综合和实现,最后生成比特流。导出 XSA 文件,供 Vitis 使用。
4.2 仿真验证的关键步骤
仿真环境搭建好后,按以下步骤验证:
- 复位释放:确保 NoC 的复位信号正确释放。NoC 的复位需要至少 16 个时钟周期。
- 寄存器配置:通过 AXI-Lite 接口配置 NoC 的 QoS 寄存器。配置顺序很重要,先配优先级,再配带宽限制,最后使能 NoC。
- 发起事务:用 AXI VIP 发起读写事务。建议先做小数据量的读写测试,确认通路正常后再加大数据量。
- 性能统计:在仿真中统计读写事务的延迟和吞吐量。如果延迟超过预期,检查 QoS 配置和 outstanding 事务数。
一个实测数据:在 VCK190 上,128 位位宽、300MHz 时钟的 NoC Master,理论带宽是 128/8 * 300M = 4.8GB/s。实际跑下来,读带宽约 3.8GB/s,写带宽约 3.5GB/s,效率在 75% 左右。这个效率在 NoC 里算正常,因为 NoC 内部有协议开销。
4.3 上板调试与性能计数器
上板后,Vivado 的 Hardware Manager 里可以查看 NoC 的性能计数器。这些计数器包括:
- 事务计数:每个 master 发起的事务总数。
- 带宽计数:每个 master 的实际带宽。
- 延迟计数:事务的平均延迟。
- 拥塞计数:NoC 内部出现拥塞的次数。
如果发现某个 master 的带宽远低于配置值,先检查 QoS 优先级是否被其他 master 抢占。如果拥塞计数很高,说明 NoC 内部路由出现了瓶颈,可以考虑调整路由策略或增加 NoC 的时钟频率。
实操心得:NoC 的性能计数器在默认情况下是关闭的,需要在 CIPS 配置里手动使能。使能后会消耗少量 NoC 资源,但对性能影响不大。
4.4 QoS 调优的实战案例
我之前做过一个项目,系统里有三个 master:视频流(Master 0)、控制流(Master 1)、调试流(Master 2)。初始配置是三个 master 优先级都是 8,带宽都不限制。结果跑起来后,调试流疯狂刷日志,把视频流的带宽抢了一大半,视频出现卡顿。
调优过程:
- 先把调试流的优先级降到 2,带宽限制到 100MB/s。视频卡顿明显改善,但偶尔还有丢帧。
- 再把视频流的优先级提到 12,控制流提到 10。视频流稳定了,但控制流的延迟偶尔超标。
- 最后给控制流单独分配了一个 NoC 虚拟通道(Virtual Channel),让它和视频流走不同的物理路径。这样控制流的延迟稳定在 100ns 以内,视频流也不再丢帧。
这个案例说明:QoS 调优不是一蹴而就的,需要根据实际业务的表现逐步调整。而且,虚拟通道是 NoC 的一个高级功能,可以在物理资源允许的情况下提供更好的隔离性。
5. 常见问题与排查技巧实录
5.1 NoC 配置后仿真能过但上板挂死
这是最经典的问题。原因通常有三个:
- 地址映射错误:仿真时 AXI VIP 的地址范围是手动指定的,可能跟 NoC 的实际地址映射不一致。上板后 PS 访问的地址不在 NoC 的映射范围内,直接挂死。
- Outstanding 事务数过大:仿真模型不会模拟 DDR 的调度延迟,所以 outstanding 设多大都能过。上板后 DDR 控制器处理不过来,返回数据乱序。
- 时钟频率不匹配:仿真时用的时钟频率可能跟实际上板的不一样。比如仿真跑 100MHz,上板跑 300MHz,时序余量不够。
排查方法:先用 ILA 抓 NoC 的 AXI 接口信号,看握手是否正常。如果 AWVALID 拉高但 AWREADY 一直不拉高,说明 NoC 内部反压了,检查 QoS 和 outstanding 配置。
5.2 NoC 带宽远低于理论值
理论带宽 = 位宽/8 * 时钟频率。实际带宽通常只有理论值的 60%-80%。如果低于 60%,检查以下几点:
- 位宽转换:如果 NMU 和 NSU 位宽不一致,NoC 内部会做位宽转换,这会引入额外开销。尽量让两边位宽一致。
- 时钟域跨越:如果 NMU 和 NSU 时钟不同,异步 FIFO 的深度会影响吞吐量。FIFO 太浅会导致频繁反压。
- QoS 抢占:高优先级 master 抢占了带宽。用性能计数器确认每个 master 的实际带宽。
- DDR 瓶颈:如果多个 master 同时访问 DDR,DDR 控制器本身可能成为瓶颈。VCK190 的 DDR4 控制器理论带宽约 19GB/s,实际能用到 12-14GB/s。
5.3 NoC 仿真速度太慢
NoC 的功能仿真本身不慢,但如果测试序列太长,仿真时间会线性增长。加速方法:
- 减少测试数据量:功能仿真不需要跑完整的数据流,用少量数据验证通路正确性即可。
- 关闭不必要的调试信号:Vivado 仿真器默认会记录所有信号的波形,关闭不需要的信号可以大幅提速。
- 使用事务级建模(TLM):如果只是验证 NoC 的配置逻辑,可以用 TLM 模型代替 RTL 仿真,速度快 10 倍以上。
5.4 常见问题速查表
| 问题现象 | 可能原因 | 排查方法 | 解决方案 |
|---|---|---|---|
| 仿真过,上板挂死 | 地址映射错误 | 检查 Address Editor | 修正地址范围 |
| 带宽低于理论值 60% | 位宽不匹配 | 检查 NMU/NSU 位宽 | 统一位宽 |
| 数据偶发错误 | Outstanding 过大 | 降低 outstanding 数 | 从 8 开始逐步增加 |
| 延迟超标 | QoS 优先级低 | 查看性能计数器 | 提高优先级或分配虚拟通道 |
| 仿真速度慢 | 测试序列太长 | 减少数据量 | 用 TLM 模型 |
| NoC 复位失败 | 复位脉冲太短 | 检查复位信号 | 至少 16 个时钟周期 |
5.5 几个容易忽略的细节
NoC 的复位顺序:NoC 必须在 CIPS 复位释放之前完成配置,否则 NoC 会处于未定义状态。我一般会在 CIPS 配置里把 NoC 的复位和 CIPS 复位绑定在一起。
NoC 的时钟必须稳定:如果 NoC 时钟在运行过程中丢失,NoC 会直接挂死,而且不会自动恢复。所以 Clocking Wizard 的锁定信号要接到 NoC 的复位逻辑里。
NoC 的功耗:NoC 是全速运行的,即使没有事务,它也会消耗静态功耗。在低功耗场景下,可以通过 CIPS 关闭未使用的 NoC 端口。
NoC 的仿真模型版本:Vivado 不同版本的 NoC 仿真模型可能有差异。建议用跟实际工程一致的 Vivado 版本做仿真,避免版本不匹配导致的问题。
6. 一些个人体会
NoC 这个东西,刚上手的时候觉得复杂,配置项一大堆,文档又厚。但用熟了之后会发现,它其实比传统的 AXI Interconnect 省心得多。以前在 Zynq 上做多 master 共享 DDR,光调仲裁和位宽转换就能耗掉一周;现在在 Versal 上,NoC 把这些都封装好了,你只需要关注 QoS 和带宽这两个核心参数。
我个人的经验是:NoC 的配置不要一次到位,要迭代。先配一个保守的版本,跑通功能,然后用性能计数器看瓶颈在哪里,再逐步调整 QoS 和 outstanding。这样比一开始就追求最优配置要稳妥得多。
另外,仿真阶段一定要做地址映射的全覆盖测试。我吃过这个亏,仿真只测了一小段地址,上板后 PS 访问了另一段地址,直接挂死,查了一整天才发现是 Address Editor 里漏配了一段。
最后再分享一个小技巧:Vivado 的 NoC 配置可以导出成 Tcl 脚本,下次建工程的时候直接 source 这个脚本,几秒钟就能把 NoC 配好,比手动点界面快多了。这个脚本还可以纳入版本管理,方便追溯配置变更。