开发 gem5 模型
作者: Mahyar Samani, Jason Lowe-Power

编程事件驱动模拟

重要提示:本幻灯片基于已在 SimObjects 简介调试 gem5 中开发的内容。


补充:事件驱动模拟回顾


gem5 架构:模拟

gem5 是一个 离散事件模拟器

在每个时间步,gem5:

  1. 将队列头部的事件出队
  2. 执行该事件
  3. 调度新事件

Example of discrete event simulation bg right:55% fit


gem5 architecture: Simulation

gem5 is a discrete event simulator

At each timestep, gem5:

  1. Event at the head is dequeued
  2. The event is executed
  3. New events are scheduled

Example of discrete event simulation bg right:55% fit


gem5 architecture: Simulation

gem5 is a discrete event simulator

At each timestep, gem5:

  1. Event at the head is dequeued
  2. The event is executed
  3. New events are scheduled

所有 SimObject 都可以将事件入队到事件队列中

Example of discrete event simulation bg right:55% fit


离散事件模拟示例

Example of discrete event simulation fit


Discrete event simulation example

Example of discrete event simulation fit


Discrete event simulation example

Example of discrete event simulation fit

要模拟需要时间的事物,在将来调度 下一个 事件(当前事件的延迟)。 可以调用函数而不是调度事件,但它们会在 同一个 tick 中发生。


离散事件模拟

“时间”需要一个单位。 在 gem5 中,我们使用一个名为 “Tick” 的单位。

需要将模拟的 “tick” 转换为用户可理解的时间,例如秒。

这是全局模拟 tick 速率。 通常这是每个 tick 1 ps 或每秒 $10^{12}$ 个 tick。


事件驱动模拟:抽象思考

事件驱动模拟 是一种模拟方法,模拟器对 事件 的发生做出反应。每种类型的 事件 都有其特定的反应。

事件 的反应通过调用特定函数来定义,该函数称为 回调 函数。

回调 函数本身可能会引起新的 事件 发生。新的 事件 可以与导致调用 回调 函数的 事件 类型相同或不同。


事件驱动模拟:抽象思考(续)

让我们看一个例子来更好地理解它。假设在时间 $t_0$ 发生事件 $A$。模拟器将通过调用 $A.callback$ 来做出反应。假设下面是 $A.callback$ 的定义。

# This is a pseudo-code (it's not python or C++)
def A::callback():
    print("Reacting to Event A")
    delay = 1000
    curr_time = Simulator.get_current_time()
    schedule(B, current_time + delay)

这样,每次事件 $A$ 发生时,事件 $B$ 将在 1000 个时间单位后发生。然后,模拟器将通过调用 $B.callback$ 来做出反应。


事件驱动模拟:实践视角

事件驱动模拟器需要提供以下功能:


事件驱动模拟:实践视角(续)

让我们看看如果你要编写自己的硬件模拟器,这会是什么样子。

1- 在开始时($t = 0$),模拟器将调度一个使 CPU 核心获取指令的事件。让我们将这种类型的事件称为 CPU::fetch

2- 当模拟器到达($t = 0$)时,模拟器将对此时调度的所有 事件 做出反应。如果我们有 2 个核心,这意味着模拟器需要调用 cpu_0::fetch::callbackcpu_1::fetch::callback

3- CPU::fetch::callback 然后必须找出下一个程序计数器是什么,并向指令缓存发送请求以获取指令。因此,它将在将来调度一个像 CPU::accessICache 这样的事件。

为了施加获取的延迟,我们将在 current_time + fetch_delay 调度 CPU::accessICache,即 schedule(CPU::accessICache, currentTime() + fetch_delay)。这将在将来 fetch_delay 个时间单位后引发两个 CPU::accessICache 事件(例如 cpu_0::accessICachecpu_1::accessICache)。


事件驱动模拟:实践视角(续)

4- 当模拟器完成对在 $t = 0$ 发生的所有事件的反应后,它将把时间移动到最近的事件调度发生时间(在这种情况下是 $t = 0 + fetch_delay$)。

5- 在时间 $t= fetch_delay$,模拟器将调用 cpu_0::accessICache::callbackcpu_1::accessICache::callback 来对这两个事件做出反应。这些事件可能会访问指令缓存,然后可能会调度事件来处理缓存未命中,如 Cache::handleMiss

