GEM5教程--修改和拓展gem5(三)
五、向SimObjects和更多事件添加参数
GEM5的Python接口最强大的部分之一是将参数从Python传递到GEM5中的C++对象的能力。在本部分中,我们将探讨SimObjects的一些参数类型,以及如何在前面部分的简单HelloObject的基础上使用它们。
1、简单参数
首先,我们将为延迟和在HelloObject中触发事件的次数添加参数。要添加参数,请修改SimObject Python文件(src/learning-gem5/HelloObject.py)中的HelloObject类。通过向包含Param类型的Python类添加新语句来设置参数。
(1)
例如,下面的代码作为参数time_to_wait,它是一个“Latency”参数,而number_of_fires是一个整数参数。
class HelloObject(SimObject):
type = 'HelloObject'
cxx_header = "learning_gem5/hello_object.hh"
time_to_wait = Param.Latency("Time before firing the event")
number_of_fires = Param.Int(1, "Number of times to fire the event before "
"goodbye")
Param.声明类型为的参数。常见的类型包括整数的Int类型、浮点的Float类型等等。这些类型的行为类似于常规的Python类。
每个参数声明接受一个或两个参数。当给定两个参数(如上面的number_of_fires)时,第一个参数是参数的默认值。在这种情况下,如果在Python配置文件中实例化HelloObject而没有为number of fires指定任何值,那么它将采用默认值1。 参数声明的第二个参数是参数的简短描述。这一定是一个Python字符串。如果您只为参数声明指定一个参数,那么它就是描述(关于time_to_wait)。
gem5还支持许多复杂的参数类型,这些类型不仅仅是内置类型。例如,time_to_wait就是一个延迟。延迟将值作为时间值作为字符串,并将其转换为模拟器信号。例如,如果默认滴答率为1皮秒(每秒10^12滴答或1T赫兹),“1ns”将自动转换为1000。还有其他方便的参数,如百分比、周期、内存大小等等。
(2)
一旦在 SimObject文件中声明这些参数,就需要在构造函数中将它们的值复制到C++类。以下代码显示对HelloObject构造函数的更改。
HelloObject::HelloObject(HelloObjectParams *params) :
SimObject(params),
event(*this),
myName(params->name),
latency(params->time_to_wait),
timesLeft(params->number_of_fires)
{
DPRINTF(Hello, "Created the hello object with the name %s\n", myName);
}
(3)
这里,我们使用参数的值作为延迟和剩余时间的默认值。此外,我们存储参数对象中的名称,以便稍后在成员变量myName中使用。每个params实例化都有一个名称,该名称在实例化时来自Python配置文件。
但是,在这里指定名称只是使用params对象的一个例子。对于所有SimObjects,都有一个始终返回名称的name()函数。因此,永远不需要存储上述名称。
在HelloObject类声明中,为名称添加一个成员变量。
class HelloObject : public SimObject
{
private:
void processEvent();
EventWrapper<HelloObject, &HelloObject::processEvent> event;
std::string myName;
Tick latency;
int timesLeft;
public:
HelloObject(HelloObjectParams *p);
void startup();
};
(4)
time_to_wait参数没有默认值。因此,我们需要更新Python配置文件(run_hello.py)来指定这个值。
root.hello = HelloObject(time_to_wait = '2us')
或者,我们可以指定time_to_wait作为成员变量。两个选项完全相同,因为C++对象直到 m5.instantiate() 才被创建。
root.hello = HelloObject()
root.hello.time_to_wait = '2us'
2、其他SimObjects作为参数
也可以将其他SimObjects指定为参数。为了演示这一点,我们将创建一个新的SimObject,GoodbyeObject。这个对象将有一个简单的函数来和另一个SimObject说“再见”。为了让它更有趣,GoodbyeObject将有一个缓冲区来写消息,并且有一个有限的带宽来写消息。
(1)
首先,在SConscript文件中声明SimObject:
Import('*')
SimObject('HelloObject.py')
Source('hello_object.cc')
Source('goodbye_object.cc')
DebugFlag('Hello')
(2)
接下来,需要在SimObject Python文件中声明新的SimObject。由于GoodbyeObject与HelloObject高度相关,我们将使用相同的文件。您可以将以下代码添加到HelloObject.py。 此对象有两个参数,都具有默认值。第一个参数是缓冲区的大小,是一个MemorySize参数。第二个是写带宽,它指定填充缓冲区的速度。一旦缓冲区已满,模拟将退出。
class GoodbyeObject(SimObject):
type = 'GoodbyeObject'
cxx_header = "learning_gem5/goodbye_object.hh"
buffer_size = Param.MemorySize('1kB',
"Size of buffer to fill with goodbye")
write_bandwidth = Param.MemoryBandwidth('100MB/s', "Bandwidth to fill "
"the buffer")
(3)
现在,我们需要实现 GoodbyeObject
。
#ifndef __LEARNING_GEM5_GOODBYE_OBJECT_HH__
#define __LEARNING_GEM5_GOODBYE_OBJECT_HH__
#include <string>
#include "params/GoodbyeObject.hh"
#include "sim/sim_object.hh"
class GoodbyeObject : public SimObject
{
private:
void processEvent();
/**
* Fills the buffer for one iteration. If the buffer isn't full, this
* function will enqueue another event to continue filling.
*/
void fillBuffer();
EventWrapper<GoodbyeObject, &GoodbyeObject::processEvent> event;
/// The bytes processed per tick
float bandwidth;
/// The size of the buffer we are going to fill
int bufferSize;
/// The buffer we are putting our message in
char *buffer;
/// The message to put into the buffer.
std::string message;
/// The amount of the buffer we've used so far.
int bufferUsed;
public:
GoodbyeObject(GoodbyeObjectParams *p);
~GoodbyeObject();
/**
* Called by an outside object. Starts off the events to fill the buffer
* with a goodbye message.
*
* @param name the name of the object we are saying goodbye to.
*/
void sayGoodbye(std::string name);
};
#endif // __LEARNING_GEM5_GOODBYE_OBJECT_HH__
#include "learning_gem5/goodbye_object.hh"
#include "debug/Hello.hh"
#include "sim/sim_exit.hh"
GoodbyeObject::GoodbyeObject(GoodbyeObjectParams *params) :
SimObject(params), event(*this), bandwidth(params->write_bandwidth),
bufferSize(params->buffer_size), buffer(nullptr), bufferUsed(0)
{
buffer = new char[bufferSize];
DPRINTF(Hello, "Created the goodbye object\n");
}
GoodbyeObject::~GoodbyeObject()
{
delete[] buffer;
}
void
GoodbyeObject::processEvent()
{
DPRINTF(Hello, "Processing the event!\n");
fillBuffer();
}
void
GoodbyeObject::sayGoodbye(std::string other_name)
{
DPRINTF(Hello, "Saying goodbye to %s\n", other_name);
message = "Goodbye " + other_name + "!! ";
fillBuffer();
}
void
GoodbyeObject::fillBuffer()
{
// There better be a message
assert(message.length() > 0);
// Copy from the message to the buffer per byte.
int bytes_copied = 0;
for (auto it = message.begin();
it < message.end() && bufferUsed < bufferSize - 1;
it++, bufferUsed++, bytes_copied++) {
// Copy the character into the buffer
buffer[bufferUsed] = *it;
}
if (bufferUsed < bufferSize - 1) {
// Wait for the next copy for as long as it would have taken
DPRINTF(Hello, "Scheduling another fillBuffer in %d ticks\n",
bandwidth * bytes_copied);
schedule(event, curTick() + bandwidth * bytes_copied);
} else {
DPRINTF(Hello, "Goodbye done copying!\n");
// Be sure to take into account the time for the last bytes
exitSimLoop(buffer, 0, curTick() + bandwidth * bytes_copied);
}
}
GoodbyeObject*
GoodbyeObjectParams::create()
{
return new GoodbyeObject(this);
}
这个GoodbyeObject的接口是一个简单的函数sayGoodbye,它接受一个字符串作为参数。调用此函数时,模拟器将生成消息并将其保存在成员变量中。然后,我们开始填充缓冲区。
为了模拟有限的带宽,每次我们将消息写入缓冲区时,都会暂停等待写入消息所需的延迟。我们使用一个简单的事件来模拟这个暂停。
因为我们在SimObject声明中使用了MemoryBandwidth参数,所以带宽变量会自动转换为每字节的滴答数,所以计算延迟只是带宽乘以要写入缓冲区的字节数。
最后,当缓冲区已满时,我们调用函数exitSimLoop,它将退出模拟。这个函数有三个参数,第一个是返回Python配置脚本的消息(exit_event.getCause()),第二个是退出代码,第三个是何时退出。
3、将GoodbyeObject作为参数添加到HelloObject
(1)
首先,我们还将向HelloObject添加一个GoodbyeObject作为参数。为此,只需指定SimObject类名作为参数的类型名。你可以有一个默认的,或者没有,就像一个普通的参数。
class HelloObject(SimObject):
type = 'HelloObject'
cxx_header = "learning_gem5/hello_object.hh"
time_to_wait = Param.Latency("Time before firing the event")
number_of_fires = Param.Int(1, "Number of times to fire the event before "
"goodbye")
goodbye_object = Param.GoodbyeObject("A goodbye object")
(2)
其次,我们将向HelloObject类添加对GoodbyeObject的引用。
class HelloObject : public SimObject
{
private:
void processEvent();
EventWrapper<HelloObject, &HelloObject::processEvent> event;
/// Pointer to the corresponding GoodbyeObject. Set via Python
const GoodbyeObject* goodbye;
/// The name of this object in the Python config file
const std::string myName;
/// Latency between calling the event (in ticks)
const Tick latency;
/// Number of times left to fire the event before goodbye
int timesLeft;
public:
HelloObject(HelloObjectParams *p);
void startup();
};
(3)
然后,我们需要更新HelloObject的构造函数和流程事件函数。我们还在构造函数中添加一个检查,以确保goodbye指针有效。通过使用空的特殊Python SimObject,可以通过参数将空指针作为SimObject传递。当这种情况发生时,我们应该惊慌失措,因为这个对象不是被编码为接受的情况。
#include "learning_gem5/part2/hello_object.hh"
#include "base/misc.hh"
#include "debug/Hello.hh"
HelloObject::HelloObject(HelloObjectParams *params) :
SimObject(params),
event(*this),
goodbye(params->goodbye_object),
myName(params->name),
latency(params->time_to_wait),
timesLeft(params->number_of_fires)
{
DPRINTF(Hello, "Created the hello object with the name %s\n", myName);
panic_if(!goodbye, "HelloObject must have a non-null GoodbyeObject");
}
(4)
一旦我们处理了参数指定的事件数,就应该调用GoodbyeObject中的sayGoodbye函数。
void
HelloObject::processEvent()
{
timesLeft--;
DPRINTF(Hello, "Hello world! Processing the event! %d left\n", timesLeft);
if (timesLeft <= 0) {
DPRINTF(Hello, "Done firing!\n");
goodbye->sayGoodbye(myName);
} else {
schedule(event, curTick() + latency);
}
}
4、升级配置脚本
最后,我们需要将GoodbyeObject添加到配置脚本中。创建一个新的配置脚本hello_goodbye.py并实例化hello和goodbye对象。例如,一个可能的脚本如下。
import m5
from m5.objects import *
root = Root(full_system = False)
root.hello = HelloObject(time_to_wait = '2us', number_of_fires = 5)
root.hello.goodbye_object = GoodbyeObject(buffer_size='100B')
m5.instantiate()
print "Beginning simulation!"
exit_event = m5.simulate()
print 'Exiting @ tick %i because %s' % (m5.curTick(), exit_event.getCause())
您可以修改这两个SimObjects的参数,并查看总体执行时间(Exiting@tick *****
)的变化。要运行这些测试,您可能需要删除调试标志,以便减少对终端的输出。