参考链接:

axlock详细解析

锁访问

主要使用LOCK信号,锁住一笔传输,拒绝其它传输的干扰,只有这笔传输完成之后释放Lock信号,才可以进行其它笔传输。

  • 在M0想要发起一笔锁定传输前,需要确保是否还有在进行的传输,必须要确保之前进行的传输都已完成。
  • M0使用AxLOCK发起locked transaction,interconnect通过内部仲裁器确保只有M0能够访问S0,其它主机的访问都会被阻塞掉,直到锁定传输完成。
  • AXI3支持锁定访问,但AXI4已经不支持了

独占访问

  • AXI4取消锁定访问的原因是新增了独占访问,比锁定访问效率更高,还是使用AxLOCK信号。
  • 锁定访问是不允许其它主机访问正在锁定访问的从机,而独占访问允许访问该从机,只是不允许其它主机访问相同的内存范围(memory range)。
  • 这样既不会出现两个主机对同一块内存空间进行更改,导致数据错误;还可以一定程度保证了总线的最大带宽和总线延迟。
  • 从机内部需要实现一个独占访问的monitor,用于记录独占事务序列的信息确定哪段地址区间被独占访问,以及识别正在执行独占访问的主机ID。
  • RRESP[1:0]和BRESP[1:0]可以表示独占访问的成功或失败
RRESP/BRESP 响应 含义
00 OKAY 表示独占访问失败/正常访问成功
01 EXOKAY 独占访问成功

独占访问的过程

  • (1)主机对slave的一个地址进行独占读操作,从机monitor记录下该master的ARID以及访问的地址位置
  • (2)一段时间之后,主机对相同地址进行独占写操作,此时从机monitor同样记录下该操作主机的AWID和要访问的地址位置
  • (3)将AWID与之前记录的ARID进行比对,如果一致,说明该地址之前并没有被其它主机访问,返回EXOKAY;否则返回OKAY,独占访问失败

另一个例子

image-20260406225156070

  • 对于在独占写之前有多笔独占读,那么monitor所记录下来的ID都将存入一张表内,之后当开始独占写时,查找这张表,如果有ID相同的,则独占写传输成功。
  • 由于该地址的数据已被更改,前面记录的ID会全部被清除,如果连着独占写,那么一定会失败,返回OKAY

AXI-Stream反压机制

在现代数字系统设计中,无论是 FPGA 还是 ASIC,数据流的处理效率往往决定了整个系统的性能上限。AXI4-Stream 协议凭借其简洁、高效的特性,成为了数据流传输的事实标准。然而,许多工程师在使用 AXI-Stream 时,往往只关注数据的“流动”,而忽视了“堵塞”的处理——即反压(Backpressure)机制

反压不仅仅是一个信号的拉低,它是一门关于时序收敛、吞吐量平衡与死锁避免的平衡艺术。本文将从设计哲学的角度,深入剖析 AXI-Stream 反压机制的底层逻辑与工程实现。

反压的时序表现

反压的本质,是接收端(Slave)通过拉低 TREADY 告诉发送端(Master):“我处理不过来了,请暂停发送”。

下面是一个典型的反压时序图。

image-20260407215644155

在 cycle 2,握手成功,传输 D0。在 cycle 3,Master 准备好了 D1(TVALID=1),但 Slave 忙碌(TREADY=0),产生反压。Master 必须保持 D1 和 TVALID 不变,直到 cycle 5 握手成功

物理现实:时序与气泡

在理想模型中,TREADY 可以瞬间反馈。但在物理实现中,反压信号往往是系统中最长的关键路径之一。

想象一个由 10 个模块级联的图像处理流水线。如果最后一个模块 FIFO 满了,它拉低 TREADY。这个信号需要像多米诺骨牌一样,一级一级向前传播到源头。如果这全是组合逻辑,路径延时将巨大,导致时序违例。

为了切断这条超长的组合逻辑路径,我们需要插入寄存器。但简单的寄存器插入会引入“气泡”或丢失数据。这就引入了 **Skid Buffer(滑模缓冲/打拍)**的概念。

Skid Buffer的设计哲学是用面积换频率。他在内部维护一个小的缓存(通常是2级深度),即使下游突然拉低TREADY,Skid Buffer也能利用内部缓存吃下上游已经发出的数据,从而给反压信号的传播争取一个时钟的缓冲时间。

