3、AXI协议深度解析(下):乱序传输、原子访问、QoS与低功耗接口
好,我们接着聊AXI协议的下半部分。上一讲我们把AXI的基本握手、通道关系讲透了,这一讲要啃的才是硬骨头——乱序、原子操作、QoS,还有低功耗接口。这些特性,说白了就是AXI协议真正拉开与AHB、APB差距的地方。你想想看,如果只是简单的读写传输,那用AHB不就够了?
3.1 乱序传输:Out-of-Order的底层逻辑
乱序传输,我习惯叫它OoO。很多刚接触AXI的同学会问:为什么需要乱序?顺序执行不好吗?
嗯,这里要注意。在GPU这种高带宽、多master的场景下,如果强制要求所有事务按顺序完成,那性能损失是灾难性的。举个例子,一个低优先级的读请求先发出了,但它要访问的DDR bank正在刷新;后面紧跟着一个高优先级的写请求,目标bank是空闲的。如果必须按顺序来,那高优先级的写就得干等——这合理吗?
AXI的乱序传输,核心靠的是ID标签。每个事务都带一个ID,从机返回数据时带上同样的ID,主机就能识别这是哪个请求的响应。ID相同的事务必须保序,ID不同的事务可以乱序。
关键点:乱序不是完全无序,而是ID域内的保序 + ID域间的乱序。
我在项目中遇到过一个问题:某个GPU shader core的load/store单元,所有请求都用同一个ID。结果发现性能死活上不去,波形一看,所有读返回都是串行的。后来改成每个bank分配独立ID,带宽直接翻倍。这个坑,我印象很深。
乱序传输的实现,从机端需要做两件事:
- 重排序缓冲区(Reorder Buffer):缓存已经完成但还不能返回的数据,等待前面的同ID事务完成
- ID跟踪表:记录每个ID当前有多少个未完成的事务
从机返回数据时,RLAST和WLAST信号也很关键。RLAST标志读传输的最后一个beat,WLAST标志写传输的最后一个beat。没有这两个信号,主机根本不知道一个突发传输什么时候结束。
3.2 原子访问:从硬件锁到原子操作
原子访问,说白了就是「读-改-写」这个操作不能被中断。在多核GPU里,多个shader core可能同时访问同一块内存,如果没有原子操作,那数据竞争会搞得一塌糊涂。
AXI5(以及AXI3/4的扩展)支持两种原子操作:
- Atomic Load-Add:读取一个值,加上一个数,写回去,返回原值
- Atomic Swap:读取一个值,写入一个新值,返回原值
- Atomic Compare-and-Swap:比较当前值和期望值,如果相等则写入新值
这些操作在AXI协议里是通过原子事务信号来实现的。主机在发送读请求时,带上原子操作类型和操作数,从机(通常是内存控制器)在内部完成原子操作,返回结果。
我的经验:原子操作在GPU里最常用的场景是全局同步和计数器更新。比如所有shader core完成一个阶段后,需要原子地增加一个全局计数器。如果不用原子操作,就得靠软件加锁,那开销就大了去了。
我曾经踩过一个坑:某个GPU设计里,原子操作直接走L2 cache,没有下到DRAM。结果多个shader core同时对一个地址做原子加,L2 cache的snoop协议处理不过来,导致数据不一致。后来改成所有原子操作必须bypass cache直达DRAM控制器,问题才解决。
原子操作的性能代价不小。一次原子操作,相当于一次读 + 一次写 + 一次内部运算,而且必须串行化。所以,能用普通读写就别用原子操作,这是优化常识。
3.3 QoS:服务质量与优先级管理
QoS,全称Quality of Service。在GPU里,不同的master对带宽和延迟的要求天差地别。比如:
- 纹理单元:高带宽,对延迟不敏感
- 顶点着色器:中等带宽,对延迟敏感
- 显示控制器:低带宽,对延迟极度敏感(不能掉帧)
AXI协议通过AxQOS信号(4位,0-15)来标识事务的优先级。值越大,优先级越高。互联结构(比如NIC-400、NOC)会根据QOS值做仲裁。
QOS的实现方式,我见过三种:
| 方式 | 描述 | 适用场景 |
|---|---|---|
| 固定优先级 | 每个master的QOS固定 | 简单系统,master角色明确 |
| 动态调整 | 根据buffer水位动态改变QOS | GPU,防止某个master饿死 |
| 比例分配 | 按权重分配带宽 | 多媒体SoC,需要保证最低带宽 |
我个人习惯在GPU里用动态调整。比如纹理单元的FIFO快满时,自动降低它的QOS,让显示控制器优先通过。这样既保证了显示不卡顿,又不会让纹理单元完全饿死。
注意:QOS不是万能的。如果互联结构的仲裁器设计得不好,高QOS的事务可能会把低QOS的事务完全堵死。这就是所谓的「饿死」问题。我曾经在一个项目里,就因为QOS设置不当,导致某个DMA控制器一直拿不到总线,最后超时复位。
3.4 低功耗接口:Clock Gating与Power Down
低功耗,现在做芯片的谁不关心?GPU动辄上百瓦,功耗控制不好,散热成本就压不住。
AXI的低功耗接口,核心是Clock Gating和Power Down两种机制。协议里定义了四个信号:
- CSYSREQ:系统请求进入低功耗模式
- CSYSACK:从机确认可以进入低功耗
- CACTIVE:从机指示自己还在活动,不能关时钟
- CWARN:从机警告即将进入低功耗
低功耗状态机一般分三步:
- 时钟门控:关闭时钟,但保持电源和状态
- 电源门控:关闭电源,丢失状态,需要保存上下文
- 深度睡眠:完全断电,唤醒需要复位
嗯,这里要注意。时钟门控和电源门控的切换时机非常关键。如果从机还在处理事务,主机就强行关时钟,那数据就丢了。所以协议里要求:主机必须先发CSYSREQ,等从机回CSYSACK,确认所有事务都完成了,才能关时钟。
我在一个移动GPU项目里,遇到过一个问题:某个IP的CACTIVE信号一直拉高,导致系统永远进不了低功耗模式。查了半天,发现是某个DMA传输完成后,没有正确清除CACTIVE。这个bug,让整个团队的功耗测试白做了两周。
避坑指南:我曾经在调试低功耗时,发现从机返回CSYSACK后,主机立刻关时钟,但此时总线上还有未完成的写数据。后来我加了一个「写数据 draining」阶段,确保W通道的所有数据都传输完毕,再进入低功耗。这个经验,分享给大家。
3.5 实战:乱序与QoS的协同设计
最后,我们聊聊乱序和QoS怎么配合。很多设计把这两个特性分开考虑,其实它们有很强的关联。
举个例子:一个高QOS的读请求,如果它前面有一个低QOS的读请求还没完成,那高QOS的请求能不能插队?
答案是:可以,但有限制。如果两个请求的ID不同,从机可以优先处理高QOS的请求,先返回数据。但如果ID相同,那就必须保序,高QOS也得等。
所以,我建议在设计主机时,把不同QOS等级的请求分配到不同的ID上。这样从机就能根据QOS做乱序优化。我在一个GPU互联结构里,就是这么做的:纹理请求用ID 0-3,QOS固定为8;显示请求用ID 4-7,QOS固定为15。这样显示请求永远优先,但纹理请求也不会被完全堵死。
好了,这一讲的内容就到这里。乱序、原子操作、QOS、低功耗,这四个特性是AXI协议的精髓。掌握了它们,你才算真正理解了AXI。下一讲,我们会进入AXI互联结构的设计,看看怎么把这些特性落地到实际的硬件里。