以前面的配置脚本为起点,本章将介绍更复杂的配置。
我们将向系统添加一个缓存层次结构,如下图所示。
此外,本章还将介绍如何理解gem5统计输出以及如何将命令行参数添加到脚本中。
创建缓存对象
我们将使用经典的缓存,而不是ruby-intro-chapter,因为我们正在建模一个单CPU系统,我们不关心建模缓存一致性。
我们将扩展Cache-SimObject并为我们的系统配置它。首先,我们必须了解用于配置Cache对象的参数。
缓存一致性是多处理器系统中确保各个处理器或核心之间缓存数据保持一致性的关键技术。在一个多核或分布式系统中,每个处理器可能都有自己的缓存,当多个处理器同时访问和修改共享数据时,可能会导致数据不一致的问题。
经典缓存和Ruby
gem5目前有两个完全不同的子系统来模拟系统中的片上缓存,即“经典缓存”和“Ruby”。历史原因是gem5是密歇根州的m5和威斯康星州的GEMS的组合。GEMS使用Ruby作为其缓存模型,而经典缓存来自m5代码库(因此称为“经典”)。
这两种模型之间的区别在于,Ruby旨在详细模拟缓存一致性。
Ruby的一部分是SLICC,一种用于定义缓存一致性协议的语言。
另一方面,经典缓存实现了简化且不灵活的MOESI一致性协议。
要选择使用哪种模型,你应该问自己你想建模什么。
如果您正在对缓存一致性协议的更改进行建模,或者一致性协议可能对您的结果产生一阶影响,请使用Ruby。
否则,如果一致性协议对您不重要,请使用经典缓存。
gem5的长期目标是将这两个缓存模型统一为一个整体模型。
缓存
Cache-SimObject声明可以在src/mem/Cache/Cache.py中找到。
此Python文件定义了可以设置SimObject的参数。
在底层,当实例化SimObject时,这些参数会传递给对象的C++实现。Cache-SimObject继承自BaseCache对象,如下所示。
在BaseCache类中,有许多参数。例如,assoc是一个整数参数。
一些参数,如write_buffers,有一个默认值,在本例中为8。
默认参数是Param.*的第一个参数,除非第一个参数是字符串。
每个参数的字符串参数都是对参数的描述(例如,tag_latency=Param.Cycles(“标记查找延迟”)意味着tag_latency控制“此缓存的命中延迟”)。
其中许多参数没有默认值,因此我们需要在调用m5.instantate()之前设置这些参数。
现在,要创建具有特定参数的缓存,我们首先要在simple.py、configs/tutorial/part1的同一目录中创建一个新文件caches.py。
代码如下所示:
""" Caches with options for a simple gem5 configuration scriptThis file contains L1 I/D and L2 caches to be used in the simple
gem5 configuration script. It uses the SimpleOpts wrapper to set up command
line options from each individual class.
"""import m5
# 第一步是导入我们将在此文件中扩展的SimObject。
from m5.objects import Cache# Add the common scripts to our path
m5.util.addToPath("../../")from common import SimpleOpts# Some specific options for caches
# For all options see src/mem/cache/BaseCache.py# 接下来,我们可以像对待任何其他Python类一样对待BaseCache对象并对其进行扩展。我们可以随心所欲地命名新的缓存。让我们从创建L1缓存开始。在这里,我们正在设置BaseCache的一些没有默认值的参数。要查看所有可能的配置选项,并找出哪些是必需的,哪些是可选的,您必须查看SimObject的源代码。在这种情况下,我们使用的是BaseCache。
# # 我们扩展了BaseCache,并在BaseCache-SimObject中设置了大多数没有默认值的参数。
class L1Cache(Cache):"""Simple L1 Cache with default values"""assoc = 2tag_latency = 2data_latency = 2response_latency = 2mshrs = 4tgts_per_mshr = 20def __init__(self, options=None):super().__init__()passdef connectBus(self, bus):"""Connect this cache to a memory-side bus"""self.mem_side = bus.cpu_side_portsdef connectCPU(self, cpu):"""Connect this cache's port to a CPU-side portThis must be defined in a subclass"""raise NotImplementedError# 接下来,让我们来看L1Cache的另外两个子类,L1DCache和L1ICache
# 接下来,我们必须为指令和数据缓存定义一个单独的connectCPU函数,因为I-cache和D-cache端口的名称不同。我们的L1ICache和L1DCache类现在变为:
class L1ICache(L1Cache):"""Simple L1 instruction cache with default values"""# Set the default sizesize = "16KiB"SimpleOpts.add_option("--l1i_size", help=f"L1 instruction cache size. Default: {size}")def __init__(self, opts=None):super().__init__(opts)if not opts or not opts.l1i_size:returnself.size = opts.l1i_sizedef connectCPU(self, cpu):"""Connect this cache's port to a CPU icache port"""self.cpu_side = cpu.icache_portclass L1DCache(L1Cache)