开发 gem5 模型
作者: Mahyar Samani, M. Mysore

在 gem5 中开发 SimObjects


让我们开始构建 gem5

让我们在基础 gem5 目录中构建 gem5,同时我们学习一些基础知识。通过运行以下命令来完成。

cd gem5
scons build/NULL/gem5.opt -j$(nproc)

gem5 构建系统的基础知识

gem5 的构建系统是复杂的

正如我们将在接下来的章节中看到的,gem5 有许多领域特定语言和源到源编译器。

这意味着两件事:

  1. 最终代码的样子并不总是显而易见的。
  2. 设置 gem5 构建有很多很多选项。

配置 gem5 构建

正如我们所看到的,有多种方式可以配置 gem5 构建,这会产生不同的二进制文件。

有两类选项:

  1. 构建时配置(例如,在二进制文件中包含哪些模型)
  2. 编译器配置(例如,优化、调试标志等)

如果你忘记了这些,

scons --help 将解释目标和 Kconfig 工具。


编译器配置

这由你构建的 gem5 二进制文件的后缀指定。例如,gem5.opt 是用 “opt” 配置构建的。选项有:

记住,这些选项是针对 gem5 二进制文件的,而不是模拟系统。选择 fastdebug 不会影响模拟器的输出(当然,除非有 bug)。


构建时配置

配置 gem5 构建有很多选项。

有两种方法:

  1. 使用 gem5/build_opts 中找到的默认值
  2. 使用 Kconfig 配置(与 Linux 内核使用的相同工具)

Build_opts

ALL                       GCN3_X86                  NULL_MOESI_hammer  X86_MESI_Two_Level
ARM                       MIPS                      POWER              X86_MI_example
ARM_MESI_Three_Level      NULL                      RISCV              X86_MOESI_AMD_Base
ARM_MESI_Three_Level_HTM  NULL_MESI_Two_Level       SPARC
ARM_MOESI_hammer          NULL_MOESI_CMP_directory  VEGA_X86
Garnet_standalone         NULL_MOESI_CMP_token      X86

build_opts 中,你会找到许多默认选项。大多数选项命名为 <ISA>_<PROTOCOL>

例如,X86_MESI_Two_Level 是 X86 ISA 和 MESI_Two_Level 协议的构建选项。

你也可以将多个 ISA 构建到一个二进制文件中(例如,ALL),但你不能将多个 Ruby 一致性协议构建到一个二进制文件中。


使用 Kconfig

Kconfig 是一个允许你交互式配置 gem5 构建的工具。

使用 Kconfig 时,首先必须创建一个构建目录。 注意:此目录可以有任何名称,并且可以位于系统上的任何位置

常见做法是在 gem5 源代码目录中创建一个名为 build 的目录,并使用 build_opts 中的默认值。

scons defconfig build/my_gem5/ build_opts/ALL

在这种情况下,我们使用 build/my_gem5 作为构建目录,使用 build_opts/ALL 作为默认配置。


使用 Kconfig(续)

创建构建目录后,你可以使用 menuconfig 目标运行 scons 来获得交互式配置工具。

scons menuconfig build/my_gem5/

整合所有内容

要构建 gem5,一旦你设置并配置了构建目录,就可以运行以下命令。

scons build/my_gem5/gem5.opt -j$(nproc)

这将使用你设置的配置构建 gem5 二进制文件。 它将构建 “opt” 二进制文件。

注意:gem5 需要很长时间来构建,因此使用多核很重要。 我不知道你有多少个核心,所以我使用了 -j$(nproc) 来使用所有核心。 如果你在系统上做其他事情,你可能想使用更少的核心。


gem5 的 Scons 构建系统

用于设置 gem5 构建的文件主要有两种类型:

我们支持大多数常见操作系统和大多数现代编译器。修复 SCons 构建中的编译器错误并不简单。

我们强烈建议你使用支持的编译器/操作系统或使用 docker 来构建 gem5。

不会发现 SCons 文档有帮助。gem5 对它进行了太多定制。


SimObjects


什么是 SimObject?

