gem5标准库的设计初衷是让用户能够以最小的努力进行大型、复杂系统的仿真,为此对系统的性质要做出合理的假设,同时要求组件以“合理”的方法连接。这些做法降低了设计的灵活性,但是能极大的简化gem5仿真中典型硬件的设置,其总的理念是让常见情况变得更加简单。
本教程将完成一个基于X86架构仿真的构建,它能够进行全系统仿真、启动Ubuntu操作系统以及运行基准测试。模拟的系统会利用gem5内核切换的能力,允许以KVM快进模式(fast-forward)启动操作系统并切换到精确的(detailed)处理器(cpu)模型以运行基准测试,并在双核设置中使用MESI一致性协议的两级Ruby高速缓存的层次结构。如果不使用gem5库,这将需要数百行Python代码,还将迫使用户必须为每个IO组件以及缓存层次结构的确切设置方式等内容指定详细信息,如果用户使用gem5标准库会发现类似任务的实现就变得非常简单。
由于仿真基于X86架构,因此必须先构建gem5的X86二进制文件:
scons build/X86/gem5.opt -j <number of threads>
首先新创建一个名为“x86-ubuntu-run.py”的Python文件,在文件开头添加以下导入语句:
from gem5.utils.requires import requires
from gem5.components.boards.x86_board import X86Board
from gem5.components.memory.single_channel import SingleChannelDDR3_1600
from gem5.components.cachehierarchies.ruby.mesi_two_level_cache_hierarchy import MESITwoLevelCacheHierarchy
from gem5.components.processors.simple_switchable_processor import SimpleSwitchableProcessor
from gem5.coherence_protocol import CoherenceProtocol
from gem5.isas import ISA
from gem5.components.processors.cpu_types import CPUTypes
from gem5.resources.resource import Resource
from gem5.simulate.simulator import Simulator
from gem5.simulate.exit_event import ExitEvent
上述添加的语句与其他Python脚本作用是一样的,它们都只是脚本中所需要的类或者函数,这些类或函数都已经被包含在gem5编译后的二进制文件中,无需从别的地方获得。需要使用requires函数来指定运行脚本所需的gem5二进制文件或设置的类型并运行,脚本如下:
requires(
isa_required=ISA.X86,
coherence_protocol_required=CoherenceProtocol.MESI_TWO_LEVEL,
kvm_required=True,
)
上述代码声明了需要编译gem5来运行X86指令集并支持MESI两级协议,这要求主机系统具有基于内核的虚拟机(Kernel-based Vitual Machine)。注意:务必确保用户的主机系统支持KVM,如果没有KVM支持则需要删除这里的kvm_required检查,还需要注意的是只有主机平台和仿真的ISA相同时(例如,X86主机和X86模拟),KVM才有效。
requires的调用不是必需的,但调用它能为运行脚本的用户提供了良好的安全线。否则,由于gem5二进制文件不兼容而导致的错误就可能没有多大意义。
接下来指定系统中的组件,首先从cache层次结构开始设置:
cache_hierarchy = MESITwoLevelCacheHierarchy(
l1d_size="32KiB",
l1d_assoc=8,
l1i_size="32KiB",
l1i_assoc=8,
l2_size="256KiB",
l2_assoc=16,
num_l2_banks=1,
)
本例此处设置了一个MESI协议的两级(ruby)缓存层次结构,通过构造函数,L1数据缓存和L1指令缓存大小都被设置为32KiB,而L2缓存大小则被设置为256KiB。
接着设置内存系统:
memory = SingleChannelDDR3_1600(size="2GiB")
从名字不难看出内存系统设置成大小为2GiB的单通道DDR3 1600,特别需要注意如果不设置SingleChannelDDR3_1600的size,则该组件的默认大小是8GiB。但是,由于X86Board已知的限制,不能使用大于3GiB的内存系统,所以这个大小不能使用默认值而必须明确设置。
接着再就是设置处理器:
processor = SimpleSwitchableProcessor(
starting_core_type=CPUTypes.KVM,
switch_core_type=CPUTypes.TIMING,
num_cores=2,
)
这里使用了gem5标准库中特殊的SimpleSwitchableProcessor处理器,如果用户希望在仿真过程中将一种类型的内核换成另一种类型的内核进行继续仿真,则可以使用这个处理器。
参数starting_core_type指定要使用哪种类型的CPU类型来启动仿真,本例使用的是KVM内核。(特别注意:如果用户主机系统不支持KVM,则仿真就不会运行,用户必须将这个参数设定更改为其它CPU类型,比如CPUTypes.ATOMIC)
参数switch_core_type指定在仿真中切换至何种CPU类型,本例配置是从KVM内核切换到TIMING内核。
最后的num_cores参数用于指定处理器中的内核数。
用户可以通过SimpleSwitchableProcessor处理器调用processor.switch()这个函数在起始内核和目标切换内核之间来回切换,本教程后面将进行相应演示。
下一步,将上述组件都添加到板(board)中:
board = X86Board(
clk_freq="3GHz",
processor=processor,
memory=memory,
cache_hierarchy=cache_hierarchy,
)
这里使用的X86Board是能在全系统模式下模拟典型X86系统的板,该板在使用时至少需要指定clk_freq, processor, memory, 和cache_hierarchy四个参数,指定后即完成了系统的设计。
现在,还需要设置在该系统上运行的工作负载:
command = "m5 exit;" \
+ "echo 'This is running on Timing CPU cores.';" \
+ "sleep 1;" \
+ "m5 exit;"
board.set_kernel_disk_workload(
kernel=Resource("x86-linux-kernel-5.4.49",),
disk_image=Resource("x86-ubuntu-18.04-img"),
readfile_contents=command,
)
X86Board的set_kernel_disk_workload函数需要设置一个内核和磁盘映像文件。这两者都可以从gem5资源仓获取。因此,这里通过Resource类指定了linux-kernel-5.4.49的内核(Linux内核,5.4.49版本,编译至X86)和x86-ubuntu-18.04-img的磁盘映像(一个包含有Ubuntu18.04,用于X86的磁盘映像)。Resource类会在本机系统没有这些资源的情况下自动检索这些资源。特别注意:如果用户希望使用自己的资源(即不属于gem5-resources中预构建的资源)则可以参考此处教程。
x86-ubuntu-18.04-img被设计为启动操作系统,自动登录并运行m5 readfile。 m5 readfile将读取一个文件并执行它。 此文件的内容通过readfile_contents参数指定。 因此,readfile_contents的值会在系统启动时执行。特别注意:readfile_contents是一个可选参数,如果set_kernel_disk_workload中未指定该参数,则在boot启动后将退出仿真。 这一行为是x86-ubuntu-18.04-img磁盘映像所特有的,并不适用于所有磁盘映像。
本教程的脚本首先运行m5 exit暂时退出仿真,从而允许从KVM CPU切换到TIMING CPU,然后等模拟恢复时在TIMING CPU上执行echo和sleep语句并再次调用 m5 exit退出并完成仿真。 用户可以通过检查m5out/system.pc.com_1.device来查看回显的输出。
最后,本例通过以下命令指定仿真的运行方式:
simulator = Simulator(
board=board,
on_exit_event={
ExitEvent.EXIT : (func() for func in [processor.switch]),
},
)
simulator.run()
需要特别注意的是这里的on_exit_event参数,用户可以覆盖(overriding)其默认行为。 m5 exit命令触发Simulator模块中的退出事件,默认情况下,仿真会完全退出运行。 本例中,m5 exit的第一次调用将处理器从KVM切换到TIMING内核。
on_exit_event参数是退出事件和Python生成器(generators)组成的 Python字典。本例是设置ExitEvent.Exit到生成器 (func() for func in [processor.switch])。这意味着processor.switch函数在生成器的第一个yield上调用(即m5 exit的第一个实例)。 此后生成器耗尽,Simulator模块将返回到默认的Exit退出事件行为。
到这里脚本的设置也就完成了,使用以下命令执行脚本:
./build/X86/gem5.opt x86-ubuntu-run.py
用户就可以在m5out/system.pc.com_1.device文件中查看仿真器的输出。本例完整的配置脚本如下,它与gem5存储库中的configs/example/gem5_library/x86-ubuntu-run.py示例脚本非常相似。
from gem5.utils.requires import requires
from gem5.components.boards.x86_board import X86Board
from gem5.components.memory.single_channel import SingleChannelDDR3_1600
from gem5.components.cachehierarchies.ruby.mesi_two_level_cache_hierarchy import (MESITwoLevelCacheHierarchy,)
from gem5.components.processors.simple_switchable_processor import SimpleSwitchableProcessor
from gem5.coherence_protocol import CoherenceProtocol
from gem5.isas import ISA
from gem5.components.processors.cpu_types import CPUTypes
from gem5.resources.resource import Resource
from gem5.simulate.simulator import Simulator
from gem5.simulate.exit_event import ExitEvent
# This runs a check to ensure the gem5 binary is compiled to X86 and supports
# the MESI Two Level coherence protocol.
requires(
isa_required=ISA.X86,
coherence_protocol_required=CoherenceProtocol.MESI_TWO_LEVEL,
kvm_required=True,
)
# Here we setup a MESI Two Level Cache Hierarchy.
cache_hierarchy = MESITwoLevelCacheHierarchy(
l1d_size="32KiB",
l1d_assoc=8,
l1i_size="32KiB",
l1i_assoc=8,
l2_size="256kB",
l2_assoc=16,
num_l2_banks=1,
)
# Setup the system memory.
# Note, by default DDR3_1600 defaults to a size of 8GiB. However, a current
# limitation with the X86 board is it can only accept memory systems up to 3GB.
# As such, we must fix the size.
memory = SingleChannelDDR3_1600("2GiB")
# Here we setup the processor. This is a special switchable processor in which
# a starting core type and a switch core type must be specified. Once a
# configuration is instantiated a user may call `processor.switch()` to switch
# from the starting core types to the switch core types. In this simulation
# we start with KVM cores to simulate the OS boot, then switch to the Timing
# cores for the command we wish to run after boot.
processor = SimpleSwitchableProcessor(
starting_core_type=CPUTypes.KVM,
switch_core_type=CPUTypes.TIMING,
num_cores=2,
)
# Here we setup the board. The X86Board allows for Full-System X86 simulations.
board = X86Board(
clk_freq="3GHz",
processor=processor,
memory=memory,
cache_hierarchy=cache_hierarchy,
)
# This is the command to run after the system has booted. The first `m5 exit`
# will stop the simulation so we can switch the CPU cores from KVM to timing
# and continue the simulation to run the echo command, sleep for a second,
# then, again, call `m5 exit` to terminate the simulation. After simulation
# has ended you may inspect `m5out/system.pc.com_1.device` to see the echo
# output.
command = "m5 exit;" \
+ "echo 'This is running on Timing CPU cores.';" \
+ "sleep 1;" \
+ "m5 exit;"
# Here we set the Full System workload.
# The `set_workload` function for the X86Board takes a kernel, a disk image,
# and, optionally, a the contents of the "readfile". In the case of the
# "x86-ubuntu-18.04-img", a file to be executed as a script after booting the
# system.
board.set_kernel_disk_workload(
kernel=Resource("x86-linux-kernel-5.4.49",),
disk_image=Resource("x86-ubuntu-18.04-img"),
readfile_contents=command,
)
simulator = Simulator(
board=board,
on_exit_event={
# Here we want override the default behavior for the first m5 exit
# exit event. Instead of exiting the simulator, we just want to
# switch the processor. The 2nd 'm5 exit' after will revert to using
# default behavior where the simulator run will exit.
ExitEvent.EXIT : (func() for func in [processor.switch]),
},
)
simulator.run()
最后,回顾一下本教程中的内容:
- 使用requires函数可指定脚本对gem5和主机的要求。
- 使用SimpleSwitchableProcessor能创建一种内核可切换的设置。
- X86Board可用于设置全系统仿真,它的set_kernel_disk_workload用于指定要使用的内核和磁盘映像。
- set_kernel_disk_work接受readfile_contents参数,用它设置要通过gem5的m5 readfile函数读取的文件内容,当使用x86-ubuntu-18.04-img磁盘映像时,这是在系统完全完成boot以后像脚本一样处理的。
- Simulator模块允许使用Python生成器覆盖(overriding)退出事件(exit events)。