本章教程将引导您设置一个简单的 gem5 仿真脚本,并首次运行 gem5。假设您已经完成了gem5模拟器入门(一)——环境配置-CSDN博客,并成功构建了带有可执行文件 build/X86/gem5.opt 的 gem5。
配置脚本将模拟一个非常简单的系统,只有一个简单的 CPU 核心,这个 CPU 核心将连接到系统范围的内存总线。我们还将有一个连接到内存总线的单一DDR3内存通道。
gem5 配置脚本
gem5 二进制文件接受一个 Python 脚本作为参数,该脚本设置并执行仿真。在这个脚本中,您将创建一个要模拟的系统,创建系统的所有组件,并指定系统组件的所有参数。然后,您可以从脚本中开始仿真。
这个脚本完全由用户定义。您可以在配置脚本中选择使用任何有效的 Python 代码。本书提供了一种风格示例,该示例在很大程度上依赖于 Python 中的类和继承。作为 gem5 用户,您可以决定将配置脚本做得多么简单或复杂。
在 gem5 中的 configs/examples 目录中,有许多示例配置脚本。这些脚本大多数都是包罗万象的,允许用户在命令行上指定几乎所有选项。与其从这些复杂的脚本开始,本书将从一个可以运行 gem5 的最简单脚本开始,然后逐步构建。希望到本节结束时,您会对仿真脚本的工作原理有一个很好的了解。
SimObjects
gem5 的模块化设计是围绕 SimObject 类型构建的。模拟系统中的大多数组件都是 SimObjects:CPU、缓存、内存控制器、总线等。gem5 将所有这些对象从其 C++ 实现导出到 Python。因此,从 Python 配置脚本中,您可以创建任何 SimObject,设置其参数,并指定 SimObjects 之间的交互。
1.创建配置文件
mkdir configs/tutorial
mkdir configs/tutorial/part1/
touch configs/tutorial/part1/simple.py
这只是一个普通的 Python 文件,将由 gem5 可执行文件中的嵌入式 Python 执行。因此,您可以使用 Python 中的任何功能和库。
在这个文件中我们要做的第一件事是导入 m5 库和我们编译的所有 SimObjects。
import m5
from m5.objects import *
接下来,我们将创建第一个 SimObject:我们要模拟的系统。System 对象将是我们模拟系统中所有其他对象的父对象。System 对象包含了许多功能性(非时序级)信息,比如物理内存范围、根时钟域、根电压域、内核(在全系统仿真中)等。要创建系统 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-cache和D-cache端口连接到内存总线上。在这个示例系统中,我们没有缓存。
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上生成一个新的响应端口,并且这个新创建的端口将连接到请求端口。
接下来,我们需要连接一些其他的端口,以确保我们的系统能够正常运行。我们需要在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构建且已静态编译的应用程序。
Full system vs syscall emulation
gem5可以在两种不同的模式下运行,称为“系统调用模拟”和“全系统”或SE和FS模式。在全系统模式下(稍后详细介绍全系统部分),gem5模拟整个硬件系统并运行未经修改的内核。全系统模式类似于运行虚拟机。
另一方面,系统调用模拟模式并没有模拟系统中的所有设备,而是专注于模拟CPU和内存系统。系统调用模拟配置要简单得多,因为您不需要实例化实际系统中需要的所有硬件设备。然而,系统调用模拟只模拟Linux系统调用,因此只模拟用户模式代码。
如果您的研究问题不需要模拟操作系统,并且需要额外的性能,那么您应该使用SE模式。然而,如果您需要对系统进行高保真建模,或者像页表遍历这样的操作系统交互很重要,那么您应该使用FS模式。
首先,我们必须创建进程(另一个SimObject)。然后,我们将进程的命令设置为我们要运行的命令。这是一个类似于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()
我们需要做的最后一件事是实例化系统并开始执行。首先,我们创建Root对象。然后,我们实例化模拟。实例化过程会遍历我们在Python中创建的所有SimObjects,并创建它们的C++等效对象。
需要注意的是,您不必实例化Python类,然后将参数显式地指定为成员变量。您还可以将参数作为命名参数传递,就像下面的Root对象一样。
root = Root(full_system = False, system = system)
m5.instantiate()
最后,我们可以启动实际的模拟了!顺便说一下,gem5现在使用Python 3风格的打印函数,因此print不再是一个语句,而必须作为一个函数调用。
print("Beginning simulation!")
exit_event = m5.simulate()
一旦模拟结束,我们就可以检查系统的状态。
print('Exiting @ tick {} because {}'.format(m5.curTick(), exit_event.getCause()))
2.运行gem5
现在我们已经创建了一个简单的模拟脚本(完整版本可以在gem5代码库中的configs/learning_gem5/part1/simple.py 找到),我们准备好运行gem5了。gem5可以接受许多参数,但只需要一个位置参数,即模拟脚本。因此,我们可以从gem5的根目录直接运行gem5:
build/X86/gem5.opt configs/tutorial/part1/simple.py
输出如下:
gem5 Simulator System. https://www.gem5.org
gem5 is copyrighted software; use the --copyright option for details.gem5 version 23.1.0.0
gem5 compiled May 28 2024 11:47:30
gem5 started May 28 2024 13:06:09
gem5 executing on Aquarius, pid 168573
command line: build/X86/gem5.opt configs/learning_gem5/part1/simple.pyGlobal frequency set at 1000000000000 ticks per second
warn: No dot file generated. Please install pydot to generate the dot file and pdf.
src/mem/dram_interface.cc:692: warn: DRAM device capacity (8192 Mbytes) does not match the address range assigned (512 Mbytes)
src/base/statistics.hh:279: warn: One of the stats is a legacy stat. Legacy stat is a stat that does not belong to any statistics::Group. Legacy stat is deprecated.
system.remote_gdb: Listening for connections on port 7000
Beginning simulation!
src/sim/simulate.cc:199: info: Entering event queue @ 0. Starting simulation...
Hello world!
Exiting @ tick 503912000 because exiting with last active thread context
在配置文件中更改参数应该会产生不同的结果。例如,如果你将系统时钟加倍,模拟应该会更快完成。或者,如果你将DDR控制器改为DDR4,性能应该会更好。
此外,您还可以将CPU模型更改为X86MinorCPU以模拟顺序CPU,或者将其更改为X86O3CPU以模拟乱序CPU。然而,请注意,X86O3CPU当前无法与simple.py一起使用,因为X86O3CPU需要具有单独的指令和数据缓存的系统(X86O3CPU可以与下一节中的配置一起使用)。
所有gem5的BaseCPU都采用格式{ISA}{Type}CPU的命名格式。因此,如果我们想要一个RISCV Minor CPU,我们将使用RiscvMinorCPU。
有效的ISA有:
- Riscv
- Arm
- X86
- Sparc
- Power
- Mips
CPU类型有:
- AtomicSimpleCPU
- O3CPU
- TimingSimpleCPu
- KvmCPU
- MinorCPU
接下来,我们将在配置文件中添加缓存,以模拟一个更复杂的系统。