2. AXI通道详解:读地址通道、读数据通道、写地址通道、写数据通道、写响应通道的握手协议

AXI总线最核心的设计思想,就是通道分离。

说白了,它把一次传输拆成了五个独立的通道。每个通道只管自己的事,互不干扰。我刚开始接触AXI时,觉得这设计有点啰嗦——搞这么多通道干嘛?后来做了一款多主多从的SoC,才明白这招有多高明。通道分离让读写可以并行,效率直接翻倍。

这五个通道分别是:

  • 读地址通道(AR)—— 主设备告诉从设备:我要读哪个地址
  • 读数据通道(R)—— 从设备把数据返回给主设备
  • 写地址通道(AW)—— 主设备告诉从设备:我要写哪个地址
  • 写数据通道(W)—— 主设备把数据发给从设备
  • 写响应通道(B)—— 从设备告诉主设备:写完了,结果如何

每个通道都有自己的握手协议。嗯,这里要注意——握手协议是AXI的命根子。搞不懂握手,后面调性能就是瞎忙活。

2.1 握手协议的基本规则

每个通道都有一对握手信号:VALID 和 READY。

  • VALID:发送方说「我准备好了,数据有效」
  • READY:接收方说「我准备好了,可以接收」

数据只在 VALID 和 READY 同时为高的时钟上升沿传输。就这么简单。

但这里有个坑——谁先谁后?

AXI允许三种情况:

情况 描述 我见过的场景
VALID先有效 发送方先准备好,等接收方 从设备处理慢时常见
READY先有效 接收方先准备好,等数据 高性能互联经常这么干
同时有效 一拍完成握手 理想情况,延迟最低

我个人习惯让从设备的 READY 早一点拉高。为什么?因为这样可以减少等待周期。你想想看,如果从设备每次都要等主设备发来VALID才拉READY,那中间至少浪费一拍。

核心原则:VALID不能依赖READY,但READY可以依赖VALID。也就是说,发送方不能等接收方说准备好了才发数据——你得先把数据摆出来。

2.2 读地址通道(AR)

读地址通道负责传输读请求。主设备通过它告诉从设备:我要读哪个地址,读多少数据。

关键信号:

  • ARADDR —— 读地址,32位或64位
  • ARLEN —— 突发长度,决定一次读几个数据
  • ARSIZE —— 每个数据的字节数
  • ARBURST —— 突发类型:FIXED、INCR、WRAP
  • ARID —— 读事务ID,用于乱序返回

握手过程很简单:主设备拉高 ARVALID,从设备拉高 ARREADY,一拍完成地址传输。

我曾经遇到过一个坑:从设备的 ARREADY 设计成了组合逻辑,直接连到 ARVALID 上。结果呢?形成了组合环路,仿真能跑,综合报了一堆时序违例。嗯,这里要记住——READY 信号最好用寄存器打一拍。

2.3 读数据通道(R)

读数据通道是反向的——从设备把数据返回给主设备。

关键信号:

  • RDATA —— 读回来的数据
  • RRESP —— 读响应状态:OKAY、EXOKAY、SLVERR、DECERR
  • RLAST —— 最后一个数据指示
  • RID —— 对应读事务的ID

注意,读数据通道支持乱序返回。什么意思?就是你先发了ARID=1的读请求,再发了ARID=2的读请求,但RID=2的数据可能先回来。这在多主设备场景下特别有用。

避坑指南:我曾经调试过一个DMA控制器,读回来的数据顺序全乱了。查了半天,发现是RID没有正确匹配。记住——主设备必须根据RID来重新排序数据,不能假设返回顺序和发送顺序一致。

2.4 写地址通道(AW)

写地址通道和读地址通道几乎一样,只是信号名前缀从AR变成了AW。

关键信号:

  • AWADDR —— 写地址
  • AWLEN —— 写突发长度
  • AWSIZE —— 写数据宽度
  • AWBURST —— 写突发类型
  • AWID —— 写事务ID

写地址通道和写数据通道是独立的。这意味着地址可以先发,数据后到;也可以数据先到,地址后发。但有个隐含要求——从设备必须能处理这种乱序。

我个人建议:写地址和写数据尽量保持同步。虽然协议允许乱序,但很多从设备实现得并不完美。你非要挑战它的极限,那调试起来就痛苦了。

2.5 写数据通道(W)

写数据通道负责传输要写入的数据。

关键信号:

  • WDATA —— 要写的数据
  • WSTRB —— 字节选通信号,指示哪些字节有效
  • WLAST —— 最后一个数据指示
  • WID —— 写数据ID(注意:AXI3有,AXI4去掉了)

