AXI-Stream反压与背靠背传输——TREADY反压丢帧、TDEST路由错误、反压死锁,流式数据一反压就出事

EADY反压丢帧、TDEST路由错误、反压死锁,流式数据一反压就出事

承接上两篇AXI4-Lite、AXI4-Full,本篇详解FPGA最常用流式接口AXI-Stream。

AXI-Stream是FPGA里用得最多的数据接口------没有"之一"。

ADC数据流、视频流、网络包、DMA引擎、FFT/滤波器......所有流式数据处理都用AXI-Stream。

但AXI-Stream的"反压"机制,是新手踩坑的重灾区。

TREADY一拉低,数据就停------停得不对,帧就丢了;停得太久,系统就死锁了。

这篇把AXI-Stream最常见的4个致命坑全部拆开。


痛苦点一:TREADY反压导致丢帧

问题描述

你的数据通路是这样的:

复制代码
ADC → FIFO → 数据处理模块 → AXI-Stream → DMA → DDR
复制代码
当DMA忙的时候,TREADY拉低(反压),数据通路暂停。但TREADY恢复后,你发现:少了几帧数据!帧计数从123直接跳到127,中间3帧不见了。

根本原因

AXI-Stream的反压规则:当TREADY=0时,Master必须保持TDATA和TVALID不变。

但"保持不变"≠"数据不丢"。问题出在反压的起点------你用什么来缓冲反压期间的数据?

text

复制代码
错误做法

verilog

// ❌ 错误1:反压时直接丢弃上游数据

always @(posedge aclk) begin

if (s_axis_tvalid && s_axis_tready) begin

data_reg = s_axis_tdata;

data_valid = 1'b1;

end else begin

data_valid = 1'b0; // ❌ TREADY=0时丢弃数据

end

end

// ❌ 错误2:FIFO太小,反压时间稍长就溢出

// ❌ 错误3:反压信号没有传递到最上游

正确做法

verilog

// ✅ 第一层:反压直接透传

assign s_axis_tready = m_axis_tready; // 下游忙 → 上游停

assign backpressure = !m_axis_tready;

// ✅ 第二层:FIFO深度计算(关键公式)

// FIFO深度 = 最大反压时间 × 数据速率 × (1 + 余量20%)

// 例:反压10μs,数据率100MHz → 深度 ≥ 1200

// ✅ 第三层:反压超限监控(核心逻辑)

always @(posedge aclk or negedge aresetn) begin

if (!aresetn) stall_cnt <= 0;

else if (tvalid && !tready) begin

stall_cnt <= stall_cnt + 1;

stall_warning <= (stall_cnt >= MAX_STALL_CYCLES/2);

stall_overflow <= (stall_cnt >= MAX_STALL_CYCLES);

end else begin

stall_cnt <= 0;

end

end

反压FIFO深度计算公式:

FIFO深度 = 最大反压时间 × 数据速率 × (1 + 余量20%)

场景 反压时间 数据率 FIFO深度
DMA短暂停顿 1μs 100MHz ≥120
DDR刷新 10μs 200MHz ≥2400
PCIe重试 100μs 250MHz ≥30000
极端场景 1ms 100MHz ≥120000

痛苦点二:TDEST/TID路由错误,数据发到了错误的端口

问题描述

你的系统有多个AXI-Stream数据流,通过Switch/Router分发:

text

但数据经常发到错误的端口------该去FFT的数据跑到了DDR,该直通的数据去了调试口。

根本原因

AXI-Stream用TDEST做路由,TDEST在帧的第一个Beat有效。如果TDEST时序不对,Switch会按照上一帧的TDEST值路由。

错误做法

verilog

// ❌ 错误:TDEST在帧中间变化

always @(posedge aclk) begin

m_axis_tdest = (beat_cnt == 0) ? 2'd0 : (beat_cnt == 5) ? 2'd1 : 2'd0;

end

正确做法

verilog

// ✅ 帧起始锁存TDEST,帧内保持不变

reg DEST_WIDTH-1:0 dest_reg;

reg in_frame;

always @(posedge aclk) begin

if (!in_frame && pkt_valid) begin

in_frame <= 1'b1;

dest_reg <= pkt_dest; // 锁存TDEST

end else if (in_frame && pkt_last) begin

in_frame <= 1'b0;

end

end

assign m_axis_tdest = dest_reg; // 整个帧期间不变

复制代码
AXI-Stream Sideband信号速查表:
信号 用途 有效时机
TDEST 路由目标 帧的第一个Beat
TID 数据源标识 帧的第一个Beat
TUSER 用户自定义 通常帧头=1,帧体=0
TLAST 帧结束 帧的最后一个Beat
TKEEP 字节有效 每个Beat

痛苦点三:反压死锁------两个模块互相等TREADY,系统挂死

问题描述

你的数据通路有两个AXI-Stream模块串联:

text

复制代码
模块A → 模块B → 下游