SimObject 是 gem5 对模拟模型的命名。我们使用 SimObject 及其子类(例如 ClockedObject)来模拟计算机硬件组件。SimObject 在 gem5 中促进以下功能:


代码中的 SimObject

在 gem5 构建中,每个基于 SimObject 的类都有 4 个相关文件。


HelloSimObject

我们将开始构建我们的第一个 SimObject,称为 HelloSimObject,我们将查看 SimObject 文件之一。

我们将从以下步骤开始。

  1. 编写定义文件。
  2. 编写头文件。
  3. 编写源文件。
  4. 编写 SConscript
  5. 编译。
  6. 编写配置脚本并运行它。

###

稍后,我们将执行以下步骤。

  1. 向定义文件添加参数。
  2. 更新源文件。
  3. 编译。
  4. 编写第二个配置脚本并运行它。

步骤 1:简单的 SimObject


SimObject 定义文件:创建文件

让我们在以下位置为我们的 SimObject 创建一个 python 文件: src/bootcamp/hello-sim-object/HelloSimObject.py

由于 gem5 仍在编译,首先打开一个新终端。

width:1140px 点击哪里打开新终端

然后,在基础 gem5 目录中运行以下命令:

cd gem5
mkdir src/bootcamp
mkdir src/bootcamp/hello-sim-object
touch src/bootcamp/hello-sim-object/HelloSimObject.py

SimObject 定义文件:导入和定义

在你选择的编辑器中打开 src/bootcamp/hello-sim-object/HelloSimObject.py

HelloSimObject.py 中,我们将定义一个表示我们 HelloSimObject 的新类。 我们需要从 m5.objects.SimObject 导入 SimObject 的定义。 将以下行添加到 HelloSimObject.py 以导入 SimObject 的定义。

from m5.objects.SimObject import SimObject

让我们为新 SimObject 添加定义。

class HelloSimObject(SimObject):
    type = "HelloSimObject"
    cxx_header = "bootcamp/hello-sim-object/hello_sim_object.hh"
    cxx_class = "gem5::HelloSimObject"

SimObject 定义文件:深入了解我们所做的工作

让我们更深入地了解我们拥有的几行代码。

class HelloSimObject(SimObject):
    type = "HelloSimObject"
    cxx_header = "bootcamp/hello-sim-object/hello_sim_object.hh"
    cxx_class = "gem5::HelloSimObject"

typecxx_headercxx_class 是由 MetaSimObject 元类定义的关键字。有关这些关键字的完整列表,请查看 src/python/m5/SimObject::MetaSimObject。可以跳过这些关键字变量中的一些(如果不是全部)。但是,我强烈建议你至少定义 typecxx_headercxx_class


给明智者的建议和对未来的小小展望


SimObject 头文件:创建文件

现在,让我们开始在 C++ 中构建我们的 SimObject。首先,通过在基础 gem5 目录中运行以下命令为我们的 SimObject 创建一个文件。记住:我们将 cxx_header 设置为 bootcamp/hello-sim-object/hello_sim_object.hh。因此,我们需要在具有相同路径的文件中添加 HelloSimObject 的定义。

touch src/bootcamp/hello-sim-object/hello_sim_object.hh

非常重要:如果 SimObject 类在 Python 中继承自另一个 SimObject 类,它在 C++ 中也应该这样做。例如,HelloSimObject 在 Python 中继承自 SimObject,所以在 C++ 中,HelloSimObject 应该继承自 SimObject非常重要SimObject 参数结构体以与 SimObject 本身相同的方式继承。例如,如果 HelloSimObject 继承自 SimObject,则 HelloSimObjectParams 继承自 SimObjectParams


SimObject 头文件:前几行

在你选择的编辑器中打开 src/bootcamp/hello-sim-object/hello_sim_object.hh 并向其中添加以下代码。

#ifndef __BOOTCAMP_HELLO_SIM_OBJECT_HELLO_SIM_OBJECT_HH__
#define __BOOTCAMP_HELLO_SIM_OBJECT_HELLO_SIM_OBJECT_HH__

#include "params/HelloSimObject.hh"
#include "sim/sim_object.hh"

