gem5 Ruby

gem5 Ruby

Posted by George Lin on January 13, 2025

TBE(Transaction Buffer Entry)

在缓存一致性协议中,TBE (Transaction Buffer Entry) 是处理“中间状态”的关键。

简单来说,TBE 就像是缓存控制器的“便签纸”或“事务记录本”。

当缓存块处于稳定状态(如 MSI)时,信息存在 CacheMemory 里;但当缓存块处于过渡状态(Transient State,比如从 I 变到 M 的过程中)时,控制器需要一个临时的地方记录“活还没干完”的细节。


MSI协议中,TBE 的核心功能包括:

  1. 追踪“过渡状态” (Transient States)。缓存一致性操作不是瞬间完成的。比如当你发出一个写请求(GetM),在收到数据之前,你的缓存块处于 IS_D(从 Invalid 变到 Shared/Modified,正在等待 Data)状态。TBE 记录了这个块“正在变成什么”,这样当网络回复数据时,控制器才知道该把数据给谁、要把状态改成什么。

  2. 充当 MSHR (Miss Status Holding Register)。在高性能处理器中,如果发生 Cache Miss,处理器不能直接停下来死等。TBE 记录了这次 Miss 的相关信息(哪个地址、什么类型的请求),允许处理器在等待数据的同时去干别的事。

  3. 等待确认包 (AcksOutstanding)。这是 MSI 协议中比较关键的一点。当你想要修改一个 Shared (S) 状态的块时,你必须确保网络中所有其他副本都被销毁。首先,目录会通知所有拥有该块的 L1 缓存执行“作废”(Invalidate)。之后,这些 L1 会给你发回 Ack(确认收到且已销毁)。TBE 的AcksOutstanding 计数器就存放在这里。每收到一个 Ack,数字减 1。只有当计数器归零时,TBE 才会告诉控制器:现在可以安全地进入 M 态了。


MSI协议中,TBE的字段包括:

字段 含义 为什么需要它
TBEState 临时状态 记录当前处于哪个过渡态(如 IM_AD:等待数据和 Ack)。
DataBlk 数据块 临时存放从网络收到的数据,或者准备写回给目录的数据。
AcksOutstanding 待接收 Ack 数 最重要字段。记录还需要多少个控制器的“准许”才能完成状态跳转。

C2C-GEM5论文小结

项目动机

异构多chip芯粒集成,前期架构探索和模型构建

项目架构

两个chip,每个chip的核心都是一个All-to-All的NoC,各带有1个RNF,1个HNF和1个SNF和1个C2C Controller (C2CI)。解决如何通过扩展Gem5 Ruby框架,实现两个NoC之间的互联。

项目的核心是C2C Controller,承担的功能是把本Chip发来的信息重新打包发给对面的C2CI,类似于透传。需要处理的信息大致可以分为4类:

  1. 需要发往远端HNF的Request。C2CI至少需要完成以下几种操作:首先,为了确保一致性,需要把这个Request的Src, Des, Address, 需不需要Data/Ack等信息存在自己的TBE中。其次,任何一个Request进来需要先查TBE,如果有别的对同一个地址的Response还没有收到,则这个Request需要被push进一个Queue中stall,直到Response拿到,一致性条件满足。再次,由于在多chip视角下存在多个HNF,需要修改下Network的routing table (SAM),让每个HNF自己管一块地址区间,防止HNF之间的一致性冲突。
  2. 需要发往远端HNF的Snoop Request。C2CI至少需要完成以下几种操作:首先,本chip的HNF需要修改Directory,使之能够记录Remote的sharer和owner(通过加一个metadata字段);其次,需要先查TBE,如果有别的对同一个地址的response还没有收到,则这个Request需要被push进一个Queue中stall,直到Response拿到,一致性条件满足。再次,需要把这个Request的Src, Des, Address,需不需要Data/Ack等信息存在自己的TBE中。
  3. 需要发往远端的Response/Data。C2CI至少需要完成以下几种操作:首先,转换格式后直接透传。
  4. 从远端收到的Request。C2CI至少需要完成以下几种操作:首先,转换格式后直接透传给本地的HNF。
  5. 从远端收到的Snoop Request。C2CI至少需要完成以下几种操作:首先,转换格式后直接透传给本地的对应RNF(不经过本地的HNF)。
  6. 从远端收到的Data/Response。C2CI至少需要完成以下几种操作。首先,转换格式后传给本地的对应RNF。其次,更新自身的TBE,放行被stall的snoop或者Request。

