参考文章:

死锁产生的条件

广义上的死锁包括很多种场景,线程死锁、任务死锁和总线死锁等,而死锁的发生必须具备一定的条件。死锁的发生必须具备以下四个必要条件,即柯林斯四条件

  • 互斥条件(Mutual Exclusion):指进程对所分配到的资源进行排它性使用,即在一段时间内某资源只由一个进程占用。如果此时还有其它进程请求资源,则请求者只能等待,直至占有资源的进程用毕释放
  • 请求和保持条件(Hold and Wait):指进程已经保持至少一个资源,但又提出了新的资源请求,而该资源已被其它进程占有,此时请求进程阻塞,但又对自己已获得的其它资源保持不放
  • 不可剥夺条件(No Preemption):指进程已获得的资源,在未使用完之前,不能被剥夺,只能在使用完时由自己释放
  • 环路等待条件(Circular Wait):指在发生死锁时,必然存在一个进程——资源的环形链,即进程集合{P0,P1,P2,···,Pn}中的P0正在等待一个P1占用的资源;P1正在等待P2占用的资源,……,Pn正在等待已被P0占用的资源

对柯林斯四条件进行详细案例的解析,比如下面这张图,多播死锁:

image-20260503170622362

  1. M0需要申请S0和S1的资源,此时已经占用了S0,等待S1资源进行处理
  2. M1同样需要申请S0和S1的资源,此时已经占用了S1,等待S0资源进行处理
  3. S0需要在响应M0后才能响应M1,M0需要S1的响应后才能结束对S0的占用,S1在响应M1后才能响应M0,M1需要收到S0的响应后才能释放S1,形成多播死锁

从上面这个例子中可以清晰看出柯林斯四条件

  • 互斥条件:S0在响应M0后在结束占用前,无法响应M1的需求;同样,S1在响应M1后结束占用前,也无法响应M0的需求
  • 请求和保持条件:S0已经响应了M0,此时又接收到了M1的访问请求;S1同理;
  • 不可剥夺条件:S0已经开始响应M0,除非M0结束占用,否则S0无法响应M1;S1同理
  • 环路等待条件:M0等待S1响应,S1等待M1释放,M1等待S0响应,S0等待M0释放,形成了一个环路

上面这四个条件同时满足的时候,死锁就发生了

死锁常见原因

对于AXI死锁的常见原因,做的总结如下所示

  • 主设备不支持乱序
  • 主设备支持outstanding操作
  • 从设备具有乱序执行能力
  • 系统拓扑存在主/从设备互异的长短路径
  • 写操作地址超前写数据发出
  • 主设备或互联通路资源不足

下面的案例分析多有体现上面这些原因

死锁场景分析

资源不足导致的死锁

下图展示的是一个两个AXI Master和两个AXI Slave互联的结构。具体的场景为AXI Master1依次发起A1和A2两个写操作,其中A1访问AXI Slave2,A2访问的是AXI Slave1;AXI Master2依次发起B1和B2两个写操作,其中B1访问的是AXI Slave1,B2访问的是AXI Slave2。AXI Slave1接收到的写顺序是A2,B1;AXI Slave2接收到的写顺序是B2,A1。

image-20260503154028686

假设Write Buffer的深度都是8,也就是说两个buffer都可以存储8个beat写数据。假设A1和B1的burst length是9,也就是说发出的数据是10个beat的write data。下面分别对AXI Slave1和AXI Slave2进行分析

  1. A2先于B1到达,根据AXI协议,write address和write data顺序要一致,因此A2的write data必须先于B1发送给Slave1
  2. A2的write data依赖于A1的write data全部被总线接收,但是Write buffer1深度只有8,只能暂时接收8个beat的A1 write data
  3. A1在Write Buffer1中的Write data要发送到AXI Slave2,必须要等到B2的write data全部发送完成
  4. B2的write data全部依赖于B1的write data全部被总线接收完毕,但是Write buffer1的深度只有8,只能暂时接收8个beat B1的write data
  5. B1的write data必须等A2的write data发送完之后,才能发送到AXI slave1口

因此,一个死锁场景发生了,依赖条件 B1 -> B2 -> A1 -> A2 -> B1,这个死锁条件的本质是:

  • AXI协议规定AXI write addr和AXI write data的顺序必须一致
  • Write buffer的深度不够

attention

AXI4才会出现上面的问题,AXI3可能不会出现这个问题,因为AXI3支持写交织,AXI4已经没有写交织了

image-20260503161902114

解决方法

