GEM5- GEM5 给 SimObject 添加 Param

2020/08/01 GEM5 共 4673 字,约 14 分钟

先废话一下吧, GEM5 仿真器的代码组织是以 SimObject 为核心的。 以 CPU 和 memory 两类重要的模块为例,在各自的文件夹下定义了好多个 class ,每个 class 都以 SimObject 类为基类。 GEM5 提供了给 SimObject 添加参数的方式,在 http://www.gem5.org/documentation/learning_gem5/part2/parameters/ 中。 原理非常简单,在此不再完整描述,只介绍必要的部分。 随后将讨论一个重要的问题,如何定义一个复杂类型的参数。

BackGround

GEM5 中的参数定义

GEM5 中给一个 SimObject 定义一个参数有如下几个步骤:

  1. 在 SimObject 对应的 .py 文件中添加参数

         Param.Int(0, 'The number of xxx') # 一个整数作为参数
         VectorParam.Int([], 'The number of xxxs') # 一个整数 vector 作为参数
    
  2. GEM5 编译器通过分析上述 .py 文件在 param 文件夹下生成与 SimObject 名字相同的 c++ 头文件,里面定义了 c++ 可引用的参数类型。

  3. 在我们写的 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++ 头文件
Intstd::string内置类型
(U)Int(8,16...)(u)int(8,16...)_t内置类型
Floatdouble内置类型
Boolbool内置类型
CounterCounterbase/type.hh
TickTickbase/type.hh
TcpPort / UdpPortuint16_t内置类型
Percentint内置类型
CyclesCyclesbase/type.hh
MemorySize / MemorySize32uint64_t内置类型
AddrAddrbase/type.hh
AddrRangeAddrRangebase/addr_range.hh
EthernetAddrNet::EthAddrbase/inet.hh
IpAddressNet::IpAddressbase/inet.hh
IpNetmaskNet::IpNetmaskbase/inet.hh
IpWithPortNet::IpWithPortbase/inet.hh
Timetmbase/time.hh
TickParamValue / Latency / Frequency / ClockTickbase/types.hh
Voltage / Current / Energy / NetworkBandwidth / MemoryBandwidthdouble内置类型
NullSimObjectNull内置类型

一般来说,上述的数据类型已经足够丰富,足够使用了。

定义新的参数类型

在我的需求中实际上是要定义一个新的复杂的数据类型。 一种直观的方式是通过 SimObject 的嵌套来实现。 但是这种方式会多定义好多 SimObject ,增加编译时间。 因此我更希望通过仿照 GEM5 的源码中定义数据类型的方式来实现。 于是我看了下源码是如何定义的。

GEM5 源码中参数定义

以 AddrRange 类型为例:

  1. 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))
    
    
  2. 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{
             ...
         }
    
         ...
    
  3. 在 SConscript 文件中设置编译以上两个文件

     PySource('m5', 'm5/params.py')
    

    以及

     Source('types.cc')
    

将两种语言定义的数据类型联系起来(pybind)

GEM5 中将两种语言的数据类型联系起来的关键是利用 pybind,与 AddrRange 相关的代码在 python/pybind11/core.cc 文件中定义:

  1. 首先定义一个 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 函数绑定
    
     }
    
  2. 将 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);
         ...
     }
    
  3. 在 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。

在任意位置定义新的参数

我的习惯是将改动都放在自己的文件夹下,这就要实现在任意位置定义新的参数。这里其实也算是个步骤总结:

  1. 定义一个 c++ class (参考上面的代码)

    1. 定义构造函数,与其他函数

    2. 在 SConscript 中设置编译:

     Source('xxx.cc')
    
  2. 利用 pybind 将上述 c++ 类转换成 python 库(参考上面的代码)

    1. 定义一个子模块 init 函数

    2. 在上述函数中将 c++ 类绑定成 python module

    3. 在 sim/init.cc 的对应位置调用上述函数 (这里不得不修改源码)

  3. 定义一个 python class (参考上面的代码)

    1. 定义 cxx_type

    2. 定义构造函数,读取 kwargs

    3. 定义 pybind_predecls 、 cxx_predecls 、 cxx_ini_predecls 、 cxx_ini_parse ,后两者可选

    4. 定义 getValue ,返回 pybind 中绑定的 c++ 类

    5. 在 SConscript 中将这个文件挂载到 m5 模块下:

       PySource('m5', 'xxx.py')
      

文档信息

Search

    Table of Contents