gem5学习9——配置/模拟脚本

来源:互联网 发布:淘宝店招上传 编辑:程序博客网 时间:2024/05/22 18:58

原文参见:gem5 Configuration / Simulation Scripts

模拟脚本控制了gem5模拟的配置和运行。gem5模拟器本身是被动的,gem5的调用就是执行用户的模拟脚本,只有当脚本调用它时才执行指定操作。

模拟脚本用Python编写,并且由Python解释器执行。目前解释器链接到了gem5可执行文件,但是脚本的执行应当和Python解释器调用不可分割。

模拟脚本通常分为两个阶段:配置阶段和模拟阶段。配置阶段,通过建立和连接各层Python模拟对象来指定目标系统;模拟阶段进行实际仿真。模拟脚本还可以定义命令行选项允许用户配置各阶段。

配置

模拟的系统由一系列模拟对象SimObject建立。脚本描述了SimObject的实例、变量和关系,来描述整个系统。脚本中包含的程序在运行时,建立了各层Python对象为模拟创建了SimObject。最简单的上手方式是使用已有脚本。在configs目录下有几个示例。

Python类

gem5提供了一组Python对象类与C++仿真模型对象类对应。Python类在m5.objects的Python模块中定义。这些对象的Python类定义可以在src目录下的.py文件中找到,通常和C++定义在相同的目录下。

指定SimObject的第一步是为对应类实例化一个Python对象。为了使Python类可见,配置文件必须首先引入m5模块的类定义:

from m5.objects import *
Python类名后跟着一对括号来实例化一个Python对象。下例初始化了一个SimpleCPU对象,并赋给了Python变量cpu:

cpu = SimpleCPU()
用Python属性来指定SimObject参数。可以用Python直接赋值或者在实例化时在括号内使用关键字。下面两个例子等价。

cpu = SimpleCPU(clock = '2GHz', width = 2)

cpu = SimpleCPU()cpu.clock = '2GHz'cpu.width = 2
赋值语句执行时,变量赋值部分生效。SimObject类的属性名(如clock)必须是已经定义的参数,赋值语句的右侧必须是(或可以转化成)该参数的正确类型。m5模块定义了大量字符串转数值的转换,可以用2GHz和64KB来描述时钟频率和内存大小。完整的参数类型和单位可以参照:Python Parameter Types。

某个SimObject类的完整参数列表(还有类型、默认值、简要描述)可以查看位于src目录下的Python类定义。Param.X属性定义了X类型的参数,VectorParam.X属性定义的参数是X类型的向量(Python列表)。注意参数是继承的,例如SimpleCPU对象的clock参数并没有在SimpleCPU.py的SimpleCPU类定义中指定,而是由SimpleCPU的Python基类BaseCPU(BaseCPU.py中)继承的。

SimObject之间的连接在构造第二个SimObject时将第一个SimObject作为参数值。例如将cache SimObject作为CPU的icache和dcache参数来指定CPU的指令和数据缓存。

配置继承

为了简化大型系统的描述,仿真目标以层次(树)的形式来组织。树中的每一个节点是一个SimObject实例。即使一个SimObject用Python实例化了,如果它不是层次树的一部分,也不会被构造。程序必须创建一个特殊的Root类作为层次树的根。配置程序执行结束后,从树根遍历确定需要构造的对象。使用和设置参数同样的语法将子节点加入SimObject,即给Python对象属性赋值。将上述SimpleCPU对象作为root对象的子节点,可以实例化:

root = Root()root.cpu = cpu

实例化时,可以用括号内的关键字赋值来给子节点的参数赋值。CPU的实例化和附着在根节点上可以用一条命令实现:

root = Root(cpu = SimpleCPU(clock = '2GHz', width = 2))
当SimObject作为另一个SimObject的参数时,也可以作为子节点。例如创建一个cache对象,将它赋给CPU的icache或dcache参数,cache对象在配置层次中作为了CPU对象的子节点。已经在层次树中存在的SimObject,赋值给另一个SimObject参数不会更改父节点。

配置层次决定了每个实例化对象的最终名字。名字描述了根节点到特定对象的路径(不包括根节点),用“.”连接。例如下述配置:

my_cpu = SimpleCPU(clock = '2GHz', width = 2)my_cpu.icache = BaseCache(size = '32KB', assoc = 2)my_cpu.dcache = BaseCache(size = '64KB', assoc = 2)my_system = LinuxSystem(cpu = my_cpu)root = Root(system = my_system)
本例中得到的SimObject有如下内部名称:system, system.cpu, system.cpu.icache, system.cpu.dcache。这些名称用于输出统计。my_cpu和my_system仅仅是Python变量;可以在Python内用于设置属性、添加子节点,但对C++部分不可见。可以通过配置层次路径来访问Python对象。

子节点属性还可以是SimObject向量。向量参数可以用Python列表来描述,例如system.cpu=[SimpleCPU(), SimpleCPU()]。Python中这些对象可以使用下标访问(如system.cpu[0])。对象的内部名称将下标直接加到属性名称后(如system.cpu0)。

SimObject属性命名规则如下:

如果属性名是SimObject的正式参数之一,右手的值转换为参数类型。如果不能转换会报错。如果值是一个不在配置层次中的SimObject,该SimObject变成了SimObject的子节点。参数名字作为赋值SimObject的最后一个元素。

如果属性名不是正式参数,右手值是一个SimObject或SimObject列表,这些SimObject成为了SimObject的子节点。属性名字作为SimObject的最后一个元素。

如果属性名不是正式参数并且右手值不是SimObject,会报错。

继承和后绑定