6- 这个过程将持续到我们正在模拟的程序完成。


gem5 中的事件驱动模拟

让我们查看 src/sim/eventq.hh。在那里,您将看到一个类 Event 的声明,它有一个名为 process 的函数,如下所示。

  public:

    /*
     * 当事件被处理(发生)时调用此成员函数。
     * 没有默认实现;每个子类必须提供自己的实现。
     * 事件在处理后不会自动删除(以允许静态分配的事件对象)。
     *
     * 如果设置了 AutoDestroy 标志,对象在处理后会被删除。
     *
     * @ingroup api_eventq
     */
    virtual void process() = 0;

事件的一个假设示例

现在让我们看看类 Event 如何在模拟 CPU 的 SimObject 中使用。注意:这是一个假设示例,完全不是 gem5 中已实现的内容。

class CPU: public ClockedObject
{
  public:
    void processFetch(); // Function to model fetch
  private:
    class FetchEvent: public Event
    {
      private:
        CPU* owner;
      public:
        FetchEvent(CPU* owner): Event(), owner(owner)
        {}
        virtual void process() override
        {
            owner->processFetch(); // call processFetch from the CPU that owns this
        }
    };
    FetchEvent nextFetch;
};

在这个示例中,每次 FetchEvent 的实例发生时(cpu_0::nextFetch 而不是 CPU::nextFetch),模拟器将从拥有该事件的 CPU 实例调用 processFetch


EventFunctionWrapper

除了类 Event 之外,您可以在 src/sim/eventq.hh 中找到 EventFunctionWrapper 的声明。此类用一个可调用对象包装一个 event,当调用 Event::process 时将调用该对象。来自 src/sim/eventq.hh 的以下行值得查看。

  public:
    /**
     * 此函数将函数包装成事件,以便稍后执行。
     * @ingroup api_eventq
     */
    EventFunctionWrapper(const std::function<void(void)> &callback,
                         const std::string &name,
                         bool del = false,
                         Priority p = Default_Pri)
        : Event(p), callback(callback), _name(name)
    {
        if (del)
            setFlags(AutoDelete);
    }
    void process() { callback(); }

对于 EventFunctionWrapper,函数 process 被定义为对 callback 的调用,该 callback 作为参数传递给 EventFunctionWrapper 的构造函数。此外,我们需要通过构造函数为每个对象指定一个名称。


补充:m5.simulate: SimObject::startup

以下是来自 src/python/m5/simulate.pym5.simulate 定义的代码片段:

def simulate(*args, **kwargs):
    # ...
    if need_startup:
        root = objects.Root.getInstance()
        for obj in root.descendants():
            obj.startup()
        need_startup = False

通过调用 m5.simulate,gem5 将调用系统中每个 SimObject 的函数 startup。让我们查看 src/sim/sim_object.hhSimObject 的头文件中的 startup

    /**
     * startup() 是模拟前的最终初始化调用。
     * 所有状态都已初始化(包括未序列化的状态,如果有的话,
     * 例如 curTick() 值),因此这是为需要它们的对象调度初始事件
     * 的适当位置。
    */
    virtual void startup();

startup 是我们调度触发模拟的初始 事件 的地方(在我们的假设场景中是 CPU::nextFetch)。


步骤 1:SimObject 事件


练习 1:nextHelloEvent

nextHelloEvent

练习 1 的完成文件位于目录 materials/03-Developing-gem5-models/03-event-driven-sim/step-1 下。

现在,让我们向 HelloSimObject 添加一个 event,以定期打印 Hello ... 一定次数(即 num_hellos)。让我们将其添加到 src/bootcamp/hello-sim-object.hhHelloSimObject 的头文件中。

首先,我们需要包含 sim/eventq.hh,以便我们可以添加一个类型为 EventFunctionWrapper 的成员。添加以下行来完成此操作。记住:确保遵循正确的包含顺序。

#include "sim/eventq.hh

nextHelloEvent

接下来,我们需要声明一个类型为 EventFunctionWrapper 的成员,我们将其称为 nextHelloEvent

我们还需要定义一个 std::function<void>() 作为 nextHelloEventcallback 函数。

为此,请将以下行添加到 HelloSimObject 类的声明中。

  private:
    EventFunctionWrapper nextHelloEvent;
    void processNextHelloEvent();

