第2章:AXI通道与握手——读地址通道、读数据通道、写地址通道、写数据通道、写响应通道,VALID/READY握手协议详解

好,咱们直接进入正题。

AXI协议最核心的东西,说白了就是五个独立的通道。它们各司其职,互不干扰。我刚开始接触AXI时,总觉得通道多、信号杂,容易搞混。后来做多了才发现,这种分离设计其实非常优雅——它让读写操作可以完全并行,性能一下子就上去了。

2.1 五个通道,各管一摊

AXI总共有五个通道,我习惯把它们分成两组:

  • 读通道组:读地址通道(AR) + 读数据通道(R)
  • 写通道组:写地址通道(AW) + 写数据通道(W) + 写响应通道(B)

你想想看,读和写是两条完全独立的流水线。读操作不需要等写完成,写操作也不需要等读完成。这在多主多从的系统中,优势非常明显。

关键点:每个通道都是单向的。地址通道永远从主机到从机,数据通道读方向是从机到主机,写方向是从机到主机。响应通道也是从机到主机。方向搞反了,你的设计就废了。

2.2 VALID/READY握手——AXI的灵魂

每个通道的传输,都依赖一对握手信号:VALID 和 READY。

VALID由发送方驱动,表示“我这边数据/地址已经准备好了”。READY由接收方驱动,表示“我现在可以接收了”。

握手成功(即传输发生)的条件只有一个:VALID和READY同时为高。

嗯,这里要注意:VALID不能依赖READY。也就是说,发送方一旦把VALID拉高,就必须保持住,直到握手成功。但READY可以等,可以晚点再拉高。

我曾经在一个项目中,因为VALID信号在握手前被拉低了,导致整个总线卡死。查了两天才找到原因——一个很隐蔽的组合逻辑毛刺。从那以后,我要求团队所有VALID信号必须用寄存器打一拍再输出。

2.3 三种握手场景

握手有三种典型情况,我画个图帮你理解:

场景1:VALID先到 CLK VALID READY 传输发生在VALID和READY都高时 场景2:READY先到 CLK VALID READY 场景3:同时到达 CLK VALID READY

场景1:VALID先拉高,等READY。场景2:READY先拉高,等VALID。场景3:一拍搞定,最理想的情况。

我个人建议在设计时,尽量让从机在空闲时就把READY拉高。这样只要主机一发VALID,一拍就能传完,延迟最小。

2.4 读通道详解

读操作分两步走:先发地址,再收数据。

读地址通道(AR)

主机通过AR通道告诉从机:我要读哪个地址,读多少数据。

信号方向说明
ARVALID主机→从机地址有效
ARREADY从机→主机从机可以接收地址
ARADDR主机→从机读地址
ARLEN主机→从机突发长度(实际传输次数 = ARLEN + 1)
ARSIZE主机→从机每次传输的字节数
ARBURST主机→从机突发类型(FIXED/INCR/WRAP)

小技巧:ARLEN是0-based的。ARLEN=0表示只传1笔数据,ARLEN=7表示传8笔。我见过不少新手在这里栽跟头,把ARLEN直接当成了传输次数。

读数据通道(R)

从机把数据通过R通道返回给主机。注意,读数据通道还带了一个RLAST信号,用来标记最后一笔数据。

信号方向说明
RVALID从机→主机读数据有效
RREADY主机→从机主机可以接收数据
RDATA从机→主机读回来的数据
RRESP从机→主机读响应状态(OKAY/EXOKAY/SLVERR/DECERR)
RLAST从机→主机最后一笔数据指示

读数据通道有一个很重要的特性:数据必须按顺序返回。你不能先回第2笔再回第1笔。这个顺序约束在AXI协议里是强制的。

2.5 写通道详解

写操作比读多一个通道,总共三步:发地址、发数据、收响应。

写地址通道(AW)

和读地址通道几乎一样,只是方向不同。主机告诉从机:我要往哪个地址写数据。

信号方向说明
AWVALID主机→从机写地址有效
AWREADY从机→主机从机可以接收写地址
AWADDR主机→从机写地址
AWLEN主机→从机突发长度
AWSIZE主机→从机每次传输的字节数
AWBURST主机→从机突发类型

写数据通道(W)

主机通过W通道把数据发给从机。这个通道有个WLAST信号,标记最后一笔数据。

信号方向说明
WVALID主机→从机写数据有效
WREADY从机→主机从机可以接收写数据
WDATA主机→从机要写的数据
WSTRB主机→从机字节选通信号(哪些字节有效)
WLAST主机→从机最后一笔数据指示