image-20260502180920617

info

Skid Buffer通过引入备用寄存器,将TREADY的时序路径打断,当ouput阻塞时,数据暂存于Skid Register,同时Input侧的TREADY信号在下一个周期才会拉低,从而实现时序隔离。

流量控制策略

在复杂的SOC系统中,简单的“满则停,低则走”策略会导致TREADY信号频繁跳变,这种高频震荡会降低总线利用率并增加功耗。更高级的设计哲学是引入滞回机制。

我们需要定义两个阈值:

  • Almost Full (高水位):当 FIFO 占用达到此线时,提前拉低 TREADY。这为流水线上的“在途数据”留出了余量,防止溢出。

  • Almost Empty (低水位):只有当 FIFO 数据消耗到此线以下时,才重新拉高 TREADY

这种机制避免了 TREADY 在临界点附近的抖动,下面的曲线展示了一个 FIFO 在波动的写入速率下,如何根据阈值触发反压

image-20260502220243214

info

橙色区域表示反压生效的时间段。可以看到,反压一旦触发(达到红色虚线),会持续直到水位降至绿色虚线以下。这种设计保证了数据流的突发传输特性,减少了总线握手的开销。

死锁

反压机制如果设计不当会导致死锁

  • 组合逻辑环路

如前所述,如果 Master 的 TVALID 依赖于 Slave 的 TREADY,而 Slave 的 TREADY 又通过组合逻辑依赖于 TVALID,就会形成死锁或高频振荡。

解决方案:TVALID一定不能依赖于TREADY,而且TVALID和TREADY最好都是寄存器输出

  • 队头堵塞

在多通道复用的场景中(例如一个 AXI-Stream 承载多个视频流),如果通道 A 的数据堵塞了 FIFO,导致通道 B 的数据也无法传输,这就是队头阻塞。

image-20260502230842630

info

当多个逻辑流共享同一个物理链路时,必须使用独立的虚拟通道或独立的 FIFO 来隔离反压,否则一个流的阻塞将瘫痪整个接口。

RTL实现

attention

下面这段代码实现了一个反压机制,这里的模块本质上是一个总线级联结构,ext_m_axis -> s_axis -> m_axis -> ext_s_axis,如果没有axis_skid_buffer这个模块,ext_m_axis和ext_s_axis应该是直连的

对外输出的s_axis_tready信号只要在缓冲buffer不是满的时候就可以拉高,反压条件为!skid_valid && s_axis_tvalid && m_axis_tvalid && !m_axis_tready,当ext_s_axis ready为低且m_axis valid信号为高的时候成立。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
// 这是一个简化的 Skid Buffer 核心逻辑示意
module axis_skid_buffer #(
parameter DATA_WIDTH = 32
)(
input wire clk,
input wire rst_n,

// Slave Port (Input)
input wire s_axis_tvalid,
output wire s_axis_tready,
input wire [DATA_WIDTH-1:0] s_axis_tdata,

// Master Port (Output)
output reg m_axis_tvalid,
input wire m_axis_tready,
output reg [DATA_WIDTH-1:0] m_axis_tdata
);

// 内部状态
reg [DATA_WIDTH-1:0] skid_buffer;
reg skid_valid;
// 核心状态机逻辑
always @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
m_axis_tvalid <= 0;
skid_valid <= 0;
end
else begin
// Output Interface Logic
if (m_axis_tready || !m_axis_tvalid) begin
if (skid_valid) begin
// 如果 Skid Buffer 有数据,优先移出
m_axis_tvalid <= 1;
m_axis_tdata <= skid_buffer;
skid_valid <= 0;
end else begin
// 否则直接透传输入数据
m_axis_tvalid <= s_axis_tvalid;
m_axis_tdata <= s_axis_tdata;
end
end
else if (!skid_valid && s_axis_tvalid && m_axis_tvalid && !m_axis_tready) begin
// 下游阻塞,且当前输出有效,输入也有效 -> 存入 Skid Buffer
skid_valid <= 1;
skid_buffer <= s_axis_tdata;
end
end
end

// Input Ready Logic
// 只要 Skid Buffer 没满,就可以接收数据
assign s_axis_tready = !skid_valid;

endmodule