nextHelloEvent:头文件

这是您的 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/eventq.hh"
#include "sim/sim_object.hh"

namespace gem5
{

class HelloSimObject: public SimObject
{
  private:
    EventFunctionWrapper nextHelloEvent;
    void processNextHelloEvent();

  public:
    HelloSimObject(const HelloSimObjectParams& params);
};

} // namespace gem5

#endif // __BOOTCAMP_HELLO_SIM_OBJECT_HELLO_SIM_OBJECT_HH__

nextHelloEvent:HelloSimObject:构造函数

现在,让我们更改 HelloSimObject 构造函数的定义以初始化 nextHelloEvent。让我们在 HelloSimObject::HelloSimObject 的初始化列表中添加以下行,您可以在 src/bootcamp/hello-sim-object/hello_sim_object.cc 中找到它。

    nextHelloEvent([this](){ processNextHelloEvent(); }, name() + "nextHelloEvent")

这是 HelloSimObject::HelloSimObject 在更改后应该看起来的样子。

HelloSimObject::HelloSimObject(const HelloSimObjectParams& params):
    SimObject(params),
    nextHelloEvent([this](){ processNextHelloEvent(); }, name() + "nextHelloEvent")
{
    for (int i = 0; i < params.num_hellos; i++) {
        std::cout << "i: " << i << ", Hello from HelloSimObject's constructor!" << std::endl;
    }
    DPRINTF(HelloExampleFlag, "%s: Hello from HelloSimObject's constructor!\n", __func__);
}

nextHelloEvent 回调:processNextHelloEvent

现在,让我们定义 processNextHelloEvent,以每 500 Ticks 打印 Hello ... num_hellos 次。为了跟踪我们已打印的 Hello ... 语句数量,让我们声明一个 private 成员来计数。将以下声明添加到 src/bootcamp/hello-sim-object/hello_sim_object.hhHelloSimObject 类的 private 作用域。

  private:
    int remainingHellosToPrintByEvent;

这是 HelloSimObject 的声明在更改后应该看起来的样子。

class HelloSimObject: public SimObject
{
  private:
    int remainingHellosToPrintByEvent;

    EventFunctionWrapper nextHelloEvent;
    void processNextHelloEvent();

  public:
    HelloSimObject(const HelloSimObjectParams& params);
};

nextHelloEvent 回调:processNextHelloEvent(续)

现在,让我们更新 HelloSimObject 的构造函数,将 remainingHellosToPrintByEvent 初始化为 params.num_hellos。通过在 nextHelloEvent 的初始化行上方添加以下行来完成此操作。

    remainingHellosToPrintByEvent(params.num_hellos)

让我们还通过向 HelloSimObject::HelloSimObject 的主体开头添加如下所示的 fatal_if 语句,确保用户为 num_hellos 传递正数。

    fatal_if(params.num_hellos <= 0, "num_hellos should be positive!");

nextHelloEvent 回调:processNextHelloEvent:即将完成

这是 HelloSimObject::HelloSimObject 在更改后应该看起来的样子。

HelloSimObject::HelloSimObject(const HelloSimObjectParams& params):
    SimObject(params),
    remainingHellosToPrintByEvent(params.num_hellos),
    nextHelloEvent([this](){ processNextHelloEvent(); }, name() + "nextHelloEvent")
{
    fatal_if(params.num_hellos <= 0, "num_hellos should be positive!");
    for (int i = 0; i < params.num_hellos; i++) {
        std::cout << "i: " << i << ", Hello from HelloSimObject's constructor!" << std::endl;
    }
    DPRINTF(HelloExampleFlag, "%s: Hello from HelloSimObject's constructor!\n", __func__);
}

nextHelloEvent 回调:processNextHelloEvent:最后一步!

现在我们已经准备好定义 HelloSimObject::processNextHelloEvent。让我们将以下代码添加到 src/bootcamp/hello-sim-object/hello_sim_object.cc

void
HelloSimObject::processNextHelloEvent()
{
    std::cout << "tick: " << curTick() << ", Hello from HelloSimObject::processNextHelloEvent!" << std::endl;
    remainingHellosToPrintByEvent--;
    if (remainingHellosToPrintByEvent > 0) {
        schedule(nextHelloEvent, curTick() + 500);
    }
}