注意:写数据通道和写地址通道是独立的。主机可以先发地址再发数据,也可以先发数据再发地址,甚至可以同时发。从机必须能处理这些情况。我见过一个设计,从机假设地址一定先到,结果遇到先发数据的场景直接挂死。

写响应通道(B)

从机在接收完所有写数据后,通过B通道返回一个响应,告诉主机这次写操作的结果。

信号方向说明
BVALID从机→主机写响应有效
BREADY主机→从机主机可以接收写响应
BRESP从机→主机写响应状态

写响应只发一次,对应整个突发操作。也就是说,你发了8笔数据,只收1个写响应。

2.6 握手依赖关系——别搞出死锁

五个通道之间,存在一些依赖关系。搞不好就会死锁。

  • 读地址和读数据:从机必须在收到读地址后,才能返回读数据。这是合理的,你都不知道读哪个地址,怎么返回数据?
  • 写地址和写响应:从机必须在收到写地址后,才能返回写响应。同样合理。
  • 写数据和写响应:从机必须在收到所有写数据(包括WLAST)后,才能返回写响应。这个也容易理解,数据没到齐,你怎么知道写成功了?

但是,写地址和写数据之间没有依赖关系。从机必须能处理地址先到、数据后到,或者数据先到、地址后到的情况。

我曾经在一个项目中,从机逻辑写死了“先等地址,再等数据”。结果主机那边为了优化性能,先发了数据再发地址。两边就这么僵住了,总线完全卡死。查了整整一天才找到问题。

避坑指南:设计从机时,写地址和写数据通道一定要独立处理。用两个独立的FSM分别处理AW和W通道的握手,等两者都完成后,再合并触发写响应。

2.7 一个完整的读事务示例

咱们看一个实际的读事务时序。假设主机要读地址0x1000,突发长度4(读4笔数据)。

// 时钟周期1:主机发送读地址
ARVALID = 1, ARADDR = 0x1000, ARLEN = 3
// 从机在周期1拉高ARREADY,地址握手成功

// 时钟周期2:从机返回第一笔数据
RVALID = 1, RDATA = data0, RRESP = OKAY, RLAST = 0
// 主机拉高RREADY,数据握手成功

// 时钟周期3:从机返回第二笔数据
RVALID = 1, RDATA = data1, RRESP = OKAY, RLAST = 0

// 时钟周期4:从机返回第三笔数据
RVALID = 1, RDATA = data2, RRESP = OKAY, RLAST = 0

// 时钟周期5:从机返回第四笔数据(最后一笔)
RVALID = 1, RDATA = data3, RRESP = OKAY, RLAST = 1
// 读事务完成

注意看,RLAST在最后一笔数据时拉高。主机看到RLAST为1,就知道这次读操作结束了。

2.8 一个完整的写事务示例

再看写操作。假设主机要写地址0x2000,突发长度4。

// 时钟周期1:主机发送写地址
AWVALID = 1, AWADDR = 0x2000, AWLEN = 3
// 从机拉高AWREADY,地址握手成功

// 时钟周期2:主机发送第一笔数据
WVALID = 1, WDATA = data0, WSTRB = 0xF, WLAST = 0

// 时钟周期3:主机发送第二笔数据
WVALID = 1, WDATA = data1, WSTRB = 0xF, WLAST = 0

// 时钟周期4:主机发送第三笔数据
WVALID = 1, WDATA = data2, WSTRB = 0xF, WLAST = 0

// 时钟周期5:主机发送第四笔数据(最后一笔)
WVALID = 1, WDATA = data3, WSTRB = 0xF, WLAST = 1

// 时钟周期6:从机返回写响应
BVALID = 1, BRESP = OKAY
// 主机拉高BREADY,写事务完成

这里WSTRB=0xF表示所有4个字节都有效。如果只写低2个字节,WSTRB就设成0x3。

个人习惯:我写RTL时,喜欢把每个通道的握手逻辑封装成独立的模块。这样不仅代码清晰,而且复用性极好。换个项目,直接拿来用就行。

2.9 总结一下

AXI的五个通道和VALID/READY握手,说白了就是一套高效、解耦的通信机制。每个通道独立握手,互不阻塞。读和写完全并行,性能拉满。

设计时记住几个要点:

  • VALID不能依赖READY,但READY可以依赖VALID
  • 写地址和写数据通道独立处理,别搞依赖
  • 读数据必须按顺序返回
  • 写响应只发一次,对应整个突发

把这些搞清楚了,AXI互连设计就成功了一半。剩下的,就是处理多主多从、地址映射、仲裁这些进阶内容了。

可可精品资料库