Zynq 实战 05|AXI 协议深度解析:握手时序、Burst 对齐与 PS-PL 总线全通
Zynq 实战 05|AXI 协议深度解析
这是《Zynq FPGA 嵌入式系统设计实战》系列的第 5 篇。 板子:Pynq-Z2(XC7Z020)。工具链:Vivado / Vitis 2023.2。 上一篇:Zynq 实战 04|PetaLinux 2023.2 构建全流程
0. 这一篇要解决什么问题
前几篇我们多次提到 AXI——GP 端口、HP 端口、AXI Interconnect——但都是在”需要时捎带一句”。这篇把它单独拆开,原因很实际:
- 我在设计第一个 DMA 加速器时,burst 效率极差,折腾了两天才发现原因是在 PS 侧 HP 端口上用了 AXI4 的 256 拍 burst——而 HP 端口是 AXI3,根本不支持超过 16 拍
- 另一个坑:突发事务起始地址
0x40000FF0,按 32-bit 做 16 拍,结果跨越了 4KB 边界,AXI 总线报错,Vivado ILA 抓到一堆 SLVERR
这些坑不是”理解了原理就不会踩”,而是原理里有几个容易被略掉的细节。这篇要把这些细节说清楚。
读完这篇,你应该能做到:
- 看懂 AXI 时序图,知道 VALID/READY 握手怎么建立
- 写 AXI4-Lite slave IP 时不会搞混通道顺序
- 知道 HP 端口 burst length 的真实上限
- 规划 burst 事务时能手算 4KB 边界
这篇不讲的:AXI Coherency Extensions(ACE)的内部机制、CHI 协议——那是多核 SoC 内部互联层,跟 Zynq-7000 的 PL 侧设计没有直接关系。
1. AMBA 协议家族:AXI 在哪一层
ARM 的 AMBA 协议是一个分层体系,按复杂度和适用场景分出多条总线:
| 协议 | 发布时间 | 定位 | 典型用途 |
|---|---|---|---|
| AHB (AMBA 2) | 1999 | 片上系统总线 | Cortex-M 系列外设互联,仍在广泛使用 |
| AXI3 (AMBA 3) | 2003 | 高带宽内存映射总线 | Zynq-7000 PS 内部!HP/GP 端口协议 |
| AXI4 (AMBA 4) | 2010 | AXI3 增强版,burst 更长 | Zynq PL 侧自定义 IP,Vivado IP catalog 默认 |
| AXI4-Lite | 2010 | AXI4 简化版,无 burst | 控制寄存器接口的标准选择 |
| AXI4-Stream | 2010 | 无地址的单向数据流 | 视频流、ADC 数据、DMA 数据通路 |
| ACE (AMBA 4) | 2011 | AXI + Cache Coherency | 多核 ARM big.LITTLE,Cortex-A53/A72 |
| CHI (AMBA 5) | 2014 | 环形拓扑一致性互联 | 服务器级 Neoverse,和 Zynq-7000 无关 |
Zynq-7000 的现实:PS 侧(ARM 核心周围)跑的是 AXI3,不是 AXI4。这不是 Xilinx 的设计缺陷,2011 年流片的 7 系列 Zynq 用的就是当时的主流协议。AXI4 和 AXI3 的最大区别只有两点:burst length 上限(256 vs 16)和 AxQOS/AxREGION 扩展信号。对 Zynq-7000 的开发者来说,最重要的影响就是burst length。
🚧 避坑:Vivado IP catalog 里所有新创建的 AXI IP(AXI DMA、VDMA、自定义 IP)默认生成 AXI4 接口。当你把这些 IP 的 master 口连接到 PS 的 HP 或 GP 端口时,AXI Interconnect IP 或 SmartConnect IP 会自动在中间做 AXI4→AXI3 协议转换,包括把超过 16 拍的 burst 拆分。但这个转换有开销,而且如果你在 DMA 代码里配置了 256 拍 burst 指望最大化带宽,实际效果会让你困惑——因为底层已经被拆成多个 16 拍事务。
2. AXI4 的五大独立通道
AXI 和 AHB/APB 最大的区别不是带宽,而是通道独立性:读和写完全分离,写地址、写数据、写响应三相也互不阻塞。
为什么要”独立”?:因为写地址可以提前发出,数据可以延迟到来,响应可以更晚确认——三条通道各自用 VALID/READY 握手,互不阻塞。相比 AHB 的”地址+数据串行”,AXI 在高延迟内存(DDR)访问时能 pipeline 多个事务,这才是实质好处。
2.1 VALID / READY 握手机制
每条通道都用同一套握手协议:传输在 VALID=1 且 READY=1 同时出现在时钟上升沿时完成。两者顺序无所谓——可以先 VALID 等 READY,也可以先 READY 等 VALID,或者同一个时钟周期一起拉高。
几个从时序图里读出来的关键事实:
- AW 和 W 通道独立:这里的例子里 W 通道在 T2 才开始(等 AW 握手完),但协议上允许 W 数据提前放上通道等地址——具体看 slave 是否支持写交织(interleaved writes,AXI4 已删除)
- WLAST 是软件对 burst 长度的”确认”:它让 slave 知道哪一拍是最后一拍,不用 slave 自己计数 AWLEN
- BVALID 可以在 WLAST 之后任意时刻发:slave 处理完才回响应——写操作本质是”发出去不等完成”,响应是异步的
- BREADY 如果一直 HIGH(master 总是 ready 接收响应):则响应延迟就取决于 slave,不取决于 master
🚧 避坑:写 AXI4-Lite slave 时,B 通道响应必须等 W 通道握手完成后才能拉高 BVALID——有些学习代码里把 BVALID 和 WVALID 同时拉高,导致协议违例。正确顺序:W 握手(WVALID & WREADY 同时 HIGH)→ 处理 → 拉 BVALID。
3. 关键事实:Zynq-7000 PS 端是 AXI3,不是 AXI4
这是这篇文章最重要的一个澄清。
协议版本对照(来自 UG585 Chapter 9 AXI Interconnect):
| 端口 | 位置 | 协议 | 最大 burst length | 数据宽度 |
|---|---|---|---|---|
| M_AXI_GP0/1 | PS master,控制 PL | AXI3 | 16 拍 | 32-bit |
| S_AXI_GP0/1 | PS slave,PL 访问 PS | AXI3 | 16 拍 | 32-bit |
| S_AXI_HP0/1/2/3 | PS slave,PL 高速访问 DDR | AXI3 | 16 拍 | 64-bit |
| S_AXI_ACP | PS slave,Cache 一致 | AXI3 | 16 拍 | 64-bit |
| PL 自定义 IP(Vivado生成) | PL 侧 | AXI4 | 256 拍 | 可变 |
两者之间是 AXI Interconnect IP(PG059)或新版 AXI SmartConnect IP(PG247)负责协议转换。
burst length 差异的实际影响:
假设你要用 DMA 搬 1MB 数据到 DDR,AXI 数据宽度 64-bit(8 字节),目标尽可能减少事务数量:
AXI4 端 burst length 最大 256 拍:
每次事务传输 = 256 × 8B = 2KB
1MB 需要事务数 = 1024KB / 2KB = 512 次事务
AXI3 端(HP 端口)burst length 最大 16 拍:
每次事务传输 = 16 × 8B = 128B
1MB 需要事务数 = 1024KB / 128B = 8192 次事务
事务数差 16 倍。每次事务都有地址握手的开销(几个时钟周期),所以 HP 端口的 DMA 效率实际上受限于 AXI3 的 16 拍上限,哪怕你的 PL DMA IP 支持 256 拍 burst,到了 HP 端口这里都会被 AXI Interconnect 拆开。
实际工程建议:
- 给 HP 端口用的 DMA,burst length 配 16(别浪费时间配 128/256,底层会拆)
- 多并发事务(多个 HP 端口同时 DMA)比拉长单个事务 burst 更有效
🚧 避坑:Xilinx AXI DMA IP(PG021)里的
Max Burst Length参数,如果你接的是 HP 端口,配超过 16 不会报错,Vivado 也不会提示,但运行时会自动被 protocol converter 拆成 16 拍。设成 16 明确表达设计意图,也省掉 protocol converter 的额外资源。
4. AXI4-Lite / AXI4-Full / AXI4-Stream 的真实差异
这三个”AXI4”变体常被混用,它们实际上是三种完全不同的使用场景:
| 特性 | AXI4-Lite | AXI4-Full | AXI4-Stream |
|---|---|---|---|
| 通道数 | 5(简化版) | 5(完整) | 1(只有数据) |
| Burst 支持 | ❌ 固定 1 拍 | ✅ 最大 256 拍 | N/A(流式) |
| 地址通道 | ✅ 每次传输都有地址 | ✅ 每次 burst 发一次地址 | ❌ 没有地址 |
| WSTRB(写字节使能) | ✅ 有,但常忽略 | ✅ 有,DMA 必须正确设置 | ❌ 不适用 |
| RLAST / WLAST | ❌ 无(只有 1 拍) | ✅ 有 | TLAST(包边界) |
| 数据宽度 | 32 or 64-bit | 32~1024-bit | 任意,常见 8/64/128/256-bit |
| 典型用途 | IP 控制寄存器读写 | DDR DMA、视频帧搬运 | 视频像素流、ADC 数据、CNN 中间结果 |
| Vivado 自定义 IP 默认 | AXI4-Lite slave | AXI4 master(DMA 用) | AXI4-Stream(数据路径用) |
一个实用判断口诀:
- 你要配置 IP(写寄存器、读状态)→ AXI4-Lite,简单、够用
- 你要搬大块数据(DMA 到 DDR、视频帧 buffer)→ AXI4-Full,burst 减少总线开销
- 你要流式数据(一拍接一拍,不关心内存地址)→ AXI4-Stream,最简单,吞吐最大
不要用 AXI4-Full 去做寄存器访问(杀鸡用牛刀,逻辑资源浪费),也不要用 AXI4-Lite 去做 DMA(没有 burst,每字节都要发一次地址,性能灾难)。
5. AXI Interconnect IP 配置要点
Vivado 里有两个 IP 可以做 AXI 路由:
- AXI Interconnect(PG059,老版):稳定,资源消耗可预测
- AXI SmartConnect(PG247,2018 年起推荐):自动优化,更省 FF/LUT,支持异步时钟转换
两者核心功能:
5.1 拓扑:Crossbar vs Shared
| 拓扑 | 适用场景 | 资源 | 特点 |
|---|---|---|---|
| Crossbar(交叉开关) | 多 master 同时访问不同 slave | 较多 | 真并行,无竞争时零开销 |
| Shared(共享) | 1 master 多 slave,或低并发 | 较少 | 同一时刻只有 1 个 master 活跃 |
Zynq 实际场景:
- Pynq-Z2 上,PS 的 M_AXI_GP0 接多个 PL slave IP:用 Crossbar 允许地址仲裁,各 slave 独立响应
- 多个 PL DMA master 同时写不同地址 → Crossbar,吞吐量更高
- 只有一个 DMA 在用 → Shared 省资源
5.2 异步时钟交叉(Clock Domain Crossing)
什么时候需要:PL 里的自定义 IP 跑 200MHz,但 HP 端口 AXI clock 是 150MHz,或者你有两个不同频率的 IP 模块互联。
怎么配:在 AXI Interconnect(或 SmartConnect)的 Block Design 配置里:
- 给 master 端口和 slave 端口分配不同
aclk(S_AXI_ACLK和M_AXI_ACLK可以不同) - IP 内部自动插入异步 FIFO(基于握手的 CDC)
注意事项:
- 异步 Clock Crossing 有延迟开销(通常 2-3 个目标时钟周期的同步延迟)
- 如果时钟是同步关系(同一 PLL 输出,整数倍关系),可以不用 CDC,用
SHARED_CLOCK模式更高效 - Pynq-Z2 上 PL 时钟来自
FCLK_CLK0(通常 100MHz),如果你另开了 MMCM 给某些 IP 用 200MHz,连 HP 端口时必须在 AXI 路径上加 Clock Crossing
🚧 避坑:忘记设置 Clock Crossing 是 Vivado Block Design 里最常见的时序违例原因之一。症状是布线后时序 timing summary 里出现大量
timing path through CDC pins的 warning,甚至 Failing Path。如果你的 PL AXI master 时钟和 HP 端口时钟不是同一个aclk,一定要在 Interconnect 里启用 CDC,或在 IP 内部自己处理。
6. Pynq-Z2 上的 AXI 端口映射
Zynq-7000 的地址空间设计来自 UG585 Table 2-2,下面是 Pynq-Z2 开发中最常用的那部分:
| 端口 | 地址范围 | 大小 | 说明 |
|---|---|---|---|
| M_AXI_GP0 | 0x4000_0000 – 0x7FFF_FFFF | 1 GB | PS 访问 PL slave IP 的默认区域 |
| M_AXI_GP1 | 0x8000_0000 – 0xBFFF_FFFF | 1 GB | 第二个 GP master,常用于第二组 PL IP |
| DDR(PS 主控) | 0x0000_0000 – 0x3FFF_FFFF | 1 GB | Pynq-Z2 板载 512MB,实际用前 512MB |
| OCM(高地址映射) | 0xFFFC_0000 – 0xFFFF_FFFF | 256 KB | Linux 启动后可用于 PS-PL 低延迟共享 |
在 Vivado Block Design 里,当你把自定义 IP 连接到 M_AXI_GP0,在 Address Editor 里会自动分配一个地址,默认是 0x4000_0000,范围 64KB(0x4000_0000 – 0x4000_FFFF)。
驱动里对应的读写就是:
// 在 Linux 里用 /dev/mem(或 mmap)访问 PL IP 寄存器
#include <sys/mman.h>
#include <fcntl.h>
#define PL_IP_BASE 0x40000000
#define REG_CTRL 0x00
#define REG_STATUS 0x04
int fd = open("/dev/mem", O_RDWR | O_SYNC);
volatile uint32_t *ip = (uint32_t *)mmap(
NULL, 0x1000, PROT_READ | PROT_WRITE,
MAP_SHARED, fd, PL_IP_BASE);
ip[REG_CTRL / 4] = 0x1; // 写控制寄存器
uint32_t st = ip[REG_STATUS / 4]; // 读状态
如果用 Pynq Python 框架(Pynq-Z2 默认环境):
from pynq import MMIO ip = MMIO(0x40000000, 0x10000) # base, size ip.write(0x00, 0x1) # 写 ctrl status = ip.read(0x04) # 读 status
7. 设计要点:Burst 对齐、4KB 边界、WRAP Burst
7.1 Burst 地址对齐
AXI 要求:每次传输地址必须对齐到传输宽度(AxSIZE)。
| AxSIZE | 传输宽度 | 地址必须对齐到 |
|---|---|---|
| 2’b00 | 1 Byte | 1 字节(无要求) |
| 2’b01 | 2 Bytes | 2 字节(地址 bit[0]=0) |
| 2’b10 | 4 Bytes | 4 字节(地址 bit[1:0]=0) |
| 2’b11 | 8 Bytes | 8 字节(地址 bit[2:0]=0) |
如果你的 burst 起始地址没对齐(比如 64-bit 传输起始于 0x40000006),AXI 协议违例,slave 行为 undefined。
7.2 4KB 边界约束
这是 AXI 规范(ARM IHI0022F Section A3.4)里的硬规则:
一次 burst 事务不能跨越 4KB(0x1000)地址边界。
原因:AXI 设计上允许地址解码只看高位(>12 位),4KB 以内的地址由 offset 处理,跨 4KB 意味着可能跨越 slave 的地址空间边界。
手算方法:
end_addr = start_addr + (AWLEN + 1) × (1 << AWSIZE) - 1
如果 (start_addr & ~0xFFF) != (end_addr & ~0xFFF):
→ 跨越 4KB 边界!必须拆分事务
例子:
start_addr = 0x40000FF0
AWLEN = 7 (8 拍)
AWSIZE = 2 (4 字节 / 拍)
end_addr = 0x40000FF0 + 8×4 - 1 = 0x4000100F
0x40000FF0 在 4KB block: 0x40000000
0x4000100F 在 4KB block: 0x40001000 ← 不同!
→ 这个 burst 违反 4KB 边界规则
→ 必须拆成两个事务:
① 0x40000FF0,4 拍(到 0x40000FFF,对齐到边界)
② 0x40001000,4 拍(从边界开始)
做 DMA 的时候,如果 buffer 地址是动态分配的(kmalloc / dma_alloc_coherent),4KB 对齐分配(用 get_free_pages 或 dma_alloc_coherent 的 4KB 对齐特性)可以彻底回避这个问题。
7.3 WRAP Burst 的真实用途:Cache Line Fill
WRAP burst(AWBURST = 2'b10)在教程里经常一笔带过。它的实际设计目的很具体:CPU Cache Line Fill(缺失填充)。
当 CPU 访问某个地址发生 Cache Miss 时,Cache Controller 需要从内存填充整条 cache line(Cortex-A9 的 L1 cache line = 32 字节 = 8 个 32-bit 字)。但填充顺序有讲究:先填 critical word(触发 Miss 的那个字),然后绕回来填剩余部分。
Cache line: 0x40001000 ~ 0x4000101F(32字节,8个32-bit word)
CPU 访问了 0x40001008(critical word,第3个word)
WRAP burst 配置:
ARADDR = 0x40001008 (critical word 起始)
ARLEN = 7 (8 拍)
ARSIZE = 2 (4 字节 / 拍)
ARBURST = 2'b10 (WRAP)
传输顺序:
0x1008 → 0x100C → 0x1010 → 0x1014 → 0x1018 → 0x101C → 0x1000 → 0x1004
(到 cache line 末尾后,wrap 回 cache line 起始)
你在 PL 设计里几乎不需要主动生成 WRAP burst——只有实现软 CPU 的 Cache Controller 才需要。但理解它有助于你在 ILA 波形里看到 ARBURST=10 时不慌,知道那是 cache fill 在走。
WRAP burst 的约束:burst length 必须是 2、4、8、16 之一,起始地址必须自然对齐到整个 burst 长度。
8. 本篇你应该带走的几个判断
- Zynq-7000 PS 侧 HP 端口是 AXI3,burst 最大 16 拍,配 DMA IP 时 burst length 不要超过 16
- PL 侧 Vivado 生成的 IP 是 AXI4,连接到 PS 端口时 AXI Interconnect / SmartConnect 自动做协议转换
- 看到
AWBURST=10不要怕,那是 WRAP burst,Cache line fill 专用 - 写 AXI4-Lite slave:B 通道响应必须在 W 握手完成之后才能发
- 做 DMA burst 规划:先算
end_addr = start + (len+1)×size,看是否跨 4KB;如果跨了必须拆分 - PL IP 和 HP 端口时钟不同时:AXI 路径上必须启用 Clock Domain Crossing
9. 下一篇预告
下一篇 《Zynq 实战 06|AXI DMA 实战:从 PL IP 搬数据到 DDR,端到端打通》,我们会动手:
- 在 Vivado 里配置 AXI DMA IP(PG021),连接 HP0 端口
- 写一个 AXI4-Stream 数据源 IP(PL 侧计数器),模拟 ADC 数据
- Linux 驱动侧用
dmaengineAPI 触发 DMA 传输 - ILA 抓 HP0 上的真实 burst 波形,验证 16 拍上限
参考资料
| 文档号 | 名称 | 用途 |
|---|---|---|
| UG585 | Zynq-7000 SoC Technical Reference Manual | Chapter 9(AXI 端口)、Table 2-2(地址映射) |
| ARM IHI0022F | AMBA AXI and ACE Protocol Specification | AXI3/4 完整协议规范,4KB 规则在 Section A3.4 |
| PG059 | AXI Interconnect Product Guide (v2.1) | Crossbar / Shared 配置、Clock Crossing 参数 |
| PG247 | AXI SmartConnect Product Guide | 新版 Interconnect,推荐用于 2020.1+ 工程 |
| PG021 | AXI DMA Product Guide | Max Burst Length 参数,HP 端口连接注意事项 |
| DS190 | Zynq-7000 SoC Data Sheet: Overview | AXI 端口带宽数字来源 |
这是《Zynq FPGA 嵌入式系统设计实战》系列第 5 篇。 如果你在 HP 端口 burst 效率或 4KB 边界这两个坑上卡过,欢迎留言交流——这两个问题折腾过很多人。