查看代码,每次 nextHelloEvent 发生时(即调用 processNextHelloEvent),我们执行以下操作:


HelloSimObject::startup:头文件

让我们在 HelloSimObject 中添加 startup 的声明。我们将使用 startup 来调度 nextHelloEvent 的第一次出现。由于 startupHelloSimObjectSimObject 继承的 publicvirtual 函数,我们将在 HelloSimObjectpublic 作用域中添加以下行。我们将添加 override 指令,告诉编译器我们打算覆盖 SimObject 中的原始定义。

  public:
    virtual void startup() override;

这是 HelloSimObject 的声明在更改后应该看起来的样子。

class HelloSimObject: public SimObject
{
  private:
    int remainingHellosToPrintByEvent;

    EventFunctionWrapper nextHelloEvent;
    void processNextHelloEvent();

  public:
    HelloSimObject(const HelloSimObjectParams& params);
    virtual void startup() override;
};

HelloSimObject::startup:源文件

现在,让我们定义 HelloSimObject::startup 来调度 nextHelloEvent。由于 startup 在模拟开始时(即 $t = 0\ Ticks$)被调用,并且只调用一次,让我们放置 panic_if 语句来断言它们。此外,nextHelloEvent 此时不应该被调度,所以让我们也断言这一点。

将以下代码添加到 src/bootcamp/hello-sim-object/hello_sim_object.cc 以定义 HelloSimObject::startup

void
HelloSimObject::startup()
{
    panic_if(curTick() != 0, "startup called at a tick other than 0");
    panic_if(nextHelloEvent.scheduled(), "nextHelloEvent is scheduled before HelloSimObject::startup is called!");
    schedule(nextHelloEvent, curTick() + 500);
}

当前版本:Python 脚本

我们已经准备好编译 gem5 以应用更改。但在编译之前,让我们查看每个文件应该看起来的样子。

Import("*")

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

Source("hello_sim_object.cc")

DebugFlag("HelloExampleFlag")
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 of times to say Hello.")

当前版本:头文件

#ifndef __BOOTCAMP_HELLO_SIM_OBJECT_HELLO_SIM_OBJECT_HH__
#define __BOOTCAMP_HELLO_SIM_OBJECT_HELLO_SIM_OBJECT_HH__

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

namespace gem5
{

class HelloSimObject: public SimObject
{
  private:
    int remainingHellosToPrintByEvent;

    EventFunctionWrapper nextHelloEvent;
    void processNextHelloEvent();

  public:
    HelloSimObject(const HelloSimObjectParams& params);
    virtual void startup() override;
};

} // namespace gem5

#endif // __BOOTCAMP_HELLO_SIM_OBJECT_HELLO_SIM_OBJECT_HH__

当前版本:源文件

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

#include <iostream>

namespace gem5
{

HelloSimObject::HelloSimObject(const HelloSimObjectParams& params):
    SimObject(params),
    remainingHellosToPrintByEvent(params.num_hellos),
    nextHelloEvent([this](){ processNextHelloEvent(); }, name() + "nextHelloEvent")
{
    fatal_if(params.num_hellos <= 0, "num_hellos should be positive!");
    for (int i = 0; i < params.num_hellos; i++) {
        std::cout << "i: " << i << ", Hello from HelloSimObject's constructor!" << std::endl;
    }
    DPRINTF(HelloExampleFlag, "%s: Hello from HelloSimObject's constructor!\n", __func__);
}

Continued

void
HelloSimObject::startup()
{
    panic_if(curTick() != 0, "startup called at a tick other than 0");
    panic_if(nextHelloEvent.scheduled(), "nextHelloEvent is scheduled before HelloSimObject::startup is called!");
    schedule(nextHelloEvent, curTick() + 500);
}

void
HelloSimObject::processNextHelloEvent()
{
    std::cout << "tick: " << curTick() << ", Hello from HelloSimObject::processNextHelloEvent!" << std::endl;
    remainingHellosToPrintByEvent--;
    if (remainingHellosToPrintByEvent > 0) {
        schedule(nextHelloEvent, curTick() + 500);
    }
}

} // namespace gem5

让我们编译和模拟

如果您想使用完成的示例,请将您的工作移动到另一个文件夹,并在 gem5 基础目录中运行以下命令以复制示例。

