接下来,我们需要连接其他几个端口,以确保我们的系统能够正常运行。我们需要在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_port
system.cpu.interrupts[0].int_responder = system.membus.mem_side_ports
system.system_port = system.membus.cpu_side_port
接下来,我们需要创建一个内存控制器并将其连接到membus上。对于这个系统,我们将使用一个简单的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模拟整个硬件系统,并运行未经修改的内核。全系统模式类似于运行一个虚拟机。
另一方面,Syscall仿真模式并不模拟系统中的所有设备,而是着重于模拟CPU和内存系统。系统调用仿真更容易配置,因为你不需要实例化真实系统中需要的所有硬件设备。然而,系统调用模拟只模拟Linux系统调用,因此只模拟用户模式代码。
如果你不需要为你的研究问题对操作系统进行建模,而且你想要额外的性能,你应该使用SE模式。但是,如果你需要对系统进行高保真建模,或者操作系统的交互,如页表行走很重要,那么你应该使用FS模式。
首先,我们必须创建进程(另一个SimObject)。然后我们将进程的命令设置为我们想要运行的命令。这是一个类似于argv的列表,可执行文件在第一个位置,可执行文件的参数在列表的其余部分。然后我们将CPU设置为使用进程作为它的工作负载,最后在CPU中创建功能执行上下文。
binary = 'test/test-progs/hello/bin/x86/linux/hello'
#用于gem5 V21及以上版本
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("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
输出应该是:
gem5 Simulator System. http://gem5.org
gem5 is copyrighted software; use the --copyright option for details.
gem5 version 21.0.0.0
gem5 compiled May 17 2021 18:05:59
gem5 started May 17 2021 22:05:20
gem5 executing on amarillo, pid 75197
command line: build/X86/gem5.opt configs/tutorial/part1/simple.py
Global frequency set at 1000000000000 ticks per second
warn: No dot file generated. Please install pydot to generate the dot file and pdf.
warn: DRAM device capacity (8192 Mbytes) does not match the address range assigned (512 Mbytes)
0: system.remote_gdb: listening for remote gdb on port 7005
Beginning simulation!
info: Entering event queue @ 0. Starting simulation...
Hello world!
Exiting @ tick 490394000 because exiting with last active thread context
配置文件中的参数可以被改变,结果应该是不同的。例如,如果你把系统时钟增加一倍,模拟就应该更快地完成。或者,如果你把DDR控制器改为DDR4,性能应该会更好。
此外,你可以将CPU模型改为X86MinorCPU来模拟一个顺序CPU,或者X86O3CPU来模拟一个乱序CPU。然而,请注意,X86O3CPU 目前不能与 simple.py 一起使用,因为 X86O3CPU 需要一个具有独立指令和数据缓存的系统(X86 O3CPU 可以与下一节的配置一起使用)。
所有 gem5 BaseCPU 的命名格式都是 {ISA}{Type}CPU。因此,如果我们想要一个RISCV Minor CPU,就应该使用RiscvMinorCPU。
有效的ISA是:
-
l Riscv
-
l Arm
-
l X86
-
l Sparc
-
l Power
-
l Mips
CPU的类型有::
-
l AtomicSimpleCPU
-
l O3CPU
-
l TimingSimpleCPu
-
l KvmCPU
-
l MinorCPU
在后面的教程中,我们将把缓存添加到我们的配置文件中,以模拟一个更复杂的系统。
作者:速易芯李昊翔