namespace gem5
{

class HelloSimObject: public SimObject
{
  public:
    HelloSimObject(const HelloSimObjectParams& params);
};

} // namespace gem5

#endif // __BOOTCAMP_HELLO_SIM_OBJECT_HELLO_SIM_OBJECT_HH__

SimObject 头文件:深入了解前几行

需要注意的事项:


SimObject 源文件:所有代码

让我们在以下位置为 HelloSimObject 创建一个源文件: src/bootcamp/hello-sim-object/hello_sim_object.cc

touch src/bootcamp/hello-sim-object/hello_sim_object.cc

在你选择的编辑器中打开 src/bootcamp/hello-sim-object/hello_sim_object.cc 并向其中添加以下代码。

#include "bootcamp/hello-sim-object/hello_sim_object.hh"

#include <iostream>

namespace gem5
{

HelloSimObject::HelloSimObject(const HelloSimObjectParams& params):
    SimObject(params)
{
    std::cout << "Hello from HelloSimObject's constructor!" << std::endl;
}

} // namespace gem5

SimObject 源文件:深入了解

需要注意的事项:


开始构建:SConscript

我们需要将 SimObject 注册到 gem5,以便将其构建到 gem5 可执行文件中。在构建时,scons(gem5 的构建系统)将搜索 gem5 目录中名为 SConscript 的文件。SConscript 文件包含需要构建的内容的指令。我们只需通过在基础 gem5 目录中运行以下命令来创建一个名为 SConscript 的文件(在我们的 SimObject 目录内)。

touch src/bootcamp/hello-sim-object/SConscript

将以下内容添加到 SConscript

Import("*")

SimObject("HelloSimObject.py", sim_objects=["HelloSimObject"])

Source("hello_sim_object.cc")

开始构建:深入了解 SConscript

需要注意的事项:


让我们编译

现在,在我们可以在配置脚本中使用 HelloSimObject 之前,剩下的唯一事情就是重新编译 gem5。在基础 gem5 目录中运行以下命令以重新编译 gem5。

scons build/NULL/gem5.opt -j$(nproc)

在等待 gem5 构建时,我们将创建一个使用 HelloSimObject 的配置脚本。在单独的终端中,让我们在 gem5/configs 内创建该脚本。首先,让我们为脚本创建目录结构。在基础 gem5 目录中运行以下命令集以创建清晰的结构。

mkdir configs/bootcamp
mkdir configs/bootcamp/hello-sim-object
touch configs/bootcamp/hello-sim-object/first-hello-example.py

配置脚本:第一个 Hello 示例:m5 和 Root

在你选择的编辑器中打开 configs/bootcamp/first-hello-example.py

要运行模拟,我们需要与 gem5 的后端接口。m5 将允许我们调用 C++ 后端来在 C++ 中实例化 SimObjects 并模拟它们。要将 m5 导入到配置脚本中,请将以下内容添加到代码中。

import m5

gem5 中的每个配置脚本都必须实例化 Root 类的对象。此对象表示 gem5 正在模拟的计算机系统中的设备树根。要将 Root 导入到配置中,请将以下行添加到脚本中。

from m5.objects.Root import Root

配置脚本:第一个 Hello 示例:在 Python 中创建实例

我们还需要将 HelloSimObject 导入到配置脚本中。为此,请将以下行添加到配置脚本中。

from m5.objects.HelloSimObject import HelloSimObject

接下来我们需要做的是创建一个 Root 对象和一个 HelloSimObject 对象。我们可以使用 . 运算符将 HelloSimObject 对象添加为 root 对象的子对象。将以下行添加到配置中以执行此操作。

root = Root(full_system=False)
root.hello = HelloSimObject()

注意:我们将 full_system=False 传递给 Root,因为我们将在 SE 模式下进行模拟。


配置脚本:第一个 Hello 示例:在 C++ 中实例化和模拟

接下来,让我们通过从 m5 调用 instantiate 来告诉 gem5 在 C++ 中实例化我们的 SimObjects。将以下行添加到代码中以执行此操作。

m5.instantiate()

现在我们已经实例化了 SimObjects,我们可以告诉 gem5 开始模拟。我们通过从 m5 调用 simulate 来做到这一点。将以下行添加到代码中以执行此操作。

