AXI-Stream 帧传输:TLAST 帧边界、TKEEP 稀疏字节与 TUSER 位置边带
更新时间:2026-09-25。本文是「芯片设计 · 进阶层」接口主题簇的枢纽深文。AXI4-Stream 与 DMA 数据流 给出了协议全貌,AXI-Stream 反压握手 把 TVALID/TREADY 的波形讲透,AXI-Stream 位宽适配器 讲残拍在拼包拆包时的处理。本篇把视角收窄到一个问题:一串连续 beat 里,帧边界、部分有效字节和位置边带到底怎么编码——这是视频、显示、网络包通路每天都在用的东西,也是 RTL 和验证最容易各自理解错一层的接口契约。
本文要回答的问题
- 一串没有地址的 beat 流过,接收端凭什么知道一帧在哪里结束、下一帧从哪里开始?
- 帧长不是总线宽度整数倍,最后一拍只有几个字节有效,TKEEP 的合法图案有哪些?
- TUSER 和 TDATA 是什么关系,为什么视频 SOF、错误标记要放在 TUSER 而不是另开一根线?
- 帧间气泡(blanking)和下游背压在波形上如何区分,stall 期间边带信号要保持什么?
- 一路真实的 1080P RGB888 视频,像素怎样组织成 64 位 beat,行尾、帧首标记落在哪一拍?
一、帧的世界观:beat、frame 与三组边带信号
AXI-Stream 的物理世界只有一个动作:TVALID 与 TREADY 同拍为高,一个 beat(数据拍)从源传递到汇。帧(frame,也称 packet)不是协议里的独立对象,而是接收端在 beat 序列上重建出来的区间——从某个 beat 开始,到带 TLAST 的 beat 结束。协议负责提供重建区间所需的全部线索,线索由三组边带信号承载:
| 信号 | 粒度 | 回答的问题 |
|---|---|---|
| TLAST | 每 beat 1 位 | 这一拍是不是本帧最后一拍? |
| TKEEP | 每字节 lane 1 位 | 这一拍里哪些字节 lane 的内容算数? |
| TUSER | 每 beat 一组,位宽自定义 | 这一拍位置上还有什么附加信息(SOF、错误、行号…)? |
三者都只在握手拍被采样,握手规则与载荷保持义务与 valid/ready 握手 完全相同。另有一个近亲 TSTRB:TKEEP=0 表示「该字节位置存在但内容无意义、可丢弃」,TSTRB=0 表示「该位置是插入的占位,没有真实数据」。纯数据帧通路几乎只用 TKEEP,本篇也只展开它。
SRC: "帧发送端" {
FSM: "帧状态机\nbeat 计数 / 长度比较"
}
LINK: "AXI-Stream 链路" {
B1: "beat0..beatN-2\nTLAST=0 TKEEP=全1"
B2: "beatN-1\nTLAST=1 TKEEP=部分有效"
GAP: "帧间气泡\nTVALID=0"
B3: "下一帧 beat0\nTUSER.SOF=1"
}
DST: "帧接收端" {
RSM: "帧重组逻辑\n按 TLAST 封帧"
}
SRC.FSM -> LINK.B1
LINK.B1 -> LINK.B2
LINK.B2 -> LINK.GAP
LINK.GAP -> LINK.B3
LINK.B3 -> DST.RSM图上四种拍覆盖了帧传输的全部形态:帧内普通拍、末拍(部分有效)、帧间气泡、下一帧首拍(带 SOF 边带)。下面逐节拆开。
二、TLAST:在 beat 序列上钉下帧边界
TLAST 是接收端唯一的「硬」封帧依据。源端在送出本帧最后一个 beat 的同一拍将 TLAST 拉高,接收端在该握手拍完成两件事:把当前累积缓存作为一帧交付(写盘、送解析器、DMA 关闭 buffer),帧计数器清零,准备接收下一帧。
工程上必须先想清楚的一个设计决策是:TLAST 标记的是哪种边界。网络 MAC 里 TLAST 标记一个报文,视频通路里有两种合法约定:
| 约定 | TLAST 语义 | 帧边界怎么找 | 典型用户 |
|---|---|---|---|
| 行尾 EOL | 每行最后一拍 | 配合 TUSER.SOF 数行,凑齐行数即帧 | Xilinx AXI4-Stream Video 类 IP |
| 帧尾 EOF | 整帧最后一拍 | 两个 TLAST 之间即一帧 | DMA S2MM、通用封包通路 |
两种约定不能在同一系统里混用而不写文档:接收端按 EOF 实现、源端按行发 EOL,结果就是每一帧被拆成上千个 1–2KB 的碎 buffer。视频行/帧的完整例子放在第六节。
TLAST 只在握手拍有效,因此它和 TVALID 一样,一旦在 TVALID 上伴随出现就必须随数据保持到握手成功——不能这拍带 TLAST、下拍被反压后 TLAST 掉了而数据还在。接收端看到的将是一帧「没有尾巴」的数据。
源端生成 TLAST 的标准结构是「长度寄存器 + beat 计数器」:帧首 beat 装入长度(总 beat 数或残拍信息),每握手一次计数加 1,比较相等的那一拍同时给出稀疏 TKEEP 与 TLAST。不要用「FIFO 读空」当 TLAST——读空只说明源暂时没货,垂直消隐、上游 stall 都可能让 FIFO 在帧中途短暂见底,拿空信号封帧等于随机截断。长度必须来自帧的元数据(描述符、传感器配置寄存器),而不是数据通路的瞬时水位。接收端则建议维护一个「帧内/帧间」状态:帧间状态只接受带 SOF 的 beat,帧内状态收到 TLAST 才允许回到帧间;两个 TLAST 之间若冒出 SOF、或长度与声明不符,直接报错而不是默默重对齐。
三、TKEEP:最后一拍的稀疏字节
帧长是字节粒度,总线宽度是 8/16/32…字节,两者相除几乎总有余数。19 字节的帧走 64 位(8 字节)总线:两拍满拍之后,第三拍只有 lane0–lane2 三个字节有效,此时 TKEEP=8'b0000_0111,TLAST 同拍为 1。这一拍称为稀疏拍(sparse beat)或残拍。
规约对 TKEEP 的图案有两个实用约束,RTL 和验证都要按此检查:
- TKEEP=1 的 lane 必须从 lane0 起连续,即只允许
1111_0000这种「低若干位为 1」的图案,不允许1011_0001这种中间镂空。位置(lane)和长度(连续 1 的个数)共同决定真实字节数。镂空图案在 Stream 里没有合法语义——那种语义属于 TSTRB。 - 非末拍 TKEEP 必须全 1。稀疏只允许出现在 TLAST=1 的那一拍;帧中间出现稀疏拍,等于在一帧内部挖洞,下游无法定位后续字节。
两个边界情形值得在验证环境里显式构造。帧长恰好是宽度整数倍时,末拍也是满拍,TKEEP=全1, TLAST=1,不要因为「没有残字节」就漏掉 TLAST。零长帧在多数实现里表达为 TKEEP=全0, TLAST=1 的一个空 beat,源端是否会发、接收端是否接受,必须在接口文档里约定一致;位宽适配器 在拼包拆包时尤其依赖这条约定,否则窄→宽拼包会把垃圾字节标成有效,宽→窄会发出一串下游误解的假拍。
四、TUSER:与拍位置绑定的边带
TUSER 是每 beat 携带一份的自定义宽度总线,规约不定义任何一位的含义,但它有一条不可违背的结构性规则:TUSER 的语义单位是「位置」,某位属于哪个 beat,就必须在该 beat 的握手拍上出现并随数据保持。 它不是每帧一根的慢信号——帧级信息(如 SOF)也是编码在「某个特定 beat 的 TUSER」上,通常是帧的首拍。
视频和数据通路的高频位定义收敛成下面几种:
- SOF(Start Of Frame):只在整帧第一个 beat 的握手拍为 1,其余 beat 为 0。它和帧尾的 TLAST 首尾呼应:接收端即使中途丢失计数,也能靠下一个 SOF 重新对齐帧——这正是 SOF 不能省的原因,行计数的长期漂移被每帧一次的硬标记截断。
- 错误 / Poison 标记:标记「这一拍位置上的数据已知有错」,但允许流水继续流动,只在最终消费点(写 DDR 前、显示输出前)拦截或替换。相比一出错就冲刷整条流水线,高带宽视频通路几乎都选 poison 方案。
- 校验子:按 beat(甚至按字节 lane)放奇偶或 ECC,接收端复算。这与 AXI4 USER 边带信号 里 WUSER 的端到端保护思路同源:边带不被任何中间级解释,只随数据流动。
- 位置类:行号、像素坐标、时间戳、trace 标签。验证记分板可以直接按 TUSER 里的 tag 对齐期望帧,比纯靠顺序更抗乱序调试。
集成时三个坑年年复现。其一,TUSER 在 Stall 期间必须与 TDATA 同生命周期:数据没被收走,TUSER 也不能换。其二,经过 FIFO、skid buffer、宽度转换时 TUSER 要逐 beat 缓冲,拍数合并/拆分时多份 TUSER 怎么拼接必须显式定义,漏寄存一级就会出现「数据是上一拍的、TUSER 是这一拍的」错位。其三,复位和空闲拍 TUSER 应输出全 0 而不是 x,否则下游组合逻辑产生 x 传播,仿真在第一帧就污染一片。
五、帧间气泡与背压:两种「没数据」要分清
波形上有两种看起来都「不动」的周期,协议语义完全不同:
- 帧间气泡(inter-frame bubble / blanking):源端没有数据,TVALID=0。视频上对应垂直消隐期,网络上对应包间隙。这是正常的流内容,接收端 TREADY 可以保持高。
- 背压停摆(stall):源端有数据且 TVALID=1、全部载荷稳定保持,只是 TREADY=0,握手不成立。数据在原地等,周期数不计入帧长度。