SimObject实例从实例化的类中继承了参数和值。配置系统的一个重要特征是值的继承大部分是后绑定,也就是说当层次实例化时,值被传递给实例,而不是当实例创建时。这样一个类参数的值可以在实例创建后设定,实例可以接收最近的参数值(只要参数没有被重载)。下例描述了这种行为:
# Instantiate some CPUs.scpu1 = SimpleCPU()scpu2 = SimpleCPU()fcpu1 = FullCPU()fcpu2 = FullCPU()# Since BaseCPU is a common base class for SimpleCPU and FullCPU, the# following statement will cause all of the above CPUs to have a clock# rate of 1GHz.BaseCPU.clock = '1GHz' # The following statement sets the width of both scpu1 and scpu2 to 4.SimpleCPU.width = 4# We can override the clock rate for a specific CPU.  Note that this# assignment will have the same effect whether it is before or after# the BaseCPU.clock assignment above.fcpu2.clock = '2GHz'

子类

用户可以从现有的gem5类中定义新的SimObject类。这一特性可以提供多种参数值。子类可以使用标准Python类定义:
class CrazyFastCPU(FullCPU):    rob_size = 10000    width = 100    clock = '10GHz'
用户可以直接定义子类或者实例化SimObject,例如obj=SimObject()。这些Python对象不会产生C++ SimObject,但是可以赋值子节点。在配置层次中创建内部节点很有用,它们代表了一组SimObject,但没有对应的C++ SimObject。

相对参考

在许多情况下,SimObject参数有不能明确命名的默认值。例如许多I/O设备需要指针指向封闭系统的物理内存对象或封闭系统对象本身。类似的,cache的默认延迟用CPU的时钟周期描述可能更方便。然而,这些对象的路径名可能各配置不相同。gem5配置系统提出了相对参考对象来解决这一问题。proxy对象代表实际的对象,只有在整个层次建立后才确定。

m5模块提供了两个相对参考对象:Self和Parent。相对于Self的属性确定了参考对象,Parent属相沿着层次树向根遍历,从参考对象的父节点开始,知道找到合适的匹配。这些对象的重要特征是与最终的参考对象实例相对,而不是进行赋值的地方。这样可以在SimObject类定义的地方为参数赋值默认值,为该类的每个实例独立resolve。

例如,可以将CPU对象的默认时钟速度设为封闭系统的时钟速度,这样在同构多核中不用为每个CPU设置时钟频率。将CPU时钟参数默认值设为Parent.clock。在最后的实例化阶段,访问CPU时钟参数时,会从CPU的父节点开始,沿层次树向上遍历,直到找到一个有时钟参数的对象。该参数的值将赋给CPU的时钟参数。

为增强灵活性,Self和Parent有一个特殊的属性any,寻找合适类型的任意值,层次节点本身或者节点的参数。该特征最常用的方法是用Parent.any作为SimObject值参数。例如许多设备使用Parent.any作为默认值来定位封闭系统对象或物理内存对象。如果Parent.any在同一层中找到了多个值会报错。

模拟

模拟脚本用Python建立了期望的配置后,可以开始实际模拟了。这时Python脚本必须开始与C++仿真内核进行交互。通过m5模块提供的Python函数调用进行交互,将其转换为C++函数调用(通过SWIG)。为了访问这些函数,模拟脚本必须引入m5包:

import m5
这条命令通常位于脚本的顶部,与from m5.objects import *一起出现。

实例化C++对象层次与调用instantiate()函数同样简单,将它传给层次树的根对象,通常叫做root。

m5.instantiate(root)
C++对象层次树实例化后,实际仿真开始。simulate()函数开始了C++事件循环。默认情况下该函数一直仿真,或者直到一些其他因素使仿真循环退出(例如目标程序调用了exit()或CPU达到了max_insts_any_thread限制)。如果仿真函数传递了一个正整数变量,仿真最多进行该数量的时钟周期,但其它因素可能导致程序提前退出。simulate()函数会返回一个事件对象,代表了退出的原因。通过getCause()方法可以查询原因。一个很简单但对用户友好的模拟脚本可以以下面代码结束:

exit_event = m5.simulate()print 'Exiting @ tick', m5.curTick(), 'because', exit_event.getCause()
上例中使用了m5函数curTick(),返回当前仿真的时钟周期数(C++变量curTick)。仿真循环可以多次调用,每次仿真到有原因导致退出到Python中。例如,下面的脚本扩展了上面的例子,每一百万周期后打印当前进展:

while 1:    exit_event = m5.simulate(1000000)    if exit_event != 'simulate() limit reached':        break    print 'Simulation reached tick', m5.curTick()print 'Exiting @ tick', m5.curTick(), 'because', exit_event.getCause()

选项

命令行中脚本名字后的选项传给模拟脚本的方式与标准Python脚本命令行变量相同(即通过sys.argv)。这些选项可以使一个脚本以用户定义的方式配置。configs/example目录下的示例脚本使用脚本选项选择CPU模型(simple vs. detailed)。

因为选项以标准Python方式传给脚本,脚本文件可以使用标准Python工具来解析选项。通常使用Python标准库的optparse模块。

下面是一个模拟脚本的片段,使用optparse进行选项解析,然后使用解析后的选项标记选择CPU模型:

parser = optparse.OptionParser()parser.add_option("-d", "--detailed", action="store_true")parser.add_option("-t", "--timing", action="store_true")(options, args) = parser.parse_args()if options.timing:    cpu = TimingSimpleCPU()elif options.detailed:    cpu = DetailedO3CPU()else:    cpu = AtomicSimpleCPU()
使用optparse模块的另一个好处是所有可用的脚本选项可以通过-h标记列出,例如:

gem5.opt <script> -h
gem5.opt -h列出了所有可用的gem5选项(见Running gem5)。

1 0
原创粉丝点击