WSTRB 是个好东西。它让你可以只写一个数据中的某些字节。比如你想修改一个32位寄存器的低8位,就可以把 WSTRB 设为4'b0001,只写最低字节。

注意:AXI4协议中,写数据通道去掉了WID信号。这意味着写数据必须按顺序发送。如果你需要乱序写,只能用AXI3或者自己加额外的逻辑。

2.6 写响应通道(B)

写响应通道是写操作的收尾。从设备通过它告诉主设备:写完了,结果如何。

关键信号:

  • BRESP —— 写响应状态:OKAY、EXOKAY、SLVERR、DECERR
  • BID —— 对应写事务的ID

写响应通道只有一对信号,没有数据。每个写事务必须有一个写响应。也就是说,你发了一个AW请求和N个W数据,最后一定会收到一个B响应。

我曾经犯过一个低级错误:写响应通道的 BVALID 忘了拉高,结果主设备一直等,整个总线卡死了。嗯,这种问题定位起来特别费劲——波形上看,所有信号都正常,就是没响应。后来我养成了一个习惯:写从设备时,先把写响应通道的握手逻辑写好,再写数据通道。

2.7 握手协议的可视化

下面这张图展示了五个通道之间的关系。我特意用SVG画了,方便你理解数据流的方向。

主设备 Master 从设备 Slave 读地址通道 (AR) ARADDR, ARLEN, ARID... 读数据通道 (R) RDATA, RRESP, RLAST... 写地址通道 (AW) AWADDR, AWLEN, AWID... 写数据通道 (W) WDATA, WSTRB, WLAST... 写响应通道 (B) BRESP, BID... 图例: 读地址 读数据 写地址 写数据 写响应

从图上可以清楚看到:读操作只需要两个通道(AR + R),写操作需要三个通道(AW + W + B)。箭头方向表示数据流的方向——主设备发地址和数据,从设备返回响应。

2.8 握手时序示例

看一个具体的写操作时序。假设我们要写一个地址,然后发两个数据:

时钟周期:  T0    T1    T2    T3    T4    T5
AWVALID:    __|‾‾|____|____|____|____|____
AWREADY:    ____|‾‾|____|____|____|____|____
WVALID:     ____|____|‾‾|____|‾‾|____|____
WREADY:     ____|____|____|‾‾|____|‾‾|____
WLAST:      ____|____|____|____|‾‾|____|____
BVALID:     ____|____|____|____|____|‾‾|____
BREADY:     ____|____|____|____|____|‾‾|____

这个时序里发生了什么?

  • T0:主设备拉高AWVALID,从设备在T1拉高AWREADY,地址在T1传输
  • T2:主设备拉高WVALID,从设备在T3拉高WREADY,第一个数据在T3传输
  • T4:主设备拉高WVALID(第二个数据),同时拉高WLAST,从设备在T5拉高WREADY,第二个数据在T5传输
  • T5:从设备拉高BVALID,主设备同时拉高BREADY,写响应在T5传输

注意看,地址传输和数据传输是流水的。地址在T1传完,数据在T3才开始传。这中间有两拍的间隔。为什么?因为从设备可能需要时间解码地址,准备好存储空间。这就是AXI的灵活性——它不要求所有通道同步。

性能调优小技巧:如果你想提高写操作的吞吐量,可以让写地址和写数据尽量重叠。比如在T0发地址的同时,T0也发第一个数据。这样地址和数据可以同时被处理,减少等待时间。

2.9 常见握手错误

我总结了几个新手容易犯的错误:

  1. VALID在READY之前撤销——协议要求VALID一旦拉高,必须保持到握手完成。你不能因为等不及就撤了。
  2. READY依赖VALID的组合逻辑——前面说过,这会导致组合环路。
  3. 写响应丢失——每个写事务必须有一个B响应,少一个都不行。
  4. 读数据通道的RLAST没拉高——主设备不知道什么时候读完,会一直等。

嗯,这些错误我在项目里都见过。最惨的一次是RLAST没拉高,仿真跑了几个小时,最后发现是状态机少了一个状态。从那以后,我写AXI从设备时,都会先画状态图,再写代码。

好了,五个通道的握手协议就讲到这里。记住一句话:VALID和READY同时为高,数据就传过去了。就这么简单,但背后的设计哲学很深——它让每个通道可以独立流水,互不阻塞。这就是AXI高性能的根源。

可可精品资料库