cp -r ../materials/03-Developing-gem5-models/03-event-driven-sim/step-1/src/bootcamp src

如果您想使用完成的配置脚本,请在 gem5 基础目录中运行以下命令:

cp -r ../materials/03-Developing-gem5-models/03-event-driven-sim/step-1/configs/bootcamp configs

让我们编译和模拟(续)

在 gem5 基础目录中运行以下命令以重新构建 gem5。

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

现在,通过在 gem5 基础目录中运行以下命令来模拟您的配置。

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

在下一张幻灯片中,有您应该看到的内容的录制。



步骤 1 结束


步骤 2:SimObjects 作为参数


练习 2:GoodByeSimObject

在这一步中,我们将学习如何将 SimObject 添加为参数。为此,让我们首先构建我们的第二个 SimObject,称为 GoodByeSimObject。如您所记得的,我们需要在 Python 中声明 GoodByeSimObject。让我们打开 src/bootcamp/hello-sim-object/HelloSimObject.py 并向其中添加以下代码。

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

另外,让我们通过编辑 SConscript 来注册 GoodByeSimObject。打开 src/bootcamp/hello-sim-object/SConscript 并将 GoodByeSimObject 添加到 HelloSimObject.py 中的 SimObjects 列表中。这是该行在更改后应该看起来的样子。

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

GoodByeExampleFlag

让我们将 goodbye_sim_object.cc(我们稍后将创建)添加为源文件。通过将以下行添加到 src/bootcamp/hello-sim-object/SConscript 来完成此操作。

Source("goodbye_sim_object.cc")

让我们还添加 GoodByeExampleFlag,以便我们可以在 GoodByeSimObject 中使用它来打印调试信息。通过将以下行添加到 src/bootcamp/hello-sim-object/SConscript 来完成此操作。

DebugFlag("GoodByeExampleFlag")

GoodByeExampleFlag(续)

CompoundFlag

除了 DebugFlags 之外,我们还可以定义 CompoundFlags,当它们被启用时,会启用一组 DebugFlags。让我们定义一个名为 GreetFlagCompoundFlag,它将启用 HelloExampleFlagGoodByeExampleFlag。为此,请将以下行添加到 src/bootcamp/hello-sim-object/SConscript

CompoundFlag("GreetFlag", ["HelloExampleFlag", "GoodByeExampleFlag"])

当前版本:HelloSimObject.py

这是 HelloSimObject.py 在更改后应该看起来的样子。

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 of times to say Hello.")

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

当前版本:SConscript

这是 SConscript 在更改后应该看起来的样子。

Import("*")

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

Source("hello_sim_object.cc")
Source("goodbye_sim_object.cc")

DebugFlag("HelloExampleFlag")
DebugFlag("GoodByeExampleFlag")
CompoundFlag("GreetFlag", ["HelloExampleFlag", "GoodByeExampleFlag"])

GoodByeSimObject:规范

在我们的设计中,让 GoodByeSimObject 调试打印一个 GoodBye ... 语句。它将在调用 sayGoodBye 函数时执行此操作,该函数将调度一个 event 来说 GoodBye。

在接下来的幻灯片中,您可以找到 src/bootcamp/hello-sim-object/goodbye_sim_object.hhsrc/bootcamp/hello-sim-object/goodbye_sim_object.cc 的完成版本。

重要提示:我不会详细介绍这些文件的细节,请仔细查看此文件,并确保您理解每一行应该做什么。


GoodByeSimObject:头文件

#ifndef __BOOTCAMP_HELLO_SIM_OBJECT_GOODBYE_SIM_OBJECT_HH__
#define __BOOTCAMP_HELLO_SIM_OBJECT_GOODBYE_SIM_OBJECT_HH__

#include "params/GoodByeSimObject.hh"
#include "sim/eventq.hh"
#include "sim/sim_object.hh"

namespace gem5
{

class GoodByeSimObject: public SimObject
{
  private:
    EventFunctionWrapper nextGoodByeEvent;
    void processNextGoodByeEvent();

  public:
    GoodByeSimObject(const GoodByeSimObject& params);

    void sayGoodBye();
};

} // namespace gem5

#endif // __BOOTCAMP_HELLO_SIM_OBJECT_GOODBYE_SIM_OBJECT_HH__

