3. AXI4-Stream协议详解:TVALID/TREADY握手、数据包格式、TLAST信号、字节对齐

AXI4-Stream协议,在我看来,是整个DMA桥设计的“血液”。数据怎么流、什么时候流、流到哪里结束,全靠这套规则。我刚开始接触Xilinx的IP核时,以为这玩意儿和普通FIFO接口差不多,结果第一次上板调试就吃了大亏——数据丢包了。嗯,从那以后,我再也不敢小看这套握手协议了。

3.1 握手信号:TVALID 与 TREADY

AXI4-Stream本质上是一个点对点的单向数据通道。没有地址线,没有读写控制,只有数据和握手。这里的核心就是两个信号:TVALID 和 TREADY。

核心规则:只有当 TVALID 和 TREADY 在同一时钟上升沿同时为高电平时,数据才完成一次握手,一个数据节拍(beat)才算传输成功。

说白了,就是一个“我准备好了,你准备好了吗?”的游戏。

  • TVALID:由发送端(Source/主设备)驱动。拉高代表“我这里有有效数据要发,你准备接收吧”。
  • TREADY:由接收端(Sink/从设备)驱动。拉高代表“我已经准备接收数据了,来吧”。

握手有三种典型场景,我习惯用下面这张时序图来解释:

// 场景一:背靠背传输 —— 效率最高
Clock   :  _-_-_-_-_-_-_-_-_-
TVALID  :  ___-----------___
TREADY  :  ___-----------___
DATA    :  X__D0__D1__D2__X  // 每个时钟都握手成功

// 场景二:发送端等待
Clock   :  _-_-_-_-_-_-_-_-_-
TVALID  :  ___---_____---___
TREADY  :  ___-----------___
DATA    :  X__D0_____D1___X  // TREADY一直高,但TVALID中间拉低

// 场景三:接收端等待(最常见的坑)
Clock   :  _-_-_-_-_-_-_-_-_-
TVALID  :  ___-----------___
TREADY  :  ___-----_____---_
DATA    :  X__D0__D1_____D2X // 数据D2等了好几个周期才发出去

我记得在做一个高速ADC采集卡的项目时,接收端用的是一块带AXI4-Stream接口的FIFO。FIFO满的时候会拉低TREADY,但我的DMA发送逻辑没处理这个场景,直接把数据扔出去就不管了。结果就是数据覆盖、帧错乱。最后还是老老实实加了一个状态机,等TREADY为高时再更新下一拍的TVALID。

个人建议:设计发送端时,TVALID 尽量不要简答地等于“有数据”,最好和 TREADY 做一次组合逻辑判断。或者直接用寄存器打一拍,避免组合路径过长导致时序紧张。

3.2 数据包格式与 TLAST 信号

AXI4-Stream虽然是流式的,但很多外设(比如以太网帧、PCIe TLP包、视频行信号)都需要分包传输。这时候 TLAST 信号就粉墨登场了。

TLAST 是发送端在最后一个有效数据节拍(beat)时拉高的信号。它告诉接收端:这个包结束了,你可以开始处理了。

我项目里最常见的用法是:DMA从DDR中搬运一段连续数据,通过AXI4-Stream发给某个IP核(比如加密模块)。每次搬运的大小可能不同,所以必须明确告知对方“数据到此为止”。

数据包结构示例:

// 一个包含3个有效数据(32-bit)的包
Clock   :  _-_-_-_-_-_
TVALID  :  ___-------_
TREADY  :  ___---_---
TDATA   :  X__A__B__C_X
TLAST   :  ________-_  // 注意:TLAST在发送C时拉高
TUSER   :  X__0__0__1_X  // 可选,常用来携带包长度或校验值

为什么TLAST这么重要?你想想看,如果没有TLAST,接收端只能依靠超时或者外部信号来判定包边界。对于确定性低的硬件传输,超时判定在实际项目中非常不靠谱。我遇到过一个案例,因为TLAST没正确拉高,接收端的FIFO一直处于“等待包结束”的状态,导致DMA回读地址永远不对——这BUG查了我整整两天。

警惕:Xilinx的多数AXI4-Stream IP核(如FIFO、DataMover)都内置了TLAST处理逻辑。如果你的设计里没有使用TLAST,务必检查IP核的配置,有些核默认开启了“PACKET模式”(即依赖TLAST)。如果你传输的是连续流且不需要分包,请配置为“SINGLE”模式,否则DMA会永远挂起等待TLAST。

3.3 字节对齐(TKEEP 与 TSTRB)