上电后,模块A的TVALID=1但TREADY=0,模块B的TVALID=1但下游TREADY=0。下游恢复TREADY=1后,系统竟然还是卡死。

根本原因

典型死锁根源 :TREADY采用组合逻辑且依赖本级/上游TVALID,形成组合反馈环路。综合工具会报combinational loop警告,电路卡死。

text

错误做法

verilog

// ❌ 死锁:TREADY依赖TVALID(组合环路)

assign s_axis_tready = !fifo_full && !s_axis_tvalid;

正确做法:标准Skid Buffer(防死锁)

verilog

// ✅ TREADY只依赖内部状态,不依赖TVALID

assign s_axis_tready = !skid_valid; // 关键

// Skid Buffer状态机核心

always @(posedge aclk or negedge aresetn) begin

if (!aresetn) skid_valid <= 0;

else case ({skid_valid, s_axis_tvalid, m_axis_tready})

3'b010: begin // 空,上游有数据,下游拒 → 存入Skid

skid_valid <= 1;

skid_data <= s_axis_tdata;

end

3'b101: begin // Skid有数据,下游接收 → 清空

skid_valid <= 0;

end

3'b111: begin // Skid有+下游收+上游新 → 先送旧,Skid清空

skid_valid <= 0;

end

default: ;

endcase

end

// 输出优先取Skid数据

assign m_axis_tdata = skid_valid ? skid_data : s_axis_tdata;

assign m_axis_tvalid = skid_valid || s_axis_tvalid;

防死锁检查清单:

检查项 要求
TREADY是否依赖TVALID ❌ 不允许
组合环路检查 综合后检查combinational loop警告
Skid Buffer逻辑 有存有取,状态明确
环形通路是否有断点 必须用FIFO/寄存器打断
初始状态是否有死锁 上电时所有TVALID=0

痛苦点四:背靠背帧传输间隙处理不当,吞吐量暴跌

问题描述

你的AXI-Stream数据通路,帧之间需要插入空闲周期。如果每帧之间插入1个空闲周期,10Gbps链路的有效吞吐量从10G降到了9G。如果帧很短(只有2-3个Beat),吞吐量可能降到5G以下。

根本原因

AXI-Stream允许背靠背传输------上一帧的TLAST和下一帧的第一个Beat可以连续发,不需要空闲周期。但很多模块在TLAST后强制插入空闲周期,导致吞吐量浪费。

错误做法

verilog

// ❌ 错误:TLAST后强制插入空闲周期

if (tlast) gap_cnt = 5; // 浪费带宽

正确做法:支持背靠背

verilog

// ✅ 背靠背:TLAST后直接发下一帧,无强制间隙

always @(posedge aclk) begin

if (!in_pkt && pkt_start) begin

in_pkt <= 1'b1;

beat_cnt <= 0;

end else if (in_pkt && m_axis_tready) begin

if (beat_cnt == PKT_LEN-1) begin

in_pkt <= 1'b0; // 帧结束

if (pkt_start) begin // ✅ 背靠背:立即开始下一帧

in_pkt <= 1'b1;

beat_cnt <= 0;

end

end else

beat_cnt <= beat_cnt + 1;

end

end

背靠背传输时序图:

背靠背 vs 帧间隙的性能对比:

帧长 无间隙 1周期间隙 5周期间隙
4 Beats 100% 80% 44%
16 Beats 100% 94% 76%
64 Beats 100% 98% 93%
256 Beats 100% 99.6% 98%

结论:短帧场景下,帧间隙对吞吐量影响巨大。务必支持背靠背。


⚠️ 重要注意事项

注意事项一:AXI-Stream没有地址

与AXI4-Lite/Full不同,AXI-Stream没有地址通道。数据是纯流式的,由TLAST分帧。这意味着:

  • 不能随机访问------只能顺序读写

  • 不需要地址映射------减少设计复杂度

  • 帧边界由TLAST定义------必须正确处理

注意事项二:TKEEP的含义

