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 三种突发类型的对比

为了让大家看得更清楚,我画了一张对比图:

三种突发类型对比 FIXED 地址固定不变 适用:FIFO、寄存器 INCR 地址递增 适用:连续内存访问 WRAP 地址回绕 适用:Cache Line 地址变化示意图 0x1000 → 0x1000 → 0x1000 0x1000 → 0x1004 → 0x1008 0x1004 → 0x1008 → 0x1000 注意事项 • FIXED:从设备必须支持同一地址多次访问 • INCR:不能跨4KB边界,地址必须对齐 • WRAP:包大小必须为2的幂,突发长度限2/4/8/16

4.6 实战中的选择建议

我个人在实际项目中的选择原则很简单:

  • 访问FIFO或状态寄存器 → 用FIXED。省地址带宽,效率最高。
  • 普通内存读写 → 用INCR。最通用,几乎所有从设备都支持。
  • Cache操作 → 用WRAP。配合cache line大小,性能最优。

还有一个细节:INCR和WRAP的地址边界处理。我建议在RTL设计中,统一用一个地址边界检查模块。不管是主设备还是从设备,都过一遍这个模块。这样可以避免很多跨边界的问题。我曾经在一个项目中,主设备用INCR突发写DDR,从设备那边没做4KB边界检查,结果写到边界时地址回绕了,数据全乱套。后来加了这个检查模块,问题就再没出现过。

小技巧:如果你不确定用哪种突发类型,就用INCR。它是AXI协议中最安全、最通用的选择。FIXED和WRAP都有特殊的使用场景,用错了反而出问题。

好了,关于三种突发类型就讲这么多。记住,突发传输是AXI性能的关键,但也是bug的高发区。多花点时间理解地址计算和边界条件,值得。

可可精品资料库