AXI-Stream TDEST 路由:TID 流标识、mux/demux 与帧边界切换
更新时间:2026-09-25。本文是「集成电路大类 · 芯片设计」序列里的一篇长尾协议实操文。AXI-Stream 的数据面只有 valid/ready 握手加上 TDATA/TLAST,但只要一条总线上要挂多个收端——比如 DMA 出来的包要分发到 DDR、Codec、外设——就必然要动 TDEST 与 TID。这两个信号在规约里各只有几行描述,真正写 mux/demux 时却最容易混用:有人拿 TID 做路由、有人在包中间切 TDEST、还有人共享一个 FIFO 结果一条慢流堵死全部快流。本文把语义、电路和切换时机一次讲清。
本文要回答的问题
- TDEST 和 TID 到底有什么区别,能不能只留一个?
- mux/demux 怎么按 TDEST 译码转发,电路上各自长什么样?
- 多个目的地共享一条流时,背压怎么传播,队头阻塞是怎么来的?
- 路由开关什么时候允许切换,为什么必须卡在 TLAST 帧边界?
一、TDEST 与 TID:路由目的地与流标识
AXI-Stream 规约里,TDATA 是载荷,TLAST 标记包的最后一个 beat,另外三个辅助信号各司其职:
| 信号 | 语义 | 回答的问题 | 是否参与路由 |
|---|---|---|---|
| TDEST | 路由目的地(destination) | 这个包要送到互连的哪个出口 | 是,互连用它译码 |
| TID | 流标识(stream identifier) | 同一出口上这是哪条逻辑流 | 否,端到端透传 |
| TUSER | 侧带附加信息(如字节位置、时间戳) | 随 beat 携带的额外元数据 | 否 |
关键区别是作用域。TDEST 给互连用:每经过一级 mux/demux,电路都要读 TDEST 决定往哪条分支送,它在拓扑里逐跳被解释。TID 给端系统用:同一条物理链路上可以复用多条逻辑流——比如同一个以太网出口上的不同会话、同一个音频接口上的左右声道——接收端按 TID 区分,互连只负责原样搬运,绝不拿它做转发判断。
规约还规定:一个包(packet)内,TDEST/TID/TUSER 的含义必须保持不变。主设备可以在任意 beat 上修改它们,但从 TLAST 之后的下一个 beat 起新值才生效。所以实践中这两个信号是「每包一次」的属性,而不是「每拍一次」。单收端的点对点连接两个都可以不接;只要路径上存在需要选路的互连,TDEST 就是必需的。
二、按 TDEST 路由:demux 译码与 mux 仲裁
一对多的分发端是 demux(解复用器):输入一路 AXI-Stream,读入站 TDEST,译码出 N 个出口的 valid,ready 则反向按当前选中的出口取回。
// demux:1 路入,N 路出,按 TDEST 选出口
logic [N-1:0] sel;
assign sel = 1 << tdest; // 假设 tdest 为 one-hot 编码值
for (genvar i = 0; i < N; i++) begin: g_out
assign m_valid[i] = s_valid & sel[i];
end
assign s_ready = |(m_ready & sel); // 只有被选中出口的 ready 算数这里有个新手必踩的坑:不能把同一个 s_valid 广播给所有出口再让 ready 简单相或。valid 必须先被 sel[i] 门控,否则所有出口都会看到同一笔数据;ready 也必须先与 sel 相与再归约,否则未选中的出口反压会错误地卡住入站。
多对一的汇聚端是 mux(复用器):N 路入、一路出,每个时钟沿按仲裁结果(固定优先级、round-robin 均可)选一路接通。注意 mux 的选路依据不是入站 TDEST——它只有一个出口,无路由可做——而是仲裁策略;入站 TDEST 原样透传到输出,供下一级 demux 使用。仲裁器一旦选中某路,在该路 TLAST 到来前不应抢占,否则会把一个包撕碎(见第四节)。
图里 mux 与 demux 是两类完全不同的设备:一个解决「谁说」,靠仲裁;一个解决「去哪」,靠 TDEST 译码。一条复杂流网络就是这两种元件的级联,TDEST 在每级 demux 上被重新解释,TID 则一路透传到终点。
三、多流复用、背压传播与队头阻塞
valid/ready 的背压规则很直接:接收端拉低 ready,发送端必须保持本拍全部信号不动。问题出在多个目的地共享缓冲的时候。假设 demux 入端只有一个 FIFO,里面依次排着 [TDEST=1 的包][TDEST=0 的包]:出口 1 的设备暂时不接收,队首的包出不去,即使出口 0 正空闲,排在后面、去往 0 的包也拿不到 FIFO 读口——这就是队头阻塞(Head-of-Line blocking)。一条慢流堵死所有快流,吞吐随最慢目的地塌方。
三种常见对策,按成本递增:
| 方案 | 做法 | 代价 |
|---|---|---|
| 入端不缓存 | demux 直接组合直通,各出口 ready 各自门控 | 无法吸收抖动,时序差,一个出口反压仍沿路径前传 |
| 每出口独立 FIFO | 入端先按 TDEST 把包写进 N 个 FIFO,出口各读各的 | 缓冲面积乘 N,但彻底隔离背压 |
| 共享缓冲 + 按目的地队列 | 一个 RAM 内逻辑分 N 条队列,写指针按 TDEST 选择 | 控制复杂,用在出口多的网络芯片里 |
实践中「每 TDEST 一条 FIFO」是最常用的口径:写侧用 TDEST 译码选写使能,读侧 N 个出口互不影响,某出口反压只让它自己的 FIFO 涨满;只有所有相关 FIFO 都满时才向更上游拉低 ready。FIFO 深度的估法见 FIFO 深度设计;跨时钟场景每出口队列各自接 异步 FIFO 与 CDC。背压本身的逐拍波形与保持规则见 AXI-Stream 反压时序。
四、开关切换时机:帧边界 TLAST
无论 demux 的选路还是 mux 的仲裁,开关都只能在帧边界翻转,即一个包的 TLAST beat 握手完成之后、下一个包的首 beat 之前。
为什么必须如此?从接收端看,一个包是不可分割的整体:它可能要写进一段连续内存、送进一个有协议状态的外设。如果包中间 demux 改了出口,这个包就被劈成两半送进两个设备,两边各自收到半截帧,TLAST 也对不上;mux 在包中间切换输入同理,出口上会出现两个包的 beat 交错,接收端无法还原。
RTL 上用一个「帧内锁定」状态即可保证:复位或 TLAST 握手后的第一个 beat,把 TDEST 采样进寄存器 sel_locked,此后整个包都用这个寄存器值,直到 TLAST 被接受才释放。mux 的仲裁器同理,给当前 grant 加条件「保持到选中流的 TLAST 完成」,这也叫帧锁定(frame-locked)仲裁。
顺带一提 TID 在这里的用法:如果发送端想让两个包交织在同一物理流上(每包仍是整体、但包与包交替),接收端靠 TID 重组;但beat 级交织必须由两端显式约定,通用互连不允许这么做。宽度转换不会改变这些规则——Stream 位宽转换器内部也要按帧锁定,TDEST/TID 只跟首 beat 走。DMA 侧的分发实践见 AXI-Stream DMA scatter。
五、验证检查清单
monitor 与记分板建议分层覆盖以下检查点:
- 帧内稳定:除 TLAST 后的 beat 外,同一包内 TDEST/TID 必须恒定,违例即协议错误;
- 路由正确:按每包首 beat 的 TDEST 建立期望出口,包内所有 beat 必须全部出现在且只出现在该出口;
- 包完整性:每个出口收到的 beat 数与 TLAST 位置和发送端一致,无粘连、无断裂、无跨包串味;
- 背压隔离:随机拉低某一出口 ready,其他出口的吞吐不应受影响(对应每出口 FIFO 方案);
- 边界切换:在 TLAST 同拍翻转 TDEST、在反压期间保持新值,验证锁定寄存器的释放与重采样;
- 覆盖率:每个 TDEST 值、出口满与空、背压窗口跨越帧边界、TID 多流并存都至少命中一次。
这一篇在知识体系里的位置
握手机制见 valid/ready 握手;反压波形见 AXI-Stream 反压时序;缓冲深度见 FIFO 深度设计 与 异步 FIFO 与 CDC;宽度适配见 Stream 位宽转换器;系统侧落地见 AXI-Stream DMA scatter。
总结
TDEST 是给互连用的逐跳路由目的地,TID 是给端系统用的逻辑流标识、全程透传,二者语义不可互换;demux 按 TDEST 译码并对 valid/ready 双向门控完成分发,mux 靠仲裁汇聚且 TDEST 原样透传;多目的地共享单一 FIFO 会因慢流产生队头阻塞,按 TDEST 拆独立队列即可隔离背压;demux 选路与 mux 仲裁都必须帧锁定,开关只在 TLAST 握手完成后的帧边界翻转,包中间切路会把整包撕碎,验证时按帧内稳定、路由正确、包完整与背压隔离四类检查点闭环。