可以通过下面这几种方法来破除这个死锁:

  • 把AXI Slave1和Slave2的outstanding能力设置为1,这样会对总线的performance影响很大;如果限制为1,那么A2就不会出现在AXI Slave1上,B2就不会出现在AXI Slave2上
  • 把Write buffer的深度加大,使它们至少能各自容纳A1和B1的全部write data beat,这样会使总线的面积增大很多;Write buffer1把A1的write data全部接收之后,就可以转发A2的write data给AXI slave1口,因此A2就可以正常完成了,进而B1、B2和A1也可以完成,打断了死锁链条
  • 在总线内部做冒险的检查,如果发现有任何未完成的写操作到不同的接口,那么就先把当前的write操作挡住不往下发,这个方法的应用比较多。总线在收到A2时发现A1已经发给AXI slave2口,但A2是打算发给AXI slave1口,因此总线就先把A2挡在内部,暂时不发给AXI slave1口。等A1结束后,再把A2发给AXI slave1口;对B1和B2的处理也类似,从而打断了死锁链条

乱序死锁

案例一

对于乱序导致死锁的例子如下,首先M0和M1是保持传输源,S0和S1支持乱序传输

image-20260503171938727

  1. M0以保序同一ID的方式先后发出到S0读请求1和到S1读请求2
  2. M1以保序同一ID的方式先后发出到S1读请求3和到S0读请求4
  3. S0支持乱序,先后接到请求1和4,请求4的数据先准备好,4优先在接口上返回给xbar
  4. S1支持乱序,先后接到请求2和3,请求2的数据先准备好,2优先在接口上返回给xbar
  5. 由于M0是保序的,必须先接收S0返回的数据1,才能接收S1返回的数据2
  6. 由于M1是保序的,必须先接收S1返回的数据3,才能接收S0返回的数据0
  7. 因此,从M0的角度来看,M0的数据1 -> 数据4 -> 数据3 -> 数据2 -> 数据1形成死锁;同理M1也形成了死锁

通过对上面这个案例分析,主要解决方法可以如下:

  • 首先Master可以支持乱序,读回来的数据不一定按照发送请求时候的数据
  • 在xbar中新增缓存模块,xbar内部进行重排列,保证Master可以按照相同的顺序接收到数据

案例二

下面是一个slave既支持乱序又有多个接口的案例

image-20260503174737292

  1. 保序的M0先后发出写请求1和写请求2,写请求2的优先级更高;
  2. S0处于阻塞状态,不能马上接收写数据,因此请求1和请求2都被缓存在入口寄存器片内
  3. S0解除阻塞后,从寄存器片中接收写请求,此时因为请求2的优先级更高,S0先把请求2从接口的寄存器片取出
  4. 此时S0需要优先接收请求2数据,但是因为M0是保序发送,只能先发送数据1再发送数据2,形成死锁

长短路径死锁

写通道没有乱序了的话能避免死锁吗?显然不能,上面就已经有一个写死锁的例子了,下面是一个长短路径写死锁的例子

image-20260503175923218

  1. M0保序先后发出写请求1访问S1,写请求2访问S0;
  2. M1保序先后发出写请求3访问S0,写请求4访问S1
  3. M0-S0为短路径,S0先收到了写请求2,之后收到了写请求3,因此S0需要先收到写数据2
  4. M1-S1为短路径,S1先收到了写请求4,之后收到了写请求1,因此S1需要先收到写数据4;
  5. S0需要先收取写数据2,写数据2依赖写数据1发出,写数据1依赖写数据4被S1收取完,写数据4依赖写数据3发出,写数据3依赖写数据2被S0收取完,于是形成了长短路径死锁,2 -> 3 -> 4 -> 1 -> 2

info

常见的死锁场景大概就是这几种,从以上几个案例来看,“多主多从、读会乱序、写爱超车、资源不足、路径参差”这几个事总是更死锁问题纠纠缠缠的,因此防御机制也基本是针对这几个方面进行的

死锁的常见防御机制

对齐传输

对齐传输有两个级别,一种是松散对齐就是w不会超越aw但aw可能超越w

image-20260503183809779

有很多的slave其本身就不支持先收取wdata后接收waddr(当然更多的是支持的)。而另外一种是严格对齐,即w不会超越aw同时aw也不会超越w,在总线上会呈现一个一个aw/w的传输时间段。

image-20260503202045146

error

能够看出,如果采用严格对齐的传输方式,那么资源不足死锁中的案例1、乱序死锁案例中的案例2和长短路径死锁的案例就不会发生了。但是在实现通道对齐传输时要避免组合逻辑环的发生

  1. master端对通道进行对齐,那么w通道必然需要参考aw通道的,为了没有效率损失,那么awvalid会参与到wvalid的逻辑中来(w第一拍和aw实现同拍发送)
  2. crossbar中进行多路调度,多路调度一种很常见的设计是,有valid了才给ready,就是有请求了才去申请总线,申请到了才能起ready,因此wvalid会作用到wready的逻辑中来
  3. aw通道需要看上一个aw对应的wlast是不是发完了,发完了才开始发下一个aw请求,因此wready和wlast会作用到awvalid逻辑中

image-20260503202706461

attention

aw通道和w通道的互看行为,导致此处有组合逻辑环出现

资源充足

在我们用一些工具、vip生成interconnect的时候,或者用NIC等组件的时候,大多是可以去分支缓存深度以及分路缓存的。例如根据下面这张图中通过增加Write Buffer的深度可以防止总线死锁。

