ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

多核缓存一致性与ACE协议:从原理到SoC实操调试

多核缓存一致性与ACE协议:从原理到SoC实操调试 去年有段时间我在调一颗四核Cortex-A系列芯片CPU0和CPU1同时对一个共享数据块做读改写结果两个核读出来的值不一样。排查了一整天最后Trace到L2 cache的行状态才发现问题不在软件而在硬件上Cache Coherence配置漏了一项。这种“两个核看到不同内存视图”的现象在单核时代想都不用想但在多核SoC里它就是缓存一致性Cache Coherence问题。而ACE Protocol也就是ARM的AMBA 4 AXI Coherency Extensions正是解决这类问题的一套总线级协议。这篇文章不是教科书式的协议讲解而是从一个做SoC集成和验证的工程师视角把Cache Coherence和ACE Protocol拆开揉碎讲清楚它到底解决什么问题、协议怎么设计、实际配置和调试时有哪些坑。适合做SoC设计、IP集成、底层验证、以及写低层驱动的人看。刚入门的朋友也不用担心我会先把基础概念用大白话捋一遍后面再进细节。1. 从一次CPU“变傻”说起缓存一致性到底在解决什么问题1.1 不同核看到的内存视图不一致先理解一下问题本身。一个多核处理器系统里每个CPU核心通常有自己私有的L1 Cache可能还有共享的L2或者L3。CPU0读了一个内存地址把数据放在自己的L1里这时候CPU1也去读同一个地址它的L1里也会有一份缓存副本。假设CPU0改了这个地址的值只改了L1里的那份没有写回内存那CPU1接下来读到的还是旧值。从软件的角度看就是“内存里的数据坏掉了”但实际上内存没坏坏的是缓存之间的同步。更麻烦的情况是两个核同时对一个地址做“读-改-写”操作。CPU0读旧值、CPU1也读旧值然后各自改各自写回最后内存里只留下后写的那份结果前一个写操作就丢了。这种现象在单核系统里基本不会出现因为只有一个缓存视图所有读写天然是串行化的但多核一上来多个缓存副本同时存在就产生了“谁先谁后、哪个副本有效”的问题。缓存一致性协议就是用来给这些缓存副本定规矩的某个副本被修改之后其他副本要么跟着更新要么被标记为无效确保所有核心对同一个地址的读操作最终能看到同一个“最新值”。这件事不是由软件自觉完成的而是由硬件协议强制保证的。否则多核跑起来第一分钟就会出现各种诡异的数据错乱。1.2 从MESI到ACE协议在总线上如何落地缓存一致性协议里最经典的是MESI协议它给每个缓存行Cache Line定义了四种状态Modified已修改、Exclusive独占、Shared共享、Invalid无效。这四种状态描述了一个缓存行在某个核的私有缓存里到底是什么身份是被本核独占了还是和其他核共享还是已经作废了。MESI协议本身只是定义了状态机和状态转换规则但真正的难点在于这些状态转换必须在多个缓存控制器之间协调完成。比如CPU0要把一个缓存行从Shared改成Modified就得先通知其他持有这份数据的核“你们那份作废吧。”这个“通知”动作在总线层面就是一个监听请求Snoop Request。在早期的ARM系统里监听逻辑往往是SoC里一个独立的一致性控制单元Cache Coherent Interconnect来负责CPU核只是被动接受监听。到了AMBA 4时代ARM把很多一致性控制逻辑直接做进了总线协议里于是就有了ACE协议。ACE不是一个新的总线它是在AXI4的基础上增加了一组用于支持缓存一致性的信号和通道让CPU核、互连网络Interconnect、DMA等各种组件可以用一种统一的方式维护缓存一致性。换句话说ACE把“缓存行状态管理”从某个黑盒控制器中搬到了总线的明面上协议参与者按照统一规则协作。1.3 谁需要关心ACE协议按我这些年看过的项目最需要认真理解ACE协议的几类人第一是SoC架构师他们需要决定系统里哪些主设备CPU、GPU、DSP、DMA接入一致性域哪些走普通AXI这个划分直接决定性能和功耗第二是IP集成工程师把第三方CPU cluster或者自研NPU接进系统时ACE接口信号的连接、缓存属性的配置、监听通道的处理都得搞明白否则上板就是各种随机挂死第三是验证工程师编写一致性测试场景、分析协议违例都需要对ACE的信号时序有非常细的了解第四是底层驱动和固件开发虽然他们不直接操作ACE信号但需要理解内存属性和屏障指令背后的硬件行为才能写出正确的高并发代码。ACE协议的价值就是让这些角色在一个共同的协议框架下协作而不是各自维护一套私人约定。明白这一点后面看信号和时序就不会觉得只是“一堆烦人的握手”。2. ACE协议的核心思路把监听通道搬进事务协议2.1 AXI与ACE接口多了什么先打个比方。普通AXI像是一个“单向快递站”主设备发请求从设备回数据每个请求独立快递员不需要去别的快递站核对包裹。但在缓存一致性场景里一次CPU读操作可能不只是从内存取数据还要确认其他cache里是不是有一份更新的数据如果有还得把那份数据“转发”过来。ACE协议的核心改动就是在AXI的读地址通道AR、写地址通道AW、读数据通道R、写数据通道W和写响应通道B之外增加了一组监听通道Snoop通道以及对应的缓存状态控制信号。这组通道让一个cache控制器可以主动去监听其他cache控制器查询某个缓存行的状态或者要求对方把数据返回、把缓存行置为无效。具体到信号层面ACE给AXI的事务地址通道上增加了一个叫ACSNOOP的信号用来描述这一次总线事务是否需要参与一致性处理以及它期望的缓存操作类型。比如一个读事务是希望拿到别的cache里的Modified数据还是只希望从内存里读都可以通过ACSNOOP编码来区分。同时ACE还增加了一组独立的监听通道包括AC通道监听地址、CD通道监听数据、CR通道监听响应用于传输来自一致性互连网络的监听请求和结果。在系统集成时这些信号不是可选项而是ACE接口的强制性组成部分。如果你把一个原本设计为ACE的CPU cluster用普通AXI方式连接监听通道就等于被丢弃了那么缓存一致性就完全无法工作。2.2 读、写、监听三个通道的协作理解ACE的通道协作核心是抓住一次“典型一致性事务”的完整流程。拿CPU0要读取一个地址但是担心其他核有最新数据来举例第一步CPU0发送一个带一致性属性的读请求到它的ACE接口地址通道上的AWSNOOP或ARSNOOP指明了这个请求是“ReadUnique”还是“ReadShared”之类的缓存操作类型。这里的ReadShared意思是“我想要一份共享副本不打算独占修改”ReadUnique则是“我准备之后修改这份数据所以请其他副本都失效”。第二步互连网络收到这个请求后并不会直接去内存读数据而是会先检查自己的目录表Directory看看有哪些cache可能持有这个地址的副本。如果有互连网络就会向这些cache发送监听请求走AC通道。被监听的cache控制器查一下自己的缓存行状态如果数据是Modified状态就必须把最新数据通过CD通道送回互连网络如果只是Shared状态就把状态信息通过CR通道返回告诉互连网络“我有共享副本但数据没被改过”。第三步互连网络把所有监听结果收齐后再根据监听返回的数据或状态决定是把内存数据返回给CPU0还是把监听到的最新数据转发给CPU0。CPU0拿到数据后更新自己cache行状态整个事务完成。这个过程中读请求、监听事务、数据返回之间是相互关联的。在AXI里读和写是独立的“火枪手”谁先谁后互不干扰但在ACE里一次读请求可能触发多个监听子事务而且最终的数据回复必须保证一致因此顺序和依赖关系变得非常重要。这也是验证ACE接口时最容易出错的地方你以为只是手握手握对了但实际上还需要考虑监听通道和读写通道之间的因果关系。2.3 DVM操作不只是数据要一致ACE协议里还有一个常被忽略但实际特别重要的部分就是DVM操作全称是Distributed Virtual Memory操作。这个机制主要用于维护TLB快表的一致性。TLB是CPU里用来缓存虚拟地址到物理地址映射的硬件每个核心有自己的私有TLB。当操作系统修改了页表比如释放了一块内存、修改了某个内存区域的权限它需要通知所有核心的TLB把对应的缓存项作废否则某个核心还会继续使用旧的地址映射出现严重错误。在没有硬件辅助的情况下操作系统只能通过软件方式往每个CPU核发送中断IPI让它执行一条TLB失效指令这在高频率页表切换场景下开销非常大。DVM操作提供了一种硬件广播机制CPU通过ACE接口发起一个DVM操作请求互连网络把它广播给系统里所有支持DVM操作的CPU核这些核收到后自动执行必要的TLB失效操作软件只需要写一条指令而不用挨个中断通知。DVM操作在ACE协议里走的是单独的通道它的请求格式跟普通内存读写请求不同而且不需要数据返回。验证DVM操作时要特别注意广播域和权限位的配置我见过不少人把DVM操作发到一个不支持该操作的外设上结果整个总线挂起原因就是这个外设根本没有对应的处理逻辑。2.4 ACE与ACElite、CHI的差别有时候芯片团队开会会听到“ACE”“ACElite”“CHI”几个词混着用其实它们不是一个东西。简单梳理一下ACE是完整的缓存一致性协议接口支持完整的监听和一致性事务ACElite是ACE的精简版本去掉了一部分缓存一致性能力通常用于不需要完整一致性的从设备比如某些IO设备但它仍然支持DVM操作和一定的系统级一致性CHI则是ARM在AMBA 5时代推出的新一代高速缓存一致性互连协议不是简单的通道扩展而是重新设计了总线架构用独立的REQ、RSP、DAT通道来传输消息性能和扩展性更强但复杂度也更高。如果你在做的是基于Cortex-A系列CPU和CCI/CMN互连的SoC大概率你接触的是ACE或CHI。ACE适合中小规模的一致性系统实现相对成熟工具链和验证IP也多CHI适合大规模多cluster系统连接更多的一致性节点HN/SN但调试难度更大对协议分析仪和总线功能模型的要求也更高。选协议从来不是“越新越好”而是看你的系统规模、IP生态和验证成本。我见过一些团队项目并不大非要用CHI结果光搭验证环境就折腾了大半年也见过在规模很大的GPU compute系统里坚持用ACE最后因为监听带宽不足而被迫重新设计。这些“后见之明”都说明理解协议差异是架构决策的基础。3. 实操多核系统里配置ACE与验证要点3.1 确定一致性域与缓存属性在实际的SoC设计里第一步是确定哪些主设备接入一致性域。ARM的典型做法是在互连网络比如CCI或CMN上挂多个ACE接口CPU cluster放在ACE0、ACE1而DMA、以太网控制器这类流式设备通常接普通AXI。为什么要这样分因为一致性是有代价的监听每多一个节点总线上的监听流量和状态存储开销都会增加没有必要让所有设备都参与cache一致性。确定一致性域以后还需要仔细检查每个主设备发出的总线事务类型。ACE接口上每个读或写事务都会携带缓存属性位比如Cacheable、Shareable、Bufferable这些属性决定了事务是否需要走一致性处理逻辑。我自己调试时最常遇到的情况是软件工程师在配置页表时把某个共享内存区域设成了Non-Cacheable导致CPU访问完全不经过cache一致性逻辑而另一个核却以Cacheable方式访问同一片区域两边数据就错乱了。这类问题的根因不在ACE协议而在内存属性没有统一但表现出来就是一致性失败。所以在做系统集成时我习惯把内存划分整理成一张表哪段是Device memory哪段是Non-cacheable哪段是Normal cacheable且shareable哪段是Normal cacheable但non-shareable然后跟软件团队逐行确认。别嫌这个工作繁琐它能在后续调试验证时帮你排除一大半问题。3.2 ACE信号连接与并发处理能力ACE接口的连接不仅仅是把信号拉通那么简单。以AXI主设备为例ACE增加了AC通道、CR通道和CD通道需要连接到互连网络上对应的监听输入端口同时一个支持监听功能的ACE从设备比如CPU cluster必须有能力在收到监听请求时按照协议规定的时间窗口内返回响应数据。如果连接时漏接了一个信号或者把同方向信号接反总线在运行到特定事务组合时会出现协议违例。另一个重点是并发处理能力。ACE接口往往需要支持多个未完成的监听事务Outstanding Snoop Transactions否则一旦某个cache长时间占用数据通道监听请求就会阻塞导致整个互连网络出现拥塞。而每个ACE节点能够Outstanding的监听事务数量取决于内部的缓存控制器和队列深度。在配置互连网络时我通常会把监听队列深度和节点内L2 cache的MSHRMiss Status Holding Register数量对齐避免一方大量发起监听而另一方队列满。关于QoS和仲裁优先级很多系统的ACE接口会有独立的优先级配置项。我见过一个项目系统里GPU和CPU共用同一个互连GPU的监听流量由于优先级设置过高持续抢占CPU核心的一致性事务仲裁导致CPU性能严重抖动。后来在互连的QoS寄存器里做了按比例分配并限制GPU监听的outstanding数量问题才缓解。这类问题在架构文档里往往只有一句话但实际调优能调很久。3.3 构建一致性测试场景如果你做的是验证工程师搭建ACE一致性测试环境跟普通AXI验证最大的不同就是你不能再假设每个事务独立。普通的AXI验证你发一个读请求然后等着数据回来比对一下对不对就行。到了ACE一个读请求可能伴随多个监听请求而且监听请求的响应可能会改变数据来源因此你需要构造多master、多缓存状态交叉的复杂场景。我常用的方法是构造一个“CPU0写、CPU1读”的经典场景但把系统状态做满先让CPU0写一个地址让该行在CPU0的cache中处于Modified状态然后CPU1发起读这时候互连必须发起Snoop强制CPU0把数据通过监听数据通道返回比对返回数据是否和CPU0写的一致。然后又让CPU1读一次同一地址这次CPU1在cache中处于Shared状态再让CPU0发起带ReadUnique属性的读请求验证Shared副本失效流程。更进阶一点需要组合多个地址、多个读写请求并发发出让监听事务和普通读写事务在总线上交叉。这种交叉场景最容易暴露协议活锁或者响应乱序的问题。我在用UVM做这类验证时会在scoreboard里对每个缓存行状态建模而不是只比对读返回数据。只有把状态也纳入比对才能捕捉到“数据对但状态错”的隐性bug。3.4 影响时序与性能的配置项ACE配置里有一组参数非常重要就是缓存行大小Cache Line Size和监听粒度。考虑一个实际计算场景系统内存带宽是DDR4-3200L2 cache line是64字节如果CPU反复读同一行每次都要发起监听请求监听带宽消耗比较大。但如果把缓存行扩大到128字节单个监听请求覆盖的数据范围更大监听次数可以减少不过无效数据占用监听数据通道的可能性也会增加需要权衡。我在做性能调优时习惯用一个经验公式来估算监听带宽监听请求速率等于“系统内一致性事务每秒次数”乘以“平均每个事务需要监听的节点数”。如果这个速率超过互连网络监听数据通道的实际带宽那就得考虑提高互连频率、增加监听数据通道位宽、或者用更高效的目录过滤机制避免对所有节点广播监听。CMN互连里常见的目录过滤Directory-based filtering就是通过记录“哪个节点可能有该行副本”来减少无效监听对降低带宽消耗非常有效但代价是需要SRAM存储目录信息增加了面积和功耗。配置这些参数时不要拍脑袋最好用性能模型或基准测试跑一遍。比如用lmbench或者自研的cache-timing工具测量不同配置下多核同时读写同一内存区域的延迟和吞吐量用数据说话。纸上谈兵容易把系统调成“看似合理但实际很慢”的状态。4. 性能、死锁与调试技巧4.1 监听带宽瓶颈与优化手段ACE系统在性能上最常见的瓶颈就是监听通道带宽。想象一下CPU0在循环里反复修改一个共享变量由于这个变量是cacheable且shareable的每一次修改都可能需要发出写监听请求让其他副本失效。如果系统里有四个CPU核每次修改都要广播给其他三个核监听流量就会成倍增长最终写操作的延迟被拉得很高。优化手段可以从几个方向入手一是靠目录过滤在互连网络里维护缓存状态目录只有可能持有该地址副本的节点才收到监听请求而不是广播给所有人二是靠软件上减少伪共享False Sharing把不同线程频繁修改的变量放到不同的缓存行上避免因为同一行里不同字节被不同核修改而互相干扰三是在互连网络上给监听通道更高的仲裁优先级因为监听请求往往关系到关键事务的完成延迟过高会直接影响CPU性能。这里补充一个真实调优经验有一次我们系统的CPU利用率不高但整体吞吐量始终上不去分析波形后发现监听通道长期处于几乎满负荷的等待状态原因是多个cluster共享同一个监听端口而仲裁策略是严格轮询没有考虑不同cluster的实际监听压力。后来改成按cluster的历史监听流量动态加权负载均衡效果好了很多。这种调优没有标准答案必须靠实测数据来引导。4.2 死锁避免协议层面和系统层面ACE系统里的死锁问题比普通AXI系统要严重得多。原因很简单普通AXI事务之间没有相互依赖而ACE里读请求可能触发监听请求监听请求的响应又依赖被监听cache的数据通道如果数据通道被其他等待响应的事务占死就可能形成循环等待。ARM在ACE协议定义里有专门避免死锁的规则比如监听事务不能阻塞在普通写数据通道之前、CR通道和CD通道有独立的credit机制、以及要求主设备不能发起一个依赖另一个ACE事务完成才能继续的新事务。协议层面能做的是把死锁风险降到协议不可违背的程度但如果系统集成时违反了这些规则比如在监听事务路径上插入了一个输出缓存而这个缓存在响应回来之前被填满就会造成实际死锁。在实际调试中如果系统挂死我会优先检查是不是监听通道和读写通道之间有循环依赖。常见做法是在互连网络里显式配置死锁避免机制比如给监听请求独立的路由通道或独立的虚拟通道。如果你的互连IP已经内置了这些保护那就需要在验证环境里构造压力用例来确认保护真正生效而不是想当然认为IP默认开着。4.3 调试ACE问题时的思路与工具调试ACE协议相关问题我最依赖的是总线协议分析仪和仿真波形。但有一点很重要别一上来就盯着波形看先看系统层面的现象。如果两个核的共享数据偶尔不一致先确认内存属性是不是设置正确如果系统偶发性挂死先看死锁发生时的停顿点把挂死前最后几个事务抓出来如果是性能不达标则优先关注监听通道的利用率、等待周期等计数。一旦确认问题在ACE信号层就要把波形里的关键事务拆开找CPU发起的读请求然后看互连是否发出了监听请求再检查监听响应返回的状态和数据最后确认最终读数据的来源。按这个顺序排查能很快锁定是“监听根本没发出来”还是“监听发出了但没响应”或者是“响应了但数据路由错了”。我个人的经验是在仿真的UVM验证环境里最好在scoreboard里对每个缓存行维护一份完整的“预期状态模型”。这样只要总线事务和模型不一致立刻就能报错而不是等到读写数据比对失败才发现。这个模型可能写起来麻烦但它能覆盖很多随机调度的边界情况比靠肉眼盯波形高效得多。4.4 几个真实案例讲个典型的“缓存属性不一致”案例。某个项目用CPU0往一地址反复写值CPU1在另一个线程里读同一个地址但软件配置页表时CPU1把这部分内存配置成了Device内存CPU0配置成了Normal memory。结果CPU0写入后数据缓存在L1里CPU1每次读都直接走外设总线去内存读自然读到旧值。排查到后来连内存内容都是对的就是两边属性不一致导致的。这个案例不是协议的错但特别容易在软硬件联调时踩中。另一个案例是监听带宽耗尽导致的软件“假死”。某个系统里多个核频繁请求同一缓存行监听风暴导致系统几乎停滞表现上像死锁但实际只是带宽被占满。当时我们用性能计数器统计了AC通道的请求速率发现长时间接近100%占用率这才把方向从“死锁”拉到“带宽优化”上。如果一开始就按死锁处理估计会白白耗费很久。还有一次是DVM操作配置范围出问题。某个核发起DVM操作后系统里有一半核没收到广播结果某个进程在不同核上看到的地址映射不一致直接访问非法地址。最后发现是互连网络里DVM广播域的掩码配置错误把部分核排除在外了。这类问题如果不熟悉DVM机制很容易怀疑到MMU配置上浪费大量时间。最后再分享一个排查顺序在我自己调ACE一致性问题的过程中最常用的排查路径是先确认内存属性和缓存策略是否在软件中统一再确认系统里一致性域的边界划分有没有问题然后看互连和ACE接口的信号连接、监听通道的outstanding能力和QoS配置最后才上协议波形分析工具去逐个事务比对。这个顺序之所以有效是因为越靠前的问题越常见排查成本也越低最复杂的协议时序问题往往放在最后才需要深入。还有一个心得多说一句ACE协议本身看起来很复杂但你真正需要掌握的其实是三个关键点——哪些事务带一致性属性、监听通道如何完成缓存行状态协商、以及数据最终从哪条路径返回。把这三个问题想清楚剩下的大多数细节都能在调试中根据现象倒推出来。做这一行没人能记住协议文档的每一行但知道问题大概出在哪一层比死记硬背更值钱。
返回列表