GEM5教程--修改和拓展gem5(二)

三、调试GEM5

gem5通过调试标志提供对printf样式的代码跟踪/调试的支持。 这些标志允许每个组件具有许多调试打印语句,而无需同时启用所有这些语句。 运行gem5时,您可以从命令行指定要启用的调试标志。

1、使用调试标志(debug flags)

(1)例如,运行“创建简单配置脚本”中的第一个simple.py脚本时,如果启用DRAM调试标志,则会得到以下输出。 请注意,这会向控制台生成大量输出(大约7 MB)。

build/X86/gem5.opt --debug-flags=DRAM configs/learning_gem5/part1/simple.py | head -n 50

(2)或者,您可能想根据CPU正在执行的确切指令进行调试。 为此,Exec调试标志可能会很有用。 该调试标志显示了模拟CPU如何执行每条指令的详细信息。

build/X86/gem5.opt --debug-flags=Exec configs/learning_gem5/part1/simple.py | head -n 50

(3)实际上,Exec标志实际上是多个调试标志的集合。 通过使用–debug-help参数运行gem5,可以看到此信息以及所有可用的调试标志。

build/X86/gem5.opt --debug-help
2、添加一个新的调试标志

(1)在前面的部分中,我们使用了一个简单的std :: cout从SimObject中进行打印。 虽然可以在gem5中使用普通的C / C ++ I / O,但强烈建议不要这样做。 因此,我们现在将替换它,而使用gem5的调试工具。

创建新的调试标志时,我们首先必须在SConscript文件中声明它。使用hello对象代码(src/learning_gem5/)将以下内容添加到目录中的SConscript文件中。

DebugFlag('Hello')

这声明了一个调试标志“Hello”。现在,我们可以在SimObject的调试语句中使用它。

通过在SConscript文件中声明标志,将自动生成一个调试头,允许我们使用调试标志。头文件位于调试目录中,与我们在SConscript文件中声明的名称(和大写)相同。因此,我们需要在计划使用调试标志的任何文件中包含自动生成的头文件。
在hello_object.cc文件中,我们需要包含头文件:

#include "debug/Hello.hh"

现在我们已经包含了必要的头文件,让我们用这样的调试语句替换std::cout调用。

DPRINTF(Hello, "Created the hello object\n");

DPRINTF是C++宏。第一个参数是在SConscript文件中声明的调试标志。我们可以使用Hello标志,因为我们在src/learning_gem5/SConscript文件中声明了它。其余的参数是可变的,可以是传递给printf语句的任何参数。现在,如果重新编译gem5并使用“Hello”调试标志运行它,将得到以下结果。:

build/X86/gem5.opt --debug-flags=Hello configs/learning_gem5/part2/run_hello.py
3、调试输出

对于每个动态DPRINTF执行,有三件事被打印到stdout。首先,执行DPRINTF时的当前刻度。第二,名为DPRINTF的SimObject的名称。此名称通常是Python配置文件中的Python变量名。但是,名称是SimObject name()函数返回的任何内容。最后,您将看到传递给DPRINTF函数的任何格式字符串。

可以使用–debug file参数控制调试输出的位置。默认情况下,所有调试输出都打印到stdout。但是,可以将输出重定向到任何文件。文件是相对于主gem5输出目录而不是当前工作目录存储的。

4、使用DPRINTF以外的函数

DPRINTF是gem5中最常用的调试函数。然而,gem5提供了一些在特定情况下有用的其他功能。

(1)DPRINTF(Flag, VA_ARGS):

获取标志、格式字符串和任何格式参数。此函数要求当前作用域中存在name()函数(例如,从SimObject成员函数调用)。仅在启用标志时打印格式化字符串。

(2)DTRACE(Flag):

如果启用了标志(标志),则返回true,否则返回false。只有在启用调试标志(标志)时,这对于执行某些代码才有用。

(3)DDUMP(Flag, data, count):

打印长度计数字节的二进制数据(数据)。它以十六进制格式以用户可读的方式格式化。此宏还假设调用范围包含name()函数。

(4)DPRINTFS(Flag, SimObject, VA_ARGS):

与DPRINTF()类似,除了接受一个额外的参数,该参数是一个具有name()函数的对象,通常是一个SimObject。此函数用于从SimObject的私有子类(具有指向其所有者的指针)进行调试。

(5)DPRINTFR(Flag, VA_ARGS):

此函数输出调试语句而不打印名称。这对于在不是没有name()函数的SimObjects的对象中使用调试语句非常有用。

(6)DDUMPN(data, count),DPRINTFN(VA_ARGS),DPRINTFNR(VA_ARGS):

