GEM5 Tutorial
GEM5 Tutorial
主要完成创建一个简单系统的配置脚本,并写一个C语言程序在gem5上运行仿真。
第二步,完成一个2级cache系统;
最后,观察改变一些系统参数比如memory types, 处理器频率,程序的性能复杂性。
模拟脚本控制了gem5模拟的配置和运行。gem5模拟器本身是被动的,gem5的调用就是执行用户的模拟脚本,只有当脚本调用它时才执行指定操作。
模拟脚本用Python编写,并且由python解释器执行。目前解释器链接到了gem5可执行文件,但是脚本的执行应当和Python解释器调用不可分割。
模拟脚本通常分为两个阶段:配置阶段和模拟阶段。
- 配置阶段,通过建立和连接各层Python模拟对象来指定目标系统
- 模拟阶段进行实际仿真
模拟脚本还可以定义命令行选项允许用户配置各阶段。
为什么能用Pyrhon类配置系统?
答: gem5提供了一组Python对象类与C++仿真模型对象类对应。Python类在m5.objects的Python模块中定义。这些对象的Python类定义可以在src目录下的.py文件中找到,通常和C++定义在相同的目录下。
创建简单配置脚本
配置部分
创建一个非常简单的处理器系统,一个简单的CPU内核,将CPU内核连接到系统的内存总线,同时将DDR3存储连接到系统内存总线。系统结构如图:
gem5将设置并执行模拟的python脚本作为参数. 在此脚本中,创建一个系统来模拟,创建系统的所有组件,并指定系统组件的所有参数。 然后,从脚本开始可以开始模拟。
注:在gem5的config/example目录下也存在实例配置脚本。但是这些脚本需要用户在命令行指定所有选项。
gem5的模块化设计围绕SimObject类型构建。 模拟系统中的大多数组件是SimObjects:CPU,缓存,内存控制器,总线等。gem5将所有这些对象从C ++实现导出到python。 因此,从python配置脚本,您可以创建任何SimObject,设置其参数,并指定SimObjects之间的交互。
下面开始gem5脚本的配置:
1. 指定SimObject的第一步是为对应类实例化一个Python对象。为了使Python类可见,配置文件必须首先引入m5模块的类定义:
import m5
from m5.objects import *
- 例化模拟的系统,Python类名后跟着一对括号来实例化一个Python对象。下例初始化一个SimObject,即我们要模拟的系统。 系统对象将是我们模拟系统中所有其他对象的父对象。 System对象包含很多功能(不是时序)信息,如物理内存范围,根时钟域,根电压域,内核(在全系统仿真中)等。
system = System()
- 参考需要模拟的系统,设置系统时钟。 先要创建一个时钟域, 然后设置该域的时钟频率。 在SimObject中设置参数与设置python中的对象的成员完全相同,因此我们可以简单地将时钟设置为1 GHz。 最后,我们必须为这个时钟域指定一个电压域。 由于我们现在不关心系统电源,所以我们只需要使用电压域的默认选项。
system.clk_domain = SrcClockDomain()
system.clk_domain.clock = '1GHz'
system.clk_domain.voltage_domain = VoltageDomain()
- 有了系统,然后设置如何模拟内存。 这里使用时序(timing)模式进行内存模拟。 除了fast-forwarding(快速转发 )和从检查点恢复的特殊情况外,基本上总是timing模式进行内存模拟。在这里设置非常小的系统,单个内存范围大小为512 M。
system.mem_mode = 'timing'
system.mem_ranges = [AddrRange('512MB')]
- 现在,例化CPU对象。这里选择gem5中最简单的时序CPU,即是TimingSimpleCPU。 该CPU模型在单个时钟周期内执行每个指令,以执行流过存储器系统的存储器请求。
system.cpu = TimingSimpleCPU()
- 下一步,创建系统内存总线
system.membus = SystemXBar()
- 已经有了内存总线,CPU连接缓存端口。 在这种情况下,由于我们想要模拟的系统没有任何缓存,我们将直接将I-Cahce和D-Cahce端口连接到系统内存总线。
system.cpu.icache_port = system.membus.slave
system.cpu.dcache_port = system.membus.slave
GEM5中端口的怎么连接?
gem5中使用端口抽象将内存系统组件连接在一起。 每个内存对象可以有两种端口,主端口和从端口。 请求从主端口发送到从端口,响应从从端口发送到主端口。 连接端口时,必须将主端口连接到从端口。
python配置文件中很容易完成端口连接。 set the master port = to the slave port,并将其连接。 例如:
memobject1.master = memobject2.slave
主机和从机可以在=的任一侧,并且将进行相同的连接。 进行连接后,现在主机发送请求到从端口。
- 为确保我们的系统正常运行需要连接几个其他端口。 如:在CPU上创建IO控制器并将其连接到内存总线。 另外,系统需要连接一个特殊的端口到系统总线,以实现系统读/写内存。
将PIO和中断端口连接到内存总线是一个x86特定的要求。 其他ISA(例如ARM)不需要这3条额外的线路。
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; #Connect the system up to the membus
- 创建内存控制器并将其连接到系统内存总线。 在这个系统中使用简单的DDR3控制器,它将负责系统的整个内存范围。
system.mem_ctrl = DDR3_1600_8x8()
system.mem_ctrl.range = system.mem_ranges[0]
system.mem_ctrl.port = system.membus.master
后面设置是让CPU执行的过程。 由于我们在系统调用模式(SE模式)下执行,所以我们将把CPU指向编译后的可执行文件。 我们将执行一个简单的“Hello world”程序。 已经有一个编译与gem5一起运行,所以我们将使用它。 您可以指定为x86构建的任何应用程序,并已静态编译。
模拟部分
- 先创建一个进程(另一个SimObject)。 然后将processes命令设置为要运行的命令。 这是一个类似于argv的列表,其中可执行文件位于第一个位置,而在其余列表中的可执行文件的参数。 然后我们将CPU设置为使用该过程作为工作负载,最后在CPU中创建功能执行上下文。
process = LiveProcess()
process.cmd = ['tests/test-progs/hello/bin/x86/linux/hello']
system.cpu.workload = process
system.cpu.createThreads()
- 最后实例化系统并开始执行。 首先,我们创建root对象。 然后我们实例化模拟。 实例化过程遍历了我们在python中创建的所有SimObjects,并创建了C ++等价物。–
不必实例化python类,然后明确指定参数。 可以直接将参数作为命名参数传递,如下面的Root对象。
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())
添加2级Cache结构到系统中
介绍个更复杂的配置。 向系统添加缓存层次结构,如下图所示。 此外,本章将介绍理解gem5统计信息输出,并在脚本中添加命令行参数。
这里使用前面的那个没有Cache的简单 CPU配置脚本基础上进行配置。
创建Cache对象
- 创建Cache对象
from m5.objects import Cache
由于对一个单一的CPU系统建模,并不关心高速缓存一致性建模,因而这里使用class Cache不是Ruby。 我们将扩展BaseCache SimObject并为我们的系统进行配置。 首先,我们必须了解用于配置BaseCache对象的参数。
BaseCache SimObject声明可以在src / mem / cache / BaseCache.py中找到。 这个Python文件定义了您可以设置SimObject的参数。 在引擎盖下,当SimObject被实例化时,这些参数被传递给对象的C ++实现。
在BaseCache类中,有很多参数。 例如,assoc是一个整数参数。 一些参数,如write_buffers具有默认值,在这种情况下为8。 默认参数是Param。*的第一个参数,除非第一个参数是一个字符串。 每个参数的字符串参数是对参数的描述(例如,hit_latency = Param.Cycles(“此缓存的命中等待时间”)意味着hit_latency控制“此缓存的命中等待时间”)。
- 像Python类一样配置扩展Cache对象,并为新的Cache命名,先制作一个L1 Cache
class L1Cache(Cache):
"""Simple L1 Cache with default values"""
assoc = 2
tag_latency = 2
data_latency = 2
response_latency = 2
mshrs = 4
tgts_per_mshr = 20
- 扩展Cache后,并设置Cache SimObject中没有默认值的参数。然后,创建两个L1 Cahce的子类L1ICache和L1DCache
class L1ICache(L1Cache):
size = '16kB'
class L1DCache(L1Cache):
size = '64kB'
- 创建并设置L2 Cache参数
class L1Cache(Cache):
"""Simple L1 Cache with default values"""
assoc = 2
tag_latency = 2
data_latency = 2
response_latency = 2
mshrs = 4
tgts_per_mshr = 20
- 指定Cache所需的所有必要参数,实例化子类,并将缓存连接到互连。 然而,将许多对象连接到复杂的互连可以使配置文件快速增长并变得不可读。 因此,我们先为我们的Cache子类添加一些帮助函数。 记住,这些只是Python类,所以我们可以做任何与Python类可以做的事情。
def connectCPU(self, cpu):
# need to define this in a base class!
raise NotImplementedError
def connectBus(self, bus):
self.mem_side = bus.slave
- 为指令和数据高速缓存定义单独的connectCPU功能,因为I-Cache和D-Cache端口具有不同的名称。 我们的L1ICache和L1DCache类变成:
class L1ICache(L1Cache):
size = '16kB'
def connectCPU(self, cpu):
self.cpu_side = cpu.icache_port
class L1DCache(L1Cache):
size = '64kB'
def connectCPU(self, cpu):
self.cpu_side = cpu.dcache_port
- 向L2Cache添加功能以连接到内存端和CPU侧总线
def connectCPUSideBus(self, bus):
self.cpu_side = bus.master
def connectMemSideBus(self, bus):
self.mem_side = bus.slave
添加Cache简易配置文件
- 导入上面创建cache对象
from **** import *
- 创建完CPU之后,创建L1 Caches;
system.cpu.icache = L1ICache()
system.cpu.dcache = L1DCache()
- 使用创建的connect函数将缓存连接到CPU端口
system.cpu.icache.connectCPU(system.cpu)
system.cpu.dcache.connectCPU(system.cpu)
- 不能将L1Cache直连到L2Cache,因为L2Cache仅希望单个端口连接到它。 因此需要创建一个L2-bus来将L1Cache连接到L2Cache。使用函数将L1Cache连接到L2总线
system.l2bus = L2XBar()
system.cpu.icache.connectBus(system.l2bus)
system.cpu.dcache.connectBus(system.l2bus)
- 可以创建L2Cache并将其连接到L2总线和内存总线
system.l2cache = L2Cache()
system.l2cache.connectCPUSideBus(system.l2bus)
system.l2cache.connectMemSideBus(system.membus)
向脚本中添加参数
为了避免每次使用不同参数测试系统需要修改配置脚本,可以在em5配置脚本中添加命令行参数。而配置脚本是Python,因此可以使用支持参数解析的Python库。 尽管optparse已经被正式弃用,但由于gem5的最小Python版本是2.5,所以配置gem5的配置脚本使用它而不是py:mod:argparse。 要开始使用optparse,可以参考在线Python文档。
要在我们的两级缓存配置中添加选项,导入缓存后,我们来添加一些选项。
from optparse import OptionParser
parser = OptionParser()
parser.add_option('--l1i_size', help="L1 instruction cache size")
parser.add_option('--l1d_size', help="L1 data cache size")
parser.add_option('--l2_size', help="Unified L2 cache size")
(options, args) = parser.parse_args()
接下来,需要将这些选项传递到配置脚本中创建的缓存。 为了做到这一点,我们将简单地将选项传递给缓存作为参数,并添加适当的构造函数。
system.cpu.icache = L1ICache(options)
system.cpu.dcache = L1DCache(options)
...
system.l2cache = L2Cache(options)
在上面创建Cache中,需要为每个类添加构造函数(Python中的init函数)。 从L1缓存开始,我们将添加一个空构造函数,因为没有适用于L1缓存的任何参数。 但是,在这种情况下,不能忘记调用超类的构造函数。 如果超级类构造函数的调用被跳过,则当您尝试实例化缓存对象时,gem5的SimObject属性查找函数将失败,结果将为“RuntimeError:maximum recursion depth exceeded”。 所以,在L1Cache中,我们需要在静态类成员之后添加以下内容。
def __init__(self, options=None):
super(L1Cache, self).__init__()
pass
接下来,在L1ICache中,我们需要使用我们创建的选项(l1i_size)来设置大小。 在以下代码中,如果选项未传递到L1ICache构造函数,并且在命令行中未指定任何选项,则会有保护。 在这些情况下,我们将使用我们已经为大小指定的默认值。
def __init__(self, options=None):
super(L1ICache, self).__init__(options)
if not options or not options.l1i_size:
return
self.size = options.l1i_size
对L1DCache可以使用同样代码
def __init__(self, options=None):
super(L1DCache, self).__init__(options)
if not options or not options.l1d_size:
return
self.size = options.l1d_size
L2 Cache配置:
def __init__(self, options=None):
super(L2Cache, self).__init__(options)
if not options or not options.l2_size:
return
self.size = options.l2_size
通过gem5输出,如何了解统计信息
除了模拟脚本打印信息之外,运行gem5之后,还有三个文件在名为m5out的目录中生成:
- config.ini
包含为模拟创建的每个SimObject列表及其参数的值。
- config.json
与config.ini相同,但以json格式。
- stats.txt
为模拟注册的所有gem5统计信息的文本表示。
创建这些文件的位置可以被控制: --outdir=DIR, -d DIR
要创建的目录,其中包含了gem5输出文件,包括config.ini,config.json,stats.txt和可能的其他文件。 如果目录中的文件已存在,将被覆盖。
config.ini
这个文件是模拟的最终版本。 在这个文件中显示了模拟的每个SimObject的所有参数,无论是在配置脚本中设置还是默认使用。
下面是从simple.py(既是第一个简单的不带Cache的配置脚本)中生成的config.ini。
[root]
type=Root
children=system
eventq_index=0
full_system=false
sim_quantum=0
time_sync_enable=false
time_sync_period=100000000000
time_sync_spin_threshold=100000000
[system]
type=System
children=clk_domain cpu dvfs_handler mem_ctrl membus
boot_osflags=a
cache_line_size=64
clk_domain=system.clk_domain
eventq_index=0
init_param=0
kernel=
kernel_addr_check=true
load_addr_mask=1099511627775
load_offset=0
mem_mode=timing
mem_ranges=0:536870911
memories=system.mem_ctrl
[system.clk_domain]
type=SrcClockDomain
children=voltage_domain
clock=1000
domain_id=-1
eventq_index=0
init_perf_level=0
voltage_domain=system.clk_domain.voltage_domain
...
[system.membus]
type=CoherentXBar
clk_domain=system.clk_domain
eventq_index=0
header_cycles=1
snoop_filter=Null
system=system
use_default_range=false
width=8
master=system.cpu.interrupts.pio system.cpu.interrupts.int_slave system.mem_ctrl.port
slave=system.cpu.icache_port system.cpu.dcache_port system.cpu.interrupts.int_master system.system_port
这里我们看到,在每个SimObject的描述的开始,首先它是在由方括号(例如[system.membus])包围的配置文件中创建的名称。
接下来,SimObject的每个参数都显示为它的值,包括配置文件中未明确设置的参数。 例如,配置文件将时钟域设置为1 GHz(在这种情况下为1000个刻度)。 但是,它没有设置缓存行大小(系统中为64)对象。
config.ini文件是一个有价值的工具,用于确保您正在模拟您认为模拟的内容。 在gem5中有许多可能的方法来设置默认值,并覆盖默认值。 始终检查config.ini是一个健康检查是一个“最佳实践”,配置文件中设置的值将传播到实际的SimObject实例化。
stats.txt
gem5具有灵活的统计生成系统。 gem5统计信息在gem5 wiki站点上有一些细节。 SimObject的每个实例化都有自己的统计信息。 在模拟结束时,或者当发出特殊的统计 - 转储命令时,所有SimObjects的统计信息的当前状态都将被转储到一个文件中。
首先,统计文件包含有关执行的一般统计信息:
---------- Begin Simulation Statistics ----------
sim_seconds 0.000346 # Number of seconds simulated
sim_ticks 345518000 # Number of ticks simulated
final_tick 345518000 # Number of ticks from beginning of simulation (restored from checkpoints and never reset)
sim_freq 1000000000000 # Frequency of simulated ticks
host_inst_rate 144400 # Simulator instruction rate (inst/s)
host_op_rate 260550 # Simulator op (including micro ops) rate (op/s)
host_tick_rate 8718625183 # Simulator tick rate (ticks/s)
host_mem_usage 778640 # Number of bytes of host memory used
host_seconds 0.04 # Real time elapsed on the host
sim_insts 5712 # Number of instructions simulated
sim_ops 10314 # Number of ops (including micro ops) simulated
统计转储开始于———- Begin Simulation Statistics ———-。 如果在gem5执行期间存在多个统计转储,则可能会在单个文件中存在多个。 这对于长时间运行的应用程序或从检查点还原时是常见的。
每个统计量都有一个名称(第一列),一个值(第二列)和一个描述(最后一列前面是#)。
大多数统计资料是自己的说明。 几个重要的统计信息是sim_seconds,它是模拟的总模拟时间,sim_insts是CPU提交的指令数,host_inst_rate告诉您gem5的性能。
接下来,打印SimObjects的统计信息。 例如,内存控制器统计。 这具有每个组件读取的字节和这些组件使用的平均带宽的信息。
system.mem_ctrl.bytes_read::cpu.inst 58264 # Number of bytes read from this memory
system.mem_ctrl.bytes_read::cpu.data 7167 # Number of bytes read from this memory
system.mem_ctrl.bytes_read::total 65431 # Number of bytes read from this memory
system.mem_ctrl.bytes_inst_read::cpu.inst 58264 # Number of instructions bytes read from this memory
system.mem_ctrl.bytes_inst_read::total 58264 # Number of instructions bytes read from this memory
system.mem_ctrl.bytes_written::cpu.data 7160 # Number of bytes written to this memory
system.mem_ctrl.bytes_written::total 7160 # Number of bytes written to this memory
system.mem_ctrl.num_reads::cpu.inst 7283 # Number of read requests responded to by this memory
system.mem_ctrl.num_reads::cpu.data 1084 # Number of read requests responded to by this memory
system.mem_ctrl.num_reads::total 8367 # Number of read requests responded to by this memory
system.mem_ctrl.num_writes::cpu.data 941 # Number of write requests responded to by this memory
system.mem_ctrl.num_writes::total 941 # Number of write requests responded to by this memory
system.mem_ctrl.bw_read::cpu.inst 168627973 # Total read bandwidth from this memory (bytes/s)
system.mem_ctrl.bw_read::cpu.data 20742769 # Total read bandwidth from this memory (bytes/s)
system.mem_ctrl.bw_read::total 189370742 # Total read bandwidth from this memory (bytes/s)
system.mem_ctrl.bw_inst_read::cpu.inst 168627973 # Instruction read bandwidth from this memory (bytes/s)
system.mem_ctrl.bw_inst_read::total 168627973 # Instruction read bandwidth from this memory (bytes/s)
system.mem_ctrl.bw_write::cpu.data 20722509 # Write bandwidth from this memory (bytes/s)
system.mem_ctrl.bw_write::total 20722509 # Write bandwidth from this memory (bytes/s)
system.mem_ctrl.bw_total::cpu.inst 168627973 # Total bandwidth to/from this memory (bytes/s)
system.mem_ctrl.bw_total::cpu.data 41465278 # Total bandwidth to/from this memory (bytes/s)
system.mem_ctrl.bw_total::total 210093251 # Total bandwidth to/from this memory (bytes/s)
后来的文件是CPU统计信息,其中包含有关系统调用数,分支数,总承诺指令等的信息。
system.cpu.apic_clk_domain.clock 16000 # Clock period in ticks
system.cpu.workload.num_syscalls 11 # Number of system calls
system.cpu.numCycles 345518 # number of cpu cycles simulated
system.cpu.numWorkItemsStarted 0 # number of work items this cpu started
system.cpu.numWorkItemsCompleted 0 # number of work items this cpu completed
system.cpu.committedInsts 5712 # Number of instructions committed
system.cpu.committedOps 10314 # Number of ops (including micro ops) committed
system.cpu.num_int_alu_accesses 10205 # Number of integer alu accesses
system.cpu.num_fp_alu_accesses 0 # Number of float alu accesses
system.cpu.num_func_calls 221 # number of times a function call or return occured
system.cpu.num_conditional_control_insts 986 # number of instructions that are conditional controls
system.cpu.num_int_insts 10205 # number of integer instructions
system.cpu.num_fp_insts 0 # number of float instructions
system.cpu.num_int_register_reads 19296 # number of times the integer registers were read
system.cpu.num_int_register_writes 7977 # number of times the integer registers were written
system.cpu.num_fp_register_reads 0 # number of times the floating registers were read
system.cpu.num_fp_register_writes 0 # number of times the floating registers were written
system.cpu.num_cc_register_reads 7020 # number of times the CC registers were read
system.cpu.num_cc_register_writes 3825 # number of times the CC registers were written
system.cpu.num_mem_refs 2025 # number of memory refs
system.cpu.num_load_insts 1084 # Number of load instructions
system.cpu.num_store_insts 941 # Number of store instructions
system.cpu.num_idle_cycles 0.001000 # Number of idle cycles
system.cpu.num_busy_cycles 345517.999000 # Number of busy cycles
system.cpu.not_idle_fraction 1.000000 # Percentage of non-idle cycles
system.cpu.idle_fraction 0.000000 # Percentage of idle cycles
system.cpu.Branches 1306 # Number of branches fetched
上一篇: PHP读文件乱码问题的解决方法
下一篇: 求教一个乱码的有关问题
推荐阅读
-
postgresql 11.1 源码 /src/tutorial/syscat.source
-
OpenCV-Python-Tutorial安装详情
-
Core Text Tutorial for iOS : Making a Magazine App 翻译
-
运行 Kurento Java Tutorial - Hello World 测试
-
kurento-tutorial--helloworld学习
-
Kurento- Running Kurento Java Tutorial - kurento-magic-mirror could not been Succeed, Why?
-
Kurento Java Tutorial - Hello World
-
Object Detection finetuning tutorial + Pytorch代码实战练兵
-
21 Flask mega-tutorial 第21章 用户通知
-
The Flask Mega-Tutorial 之 Chapter 8: Followers