GoodByeSimObject:源文件

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

#include "base/trace.hh"
#include "debug/GoodByeExampleFlag.hh"

namespace gem5
{

GoodByeSimObject::GoodByeSimObject(const GoodByeSimObjectParams& params):
    SimObject(params),
    nextGoodByeEvent([this]() { processNextGoodByeEvent(); }, name() + "nextGoodByeEvent" )
{}

void
GoodByeSimObject::sayGoodBye() {
    panic_if(nextGoodByeEvent.scheduled(), "GoodByeSimObject::sayGoodBye called while nextGoodByeEvent is scheduled!");
    schedule(nextGoodByeEvent, curTick() + 500);
}

void
GoodByeSimObject::processNextGoodByeEvent()
{
    DPRINTF(GoodByeExampleFlag, "%s: GoodBye from GoodByeSimObejct::processNextGoodByeEvent!\n", __func__);
}

} // namespace gem5

GoodByeSimObject 作为参数

在这一步中,我们将向 HelloSimObject 添加一个类型为 GoodByeSimObject 的参数。为此,我们只需在 src/bootcamp/hello-sim-object/HelloSimObject.pyHelloSimObject 的声明中添加以下行。

    goodbye_object = Param.GoodByeSimObject("GoodByeSimObject to say goodbye after done saying hello.")

这是 HelloSimObject 的声明在更改后应该看起来的样子。

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

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

    goodbye_object = Param.GoodByeSimObject("GoodByeSimObject to say goodbye after done saying hello.")

HelloSimObject:头文件

添加 goodbye_object 参数将向 HelloSimObjectParams 添加一个类型为 gem5::HelloSimObject* 的新成员。我们将在以后看到这一点。

我们可以使用该参数来初始化指向 GoodByeSimObject 对象的指针,当我们用完要打印的 Hello ... 语句时,我们将使用它来调用 sayGoodBye

首先,让我们通过在 src/bootcamp/hello-sim-object/hello_sim_object.hh 中添加以下行来包含 GoodByeSimObject 的头文件。记住:遵循 gem5 的包含顺序约定。

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

现在,让我们向 HelloSimObject 添加一个指向 GoodByeSimObject 的新成员。将以下行添加到 src/bootcamp/hello-sim-object/hello_sim_object.hh

  private:
    GoodByeSimObject* goodByeObject;

HelloSimObject:源文件

现在,让我们通过在 HelloSimObject::HelloSimObject 的初始化列表中添加以下行来从参数初始化 goodByeObject

    goodByeObject(params.goodbye_object)

现在,让我们在 processNextHelloEvent 中为 if (remainingHellosToPrintByEvent > 0) 添加一个 else 主体,以从 goodByeObject 调用 sayGoodBye。以下是 src/bootcamp/hello-sim-object/hello_sim_object.ccprocessNextHelloEvent 在更改后应该看起来的样子。

void
HelloSimObject::processNextHelloEvent()
{
    std::cout << "tick: " << curTick() << ", Hello from HelloSimObject::processNextHelloEvent!" << std::endl;
    remainingHellosToPrintByEvent--;
    if (remainingHellosToPrintByEvent > 0) {
        schedule(nextHelloEvent, curTick() + 500);
    } else {
        goodByeObject->sayGoodBye();
    }
}

当前版本:HelloSimObject:头文件

这是 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 "bootcamp/hello-sim-object/goodbye_sim_object.hh"
#include "params/HelloSimObject.hh"
#include "sim/eventq.hh"
#include "sim/sim_object.hh"

namespace gem5
{

class HelloSimObject: public SimObject
{
  private:
    int remainingHellosToPrintByEvent;
    GoodByeSimObject* goodByeObject;

    EventFunctionWrapper nextHelloEvent;
    void processNextHelloEvent();

  public:
    HelloSimObject(const HelloSimObjectParams& params);
    virtual void startup() override;
};

} // namespace gem5

#endif // __BOOTCAMP_HELLO_SIM_OBJECT_HELLO_SIM_OBJECT_HH__

当前版本:HelloSimObject:源文件

这是 src/bootcamp/hello-sim-object/hello_sim_object.cc 在更改后应该看起来的样子。

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

#include <iostream>

#include "base/trace.hh"
#include "debug/HelloExampleFlag.hh"