TDATA的数据宽度通常是字节的整数倍,比如32位(4字节)、64位(8字节)、128位(16字节)。但不是每个时钟周期都要把所有字节都填满数据。这时就需要 TKEEP 和 TSTRB 来标识哪些字节是有效的。

这两个信号都是位宽对齐的(每个字节对应1bit)。区别在于:

信号 宽度(对应T字节) 作用 典型场景
TSTRB TDATA/8 指示对应的数据字节是“有效数据”还是“位置填充” PCIe、带有内存写操作的流
TKEEP TDATA/8 指示对应的数据字节是“有效数据”还是“被屏蔽的dummy数据” AXI4-Stream标准中建议优先使用TKEEP,TSTRB可选

本质上,TKEEP更偏向“数据流护送”,TSTRB则往往配合写字节使能。在Xilinx的DMA Bridge设计中,我强烈推荐统一使用TKEEP。因为大部分Xilinx IP核只实现了TKEEP,TSTRB会被忽略。

举个例子:你从一个以太网MAC收到一个64字节的包,但最后一拍只有3个字节有效。如果TDATA是64位(8字节宽),那最后一拍的TKEEP应该是8'b00000111(低3位为1,其余为0)。接收端根据TKEEP就知道,这8个字节里只有最后的3个是需要保留的。

避坑指南:我曾在设计一个字节对齐模块时,错误地把TKEEP和TSTRB的更新条件和TVALID分开处理了。结果导致TKEEP信号在TVALID为高时还在变化,接收端看到的数据和掩码对不上。正确的做法是:TKEEP和TSTRB必须与TDATA保持严格的同步,同一拍变,同一拍握。

3.4 知识体系结构图

说了这么多,我用一张图来总结AXI4-Stream协议的核心结构:

AXI4-Stream 协议知识架构 核心握手信号 • TVALID:数据有效 • TREADY:准备接收 • 同时拉高 → 握手成功 • 任一无效 → 等待 数据传输基础规则 包边界控制 • TLAST:包结束标识 • 与最后1个beat同时拉高 • 接收端以此裁定包边界 • PACKET模式必须使用 分包传输定义 字节级控制 • TKEEP:数据字节屏蔽 • TSTRB:字节使能 • 推荐优先采用TKEEP • 必须与TDATA同步 非对齐数据处理 AXI4-Stream 传输核心流程 1. 发送端准备数据,拉高TVALID 2. 接收端就绪时拉高TREADY 3. TVALID & TREADY 同时拉高 → 数据拍传输成功 ⚠ 关键区分: TLAS定义包边界(包级控制),TKEEP/TSTRB定义包内有效字节(字节级控制),两者互补,缺一不可。

这张图展示了我个人理解的AXI4-Stream协议三层结构:

  1. 基础握手层:TVALID + TREADY,决定了基本的数据传输节拍。
  2. 包边界层:TLAST,决定了什么时候一个数据包结束。
  3. 字节完好层:TKEEP/TSTRB,决定了每个字节是否真的有效。

任何一个FPGA工程师在设计AXI4-Stream接口时,都要同时考虑这三层。跳过去任何一层,都可能在系统联调时翻车。

3.5 常见错误清单与对策

序号 典型错误 具体表现 解决方法
1 TVALID在TREADY拉低后仍变化 数据从发送端丢失,接收端看到错误数据 设计状态机:TVALID一旦拉高,必须维持到TREADY为高,握手成功才能更新
2 TLAST未在最后一拍拉高 接收端(如DMA)永远不认为包结束,系统卡死 使用计数器记录包长度,在最后一拍组合逻辑生成TLAST
3 TKEEP与TDATA时序不同步 有效和无效字节错位,数据校验失败 使用相同寄存器级联TDATA和TKEEP,保证二者经过相同cycle数
4 忽略TREADY回压 发送端不管接收端是否满,一直发数据 务必采样TREADY,握手失败则不更新TVALID

特别提醒:调试AXI4-Stream接口时,如果SignalTap或ILA抓到的TVALID、TREADY和TDATA看起来都正常,但功能还是不对——请一定检查TLAST和TKEEP。我曾经就是栽在这个坑里:TDATA完全正确,TREADY也一直高,但是TLAST比实际数据晚了一个时钟,导致接收端多收了一个垃圾beat。

把AXI4-Stream搞透,后面的DMA Bridge设计才能走得顺。毕竟数据从哪来、去哪、在哪停,全由这几个信号说了算。我个人习惯在正式模块设计之前,先写一个简单的AXI4-Stream环回测试(loopback),把发送端和接收端用一根线接起来,用ILA抓一下握手时序——千万别嫌麻烦,这个习惯帮我省了至少一半的联调时间。