TKEEP值 含义
全1(如64bit: 8'hFF) 所有字节有效
非全1 部分字节有效(帧尾/不对齐)
全0 当前节拍数据无效,常见于帧间隙/填充位

注意事项三:TUSER的常见约定

TUSER位 常见含义
TUSER0 帧头标记(SOP)
TUSER1 帧尾错误(EOP Error)
TUSER2 帧校验错误
其他 协议自定义

注意:TUSER不是AXI-Stream协议强制定义的,具体含义由使用场景决定。

注意事项四:AXI-Stream Data FIFO配置要点

Vivado的AXI-Stream Data FIFO IP核配置:

参数 推荐值 说明
FIFO Depth 按反压时间计算 见上文公式
Interface Type Full AXI-Stream 支持所有Sideband信号
TDEST/TID/TUSER Width 与上下游一致 否则信号被截断
Packet Mode 勾选 保证整帧原子读写,禁止跨帧截断,流式帧业务必开启
FWFT 勾选 降低延迟1个时钟周期

注意事项五:仿真验证反压行为

verilog

// ✅ 反压测试TB:随机TREADY

initial begin

forever begin

@(posedge aclk);

m_axis_tready = ($random % 3 != 0); // 2/3概率拉高

end

end

// ✅ 检查:反压后帧完整性

integer frame_cnt;

always @(posedge aclk) begin

if (m_axis_tvalid && m_axis_tready && m_axis_tlast)

frame_cnt <= frame_cnt + 1;

end

// 测试结束检查:if (frame_cnt != expected_frames) $error("帧数不匹配!");


常见问题 FAQ

Q1:AXI-Stream和AXI4-Full的接口能互连吗?

不能直接互连。需要用桥接IP:Xilinx的XDMA、MCDMA等IP核内部完成了AXI4到AXI-Stream的转换。

Q2:TVALID能不能一直拉高?

可以。但要注意:TVALID拉高后,TREADY=0期间,所有伴随信号(TDATA/TLAST/TDEST/TKEEP等)必须保持不变。这种场景下必须保证反压能传递到数据源,否则FIFO会溢出。

Q3:帧长度有上限吗?

AXI-Stream协议本身没有帧长度限制。实际限制来自:

  • FIFO深度(几KB到几MB)

  • DDR缓冲区(几MB到几GB)

  • 网络MTU(1500字节)

  • 协议规定(如Aurora限制4KB)

    建议:除非有特殊需求,帧长度不要超过4KB。

Q4:Skid Buffer和FIFO有什么区别?

对比 Skid Buffer FIFO
深度 1 可配置
用途 缓冲1个Beat 缓冲多帧数据
延迟 0-1周期 1-2周期
复杂度
适用场景 模块内部反压缓冲 跨时钟域/大缓冲

Q5:反压时TLAST怎么处理?

TLAST是TDATA的一部分,反压规则相同:

  • TREADY=0时,TLAST必须保持不变

  • TLAST=1的Beat就是帧的最后一个Beat

  • 下一帧从TREADY=1的第一个Beat开始

Q6:如何调试AXI-Stream的帧丢失?

三步排查法:

  1. 加帧计数器:每个模块入口/出口计数,对比是否一致

  2. 加ILA:抓TVALID/TREADY/TLAST,看反压时序

  3. 检查FIFO:查FIFO的almost_full/overflow标志

verilog

// ✅ 帧计数器代码

reg 31:0 rx_frame_cnt;

always @(posedge aclk) begin

if (s_axis_tvalid && s_axis_tready && s_axis_tlast)

rx_frame_cnt <= rx_frame_cnt + 1'b1;

end


总结

痛苦点 根因 核心解法
TREADY反压丢帧 反压没传到上游/FIFO不够 三层防护:反压传递 + FIFO(公式计算) + 超限告警
TDEST路由错误 TDEST时序不对/帧中间变化 帧起始锁存TDEST,帧内保持不变
反压死锁 TREADY依赖TVALID/组合环路 Skid Buffer标准模块 + TREADY只依赖内部状态
背靠背帧间隙 TLAST后强制空闲 支持零间隔背靠背传输

AXI-Stream的精髓是反压------正确传递反压、足够缓冲、避免死锁、支持背靠背。

关注我,AXI协议实战专题持续更新。承接FPGA定制开发、项目调试、IP定制,需要开发服务可私信咨询。

相关推荐
国科安芯4 小时前
ASC8T245S 8通道电平转换设计实战:从系统架构到QFN24 Layout再到量产测试
网络·单片机·物联网·安全·fpga开发·系统架构
传感器与混合集成电路21 小时前
基于FPGA与ADC协同架构的高密度数据采集模块设计原理与应用场景分析
fpga开发·架构
Rambo.xia1 天前
10Gbps实时图像重采样:一个FPGA项目的完整交付复盘
fpga开发
zlinear数据采集卡1 天前
2mV纹波的代价:硬核拆解ZLinear模拟前端放大器与正负可调电源设计
arm开发·单片机·嵌入式硬件·fpga开发·架构·开源
小麦嵌入式1 天前
FPGA入门(八):一篇讲透跑马灯、闪烁灯、呼吸灯的原理与模拟波形分析
fpga开发
Rambo.xia1 天前
AXI4-Full突发传输掉数据——突发长度算错、WRAP边界、窄传输字节错位,调试一周才发现是协议理解错了
fpga开发
zlinear数据采集卡1 天前
硅片里的“自动纠错“:硬核拆解LHAMP188自动归零技术原理与三种封装的PCB布局实战
arm开发·单片机·嵌入式硬件·fpga开发·开源
Mr-pn-junction2 天前
clk_gate
单片机·嵌入式硬件·fpga开发
知行合一←_←2 天前
误码仪与时钟极性
fpga开发