namespace gem5
{

HelloSimObject::HelloSimObject(const HelloSimObjectParams& params):
    SimObject(params),
    remainingHellosToPrintByEvent(params.num_hellos),
    goodByeObject(params.goodbye_object),
    nextHelloEvent([this](){ processNextHelloEvent(); }, name() + "nextHelloEvent")
{
    fatal_if(params.num_hellos <= 0, "num_hellos should be positive!");
    for (int i = 0; i < params.num_hellos; i++) {
        std::cout << "i: " << i << ", Hello from HelloSimObject's constructor!" << std::endl;
    }
    DPRINTF(HelloExampleFlag, "%s: Hello from HelloSimObject's constructor!\n", __func__);
}

Continued

void
HelloSimObject::startup()
{
    panic_if(curTick() != 0, "startup called at a tick other than 0");
    panic_if(nextHelloEvent.scheduled(), "nextHelloEvent is scheduled before HelloSimObject::startup is called!");
    schedule(nextHelloEvent, curTick() + 500);
}

void
HelloSimObject::processNextHelloEvent()
{
    std::cout << "tick: " << curTick() << ", Hello from HelloSimObject::processNextHelloEvent!" << std::endl;
    remainingHellosToPrintByEvent--;
    if (remainingHellosToPrintByEvent > 0) {
        schedule(nextHelloEvent, curTick() + 500);
    } else {
        goodByeObject->sayGoodBye();
    }
}

} // namespace gem5

让我们构建

如果您想运行完成的示例,请将您的工作移动到另一个目录,然后在 gem5 基础目录中运行以下命令。

cp -r ../materials/03-Developing-gem5-models/03-event-driven-sim/step-2/src/bootcamp src

在所有更改后,在 gem5 基础目录中运行以下命令以重新构建 gem5。

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

让我们构建(续)

编译完成后,查看 build/NULL/params/HelloSimObject.hh。注意 gem5::GoodByeSimObject * goodbye_object 已被添加。以下是 HelloSimObjectParams 的声明。

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

} // namespace gem5

配置脚本

让我们通过复制 configs/bootcamp/hello-sim-object/second-hello-example.py 来创建一个新的配置脚本(third-hello-example.py)。通过在 gem5 基础目录中运行以下命令来完成此操作。

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

现在,我们需要为 HelloSimObjectgoodbye_object 参数赋值。我们将为此参数创建一个 GoodByeSimObject 对象。

让我们从导入 GoodByeSimObject 开始。只需将 GoodByeSimObject 添加到 from m5.objects.HelloSimObject import HelloSimObject 即可。这是导入语句在更改后应该看起来的样子。

from m5.objects.HelloSimObject import HelloSimObject, GoodByeSimObject

配置脚本(续)

现在,让我们添加以下行以从 root.hellogoodbye_object 赋值。

root.hello.goodbye_object = GoodByeSimObject()

这是 configs/bootcamp/hello-sim-object/third-hello-example.py 在更改后应该看起来的样子。

import m5
from m5.objects.Root import Root
from m5.objects.HelloSimObject import HelloSimObject, GoodByeSimObject

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

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

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

让我们模拟

如果您想运行完成的脚本,请在 gem5 基础文件夹中运行以下命令,将完成的 third-hello-example.py 移动到 gem5 目录中:

cp -r ../materials/03-Developing-gem5-models/03-event-driven-sim/step-2/configs/bootcamp/hello-sim-object/third-hello-example.py configs/bootcamp/hello-sim-object

现在让我们分别使用启用 GoodByeExampleFlag 和启用 GreetFlag 来模拟 third-hello-example.py,并比较输出。

在 gem5 基础目录中运行以下命令,以启用 GoodByeExampleFlag 来模拟 third-hello-example.py

./build/NULL/gem5.opt --debug-flags=GoodByeExampleFlag configs/bootcamp/hello-sim-object/third-hello-example.py

在下一张幻灯片中,有我运行上述命令时的终端录制。



让我们模拟:第 2 部分

在 gem5 基础目录中运行以下命令,以启用 GreetFlag 来模拟 third-hello-example.py

./build/NULL/gem5.opt --debug-flags=GreetFlag configs/bootcamp/hello-sim-object/third-hello-example.py

在下一张幻灯片中,有我运行上述命令时的终端录制。



步骤 2 结束