exit_event = m5.simulate()

此时,模拟将开始。它将返回一个保存模拟状态的对象。我们可以通过从 exit_event 调用 getCause 来查看模拟退出的原因。将以下行添加到代码中以执行此操作。

print(f"Exited simulation because: {exit_event.getCause()}.")

一切都在这里

这是我们配置脚本的完整版本。

import m5
from m5.objects.Root import Root
from m5.objects.HelloSimObject import HelloSimObject

root = Root(full_system=False)
root.hello = HelloSimObject()

m5.instantiate()
exit_event = m5.simulate()

print(f"Exited simulation because: {exit_event.getCause()}.")

模拟:第一个 Hello 示例

在基础 gem5 目录中使用以下命令运行。

./build/NULL/gem5.opt ./configs/bootcamp/hello-sim-object/first-hello-example.py

步骤 1 结束


一点绕路:m5.instantiate


绕路:m5.instantiate:SimObject 构造函数和连接端口

以下是 m5.instantiate 定义中的代码片段:

# Create the C++ sim objects and connect ports
    for obj in root.descendants():
        obj.createCCObject()
    for obj in root.descendants():
        obj.connectPorts()

当你调用 m5.instantiate 时,首先,所有 SimObjects 都被创建(即调用它们的 C++ 构造函数)。然后,创建所有 port 连接。如果你不知道 Port 是什么,别担心。我们将在后面的幻灯片中介绍。现在,将 ports 视为 SimObjects 相互发送数据的一种方式。


绕路:m5.instantiate:SimObject::init

以下是 instantiate 中稍后的代码片段。

    # Do a second pass to finish initializing the sim objects
    for obj in root.descendants():
        obj.init()

在此步骤中,gem5 将从每个 SimObject 调用 init 函数。init 是由 SimObject 类定义的虚函数。每个基于 SimObject 的类都可以重写此函数。init 函数的目的是类似于构造函数。但是,保证当从任何 SimObject 调用 init 函数时,所有 SimObjects 都已创建(即已调用它们的构造函数)。

Below is the declaration for init in src/sim/sim_object.hh.

    /* init() is called after all C++ SimObjects have been created and
    *  all ports are connected.  Initializations that are independent
    *  of unserialization but rely on a fully instantiated and
    *  connected SimObject graph should be done here. */
    virtual void init();

绕路:m5.instantiate:SimObject::initState、SimObject::loadState

下面显示了来自 instantiate 的另一个代码片段:

# Restore checkpoint (if any)
    if ckpt_dir:
        _drain_manager.preCheckpointRestore()
        ckpt = _m5.core.getCheckpoint(ckpt_dir)
        for obj in root.descendants():
            obj.loadState(ckpt)
    else:
        for obj in root.descendants():
            obj.initState()

initStateloadState 是初始化 SimObjects 的最后一步。但是,每次模拟只调用其中一个。loadState 被调用来从检查点反序列化 SimObject 的状态,而 initState 仅在启动新模拟时调用(即不从检查点)。

继续下一页。


Detour: m5.instantiate: SimObject::initState, SimObject::loadState: C++

Below is the declaration for initState and loadState in src/sim/sim_object.hh.

    /* loadState() is called on each SimObject when restoring from a
    *  checkpoint.  The default implementation simply calls
    *  unserialize() if there is a corresponding section in the
    *  checkpoint.  However, objects can override loadState() to get
    *  other behaviors, e.g., doing other programmed initializations
    *  after unserialize(), or complaining if no checkpoint section is
    *  found. */
    virtual void loadState(CheckpointIn &cp);
    /* initState() is called on each SimObject when *not* restoring
    *  from a checkpoint.  This provides a hook for state
    *  initializations that are only required for a "cold start". */
    virtual void initState();

我们稍后会看到

你可能已经注意到,我们也在配置脚本中调用了 m5.simulate。目前,HelloSimObject 在模拟期间不做任何有趣的事情。我们稍后将查看 simulate 的详细信息。


参数


步骤 2:SimObject 参数


让我们谈谈参数:模型 vs 参数