逐拍读图:T1 帧首 beat 上总线,TVALID=1、TUSER.SOF=1,TREADY 已高,当拍完成首 beat 传递;T2 SOF 回到 0;T3 下游突然反压,TREADY 拉低一拍——此时帧内某个 beat 被顶在原地,TVALID 与所有边带保持原值,T4 TREADY 恢复后流水继续,反压只吃掉一个周期的吞吐,不改变帧内容;T2–T7 帧内 beat 连续传递;T7 末 beat 带 TLAST 完成封帧;T8–T9 TVALID 为低,这两拍是帧间气泡而非 stall,区分判据就是 TVALID;T10 下一帧首 beat 到来。
性能核算时两者都表现为带宽损失,但处理手段相反:气泡靠加深 FIFO、提前预取来填(深度算法定见 FIFO 深度计算与设计),stall 则是反压机制正常工作、数据零损失。多级链路里下游一级 stall,ready 沿链路反向组合传播、上游各级同拍冻结的完整波形与 skid buffer 修法,AXI-Stream 反压握手 有逐拍展开,跨时钟则走 异步 FIFO 的 CDC。
六、完整实例:1080P RGB888 视频行/帧打包
把前面所有要素串到一条真实通路上。规格:1920×1080 分辨率,每像素 RGB888 共 3 字节,TDATA=64 位(8 字节 lane),行尾发 TLAST(EOL 约定),TUSER bit0 作 SOF。
第一步,像素到字节 lane 的组织。字节按光栅顺序紧密排列,每拍塞 8 个字节,即一拍装 2 个完整像素加第 3 个像素的前 2 字节:
注意 beat 边界与像素边界不对齐:P2 的 B 分量落在下一 beat 的 lane0。这就是为什么通路必须按字节 lane 而不是按像素做拼装——打包器维护一个字节写入指针(0–7 循环)和像素输入 FIFO,每来一个像素就写 3 个连续字节 lane,指针回绕即发出一个满 beat。
第二步,行长度核算。每行 1920×3 = 5760 字节,5760 ÷ 8 = 720,恰好整除:每行 720 个满 beat,第 720 beat(下标 719)TKEEP=全 1、TLAST=1 表示行尾。本例行没有残拍;换成 1920 像素的 RGBA(每像素 4 字节)结论相同,而 1280×720 RGB888 的行长 3840 字节同样整除——真实设计里故意选整除尺寸就是为了避开残拍。若尺寸不可选(如 1000 像素宽的传感器,3000 ÷ 8 余 0…也整除;999 像素则余 5 字节),行尾 beat 用 TKEEP=8'b0001_1111 精确封口,少一个 1 就丢像素、多一个 1 下游读出脏像素。
第三步,帧标记与数据流。一帧 1080 行:第 0 行的第 0 个 beat 上 TUSER.SOF=1;此后每行最后 beat 的 TLAST 让接收端行计数器加 1;第 1079 行的 TLAST 同时是帧尾,接收端收齐 1080 行即交付一帧;垂直消隐期 TVALID 拉低形成帧间气泡,下一个 SOF 开启下一帧。
显示侧再从帧缓冲经 DMA 反向读出、送显示控制器的链路,见 显示控制器与帧缓冲 DMA。这个例子里每个信号各司其职、缺一不可:没有 TLAST,DMA 不知道一行在哪里关 buffer;没有 TKEEP 的精确约定,非标尺寸行尾必然出错;没有 TUSER.SOF,行计数一旦漂移就再也找不回帧首;TVALID/TREADY 的保持义务则保证消隐期与背压期都不丢一个像素。
打包器实现上还有一个值得写清楚的细节:写指针每收到一个像素前进 3 格,它完全可以在一个时钟周期内跨 beat 回绕,因此像素写入需要拆成「本 beat 尾部几字节 + 下 beat 头部几字节」两次使能,或者要求输入侧允许打包器用两拍消费一个跨边界像素。新手常犯的错是默认一个像素一拍能放下,结果仿真前几百个像素(beat0 恰好从 P0 开始)全对,跑到 beat 边界第一次跨越时字节整体错位两拍,而且之后再也恢复不了。用 3 与 8 的最小公倍数理解就很直观:每 8 个像素(24 字节)相位重新对齐一次,验证环境至少要覆盖超过这个周期的连续行。
验证清单(这条通路的最小完备集):随机 TREADY 拉低间隔并检查 stall 期间 TDATA/TKEEP/TLAST/TUSER 全稳定;断言非末拍 TKEEP 全 1、TKEEP 的 1 连续;每个 SOF 之后精确数到 1080 个 TLAST 才允许下一个 SOF;注入一次 beat 错误、检查 poison 位在写盘点被拦截;构造非标宽度跑通残拍;检查 FIFO 两侧 TUSER 无错位。
这一篇在知识体系里的位置
协议全貌与 DMA 桥接见 AXI4-Stream 与 DMA 数据流;握手机制、气泡与 skid buffer 见 valid/ready 握手 与 AXI-Stream 反压握手;残拍在宽度转换里的处理见 AXI-Stream 位宽适配器;USER 边带在内存映射侧的语义见 AXI4 USER 边带信号;视频落地方向见 显示控制器与帧缓冲 DMA。
总结
AXI-Stream 帧传输的本质是接收端在无地址 beat 序列上重建帧区间:TLAST 在最后一个握手拍钉下帧(或行)边界,TKEEP 用从 lane0 起连续的 1 标记末拍的有效字节、非末拍必须全 1,TUSER 则携带与拍位置严格绑定、随数据同生共死的边带——视频 SOF 落在帧首 beat、错误用 poison 位、校验与坐标信息逐 beat 流动;TVALID=0 的帧间气泡是正常空白、TVALID=1 而 TREADY=0 的背压是原地保持,二者在波形上靠 TVALID 区分;1080P RGB888 实例中像素按字节紧密打包、beat 边界与像素边界不对齐,720 个满 beat 成行、行尾 TLAST、帧首 TUSER.SOF,数据流经打包器、FIFO、DMA 落帧缓冲,任何一个边带缺失或错位都会以碎包、脏字节或帧漂移的形式表现出来。