这些函数与前面的函数DDUMP()、DPRINTF()和DPRINTFR()类似,只是它们不接受标志作为参数。因此,每当启用调试时,这些语句都将始终打印。

只有在“opt”或“debug”模式下编译gem5时,才会启用所有这些函数。所有其他模式对上述函数使用空的占位符宏。因此,如果要使用调试标志,必须使用“gem5.opt”或“gem5.debug”。

四、事件驱动编程

gem5是一个事件驱动的模拟器。在本章中,我们将探讨如何创建和安排事件。我们将从简单的HelloObject,来自“创建一个非常简单的SimObject”。

1、创建简单事件回调

在gem5的事件驱动模型中,每个事件都有一个回调函数来处理事件。通常,这是一个从事件继承的类。然而,gem5提供了一个用于创建简单事件的包装函数。

(1)在HelloObject的头文件中,我们只需要声明一个新函数,每次事件触发时我们都要执行它(processEvent())。此函数不能接受任何参数,也不能返回任何内容。

(2)接下来,我们添加一个事件实例。在本例中,我们将使用EventFunctionWrapper,它允许我们执行任何函数。 我们还添加了一个startup()函数,将在下面进行解释。

 class HelloObject : public SimObject
{
  private:
    void processEvent();

    EventFunctionWrapper event;

  public:
    HelloObject(HelloObjectParams *p);

    void startup();
};

(3)接下来,我们必须在HelloObject的构造函数中构造此事件。eventFunctionWrapper有两个参数,一个要执行的函数和一个名称。名称通常是拥有事件的SimObject的名称。打印名称时,将在名称末尾附加一个自动的“.wrapped_function_event”。

第一个参数只是一个不接受任何参数且没有返回值的函数(std::function<void(void)>)。通常,这是一个调用成员函数的简单lambda函数。但是,它可以是任何你想要的功能。下面,我们在lambda([this])中对其进行大写,这样就可以调用类实例的成员函数。

HelloObject::HelloObject(HelloObjectParams *params) :
    SimObject(params), event([this]{processEvent();}, name())
{
    DPRINTF(Hello, "Created the hello object\n");
}
2、安排事件

最后,要处理事件,我们首先必须安排事件。为此,我们使用schedule()函数。此函数用于在将来的一段时间内安排事件的某些实例(事件驱动的模拟不允许在过去执行事件)。

schedule(Event *event, Tick when):计划在时间执行的事件(事件)。此函数将事件放入事件队列,并在勾选时执行事件。

我们将首先在添加到HelloObject类的startup()函数中安排事件。函数的作用是允许SimObjects安排内部事件。直到模拟第一次开始(即从Python配置文件调用simulate()函数),它才会被执行。

void
HelloObject::startup()
{
    schedule(event, 100);
}

在这里,我们只是将事件安排在tick 100执行。通常,您会使用curTick()的一些偏移量,但是由于我们知道startup()函数是在当前时间为0时调用的,所以我们可以使用显式的tick值。

3、更多事件调度

我们还可以在事件流程动作中安排新事件。例如,我们将向HelloObject添加一个延迟参数,并为触发事件的次数添加一个参数。在下一部分中,我们将从Python配置文件中访问这些参数。

(1)在HelloObject类声明中,为延迟和要触发的次数添加一个成员变量。

class HelloObject : public SimObject
{
  private:
    void processEvent();

    EventFunctionWrapper event;

    Tick latency;

    int timesLeft;

  public:
    HelloObject(HelloObjectParams *p);

    void startup();
};

(2)然后,在构造函数中添加延迟和剩余时间的默认值。

HelloObject::HelloObject(HelloObjectParams *params) :
    SimObject(params), event([this]{processEvent();}, name()),
    latency(100), timesLeft(10)
{
    DPRINTF(Hello, "Created the hello object\n");
}

(3)最后,更新startup()和processEvent()。

void
HelloObject::startup()
{
    schedule(event, latency);
}

void
HelloObject::processEvent()
{
    timesLeft--;
    DPRINTF(Hello, "Hello world! Processing the event! %d left\n", timesLeft);

    if (timesLeft <= 0) {
        DPRINTF(Hello, "Done firing!\n");
    } else {
        schedule(event, curTick() + latency);
    }
}

GEM5系列教程索引

GEM5教程–gem5开始之旅(一)

GEM5教程–gem5开始之旅(二)

GEM5教程–修改和拓展gem5(一)

GEM5教程–修改和拓展gem5(二)

GEM5教程–修改和拓展gem5(三)

GEM5教程–修改和拓展gem5(四)

GEM5教程-互联网络

GEM5教程-Garnet

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值