正如我们之前提到的,gem5 允许我们参数化模型。gem5 中的整个参数类集在 m5.params 下定义,因此让我们继续从 m5.params 将所有内容导入到 SimObject 定义文件中。在你选择的编辑器中打开 src/bootcamp/hello-sim-object/HelloSimObject.py 并向其中添加以下行。

from m5.params import *

现在,我们只需要为 HelloSimObject 定义一个参数。将以下行添加到同一文件(HelloSimObject 定义)中。你应该在 class HelloSimObject 的定义下添加此行。

num_hellos = Param.Int("Number of times to say Hello.")

请务必查看 src/python/m5/params.py 以获取有关不同参数类以及如何添加参数的更多信息。 注意Params 允许你为它们定义默认值。我强烈建议除非真的需要,否则不要定义默认值。


HelloSimObject 定义文件现在

这是你的 HelloSimObject 定义文件在更改后应该看起来的样子。

from m5.objects.SimObject import SimObject
from m5.params import *

class HelloSimObject(SimObject):
    type = "HelloSimObject"
    cxx_header = "bootcamp/hello-sim-object/hello_sim_object.hh"
    cxx_class = "gem5::HelloSimObject"

    num_hellos = Param.Int("Number times to say Hello.")

注意:对 HelloSimObject.py 的此更改将在下次编译 gem5 时向 HelloSimObjectParams 添加一个属性。这意味着我们现在可以在 C++ 代码中访问此参数。


使用 num_hellos

现在,我们将使用 num_hellosHelloSimObject 的构造函数中多次打印 Hello from ...。在你选择的编辑器中打开 src/bootcamp/hello-sim-object/hello_sim_object.cc

如下更改 HelloSimObject::HelloSimObject

HelloSimObject::HelloSimObject(const HelloSimObjectParams& params):
    SimObject(params)
{
    for (int i = 0; i < params.num_hellos; i++) {
        std::cout << "i: " << i << ", Hello from HelloSimObject's constructor!" << std::endl;
    }
}

确保不要删除 include 语句和任何包含 namespace gem5 的行

重新编译:我们现在需要做的就是重新编译 gem5。只需在基础 gem5 目录中运行以下命令即可。

scons build/NULL/gem5.opt -j$(nproc)

params/HelloSimObject.hh

正如我们之前提到的,SimObject 的参数在自动生成的头文件中定义,文件名与 SimObject 的名称相同。

现在我们已经向 HelloSimObject 添加了一个参数,它现在应该在 build/NULL/params/HelloSimObject.hh 中的 HelloSimObjectParams 下定义。

如果你查看头文件,你应该看到类似这样的内容。

###

#ifndef __PARAMS__HelloSimObject__
#define __PARAMS__HelloSimObject__

namespace gem5 {
class HelloSimObject;
} // namespace gem5
#include <cstddef>
#include "base/types.hh"

#include "params/SimObject.hh"

namespace gem5
{
struct HelloSimObjectParams
    : public SimObjectParams
{
    gem5::HelloSimObject * create() const;
    int num_hellos;
};

} // namespace gem5

#endif // __PARAMS__HelloSimObject__

配置脚本:第二个 Hello 示例

让我们创建 first-hello-example.py 的副本,命名为 second-hello-example.py。只需在基础 gem5 目录中运行以下命令即可。

cp configs/bootcamp/hello-sim-object/first-hello-example.py configs/bootcamp/hello-sim-object/second-hello-example.py

现在,在你选择的编辑器中打开 second-hello-example.py 并更改代码,以便在实例化 HelloSimObject 时为 num_hellos 传递一个值。下面是一个完整的示例。

import m5
from m5.objects.Root import Root
from m5.objects.HelloSimObject import HelloSimObject

root = Root(full_system=False)
root.hello = HelloSimObject(num_hellos=5)

m5.instantiate()
exit_event = m5.simulate()

print(f"Exited simulation because: {exit_event.getCause()}.")

模拟:第二个 Hello 示例

在基础 gem5 目录中使用以下命令运行。

./build/NULL/gem5.opt ./configs/bootcamp/hello-sim-object/second-hello-example.py

步骤 2 结束


步骤总结