总体上,C2CI的核心是一套用SLICC语言编写的状态机逻辑。这套状态机主要有几个概念: event, action和transition。

项目关键参数

L1 data cache: 64KB, 4-way set-associative L1 data cache

L2 cache: 1MB 8-way set-associative

Memory Range: each chip 1.5GB

memory: DDR3, 8banks

CPU model: timing, in-order

Benchmark: PARSEC benchmark (比如raytrace, canneal, blackscholes)

Time: 15 hours

Testing: 先random test, 15分钟不报错,再跑具体benchmark

项目future work

增加支持的chip数量;提高local NoC的accuracy,减少remote access;加入NoC仿真器比如Garnet

CHI协议相比ACE协议的优势是什么?

从 ACE 到 CHI,AMBA 协议完成了从基于总线、基于广播(Snooping)基于分层、基于封包(Network-on-Chip)的质变。

首先,CHI具有更好的可扩展性。ACE是基于广播/监听的,当核心数量增加时,每个请求都要“询问”所有其他核心是否有数据(Snoop),这会产生指数级增长的流量。通常核心超过 8-16 个,总线就会瘫痪。CHI是基于目录/路由的,引入了 Directory(目录) 的概念。请求先发给目录(HN-F 节点),目录精确记录了谁持有数据,只去询问有数据的人。配合 NoC (Network-on-Chip) 拓扑(如 Mesh 网格),可以轻松扩展到上百个核心。

其次,CHI具有更高的链路效率。一方面,ACE 是基于信号线的,地址、数据、控制信号有固定的连接逻辑。CHI 将所有信息拆分为 FLIT(Flow Control Units)。另一方面,在 CHI 中,地址请求、数据传输、响应确认是完全解耦的。这意味着链路不会因为等待某个慢速响应而被长时间占用,可以极大提高带宽利用率。

再次,CHI支持复杂的网络拓扑。ACE主要受限于 Crossbar(交叉开关)结构,难以支持大规模的物理布局。CHI天生为 NoC 设计。无论芯片内部是 Mesh(网格)Ring(环形) 还是 Torus(环面) 拓扑,CHI 都能通过路由器进行多跳传输,解决大型 SoC 物理布线难(Routing Congestion)的问题。

最后,CHI支持很多ACE不具有的功能特性。比如Dataless Transactions,允许节点在不搬移实际数据的情况下,仅改变缓存块的状态(例如直接作废其他缓存),节省带宽。比如Atomic Operations, 支持在近内存端执行原子操作(如加法、比较交换),无需将数据拉回到 CPU 核心内部。比如Direct Cache Transfer,允许数据从一个 Cache 直接传给另一个 Cache(Cache-to-Cache),或者从 Cache 直接传给 I/O,无需绕道内存。

总结而言,ACE适合核心较少的移动端处理器(如手机 SoC 的小核心簇)。CHI适合数据中心、服务器、高性能计算(HPC)以及拥有大量 NPU/GPU 异构核心的顶级芯片。

CPU和NPU之间需要CHI协议吗?

取决于NPU的角色和数据交换的频繁程度。

需要CHI的情况:NPU和CPU共享一块物理内存,并且存在频繁的读写交互。

不需要CHI,使用ACE的情况:NPU被视为一个外设,CPU在缓存中准备好一块数据,告诉NPU去该地址读,读完发个中断给CPU。即:单向一致性(NPU可以读取CPU的缓存,但CPU不会读取NPU的缓存)

仅需要AXI的情况:NPU有自己独立的内存空间(显存),或者通过DMA进行大块数据的显式搬运

MI、MSI、MOSI、MESI、MOESI、CHI区别

这些协议定义了一个缓存块(Cache Line)在处理器中可能处于的状态。每一个字母都代表一个状态:

  • M (Modified): 脏数据,只有我有,且我改过了,与内存不一致。
  • O (Owned): 脏数据,我有,别人也可以有(只读),但我负责把数据写回内存。
  • E (Exclusive): 洁净数据,只有我有,且与内存一致。evict时直接丢,不用写回。
  • S (Shared): 洁净数据,大家都可以有。
  • I (Invalid): 数据无效。

区别对比表:

协议 状态数量 特点 适用场景
MI 2 最简单的协议。数据要么是修改过的(M),要么是无效的(I)。只要读写就必须加锁或广播,效率极低。 理论研究基准
MSI 3 经典起点。引入了 Shared 状态,支持多核同时读取数据,极大提升了读性能。 入门教材必讲
MESI 4 目前主流。增加了 Exclusive 状态。如果 CPU 发现数据只有自己在用,修改时不需要广播,减少了总线压力。读升级为写时不用广播,evict不用写回。 大多数中小型多核系统
MOSI 4 允许在缓存之间直接传“脏数据”,无需写回内存。但由于没有 E 态,即使只有一个人用也得走同步流程。 较少单独使用
MOESI 5 全能选手。结合了 MESI 的独占优势和 MOSI 的脏数据共享优势。 高性能服务器 CPU

CHI 如何对应 MOESI?

CHI 使用了 Valid (V), Dirty (D), 和 Unique (U) 三个维度的组合来定义状态,这直接涵盖了 MOESI 的逻辑:

MOESI 状态 CHI 对应状态 (典型) 状态含义解析
M (Modified) UD_L (Unique Dirty Library) 唯一、脏。我有唯一副本,且我改过了,我负责写回。
O (Owned) SD (Shared Dirty) 共享、脏。我有副本,别人也可以有,但我负责写回内存。
E (Exclusive) UC (Unique Clean) 唯一、洁净。只有我有,且和内存一致。
S (Shared) SC (Shared Clean) 共享、洁净。大家都有,且和内存一致。
I (Invalid) I (Invalid) 无效

CHI还引入了一些MOESI没有的细分状态(UDP和UCE),更加精细。

同时,CHI不仅仅是状态,它把操作解耦成了三个层次,这是 MOESI 这种纯状态协议不具备的:

  1. Protocol Layer (协议层):这就是你看到的类似 MOESI 的状态机(RN 节点的行为)。

  2. Network Layer (网络层):负责把状态转换的消息打包成 FLIT(流控制单元),并在 Mesh 网络中路由。

  3. Link Layer (链路层):负责物理信号的握手和校验。

Gem5 Ruby中Event, Action, Transition, State的区别

  1. Event (事件) —— “如果发生了什么?”

Event 是状态机运转的触发器。它代表了系统中发生的、需要控制器做出反应的任何事情。

在 SLICC 代码中,Event 通常来自三个地方:

  • 来自处理器(CPU):例如 Load(读请求)、Store(写请求)。
  • 来自网络(Message Buffer):例如收到了 Data(数据包)、Inv(作废请求)、Ack(确认包)。
  • 来自内部/计时器:例如 Replacement(缓存块被踢出)、Timeout(超时重新发送)。

例子Event:Data 表示“有一个装有数据的响应包从网络飞到了我的收件箱里”。


  1. Action (动作) —— “要做哪些具体的事?”

Action 是协议执行的最小原子单元。它们是代码块,定义了硬件在接收到事件时需要执行的物理操作。

一个 Action 通常只做一件简单的事:

  • 发送消息enqueue(request_out, ...)
  • 修改数据writeDataToCache
  • 计数操作decrAcks(计数器减一)。
  • 管理队列popResponseQueue(把处理完的消息从缓冲区删掉)。

注意:Action 的执行必须是原子性的,即在这个 Action 完成之前,状态机不会被其他事情中断。


  1. Transition (状态转换) —— “当处于某状态,发生了某事,该怎么变?”

Transition 是协议的核心逻辑表。它将上述的 State(当前状态)Event(触发事件)Action(执行动作) 缝合在一起。

其标准格式为: transition(当前状态, 触发事件, 目标状态) { 执行动作A; 执行动作B; ... }

实例解析

看看这段典型的代码:

代码段

transition(IS_D, Data, S) {
    writeDataToCache;   // Action 1: 存数据
    deallocateTBE;      // Action 2: 销毁事务记录
    externalLoadHit;    // Action 3: 通知CPU拿数据
    popResponseQueue;   // Action 4: 弹出消息
}

这行代码的意思是:

  • 当(State):缓存块正处于 IS_D(等待数据的中间态)。
  • 如果(Event):收到了 Data(数据回传)。
  • 那么执行(Actions):把数据存入缓存、注销 TBE、响应 CPU、清理队列。
  • 最后变为(Next State)S(稳定的共享态)。

  1. 三者的关系图解

我们可以用“自动售卖机”来类比:

  • State(状态):售卖机现在是“待机”还是“已投币”?
  • Event(事件):用户“投入了硬币”或者“按下了购买键”。
  • Action(动作):售卖机“滚出可乐”、“找零”、“更新余额显示”。
  • Transition(转换):如果当前是“待机”状态,发生了“投币”事件,那么执行“显示余额”动作,并转变为“已投币”状态。