image-20260503161902114

SSPID

info

SSPID 在这里一般指 Single Slave Per ID,是 AXI / NIC 互连里的 CDAS(Cyclic Dependency Avoidance Scheme,环形依赖规避)之一。它的目标不是“加一把软件锁”,而是在总线互连的入口处限制事务分流,避免 AXI 多 outstanding、多目标、多通道背压组合成死锁。

SSPID 的规则可以一句话概括:同一个入口、同一个方向、同一个 AXI ID,在还有未完成事务时,只允许打到同一个目标 slave,具体可以参考下面这张表:

新事务情况 SSPID 行为
该 ID 当前没有 outstanding 放行,并记录这个 ID 当前指向的目标
该 ID 已有 outstanding,且目标相同 放行
该 ID 已有 outstanding,但目标不同 stall,直到该 ID 的 outstanding 清空

以下面这张图为例,M0先发送写数据请求1到S0,这时候已经被记录下来了,因此M0到S1的就会被先阻塞下来,同样的写数据请求4也会被阻塞下来,这样当M0释放S0的时候,写数据请求3被响应,同样当M1释放S1的时候,写数据2被响应,这样就不会导致死锁。

image-20260503204149922

SAS

SAS(single active slave,单个活跃从设备),与单ID从端模式(SSPID)原理相同,但针对写事务增加了一项检查机制。SAS确保主设备必须等待前一个写事务的所有数据均发送完毕后,才能发起新的写地址请求。在 SSPID 的基础上,专门再管住 AXI 写地址通道 AW写数据通道 W 之间可能造成的死锁。

举个例子详细解释一下SAS新增的检查内容,假设 master 连到互连,后面能访问 DDR 和 OCRAM

1
2
AW0: ID=1 -> DDR, burst 8 beats
W0 : 还有 8 拍数据没发完

这时又来了:

1
AW1: ID=2 -> OCRAM

在 SSPID 下,ID=2 和 ID=1 不同,可能允许通过。但在 SAS 下,因为 AW1 要切到 OCRAM,而 DDR 的 W0 数据还没发完,所以 AW1 会被阻塞,等到 W0 的最后一拍 WLAST 已经送出,之后,AW1 -> OCRAM 才能继续。

attention

在OCRAM被阻塞的时候,可能引起乱序锁死案例2的情况

SS

SS(single slave,单从设备),确保交换机的从接口满足以下条件:

  • 所有未完成的读事务必须发往同一终端目的地
  • 所有未完成的写事务必须发往同一终端目的地

若主设备试图向不同于当前事务类型(读或写)目标地址的目的地发起新事务,网络将阻塞该类型的所有事务,直至该类型下所有未完成事务全部处理完毕。

简单点说就是master在一段时间内只能访问同一个slave,如果要访问其他的,那么需要等对前一个slave的访问全部完成,这种严格的情况下已经可以规避绝大多数死锁问题了,如下所示

1
2
3
4
ARID=1 -> DDR    已放行,还没返回完
ARID=2 -> DDR 放行
ARID=9 -> DDR 放行
ARID=3 -> OCRAM 阻塞

info

为什么 SS 能防锁死 ?

AXI 互连最容易出问题的地方,是 master 可以同时发多个outstanding transaction,并且这些事务可能去不同 slave。不同 slave 的返回顺序、响应通道、缓冲资源、仲裁器和 READY/VALID 背压组合起来,可能形成环形依赖。

对SSPID,SAS,SS总结成一张表,如下所示

机制 限制粒度 核心规则 主要管什么 防锁死强度 性能影响
SS 整个入口/ 同一方向 只要该方向还有 outstanding,就只能继续访问同一个 slave 读方向 AR/R、写方向 AW/W/B 最强 最大
SSPID 同一个 AXI ID 同一个 ID 在 outstanding 未清空前,只能访问同一个 slave 同 ID 的读/写事务分流 中等 较小
SAS SSPID + 写数据状态 在 SSPID 基础上,若前一写事务的 W 数据还没发完,不允许 AW 切到另一个 slave 主要防 AW/W 分离导致的写通道环形依赖 强于 SSPID,弱于 SS 中等

距离说明三种情况下的阻塞和放行:

场景 SS SSPID SAS
ID=1 -> DDR 未完成,又来 ID=2 -> OCRAM 读 阻塞 放行 放行
ID=1 -> DDR 未完成,又来 ID=1 -> OCRAM 读 阻塞 阻塞 阻塞
ID=1 -> DDR 未完成,又来 ID=2 -> DDR 读 放行 放行 放行
AW -> DDR 已接收,DDR 的 W 数据未发完,又来 AW -> OCRAM 阻塞 不一定阻塞,取决于 ID 阻塞
AW -> DDR 已接收,DDR 的 W 数据未发完,又来 AW -> DDR 放行 放行,若 ID 规则满足 放行,若 SSPID 规则满足