先废话一下吧, GEM5 仿真器的代码组织是以 SimObject 为核心的。 以 CPU 和 memory 两类重要的模块为例,在各自的文件夹下定义了好多个 class ,每个 class 都以 SimObject 类为基类。 GEM5 提供了给 SimObject 添加参数的方式,在 http://www.gem5.org/documentation/learning_gem5/part2/parameters/ 中。 原理非常简单,在此不再完整描述,只介绍必要的部分。 随后将讨论一个重要的问题,如何定义一个复杂类型的参数。
BackGround
GEM5 中的参数定义
GEM5 中给一个 SimObject 定义一个参数有如下几个步骤:
在 SimObject 对应的 .py 文件中添加参数
Param.Int(0, 'The number of xxx') # 一个整数作为参数 VectorParam.Int([], 'The number of xxxs') # 一个整数 vector 作为参数
GEM5 编译器通过分析上述 .py 文件在 param 文件夹下生成与 SimObject 名字相同的 c++ 头文件,里面定义了 c++ 可引用的参数类型。
在我们写的 SimObject 的 c++ class 中,将 2 步骤中的参数类型传递给构造函数,就可以在构造函数中解析参数。
GEM5 中的参数类型
我们先看一下 GEM5 中提供了哪几种参数。
通过观察 GEM5 中定义参数的步骤,可以分析出来 GEM5 是通过某种方式将 python 中的数据类型和 c++ 中的数据类型打通,进而完成将 python config 脚本中的参数传递给 c++ 代码的任务。 因此在 GEM5 的源码中,默认参数类型在 c++ 文件和 python 文件分别定义了一份。 c++ 文件为 base/types.hh , python 文件为 python/m5/params.py 。
下面的列表我们将描述 python 文件中定义的数据类型,并列出对应的 c++ 数据类型:
Python 类型 | c++ 类型 | c++ 头文件 |
---|---|---|
Int | std::string | 内置类型 |
(U)Int(8,16...) | (u)int(8,16...)_t | 内置类型 |
Float | double | 内置类型 |
Bool | bool | 内置类型 |
Counter | Counter | base/type.hh |
Tick | Tick | base/type.hh |
TcpPort / UdpPort | uint16_t | 内置类型 |
Percent | int | 内置类型 |
Cycles | Cycles | base/type.hh |
MemorySize / MemorySize32 | uint64_t | 内置类型 |
Addr | Addr | base/type.hh |
AddrRange | AddrRange | base/addr_range.hh |
EthernetAddr | Net::EthAddr | base/inet.hh |
IpAddress | Net::IpAddress | base/inet.hh |
IpNetmask | Net::IpNetmask | base/inet.hh |
IpWithPort | Net::IpWithPort | base/inet.hh |
Time | tm | base/time.hh |
TickParamValue / Latency / Frequency / Clock | Tick | base/types.hh |
Voltage / Current / Energy / NetworkBandwidth / MemoryBandwidth | double | 内置类型 |
NullSimObject | Null | 内置类型 |
一般来说,上述的数据类型已经足够丰富,足够使用了。
定义新的参数类型
在我的需求中实际上是要定义一个新的复杂的数据类型。 一种直观的方式是通过 SimObject 的嵌套来实现。 但是这种方式会多定义好多 SimObject ,增加编译时间。 因此我更希望通过仿照 GEM5 的源码中定义数据类型的方式来实现。 于是我看了下源码是如何定义的。
GEM5 源码中参数定义
以 AddrRange 类型为例:
python 文件中的定义(python/m5/params.py):
class AddrRange(ParamValue): # 以ParamValue为基类来定义一个数据类型 cxx_type = 'AddrRange' # 定义对应的 c++ class 名称 def __init__(self, *args, **kwargs): ... if 'end' in kwargs: self.end = Addr(kwargs.pop('end')) # 读取 kwargs ... # 定义 python 方法 (可选) def __str__(self): ... # pybind_predecls 方法: # 定义一个引用文件(即 AddrRange 对应的 c++ class 定义的位置) # 这句 code 在某个使用 AddrRange 参数的 SimObject 生成的 build/X86/python/_m5/param_xxx.cc 中,会出现在引用头文件的部分 # build/X86/python/_m5/param_xxx.cc 文件是用于 SimObject 的 c++ 和 python 文件之间的 pybind @classmethod def pybind_predecls(cls, code): ... code('#include "base/addr_range.hh"') # cxx_predecls 方法: # 定义一个引用文件(即 AddrRange 对应的 c++ class 定义的位置) # 这句 code 在某个使用 AddrRange 参数的 SimObject 生成的 build/X86/params/xxx.hh 中,会出现在引用头文件的部分 # build/X86/params/xxx.hh 文件是用于定义 SimObject 的 c++ 语言的参数类型 @classmethod def cxx_predecls(cls, code): ... code('#include "base/addr_range.hh"') # cxx_ini_predecls 和 cxx_ini_parse 方法:(可选) # 定义一个引用文件和sparse代码 # 这句 code 在由 .py 配置文件生成 .ini 配置文件时生成参数的信息 @classmethod def cxx_ini_predecls(cls, code): ... code('#include <vector>') @classmethod def cxx_ini_parse(cls, code, src, dest, ret): ... code('${ret} _ret;') # 定义 getValue函数: # 这个是 python 和 c++ 代码之间联系的关键之一,这个含义我们在接下来pybind一节中介绍 def getValue(self): from _m5.range import AddrRange return AddrRange(long(self.start), long(self.end), self.masks, int(self.intlvMatch))
c++ 文件中的定义(base/addr_range.hh):
class AddrRange{ private: // 定义私有成员变量,可自由发挥 Addr _start; Addr _end; ... public: // 定义构造函数 AddrRange() : _start(1), _end(0), ... {} AddrRange(Addr _start, Addr _end, ...) : _start(_start), _end(_end), ... {} // 定义其他方法,以下面这个为例 std::string to_string() const{ ... } ...
在 SConscript 文件中设置编译以上两个文件
PySource('m5', 'm5/params.py')
以及
Source('types.cc')
将两种语言定义的数据类型联系起来(pybind)
GEM5 中将两种语言的数据类型联系起来的关键是利用 pybind,与 AddrRange 相关的代码在 python/pybind11/core.cc 文件中定义:
- 首先定义一个 python module , 名为 core.range:
void pybind_init_core(py::module &m_native) { py::module m_core = m_native.def_submodule("core"); ... // 调用下面的函数定义一个子模块 core.range init_range(m_native); } static void init_range(py::module &m_native) { py::module m = m_native.def_submodule("range"); // 绑定数据类型 AddrRange py::class_<AddrRange>(m, "AddrRange") .def(py::init<>()) // 绑定构造函数 .def(py::init<Addr &, Addr &>()) // 绑定另一个构造函数 ... // .def("__str__", &AddrRange::to_string)// 将 python 文件中定义的 __str__ 函数与 c++ 文件中定义的 to_string 函数绑定 }
将 core 模块挂载到 _m5 模块下
在 sim/init.cc 文件中,已经定义了 _m5 模块。在这个文件的如下代码中,调用了上述 pybind_init_core 函数,将 range 挂载到了 _m5 模块下:
EmbeddedPyBind::initAll() { std::list<EmbeddedPyBind *> pending; py::module m_m5 = py::module("_m5"); m_m5.attr("__package__") = py::cast("_m5"); pybind_init_core(m_m5); ... }
在 python 定义的 AddrRange 中定义 getValue 函数
回头再说之前代码中的 getValue函数:
def getValue(self): from _m5.range import AddrRange return AddrRange(long(self.start), long(self.end), self.masks, int(self.intlvMatch))
现在可以理解,这实际上是引用了 _m5 模块中的 c++ 类型 AddrRange,并调用它的构造函数,返回一个 c++ 类型的 AddrRange。
在任意位置定义新的参数
我的习惯是将改动都放在自己的文件夹下,这就要实现在任意位置定义新的参数。这里其实也算是个步骤总结:
定义一个 c++ class (参考上面的代码)
定义构造函数,与其他函数
在 SConscript 中设置编译:
Source('xxx.cc')
利用 pybind 将上述 c++ 类转换成 python 库(参考上面的代码)
定义一个子模块 init 函数
在上述函数中将 c++ 类绑定成 python module
在 sim/init.cc 的对应位置调用上述函数 (这里不得不修改源码)
定义一个 python class (参考上面的代码)
定义 cxx_type
定义构造函数,读取 kwargs
定义 pybind_predecls 、 cxx_predecls 、 cxx_ini_predecls 、 cxx_ini_parse ,后两者可选
定义 getValue ,返回 pybind 中绑定的 c++ 类
在 SConscript 中将这个文件挂载到 m5 模块下:
PySource('m5', 'xxx.py')
文档信息
- 本文作者:Yilong Zhao
- 本文链接:https://xiaoke0515.github.io/2020/08/01/3_GEM5_Param/
- 版权声明:自由转载-非商用-非衍生-保持署名(创意共享3.0许可证)