鉴于官网的一些东西已经过时,我觉得有必要专门写一个新的Gem5教程,这也是本文章的初衷!
强烈建议在Ubuntu 16.04下安装编译(之前的ubuntu还需要你自己更新gcc版本),编译前通过apt管理器安装git build-essential scons python-dev swig libprotobuf-dev python-protobuf protobuf-compiler libgoogle-perftools-dev zlib1g-dev m4 mercurial这些包,关于每个包的介绍参考官网:http://www.m5sim.org/Dependencies
下载gem5源代码文件,使用以下命令克隆:
hg clone http://repo.gem5.org/gem5
对于每一个要模拟的ISA需要单独编译gem5。而且如果使用ruby,要为每个 cache coherence 协议单独编译。
使用scons命令来编译,在build_opts/X86中可以查看那些编译的选项,你可以自己修改,当然为了方便我们使用官方给的默认参数:
scons build/X86/gem5.opt -j4 #这里j4代表我的计算机有4个可用核心,调度更多核心能更快地编译
根据你的电脑性能,整个过程需要15~45分钟。
参考:gem5二进制类型区别
5 different binaries you can build for gem5: debug, opt, fast, prof, and perf.
这5种类型区别如下
debug
Built with no optimizations and debug symbols. This binary is useful when using a debugger to debug if the variables you need to view are optimized out in the opt version of gem5. Running with debug is slow compared to the other binaries.
opt
This binary is build with most optimizations on (e.g., -O3), but with debug symbols included. This binary is much faster than debug, but still contains enough debug information to be able to debug most problems.
fast
Built with all optimizations on (including link-time optimizations on supported platforms) and with no debug symbols. Additionally, any asserts are removed, but panics and fatals are still included. fast is the highest performing binary, and is much smaller than opt. However, fast is only appropriate when you feel that it is unlikely your code has major bugs.
prof and perf
These two binaries are build for profiling gem5. prof includes profiling information for the GNU profiler (gprof), and perf includes profiling information for the Google performance tools (gperftools).
本节将模拟一个非常简单的系统,我们只有一个简单的CPU内核。该CPU内核将连接到系统内的内存总线。我们将有一个DDR3内存通道,也连接到内存总线。
Gem5在运行前需要一个python脚本作为参数,在这个脚本文件里制定你自己的参数以按需仿真系统。
并且gem5中已经提供了许多示例配置脚本configs/examples。
本节从最简单的脚本文件开始,学习怎么构建一个配置脚本。
命令行里敲入如下命令,创建一个python文件,当然你也可以使用GUI的工具。
mkdir configs/tutorial
touch configs/tutorial/simple.py
使用编辑器打开该文件,首先需要导入m5库和编译的SimObjects:
import m5
from m5.objects import *
接下来创建第一个SimObject:我们要模拟的系统。该System
对象将是我们模拟系统中所有其他对象的父对象。
system = System()
然后在这个system上设置时钟。首先设置一个时钟域,然后在这个是时钟域上设置频率,最后设置电压,不过我们不关心电压大小,用默认选项即可。
system.clk_domain = SrcClockDomain()
system.clk_domain.clock = '1GHz'
system.clk_domain.voltage_domain = VoltageDomain()
现在开始模拟内存,这里使用timing mode来模拟内存,大多数情况都要用timing,除非在fast-forwarding和 restoring from a checkpoint这两种情况。然后设置一下内存的大小。
system.mem_mode = 'timing'
system.mem_ranges = [AddrRange('512MB')]
接下来创建一个最简单的基于时序的CPU,然后创建一个系统范围的内存总线,现在有内存总线后把CPU的Cache端口连接到它。
system.cpu = TimingSimpleCPU()
system.membus = SystemXBar()
system.cpu.icache_port = system.membus.slave
system.cpu.dcache_port = system.membus.slave
下一步连接一些必要的端口,在CPU上创建一个I/O控制器并将它连接到内存总线。(只有X86需要3条中断端口线路,arm等其它isa不需要)
system.cpu.createInterruptController()
system.cpu.interrupts[0].pio = system.membus.master
system.cpu.interrupts[0].int_master = system.membus.slave
system.cpu.interrupts[0].int_slave = system.membus.master
system.system_port = system.membus.slave
创建一个内存控制器连接到这个membus,对于这个系统使用简单的DDR3即可。
system.mem_ctrl = DDR3_1600_8x8()
system.mem_ctrl.range = system.mem_ranges[0]
system.mem_ctrl.port = system.membus.master
至此,我们完成了一个没有cache的简单系统,整体结构如下:
完成系统后还需要给CPU加一些进程测试一下。我们准备使用SE Mode,这个模式下将CPU指向编译的可执行文件,这里以Hello World为例:创建一个process,设置相关的命令和参数,让CPU用这个process作为它的负载(workload),最后在CPU上创建一个功能执行的环境( functional execution contexts)
process = Process()
process.cmd = ['tests/test-progs/hello/bin/x86/linux/hello']
system.cpu.workload = process
system.cpu.createThreads()
最后一步就是实例化对象,创建root类,实例化每个SimObject,这个过程将创建对应的C++程序。
root = Root(full_system = False, system = system)
m5.instantiate()
启动这个实例并且在完成后检查系统状态
print ('Beginning simulation!')
exit_event = m5.simulate()
print ('Exiting @ tick %i because %s' % (m5.curTick(), exit_event.getCause()))
命令行进入Gem5的根目录,然后运行:
build/X86/gem5.opt configs/tutorial/simple.py
本小节配置一个如图的具有two level cache的层次结构。
这里使用classic cache,之所以不用ruby,是因为我们在构建CPU系统这里不考虑cache coherence。
先在configs/tutorial
创建一个caches.py
from m5.objects import Cache
# Some specific options for caches
# For all options see src/mem/cache/Cache.py
class L1Cache(Cache):
"""Simple L1 Cache with default values"""
# Default parameters for both L1 I and D caches
assoc = 2
tag_latency = 2
data_latency = 2
response_latency = 2
mshrs = 4
tgts_per_mshr = 20
def connectCPU(self, cpu):
"""Connect this cache's port to a CPU-side port
This must be defined in a subclass"""
raise NotImplementedError
def connectBus(self, bus):
"""Connect this cache to a memory-side bus"""
self.mem_side = bus.slave
class L1ICache(L1Cache):
"""Simple L1 instruction cache with default values"""
# Set the default size
size = '16kB'
def connectCPU(self, cpu):
"""Connect this cache's port to a CPU icache port"""
self.cpu_side = cpu.icache_port
class L1DCache(L1Cache):
"""Simple L1 data cache with default values"""
# Set the default size
size = '64kB'
def connectCPU(self, cpu):
"""Connect this cache's port to a CPU dcache port"""
self.cpu_side = cpu.dcache_port
class L2Cache(Cache):
"""Simple L2 Cache with default values"""
# Default parameters
size = '256kB'
assoc = 8
tag_latency = 20
data_latency = 20
response_latency = 20
mshrs = 20
tgts_per_mshr = 12
def connectCPUSideBus(self, bus):
""""Connect this cache to a cpu-side bus"""
self.cpu_side = bus.master
def connectMemSideBus(self, bus):
""""Connect this cache to a memory-side bus"""
self.mem_side = bus.slave
注意以上的代码表示配置cache simObject。其中assoc代表关联度,tag_latency代表标签查找延迟,data_latency代表数据访问延迟,response_latency代表miss后的返回延迟。
这里为了方便,用之前创建的simple.py:
cp simple.py two_level.py
修改这个新的脚本如下:
from caches import *
进行gem5实验时,每次编辑配置脚本显得比较繁琐,你也可以在此脚本文件中添加接收参数的功能,这个不是必须的,详细教程见:http://learning.gem5.org/book/part1/cache_config.html#adding-parameters-to-your-script
除了模拟出的信息,运行gem5后在 m5out
目录下会生成下列3个文件:
config.ini
包含为模拟创建的每个SimObject列表以及其参数的值
config.json
与config.ini相同,但是采用json格式
stats.txt
所有为仿真注册的gem5统计信息的文本表示
之前我们自己手动编写了配置脚本,但是很多系统(ARM、X86)的设置非常复杂,幸运的是gem5提供了许多默认的脚本文件。
所有gem5的配置文件在configs/
下可以找到,目录的结构如下所示:
configs/boot:
ammp.rcS halt.sh micro_tlblat2.rcS netperf-stream-udp-local.rcS
...
configs/common:
Benchmarks.py cpu2000.py Options.py
Caches.py FSConfig.py O3_ARM_v7a.py SysPaths.py
CacheConfig.py CpuConfig.py MemConfig.py Simulation.py
configs/dram:
sweep.py
configs/example:
fs.py read_config.py ruby_mem_test.py ruby_random_test.py
memtest.py ruby_direct_test.py ruby_network_test.py se.py
configs/ruby:
MESI_Three_Level.py MI_example.py MOESI_CMP_token.py Network_test.py
MESI_Two_Level.py MOESI_CMP_directory.py MOESI_hammer.py Ruby.py
configs/splash2:
cluster.py run.py
configs/topologies:
BaseTopology.py Cluster.py Crossbar.py MeshDirCorners.py Mesh.py Pt2Pt.py Torus.py
解释几个重要的:
boot/
这些是在Full System mode下使用的,由Linux启动后被模拟器加载并由shell执行。
common/
Caches.py
和我们之前创建的caches.py文件等很相似,也是一些关于cache的设置
Options.py
包含了一些关于在命令行设置的选项,比如CPU数量,系统时钟等等
CacheConfig.py
包含了为classic memory system设置的相关cache参数
MemConfig.py
提供了设置memory system的一些帮助函数
examples/
此文件中的函数用来执行gem5仿真,此文件非常复杂,但是它在运行仿真的时候灵活性非常大
examples/
包含一些可用于运行gem5的开箱即用的示例的gem5配置脚本。se.py
并且fs.py
非常有用。此目录中还有一些其他实用程序配置脚本。
dram/
一些测试DRAM的脚本
ruby/
包含ruby和cache coherence 的相关配置脚本
topologies/
包含创建Ruby cache hierarchy时候可用的拓扑实现
这节讨论用命令行传递选项给se.py和fs.py。比如只需要如下就能运行hello world程序:
build/X86/gem5.opt configs/example/se.py --cmd=tests/test-progs/hello/bin/x86/linux/hello
然鹅,默认情况下gem5采取的是原子CPU模式和原子内存访问,所以报告不是真实的时序,你不信的话看看config.ini里[system.cpu]
部分,type那里写的清清楚楚。
现在我们想用时序模式,按如下运行gem5:
build/X86/gem5.opt configs/example/se.py --cmd=tests/test-progs/hello/bin/x86/linux/hello --cpu-type=TimingSimpleCPU --l1d_size=64kB --l1i_size=16kB
虽然时序问题解决了,但是你在config.ini里找不到cache部分,虽然我们明确了cache的size,但我们没告诉这个system我们要用cache,所以自然就没创建cache,正确的形式如下:
build/X86/gem5.opt configs/example/se.py --cmd=tests/test-progs/hello/bin/x86/linux/hello --cpu-type=TimingSimpleCPU --l1d_size=64kB --l1i_size=16kB --caches
对比两次运行的ticks,发现后者的总时间更少,所以cache应该是启用的了。
详情在这里:http://learning.gem5.org/book/part1/example_configs.html#some-common-options-se-py-and-fs-py