总结:在 Ruby 里的协同工作流

  1. 输入:一条消息进入 MessageBuffer
  2. 触发:Ruby 扫描缓冲区,识别出对应的 Event
  3. 查表:Ruby 根据该块目前的 State 和新发生的 Event,在 Transition 表中找到对应的条目。
  4. 执行:依次执行该条目下定义的所有 Action
  5. 更新:将缓存块的状态修改为 Next State

Classic Cache和Ruby Cache的区别

Classic Cache (经典缓存)

  • 定位:侧重于性能仿真。它源于 M5 模拟器,设计目标是简洁、高效。
  • 哲学:侧重于“数据怎么流动”。它使用 C++ 硬编码的一致性协议(主要是基础的 MESI),不支持复杂的自定义协议。

Ruby Cache (Ruby 缓存)

  • 定位:侧重于内存子系统与一致性协议研究。它源于 GEMS 项目,是一个极其详细且灵活的内存模拟器。
  • 哲学:侧重于“协议怎么运转”。它使用 SLICC 这种专用语言来定义状态机,允许研究者实现任何自定义的一致性协议(如 MSI, MOESI, CHI, Token Coherence 等)。
特性 Classic Cache Ruby Cache
一致性协议 固定、硬编码 (Basic MESI) 高度可定制 (SLICC 语言)
建模精度 周期近似 (Cycle-approximate) 周期精确 (Cycle-accurate)
互联网络 简单的 Crossbar / Bus 详细的 NoC (Garnet),支持 Mesh/Ring 等
仿真速度 较快 较慢(由于协议状态机解析开销)
配置复杂度 简单(Python 脚本直接配置) 复杂(需要编译特定的协议)
适用场景 快速评估 CPU 架构、算法验证 一致性协议研究、大规模多核 SoC、NoC 设计

CHI常见死锁及解决方案

首先是资源依赖链死锁。RN持续发出的大量Request占满了HN的缓冲区,导致HN无法处理Resp。而响应因为被请求堵在网络中而无法到达RN, RN也就无法释放占用的缓冲区。即:HN在等(HN 必须把手里的 RSP 发出去,才能腾出缓冲区(Tracker)来接收新的 REQ),网络在等(RSP 消息在等网络空闲,但前方被密密麻麻的 REQ 堵死了),REQ在等(REQ 消息在等 HN 腾出缓冲区以便被接收)。结果是HN 拿不出空间收 REQ,网络挪不动位置传 RSP,系统彻底锁死。

解决方案是CHI 强制将消息分为不同的类,并运行在相互隔离的虚拟网络上:优先级高到低分别是RSP/DAT, SNP, REQ,高优先级的消息永远不能被低优先级的消息阻塞。例如,DAT 必须能够绕过被堵住的 REQ。

其次是转发响应死锁(Forwarding Deadlock)。在 Direct Data Transfer(数据直传)中,RN-A 请求数据,HN 转发给 RN-B,RN-B 直接发给 RN-A。如果 RN-A 的接收缓冲区满了,且 RN-A 同时还在发新的请求,就可能形成环路。

解决方案是CHI 引入了 Retry 机制。当 HN 的缓冲区(Tracker)满时,它不直接阻塞请求,而是回一个 RetryAck,让 RN 过一会儿再发。

再次是路由层死锁,这主要发生在 NoC(如 Mesh 网络)的路由器中。四个路由器(A, B, C, D)形成一个环,每个路由器都填满了发往下一个路由器的 Flit,导致谁也动不了。

解决方案有好几个,可加虚拟通道,也可采用维度优先路由(比如X-Y路由)。

除此之外还有独占访问死锁。即两个核心(RN)同时尝试对同一个内存地址执行 Exclusive Load/Store(原子操作)时。如果协议处理不当,可能出现两个核互相不断作废(Invalidate)对方的缓存块,导致程序永远无法向前推进(虽然这更接近 活锁 Livelock,但在系统表现上与死锁类似)。

解决方案是CHI 通过 HN-F 的 PoC (Point of Coherency) 进行序列化。HN-F 会作为一个公平的仲裁者,确保一个请求完成后再处理下一个。

CHI协议的三层(Protocol Layer, Network Layer, Link Layer)分别解决哪些问题

