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、WRAPARID—— 读事务ID,用于乱序返回
握手过程很简单:主设备拉高 ARVALID,从设备拉高 ARREADY,一拍完成地址传输。
我曾经遇到过一个坑:从设备的 ARREADY 设计成了组合逻辑,直接连到 ARVALID 上。结果呢?形成了组合环路,仿真能跑,综合报了一堆时序违例。嗯,这里要记住——READY 信号最好用寄存器打一拍。
2.3 读数据通道(R)
读数据通道是反向的——从设备把数据返回给主设备。
关键信号:
RDATA—— 读回来的数据RRESP—— 读响应状态:OKAY、EXOKAY、SLVERR、DECERRRLAST—— 最后一个数据指示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、DECERRBID—— 对应写事务的ID
写响应通道只有一对信号,没有数据。每个写事务必须有一个写响应。也就是说,你发了一个AW请求和N个W数据,最后一定会收到一个B响应。
我曾经犯过一个低级错误:写响应通道的 BVALID 忘了拉高,结果主设备一直等,整个总线卡死了。嗯,这种问题定位起来特别费劲——波形上看,所有信号都正常,就是没响应。后来我养成了一个习惯:写从设备时,先把写响应通道的握手逻辑写好,再写数据通道。
2.7 握手协议的可视化
下面这张图展示了五个通道之间的关系。我特意用SVG画了,方便你理解数据流的方向。
从图上可以清楚看到:读操作只需要两个通道(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 常见握手错误
我总结了几个新手容易犯的错误:
- VALID在READY之前撤销——协议要求VALID一旦拉高,必须保持到握手完成。你不能因为等不及就撤了。
- READY依赖VALID的组合逻辑——前面说过,这会导致组合环路。
- 写响应丢失——每个写事务必须有一个B响应,少一个都不行。
- 读数据通道的RLAST没拉高——主设备不知道什么时候读完,会一直等。
嗯,这些错误我在项目里都见过。最惨的一次是RLAST没拉高,仿真跑了几个小时,最后发现是状态机少了一个状态。从那以后,我写AXI从设备时,都会先画状态图,再写代码。
好了,五个通道的握手协议就讲到这里。记住一句话:VALID和READY同时为高,数据就传过去了。就这么简单,但背后的设计哲学很深——它让每个通道可以独立流水,互不阻塞。这就是AXI高性能的根源。