文章目录
- 创建简单的配置脚本
- gem5 配置脚本
- 关于模拟对象的插话
- 创建配置文件
- 全系统与系统调用模拟
- 运行Gem5
创建简单的配置脚本
本章教程将指导你如何为 gem5 设置一个简单的模拟脚本,并首次运行 gem5。我们假定你已完成本教程第一章的学习,并已成功创建 gem5 的可执行文件 build/X86/gem5.opt。
我们的配置脚本将模拟一个非常简单的系统。我们只有一个简单的 CPU 内核。这个 CPU 内核将连接到整个系统的内存总线上。我们将有一个 DDR3 内存通道,也连接到内存总线上。
gem5 配置脚本
gem5 二进制文件的参数是一个用于设置和执行模拟的 python 脚本。在这个脚本中,你需要创建一个要模拟的系统,创建系统的所有组件,并指定系统组件的所有参数。然后,您就可以从脚本中开始模拟。
该脚本完全由用户定义。您可以选择在配置脚本中使用任何有效的 Python 代码。本书提供了一个在很大程度上依赖 Python 中类和继承的风格示例。作为 gem5 用户,您可以自行决定让您的配置脚本变得多么简单或复杂。
在 configs/examples 中,gem5 提供了大量示例配置脚本。这些脚本大多包罗万象,允许用户在命令行中指定几乎所有选项。在本书中,我们将不从这些复杂的脚本开始,而是从能够运行 gem5 的最简单脚本开始,并以此为基础进行构建。希望在本节结束时,你能对模拟脚本的工作原理有所了解。
关于模拟对象的插话
gem5 的模块化设计围绕 SimObject 类型展开。模拟系统中的大部分组件都是 SimObjects: gem5将所有这些对象从其C++实现导出到python。因此,通过 python 配置脚本,你可以创建任何 SimObject、设置其参数并指定 SimObject 之间的交互。
更多信息,请参阅 SimObject 详情。
创建配置文件
让我们先创建一个新的配置文件并打开它:
mkdir configs/tutorial/part1/
touch configs/tutorial/part1/simple.py
这只是一个普通的 python 文件,将由 gem5 可执行文件中的嵌入式 python 执行。因此,你可以使用 python 中的任何功能和库。
在该文件中,我们要做的第一件事就是导入 m5 库和所有已编译的模拟对象。
import m5
from m5.objects import *
接下来,我们将创建第一个 SimObject:我们要模拟的系统。系统对象将是模拟系统中所有其他对象的父对象。系统对象包含大量功能(非时序级)信息,如物理内存范围、根时钟域、根电压域、内核(在全系统仿真中)等。要创建系统 SimObject,我们只需像普通 Python 类一样将其实例化:
system = System()
现在我们有了要模拟的系统的参考,让我们来设置系统的时钟。我们首先要创建一个时钟域。然后,我们可以在该域上设置时钟频率。在 SimObject 上设置参数与在 python 中设置对象成员完全相同,因此我们可以简单地将时钟频率设置为 1 GHz。最后,我们必须为该时钟域指定一个电压域。由于我们现在并不关心系统功耗,因此只需使用电压域的默认选项即可。
system.clk_domain = SrcClockDomain()
system.clk_domain.clock = ‘1GHz’
system.clk_domain.voltage_domain = VoltageDomain()
有了系统后,让我们来设置如何模拟内存。我们将使用定时模式进行内存模拟。除了快进和从检查点恢复等特殊情况外,内存模拟几乎总是使用定时模式。我们还将设置一个 512 MB 大小的单一内存范围,这是一个非常小的系统。请注意,在 python 配置脚本中,只要需要指定内存大小,就可以用 "512MB "这样的普通话和单位来指定。同样,对于时间,您也可以使用时间单位(如 “5ns”)。这些单位将分别自动转换为通用表示法。
system.mem_mode = ‘timing’
system.mem_ranges = [AddrRange(‘512MB’)]
现在,我们可以创建一个 CPU。我们将从 gem5 中最简单的基于 X86 ISA 的定时 CPU X86TimingSimpleCPU 开始。该 CPU 模型在一个时钟周期内执行每条指令,但内存请求除外,内存请求流经内存系统。要创建 CPU,只需实例化该对象即可:
system.cpu = X86TimingSimpleCPU()
如果我们想使用 RISCV ISA,可以使用 RiscvTimingSimpleCPU;如果我们想使用 ARM ISA,可以使用 ArmTimingSimpleCPU。不过,在本练习中我们将继续使用 X86 ISA。
接下来,我们将创建全系统内存总线:
system.membus = SystemXBar()
既然有了内存总线,我们就将 CPU 上的高速缓存端口连接到内存总线上。在本例中,由于我们要模拟的系统没有任何缓存,我们将把 I 缓存和 D 缓存端口直接连接到内存总线上。在本示例系统中,我们没有缓存。
system.cpu.icache_port = system.membus.cpu_side_ports
system.cpu.dcache_port = system.membus.cpu_side_ports
- 关于 gem5 端口的插话
为了将内存系统组件连接在一起,gem5 使用了端口抽象。每个内存对象可以有两种端口,即请求端口和响应端口。请求从请求端口发送到响应端口,响应从响应端口发送到请求端口。连接端口时,必须将请求端口连接到响应端口。
通过 python 配置文件很容易将端口连接在一起。只需设置请求端口 = 响应端口,它们就会连接在一起。例如
system.cpu.icache_port = system.l1_cache.cpu_side
在本例中,cpu 的 icache_port 是请求端口,缓存的 cpu_side 是响应端口。请求端口和响应端口可以位于 = 的任何一侧,并将建立相同的连接。建立连接后,请求者就可以向响应者发送请求。建立连接的幕后工作非常复杂,对大多数用户来说,其中的细节并不重要。
在 gem5 Python 配置中,另一个值得注意的神奇之处是,可以在一侧使用一个端口,而在另一侧使用端口数组。例如
system.cpu.icache_port = system.membus.cpu_side_ports
在本例中,cpu 的 icache_port 是一个请求端口,而 membus 的 cpu_side_ports 是一个响应端口数组。在这种情况下,cpu_side_ports 上会产生一个新的响应端口,这个新创建的端口将连接到请求端口。
我们将在 MemObject 章节详细讨论端口和 MemObject。
接下来,我们需要连接其他几个端口,以确保系统正常运行。我们需要在 CPU 上创建一个 I/O 控制器,并将其连接到内存总线。此外,我们还需要将系统中的一个特殊端口连接到内存总线上。该端口为功能专用端口,允许系统读写内存。
将 PIO 和中断端口连接到内存总线是 x86 的特定要求。其他 ISA(如 ARM)不需要这 3 条额外的线路。
system.cpu.createInterruptController()
system.cpu.interrupts[0].pio = system.membus.mem_side_ports
system.cpu.interrupts[0].int_requestor = system.membus.cpu_side_ports
system.cpu.interrupts[0].int_responder = system.membus.mem_side_portssystem.system_port = system.membus.cpu_side_ports
接下来,我们需要创建一个内存控制器,并将其连接到内存总线。在本系统中,我们将使用一个简单的 DDR3 控制器,它将负责整个系统的内存范围。
system.mem_ctrl = MemCtrl()
system.mem_ctrl.dram = DDR3_1600_8x8()
system.mem_ctrl.dram.range = system.mem_ranges[0]
system.mem_ctrl.port = system.membus.mem_side_ports
完成这些最后的连接后,我们就完成了模拟系统的实例化!我们的系统应该如下图所示。
接下来,我们需要设置希望 CPU 执行的进程。由于我们是在系统调用模拟模式(SE 模式)下执行,因此只需将 CPU 指向编译后的可执行文件即可。我们将执行一个简单的 "Hello world "程序。gem5 中已经有一个编译好的程序,我们就用它。你可以指定任何为 x86 构建并经过静态编译的应用程序。
全系统与系统调用模拟
gem5 可在两种不同的模式下运行,即 "系统调用模拟 "和 "全系统 "模式,或称 SE 和 FS 模式。在全系统模式(稍后将介绍全系统部分)下,gem5 会模拟整个硬件系统,并运行未经修改的内核。全系统模式类似于运行虚拟机。
另一方面,系统调用仿真模式并不仿真系统中的所有设备,而是专注于模拟 CPU 和内存系统。由于不需要实例化真实系统中所需的所有硬件设备,因此系统调用仿真更易于配置。不过,系统调用仿真只能仿真 Linux 系统调用,因此只能模拟用户模式代码。
如果研究问题时不需要对操作系统进行建模,又希望获得更高的性能,则应使用 SE 模式。但是,如果你需要高保真的系统建模,或者操作系统交互(如页表走行)很重要,那么你就应该使用 FS 模式。
首先,我们必须创建进程(另一个模拟对象)。然后将进程命令设置为要运行的命令。这是一个类似 argv 的列表,第一个位置是可执行文件,其余位置是可执行文件的参数。然后我们将 CPU 设置为使用进程作为其工作负载,最后在 CPU 中创建功能执行上下文。
binary = ‘tests/test-progs/hello/bin/x86/linux/hello’
#for gem5 V21 and beyond(#后面应该要有一个空格)
system.workload = SEWorkload.init_compatible(binary)
process = Process()
process.cmd = [binary]
system.cpu.workload = process
system.cpu.createThreads()
我们需要做的最后一件事就是实例化系统并开始执行。首先,我们创建根对象。然后实例化模拟。实例化过程将遍历我们在 python 中创建的所有 SimObjects,并创建对应的 C++ 对象。
需要注意的是,您不必先实例化 python 类,然后再将参数明确指定为成员变量。您也可以将参数作为命名参数传递,例如下面的根对象。
root = Root(full_system = False, system = system)
m5.instantiate()
最后,我们就可以开始实际模拟了!另外,gem5 现在使用 Python 3 风格的 print 函数,因此 print 不再是语句,而必须作为函数调用。
print(“Beginning simulation!”)
exit_event = m5.simulate()
模拟完成后,我们就可以检查系统的状态。
print(‘Exiting @ tick {} because {}’
.format(m5.curTick(), exit_event.getCause()))
运行Gem5
现在,我们已经创建了一个简单的模拟脚本(其完整版本可在gem5代码库中的configs/learning_gem5/part1/simple.py找到),我们已经准备好运行gem5了。gem5可以接受许多参数,但只需要一个位置参数,即模拟脚本。因此,我们只需在 gem5 根目录下运行 gem5 即可:
build/X86/gem5.opt configs/tutorial/part1/simple.py
输出结果应为
可以更改配置文件中的参数,但结果应有所不同。例如,如果将系统时钟提高一倍,模拟完成的速度应该会更快。或者,如果将 DDR 控制器更改为 DDR4,性能应该会更好。
此外,您还可以将 CPU 模型更改为 X86MinorCPU 以模拟顺序内 CPU,或 X86O3CPU 以模拟顺序外 CPU。但请注意,X86O3CPU 目前不能与 simple.py 一起使用,因为 X86O3CPU 要求系统具有独立的指令缓存和数据缓存(X86O3CPU 可与下一节中的配置一起使用)。
所有 gem5 BaseCPU 的命名格式都是 {ISA}{Type}CPU。因此,如果我们要使用 RISCV MinorCPU,就必须使用 RiscvMinorCPU。
有效的 ISA 有
- Riscv
- Arm
- X86
- Sparc
- Power
- Mips
CPU 类型有
- 原子简单 CPU(AtomicSimpleCPU)
- O3CPU(O3CPU)
- 定时简单CPU(TimingSimpleCPU)
- KvmCPU(KvmCPU)
- 次要 CPU(MinorCPU)
接下来,我们将在配置文件中添加缓存,以模拟更复杂的系统。