Gem5 Version: v25.1.0.0

gem5 多核互联主要分成两条路径: 分别采用 ClassicRuby 两种 memory system

  1. Classic memory system:
    每个 core 的 cache hierarchy 最终通过一个或多个共享 crossbar/bus 接到 memory side。这里的互联核心对象通常是 SystemXBarL2XBar
  2. Ruby memory system:
    每个 core 并不是直接挂到共享 bus 上,而是先绑定到对应的 Ruby controllerRubySequencer,再由 networktopology 将这些 controller 连接起来。

连接关系如下:

  • Classic: CPU -> cache hierarchy -> XBar -> memory
  • Ruby: 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 L1IL1D,但它们的 mem_side 都接到同一个 SystemXBar

1
2
3
4
core0 -> L1I/L1D \
core1 -> L1I/L1D \
core2 -> L1I/L1D -> SystemXBar -> memory
... /

这张图对应的是最直接的 Classic shared interconnect 形式。所有 cores 的 private L1 后端都汇入同一个 SystemXBar,因此这个 SystemXBar 是主要 shared point,也是多核共享访问路径上的核心仲裁点

关键代码:
src/python/gem5/components/cachehierarchies/classic/private_l1_cache_hierarchy.py

关键逻辑:

  1. _get_default_membus() 创建默认的 SystemXBar(width=64)
  2. incorporate_cache() 中,按 board.get_processor().get_num_cores() 为每个 core 创建 private L1I/L1D
  3. 对每个 core:
    • cpu.connect_icache(...)
    • cpu.connect_dcache(...)
    • self.l1icaches[i].mem_side = self.membus.cpu_side_ports
    • self.l1dcaches[i].mem_side = self.membus.cpu_side_ports

结构二: private L1 + shared L2 + SystemXBar

另一种常见结构是所有 cores 的 private L1 先接到一个共享 L2XBar,再通过 shared L2Cache 接到 SystemXBar

1
2
3
4
core0 -> private L1 \
core1 -> private L1 \
core2 -> private L1 -> shared L2XBar -> shared L2 -> SystemXBar -> memory
... /

这张图里有两个关键 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

关键逻辑:

  1. 创建默认 SystemXBar
  2. 按 core 数量创建 private L1I/L1D
  3. 创建一个共享 L2XBar 和一个共享 L2Cache
  4. 每个 core 的 L1 cache 都接到 self.l2bus.cpu_side_ports
  5. self.l2bus.mem_side_ports = self.l2cache.cpu_side
  6. self.membus.cpu_side_ports = self.l2cache.mem_side

结构三: private L1 + private L2 + shared SystemXBar

还有一种结构是每个 core 都有自己的 L2XBar 和 private L2Cache,但各个 L2 的后端仍并到同一个 SystemXBar

1
2
3
4
5
6
7
8
9
10
11
core0 -> L1I/L1D -> L2XBar_0 -> private L2_0 --\
\
core1 -> L1I/L1D -> L2XBar_1 -> private L2_1 ----> +===================+
|| SystemXBar ||
core2 -> L1I/L1D -> L2XBar_2 -> private L2_2 ----> +===================+
/ |
/ v
/ Mem ctrl
/ |
/ v
/ memory

这个结构把每个 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

关键逻辑:

  1. 创建默认 SystemXBar
  2. 按 core 数量创建 self.l2buses = [L2XBar() ...]
  3. 对每个 core:
    • 创建 private L2Cache
    • 再在其下创建 private L1IL1D
    • self.l2buses[i].mem_side_ports = l2_node.cache.cpu_side
    • self.membus.cpu_side_ports = l2_node.cache.mem_side

core 数限制

在 stdlib 里,core 通常来自 processor 对象:

  • src/python/gem5/components/processors/abstract_processor.py
  • src/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
  • network
  • topology

Ruby 中,CPU 的 memory access 会先进对应的 RubySequencer/controller,之后消息通过 Ruby network 在多个 controller 之间传递。这里的“互联拓扑”主要由 network + topology 决定,而不是 SystemXBar

Ruby 的主要入口文件是: configs/ruby/Ruby.py

关键流程:

  1. create_system() 创建 RubySystem
  2. 通过 Network.create_network() 创建 network 对象
  3. 根据当前编译的 protocol 调用 configs/ruby/<protocol>.pycreate_system()
  4. 由 protocol 脚本返回:
    • cpu_sequencers
    • dir_cntrls
    • topology
  5. 再调用 topology.makeTopology(...) 将 controllers 具体连接成网络

Ruby network and topology

network 选项定义在: configs/network/Network.py

默认值:

  • --topology=Crossbar
  • --network=simple

其中,

  • network:
    选择底层网络实现,如 simplegarnet
  • topology:
    决定 controllers 和 routers 如何连接,如 CrossbarPt2PtMesh_XY

Ruby Typical Topology

Crossbar

关键代码: configs/topologies/Crossbar.py

机制:

  1. 为每个 controller 建一个 router
  2. 再额外建一个 central xbar router
  3. 每个 controller 通过 ExtLink 接到自己的 router
  4. 每个 router 再通过 IntLink 双向连接到 central xbar