协议层解决“干什么”和“一致性逻辑”的问题。

网络层解决“去哪里”和“怎么走”的问题。

链路层解决“怎么传”和“可靠性”的问题。

协议层(Protocol Layer) 是最顶层,它完全不关心数据是怎么走过去的,只关心事务的语义

  • 一致性状态管理: 定义了各个节点的 Cache 状态(如 MOESI 状态及其变体)。
  • 事务定义: 规定了各种请求(Request)、响应(Response)和数据(Data)的消息类型。例如:ReadSharedMakeInvalidSnoop 等。
  • 保序逻辑: 处理事务之间的依赖关系,确保在多核并发访问时,内存模型(Memory Model)不被破坏。
  • 信用机制 (P-Credit): 处理协议层层面的流控,确保接收端有足够的 Buffer(如 Tracker 资源)来处理一个新的事务。

网络层(Network Layer) 负责将协议层的消息封装成网络包(Packet),并在复杂的拓扑结构中完成路由。

  • 寻址与路由: 根据目标 ID(Target ID)决定包的路径。在 Mesh 拓扑中,它决定了 Flit 是向 X 方向还是 Y 方向传输。
  • 拓扑映射: 屏蔽底层物理结构的差异。无论是单层交叉开关(Crossbar)还是多级网格(Mesh),协议层看到的都是逻辑地址,而网络层负责物理映射。
  • 虚拟通道 (Virtual Channels): 为了避免协议级死锁(例如请求包被响应包堵死),网络层通过划分不同的 VC 来隔离不同类型的流量(REQ, RSP, DAT, SNP)。

链路层(Link Layer) 负责两个相邻物理节点(Port-to-Port)之间最底层的握手与数据交换

  • 切片化 (Flit): 将网络层的 Packet 切分为固定大小的 Flits (Flow Control Units) 进行传输。
  • 链路级流控 (L-Credit): 这是最底层的流控,基于 Credit 机制确保发送端发送 Flit 时,接收端物理层有空间接收,防止 Buffer 溢出。
  • 握手与激活: 负责链路的激活(Activation)、断开(Deactivation)以及低功耗状态管理。
  • 错误检测: 某些实现中会包含 CRC 或 Parity 校验,确保两点之间数据传输的完整性。
层级 术语单位 关注核心 类比 (快递系统)
Protocol Transaction 一致性与业务逻辑 寄件人决定写信还是寄包裹
Network Packet 路由、寻址、避锁 邮局根据地址决定运输路线
Link Flit 物理传输、点对点流控 货车在两个收费站之间的行驶

Cache的多路组相联(Set-Associative)

多路组相联(Set-Associative) 是一种平衡“查找速度”与“存储效率”的折中方案。

简单来说,它将 Cache 空间切分成多个组(Set),每个组里包含固定数量的路(Way)

我们可以用“停车场”来打比方:

  • 直接映射 (Direct-Mapped): 每个人只能停在自己编号对应的那个车位。如果两个人都对应 5 号位,就得互相踢走对方。
  • 全相联 (Fully-Associative): 谁来都能停在任何空车位。虽然利用率高,但你要找自己的车时,得把几千个车位全看一遍(硬件查找电路极其复杂且耗电)。
  • 多路组相联 (Set-Associative): 你根据车牌号被分配到一个特定的区域(Set),但在该区域内的 N 个车位(Ways) 里,你可以随便停哪一个。

在硬件层面,地址被分为三个部分:

  1. Offset(偏移量): 在一个 Cache Line 内部定位具体的字节。
  2. Index(索引): 决定这个地址对应哪一个 Set
  3. Tag(标记): 用来在 Set 内部区分不同的地址。

多路组相联的优势

  • 减少冲突(Conflict Miss): 相比直接映射,如果两个地址的 Index 相同,它们可以分别占用同一个 Set 里的两个不同的 Way,而不会立即互相踢出。
  • 复杂度可控: 相比全相联,硬件只需要并行比较一个 Set 内的 N 个 Tag(比如 4 路就比 4 个,8 路就比 8 个),时序和面积开销在可接受范围内。
  • 替换算法(Replacement Policy): 因为一个组内有多个路,我们可以引入 LRU(最近最少使用) 算法,优先踢出最不常用的数据。

设计因素考量

  • Way 数越多: 冲突概率越低,命中率越高,但访问延迟(Latency)和比较电路的功耗会增加。
  • Set 数越多: 总容量越大,但需要更多的地址位做 Index。