gem5 MultiCore Memory System
Gem5 Version:
v25.1.0.0
gem5 多核互联主要分成两条路径: 分别采用 Classic 与 Ruby 两种 memory system
Classicmemory system:
每个 core 的 cache hierarchy 最终通过一个或多个共享crossbar/bus接到 memory side。这里的互联核心对象通常是SystemXBar和L2XBar。Rubymemory system:
每个 core 并不是直接挂到共享 bus 上,而是先绑定到对应的Ruby controller和RubySequencer,再由network和topology将这些 controller 连接起来。
连接关系如下:
Classic:CPU -> cache hierarchy -> XBar -> memoryRuby:CPU -> RubySequencer/controller -> network/topology -> directory/memory controller
Classic memory system 的多核互联机制
Classic memory system 的 interconnect 主要由 BaseXBar 的具体实现承担。最常见的是:
SystemXBar: 系统级共享 interconnect,连接 caches、memory controllers、I/O。L2XBar: 较靠近 core side 的局部 interconnect,通常用于把多个 L1 cache 汇聚到 shared L2,或者作为单核 private L2 前端的局部连接点
在 stdlib 的 cache hierarchy 里,多核通常不是直接“核与核相连”,而是各个 core 的 cache hierarchy 汇到共享 XBar
因此,多核之间的共享路径体现为“共享 interconnect + 共享下层 cache/memory”
结构一: private L1 + shared SystemXBar
最简单的 Classic 多核结构是每个 core 有 private L1I 和 L1D,但它们的 mem_side 都接到同一个 SystemXBar。
1 | core0 -> L1I/L1D \ |
这张图对应的是最直接的 Classic shared interconnect 形式。所有 cores 的 private L1 后端都汇入同一个 SystemXBar,因此这个 SystemXBar 是主要 shared point,也是多核共享访问路径上的核心仲裁点
关键代码:src/python/gem5/components/cachehierarchies/classic/private_l1_cache_hierarchy.py
关键逻辑:
_get_default_membus()创建默认的SystemXBar(width=64)incorporate_cache()中,按board.get_processor().get_num_cores()为每个 core 创建 privateL1I/L1D- 对每个 core:
cpu.connect_icache(...)cpu.connect_dcache(...)self.l1icaches[i].mem_side = self.membus.cpu_side_portsself.l1dcaches[i].mem_side = self.membus.cpu_side_ports
结构二: private L1 + shared L2 + SystemXBar
另一种常见结构是所有 cores 的 private L1 先接到一个共享 L2XBar,再通过 shared L2Cache 接到 SystemXBar。
1 | core0 -> private L1 \ |
这张图里有两个关键 shared point: shared L2XBar 和 shared L2Cache。
相比上一种结构,它把多个 cores 的流量先在更靠近 CPU side 的 shared L2 层级汇聚,再进入系统级 SystemXBar。
关键代码:src/python/gem5/components/cachehierarchies/classic/private_l1_shared_l2_cache_hierarchy.py
关键逻辑:
- 创建默认
SystemXBar - 按 core 数量创建 private
L1I/L1D - 创建一个共享
L2XBar和一个共享L2Cache - 每个 core 的 L1 cache 都接到
self.l2bus.cpu_side_ports self.l2bus.mem_side_ports = self.l2cache.cpu_sideself.membus.cpu_side_ports = self.l2cache.mem_side
结构三: private L1 + private L2 + shared SystemXBar
还有一种结构是每个 core 都有自己的 L2XBar 和 private L2Cache,但各个 L2 的后端仍并到同一个 SystemXBar。
1 | core0 -> L1I/L1D -> L2XBar_0 -> private L2_0 --\ |
这个结构把每个 core 的上层 cache hierarchy 尽量本地化,shared point 被下推到系统级 SystemXBar。因此,core 之间不会共享 L2,但仍通过同一个 system interconnect 访问更远端的 memory system。
关键代码:src/python/gem5/components/cachehierarchies/classic/private_l1_private_l2_cache_hierarchy.py
关键逻辑:
- 创建默认
SystemXBar - 按 core 数量创建
self.l2buses = [L2XBar() ...] - 对每个 core:
- 创建 private
L2Cache - 再在其下创建 private
L1I和L1D self.l2buses[i].mem_side_ports = l2_node.cache.cpu_sideself.membus.cpu_side_ports = l2_node.cache.mem_side
- 创建 private
core 数限制
在 stdlib 里,core 通常来自 processor 对象:
src/python/gem5/components/processors/abstract_processor.pysrc/python/gem5/components/processors/simple_processor.py
其中:
SimpleProcessor会按照num_cores直接创建SimpleCore列表。- cache hierarchy 则按
board.get_processor().get_num_cores()循环创建 cache 和连接。
Ruby memory system 的多核互联机制
Ruby 的设计思路与 Classic 不同。它并不是让 CPU 直接接共享 bus,而是引入一套更显式的 memory system 组件:
RubySequencer- protocol-specific
controller networktopology
在 Ruby 中,CPU 的 memory access 会先进对应的 RubySequencer/controller,之后消息通过 Ruby network 在多个 controller 之间传递。这里的“互联拓扑”主要由 network + topology 决定,而不是 SystemXBar。
Ruby 的主要入口文件是: configs/ruby/Ruby.py
关键流程:
create_system()创建RubySystem- 通过
Network.create_network()创建 network 对象 - 根据当前编译的 protocol 调用
configs/ruby/<protocol>.py的create_system() - 由 protocol 脚本返回:
cpu_sequencersdir_cntrlstopology
- 再调用
topology.makeTopology(...)将 controllers 具体连接成网络
Ruby network and topology
network 选项定义在: configs/network/Network.py
默认值:
--topology=Crossbar--network=simple
其中,
network:
选择底层网络实现,如simple或garnettopology:
决定 controllers 和 routers 如何连接,如Crossbar、Pt2Pt、Mesh_XY
Ruby Typical Topology
Crossbar
关键代码: configs/topologies/Crossbar.py
机制:
- 为每个 controller 建一个 router
- 再额外建一个 central
xbarrouter - 每个 controller 通过
ExtLink接到自己的 router - 每个 router 再通过
IntLink双向连接到 centralxbar
1 | CPU0 -> RubySequencer0 -> L1 controller0 -> router0 --\ |
这里的 shared point 是中心 xbar router。与 Classic 的 shared SystemXBar 类似,它也是集中式结构,但运行语义是 Ruby network 上的 message transfer,而不是普通 port 直接连线。
Pt2Pt
关键代码: configs/topologies/Pt2Pt.py
机制:
- 每个 controller 一个 router
- 每个 router 与其它所有 routers 全互连
- 因此 internal link 数量是
O(N^2)
1 | CPU0 -> seq0 -> controller0 -> router0 |
Pt2Pt 没有单一中心节点,所有 routers 彼此直接互联。它的优点是路径直观,缺点是 router 和 link 数量增长很快,因此在 node 数量上去后会明显变重。
这种 topology 在 node 数量增大时会迅速膨胀,因此虽然没有源码层面的硬上限,但实际可扩展性受限。
Mesh_XY
关键代码: configs/topologies/Mesh_XY.py
机制:
num_routers = options.num_cpusnum_rows = options.mesh_rows- 按
num_rows x num_columns构建二维 mesh - controller 通过
ExtLink均匀分布到 routers - router 之间建立 East/West/North/South
IntLink - link
weight用于实现XY routing
1 | R(0,0) ---- R(0,1) ---- R(0,2) |
在这个 topology 中,shared point 不再是单个中心节点,而是整个 mesh fabric。controller 通过 ExtLink 分布到不同 routers 上,消息按照 XY routing 穿过 mesh 到达目标 controller。
Mesh_XY 是 Ruby 中更接近典型 NoC 的多核互联形式。
Ruby Typical System Config
Ruby 系统采用的一致性协议参考 src/mem/ruby/protocol/*.sm 里的状态机
Ruby 典型系统的配置在 configs/ruby/<protocol>.py:
MESI_Two_Level: private L1 + shared L2 bank + directory
关键代码: configs/ruby/MESI_Two_Level.py
- 每个 CPU 创建一组 private
L1I/L1D - 每个 CPU 绑定一个
MESI_Two_Level_L1Cache_Controller和一个RubySequencer - 系统再创建若干个共享
L2Cache_Controller - 更后面是 directory controller 和可选 DMA controller
因此它对应的典型结构是:
1 | CPU[i] |
这里有两个实现细节值得注意:
l2_bits = log2(num_l2caches)会参与l2_select_num_bits,说明多个 L2 controller 通常是按地址分片或 bank 化选择的,而不是“一个大一统 L2”ruby_system.network.number_of_virtual_networks = 3,说明这个协议把网络消息大致分成 request/response/unblock 这类基础通道
MESI_Two_Level 更像是 Ruby 版本的“private L1 + shared LLC slice/directory”配置
MESI_Three_Level: private L0 + private/cluster-local L1 + cluster-shared L2
关键代码: configs/ruby/MESI_Three_Level.py
这份配置比 two-level 多了一层,并且显式引入 cluster 概念:
- 每个 core 有 private
L0I和L0D - 每个 core 还有一个 private
L1controller - 每个 cluster 下面再放若干共享
L2controller - 所有 cluster 之后才接 directory 和 memory
1 | cluster c: |
有两个约束:
num_cpus % num_clusters == 0num_l2caches % num_clusters == 0
MESI_Three_Level 把 CPU 和 L2 都均匀切到各个 cluster 上
- 协议里
L0 <-> L1之间是直接用本地MessageBuffer相连,L1 <-> L2才进入ruby_system.network - 从互联角度说,真正暴露给 topology 的不是最靠近 CPU 的 L0,而是 L1/L2/directory controller
MOESI_CMP_directory: private L1 + banked shared L2 + directory,强调 directory/forward path
关键代码: configs/ruby/MOESI_CMP_directory.py
- 每个 CPU 一个 private
L1I/L1D - 系统创建多个共享
L2 - 后面接 directory 和 DMA
特征:
- L2 的
addr_ranges显式按地址区间切片,说明 L2 是带 home 范围划分的 banked shared cache - directory 侧除了 request/response,还专门连了
forwardFromDir,说明协议层更强调 directory 发起的 forward/snoop 行为
MOESI_hammer: per-core private hierarchy + directory,可选 Probe Filter
关键代码: configs/ruby/MOESI_hammer.py
MOESI_hammer 的一个明显区别是,每个 core 的 controller 里同时绑了 L1I、L1D 和 L2cache:
- private cache hierarchy 更大一部分被收进了单个 core-side controller
- 系统级共享点更靠后,主要落在 directory 和 memory side
此外还有两个目录侧开关:
--pf-on: 开启ProbeFilter--dir-on: 开启 full-bit directory
1 | CPU[i] -> seq -> private L1/L2 controller[i] -> Ruby network -> directory(+ optional ProbeFilter) -> memory |
MOESI_AMD_Base: CorePair + private mid-level cache + shared L3/directory
关键代码: configs/ruby/MOESI_AMD_Base.py
这份配置显式定义了 CorePair_Controller:
- 两个 CPU 组成一个 core pair
- 一个 pair 内部带
L1I、两个L1D,以及共享的L2 - 系统再创建共享
L3controller 和 directory controller
1 | 2 CPUs |
有一个直接约束: assert (options.num_cpus % 2) == 0
- 其建模的是 AMD 风格的成对核心组织,而不是任意核数随便平铺
- 配置显式使用了
Cluster拓扑对象,把 CPU cluster 和主 cluster 分开组织
CHI: request/home/subordinate node 显式分层的 NoC 化系统
关键代码: configs/ruby/CHI.py
CHI 与前面协议的最大差别,是配置脚本不再围绕 “L1 controller / L2 controller / directory controller” 来搭系统,而是直接按 CHI node type 建模:
RNF: CPU requester 侧HNF: home node,同时也是 LLC sliceSNF: memory side nodeRNI: DMA / I/O requesterMN: misc node
CHI 要求:
num_dirs >= 1num_l3caches >= 1
将 number_of_virtual_networks 提升到 4,显式区分 request、snoop、response、data 四类通道
CHI 的 node type 分层:
1 | CPU0 -> RNF0 --\ |
RNF代表 CPU side request nodeHNF代表 home node / LLC sliceSNF代表 memory side nodeRNI代表 I/O 或 DMA 入口
整体通过一张 NoC fabric 互联
关键代码: configs/ruby/CHI.py
关键逻辑:
- 先检查
num_dirs、num_l3caches等约束 - 按
options.num_cpus生成RNF - 生成
HNF、SNF、DMARNI、I/ORNI - 设置 downstream destination
- 根据
options.topology决定用哪些 nodes 去创建 topology
GPU_VIPER: CPU + GPU 共享 Ruby fabric 的异构配置
关键代码: configs/ruby/GPU_VIPER.py
这个配置存在:
- CPU 侧
CorePair_Controller - GPU compute side 的
TCP - instruction / scalar 侧的
SQC - GPU 末级共享缓存
TCC - directory / memory 侧 controller
配置使用 Cluster 把 CPU cluster、GPU cluster、main cluster 分开组织,最后设置ruby_system.network.number_of_virtual_networks = 11
Ruby 的 core 数约束
实际约束主要来自 protocol 和 topology
包括:
Mesh_XY:
要求mesh_rows > 0,且num_columns * num_rows == num_routersMESI_Three_Level/MESI_Three_Level_HTM:
要求options.num_cpus % options.num_clusters == 0MOESI_AMD_Base:
要求options.num_cpus为偶数CHI:
要求num_dirs >= 1、num_l3caches >= 1