1
2
3
4
5
6
CPU0 -> RubySequencer0 -> L1 controller0 -> router0 --\
CPU1 -> RubySequencer1 -> L1 controller1 -> router1 ---\
CPU2 -> RubySequencer2 -> L1 controller2 -> router2 ----> [ central xbar router ]
L2 controller0 -----------------------------> router3 ---/
Dir controller0 ----------------------------> router4 --/
DMA / I/O controller -----------------------> router5 -/

这里的 shared point 是中心 xbar router。与 Classic 的 shared SystemXBar 类似,它也是集中式结构,但运行语义是 Ruby network 上的 message transfer,而不是普通 port 直接连线。

Pt2Pt

关键代码: configs/topologies/Pt2Pt.py

机制:

  1. 每个 controller 一个 router
  2. 每个 router 与其它所有 routers 全互连
  3. 因此 internal link 数量是 O(N^2)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
CPU0 -> seq0 -> controller0 -> router0
CPU1 -> seq1 -> controller1 -> router1
CPU2 -> seq2 -> controller2 -> router2
Dir0 -----------------------> router3
L2_0 -----------------------> router4

router0 <-> router1
router0 <-> router2
router0 <-> router3
router0 <-> router4
router1 <-> router2
router1 <-> router3
router1 <-> router4
router2 <-> router3
router2 <-> router4
router3 <-> router4

Pt2Pt 没有单一中心节点,所有 routers 彼此直接互联。它的优点是路径直观,缺点是 router 和 link 数量增长很快,因此在 node 数量上去后会明显变重。

这种 topology 在 node 数量增大时会迅速膨胀,因此虽然没有源码层面的硬上限,但实际可扩展性受限。

Mesh_XY

关键代码: configs/topologies/Mesh_XY.py

机制:

  1. num_routers = options.num_cpus
  2. num_rows = options.mesh_rows
  3. num_rows x num_columns 构建二维 mesh
  4. controller 通过 ExtLink 均匀分布到 routers
  5. router 之间建立 East/West/North/South IntLink
  6. link weight 用于实现 XY routing
1
2
3
4
5
R(0,0) ---- R(0,1) ---- R(0,2)
| | |
R(1,0) ---- R(1,1) ---- R(1,2)
| | |
R(2,0) ---- R(2,1) ---- R(2,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
2
3
4
5
6
7
CPU[i]
-> RubySequencer[i]
-> private L1 controller[i] (L1I + L1D)
-> Ruby network
-> shared L2 controller[j]
-> directory controller[k]
-> memory

这里有两个实现细节值得注意:

  • 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 L0IL0D
  • 每个 core 还有一个 private L1 controller
  • 每个 cluster 下面再放若干共享 L2 controller
  • 所有 cluster 之后才接 directory 和 memory
1
2
3
4
5
6
7
8
9
10
11
cluster c:
CPU[i]
-> seq
-> L0 controller (private L0I/L0D)
-> L1 controller (per-core)
-> Ruby network
-> cluster-shared L2 controller

all clusters
-> directory
-> memory

有两个约束:

  • num_cpus % num_clusters == 0
  • num_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 里同时绑了 L1IL1DL2cache:

  • 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
  • 系统再创建共享 L3 controller 和 directory controller
1
2
3
4
5
6
2 CPUs
-> one CorePair controller
-> private/shared-on-pair L1/L2
-> shared L3 slices
-> directory
-> memory

有一个直接约束: 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 slice
  • SNF: memory side node
  • RNI: DMA / I/O requester
  • MN: misc node

CHI 要求:

  • num_dirs >= 1
  • num_l3caches >= 1

number_of_virtual_networks 提升到 4,显式区分 request、snoop、response、data 四类通道

CHI 的 node type 分层:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
CPU0 -> RNF0 --\
CPU1 -> RNF1 ---\
CPU2 -> RNF2 ----\
\
+----------- NoC fabric -----------+
DMA ----> RNI_DMA --/ \
I/O ----> RNI_IO --/ \
\
+--------+ +--------+ +--------+
| HNF0 | | HNF1 | | HNF2 |
| LLC | | LLC | | LLC |
+--------+ +--------+ +--------+
| | |
+------------+------------+
|
v
+----------------+
| SNF / MainMem |
+----------------+
|
v
memory

Additional CHI nodes:
MN: misc node, used for system-wide CHI functions
Boot/ROM memory: optional BootMem SNF nodes
  • RNF 代表 CPU side request node
  • HNF 代表 home node / LLC slice
  • SNF 代表 memory side node
  • RNI 代表 I/O 或 DMA 入口
    整体通过一张 NoC fabric 互联

关键代码: configs/ruby/CHI.py

关键逻辑:

  1. 先检查 num_dirsnum_l3caches 等约束
  2. options.num_cpus 生成 RNF
  3. 生成 HNFSNF、DMA RNI、I/O RNI
  4. 设置 downstream destination
  5. 根据 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_routers
  • MESI_Three_Level / MESI_Three_Level_HTM:
    要求 options.num_cpus % options.num_clusters == 0
  • MOESI_AMD_Base:
    要求 options.num_cpus 为偶数
  • CHI:
    要求 num_dirs >= 1num_l3caches >= 1