TBE(Transaction Buffer Entry)
在缓存一致性协议中,TBE (Transaction Buffer Entry) 是处理“中间状态”的关键。
简单来说,TBE 就像是缓存控制器的“便签纸”或“事务记录本”。
当缓存块处于稳定状态(如 M、S 或 I)时,信息存在 CacheMemory 里;但当缓存块处于过渡状态(Transient State,比如从 I 变到 M 的过程中)时,控制器需要一个临时的地方记录“活还没干完”的细节。
MSI协议中,TBE 的核心功能包括:
-
追踪“过渡状态” (Transient States)。缓存一致性操作不是瞬间完成的。比如当你发出一个写请求(GetM),在收到数据之前,你的缓存块处于
IS_D(从 Invalid 变到 Shared/Modified,正在等待 Data)状态。TBE 记录了这个块“正在变成什么”,这样当网络回复数据时,控制器才知道该把数据给谁、要把状态改成什么。 -
充当 MSHR (Miss Status Holding Register)。在高性能处理器中,如果发生 Cache Miss,处理器不能直接停下来死等。TBE 记录了这次 Miss 的相关信息(哪个地址、什么类型的请求),允许处理器在等待数据的同时去干别的事。
-
等待确认包 (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类:
- 需要发往远端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之间的一致性冲突。
- 需要发往远端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中。
- 需要发往远端的Response/Data。C2CI至少需要完成以下几种操作:首先,转换格式后直接透传。
- 从远端收到的Request。C2CI至少需要完成以下几种操作:首先,转换格式后直接透传给本地的HNF。
- 从远端收到的Snoop Request。C2CI至少需要完成以下几种操作:首先,转换格式后直接透传给本地的对应RNF(不经过本地的HNF)。
- 从远端收到的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 这种纯状态协议不具备的:
-
Protocol Layer (协议层):这就是你看到的类似 MOESI 的状态机(RN 节点的行为)。
-
Network Layer (网络层):负责把状态转换的消息打包成 FLIT(流控制单元),并在 Mesh 网络中路由。
-
Link Layer (链路层):负责物理信号的握手和校验。
Gem5 Ruby中Event, Action, Transition, State的区别
- Event (事件) —— “如果发生了什么?”
Event 是状态机运转的触发器。它代表了系统中发生的、需要控制器做出反应的任何事情。
在 SLICC 代码中,Event 通常来自三个地方:
- 来自处理器(CPU):例如
Load(读请求)、Store(写请求)。 - 来自网络(Message Buffer):例如收到了
Data(数据包)、Inv(作废请求)、Ack(确认包)。 - 来自内部/计时器:例如
Replacement(缓存块被踢出)、Timeout(超时重新发送)。
例子:
Event:Data表示“有一个装有数据的响应包从网络飞到了我的收件箱里”。
- Action (动作) —— “要做哪些具体的事?”
Action 是协议执行的最小原子单元。它们是代码块,定义了硬件在接收到事件时需要执行的物理操作。
一个 Action 通常只做一件简单的事:
- 发送消息:
enqueue(request_out, ...)。 - 修改数据:
writeDataToCache。 - 计数操作:
decrAcks(计数器减一)。 - 管理队列:
popResponseQueue(把处理完的消息从缓冲区删掉)。
注意:Action 的执行必须是原子性的,即在这个 Action 完成之前,状态机不会被其他事情中断。
- 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(稳定的共享态)。
- 三者的关系图解
我们可以用“自动售卖机”来类比:
- State(状态):售卖机现在是“待机”还是“已投币”?
- Event(事件):用户“投入了硬币”或者“按下了购买键”。
- Action(动作):售卖机“滚出可乐”、“找零”、“更新余额显示”。
- Transition(转换):如果当前是“待机”状态,发生了“投币”事件,那么执行“显示余额”动作,并转变为“已投币”状态。
总结:在 Ruby 里的协同工作流
- 输入:一条消息进入
MessageBuffer。 - 触发:Ruby 扫描缓冲区,识别出对应的
Event。 - 查表:Ruby 根据该块目前的
State和新发生的Event,在Transition表中找到对应的条目。 - 执行:依次执行该条目下定义的所有
Action。 - 更新:将缓存块的状态修改为
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)的消息类型。例如:
ReadShared、MakeInvalid、Snoop等。 - 保序逻辑: 处理事务之间的依赖关系,确保在多核并发访问时,内存模型(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) 里,你可以随便停哪一个。
在硬件层面,地址被分为三个部分:
- Offset(偏移量): 在一个 Cache Line 内部定位具体的字节。
- Index(索引): 决定这个地址对应哪一个 Set。
- Tag(标记): 用来在 Set 内部区分不同的地址。
多路组相联的优势
- 减少冲突(Conflict Miss): 相比直接映射,如果两个地址的 Index 相同,它们可以分别占用同一个 Set 里的两个不同的 Way,而不会立即互相踢出。
- 复杂度可控: 相比全相联,硬件只需要并行比较一个 Set 内的 N 个 Tag(比如 4 路就比 4 个,8 路就比 8 个),时序和面积开销在可接受范围内。
- 替换算法(Replacement Policy): 因为一个组内有多个路,我们可以引入 LRU(最近最少使用) 算法,优先踢出最不常用的数据。
设计因素考量
- Way 数越多: 冲突概率越低,命中率越高,但访问延迟(Latency)和比较电路的功耗会增加。
- Set 数越多: 总容量越大,但需要更多的地址位做 Index。