4. 突发传输:AXI突发传输机制,FIXED/INCR/WRAP三种突发类型
突发传输,说白了就是AXI总线最核心的“打包发货”机制。你想想看,如果每次读写一个数据都要单独发一次地址,那效率得多低?突发传输允许你只发一次起始地址,然后连续传输多个数据。我刚开始接触AXI时,觉得这玩意儿挺简单,不就是连续读几个数嘛。后来在项目中踩过坑才明白,三种突发类型各有各的脾气,用不对地方,轻则带宽浪费,重则系统死锁。
4.1 突发传输的基本概念
突发传输由三个关键信号控制:ARLEN/AWLEN(突发长度)、ARSIZE/AWSIZE(突发大小)、ARBURST/AWBURST(突发类型)。
突发长度表示一次突发传输中传输的数据个数。注意,LEN字段的值是“传输次数减1”。比如LEN=3,实际传输4个数据。这个设计我一开始也觉得别扭,后来习惯了反而觉得方便——因为很多计数器就是从0开始的。
突发大小表示每个数据的字节数。比如SIZE=2,表示每个数据4字节。突发大小不能超过数据总线的宽度,这是基本常识。我记得有次带新人,他设了个SIZE=4(16字节),但数据总线只有64位(8字节),结果仿真直接报错。嗯,这种低级错误其实很容易犯。
核心公式:
突发传输的总字节数 = (LEN + 1) × (2SIZE)
这个公式一定要记牢,我在做地址对齐检查时经常用到。
4.2 FIXED 突发类型
FIXED类型,顾名思义,地址固定不变。每次传输都访问同一个地址。这玩意儿什么时候用?说白了就是操作FIFO或者状态寄存器的时候。你往同一个地址写数据,FIFO内部会自动把数据压栈。
我个人习惯在验证FIFO控制器时用FIXED突发。比如写一个深度为16的FIFO,用LEN=15的FIXED写突发,一次性写入16个数据。但要注意,FIFO的满标志必须提前处理,否则数据会丢失。我曾经在项目中遇到过这种情况:FIFO深度只有8,我却发了LEN=15的突发,结果后面8个数据全丢了。排查了半天才发现是突发长度没算对。
警告:FIXED突发只适用于支持多次访问同一地址的从设备。普通SRAM或DDR存储器不支持FIXED突发,因为每次写操作会覆盖前一次的数据。
4.3 INCR 突发类型
INCR是最常用的突发类型。地址每次递增,递增步长等于突发大小。比如起始地址0x1000,SIZE=2(4字节),那么地址依次为0x1000、0x1004、0x1008……
INCR突发的地址计算很简单:
地址_n = 起始地址 + n × (2^SIZE)
其中 n = 0, 1, 2, ..., LEN
这里有个坑:地址对齐。起始地址必须与突发大小对齐。比如SIZE=3(8字节),起始地址必须是8的倍数。如果不满足,AXI协议规定从设备可以返回SLVERR。我建议大家在设计地址生成逻辑时,一定要加一个地址对齐检查。我自己写RTL时,会在地址通道加一个断言:
// 地址对齐检查
always_ff @(posedge clk) begin
if (awvalid & awready) begin
assert (awaddr % (2**awsize) == 0)
else $error("Address not aligned to size");
end
end
INCR突发还有一个边界问题。当地址递增到超过4KB边界时,必须结束当前突发,重新发起新的突发。这是AXI协议的死规定。为什么?因为4KB是一个页的大小,跨页访问会导致内存管理单元(MMU)出问题。我在调试一个DMA控制器时,就遇到过跨4KB边界导致数据错乱的bug。排查过程很痛苦,最后发现是DMA的地址生成逻辑没有做4KB边界检查。
4.4 WRAP 突发类型
WRAP类型,说白了就是“绕圈圈”。地址递增到上限后,自动回绕到起始地址。这种类型特别适合cache line fill操作。比如一个64字节的cache line,用WRAP突发一次性读入,效率非常高。
WRAP突发的地址计算稍微复杂一点:
地址_n = (起始地址 & ~(包大小-1)) | ((起始地址 + n × 突发大小) mod 包大小)
其中包大小 = (LEN+1) × (2^SIZE)
举个例子:起始地址0x1004,LEN=3(4个数据),SIZE=2(4字节)。包大小=16字节。地址计算如下:
| 传输序号 | 地址计算 | 实际地址 |
|---|---|---|
| 0 | 0x1004 | 0x1004 |
| 1 | 0x1008 | 0x1008 |
| 2 | 0x100C | 0x100C |
| 3 | 0x1010 → 回绕到0x1000 | 0x1000 |
注意,WRAP突发的起始地址不一定在包边界上。上面的例子中,包边界是0x1000,起始地址是0x1004。这种“非对齐起始”是WRAP突发的特点。我刚开始学的时候,总觉得这设计很奇怪,后来做cache验证时才明白——这正好匹配了cache line中任意位置开始的填充需求。
经验之谈:WRAP突发的包大小必须是2的幂次方,且突发长度只能是2、4、8、16。这是协议规定的。如果你需要其他长度的回绕操作,只能用INCR手动实现。
4.5 三种突发类型的对比
为了让大家看得更清楚,我画了一张对比图:
4.6 实战中的选择建议
我个人在实际项目中的选择原则很简单:
- 访问FIFO或状态寄存器 → 用FIXED。省地址带宽,效率最高。
- 普通内存读写 → 用INCR。最通用,几乎所有从设备都支持。
- Cache操作 → 用WRAP。配合cache line大小,性能最优。
还有一个细节:INCR和WRAP的地址边界处理。我建议在RTL设计中,统一用一个地址边界检查模块。不管是主设备还是从设备,都过一遍这个模块。这样可以避免很多跨边界的问题。我曾经在一个项目中,主设备用INCR突发写DDR,从设备那边没做4KB边界检查,结果写到边界时地址回绕了,数据全乱套。后来加了这个检查模块,问题就再没出现过。
小技巧:如果你不确定用哪种突发类型,就用INCR。它是AXI协议中最安全、最通用的选择。FIXED和WRAP都有特殊的使用场景,用错了反而出问题。
好了,关于三种突发类型就讲这么多。记住,突发传输是AXI性能的关键,但也是bug的高发区。多花点时间理解地址计算和边界条件,值得。