last edited: 2024-12-20 23:08:46 +0000
Event-driven programming
gem5 is an event-driven simulator. In this chapter, we will explore how
to create and schedule events. We will be building from the simple
HelloObject
from hello-simobject-chapter.
Creating a simple event callback
In gem5’s event-driven model, each event has a callback function in which the event is processed. Generally, this is a class that inherits from :cppEvent. However, gem5 provides a wrapper function for creating simple events.
In the header file for our HelloObject
, we simply need to declare a
new function that we want to execute every time the event fires
(processEvent()
). This function must take no parameters and return
nothing.
Next, we add an Event
instance. In this case, we will use an
EventFunctionWrapper
which allows us to execute any function.
We also add a startup()
function that will be explained below.
class HelloObject : public SimObject
{
private:
void processEvent();
EventFunctionWrapper event;
public:
HelloObject(const HelloObjectParams &p);
void startup() override;
};
Next, we must construct this event in the constructor of HelloObject
.
The EventFuntionWrapper
takes two parameters, a function to execute
and a name. The name is usually the name of the SimObject that owns the
event. When printing the name, there will be an automatic
“.wrapped_function_event” appended to the end of the name.
The first parameter is simply a function that takes no parameters and
has no return value (std::function<void(void)>
). Usually, this is a
simple lambda function that calls a member function. However, it can be
any function you want. Below, we captute this
in the lambda ([this]
)
so we can call member functions of the instance of the class.
HelloObject::HelloObject(const HelloObjectParams ¶ms) :
SimObject(params), event([this]{processEvent();}, name())
{
DPRINTF(HelloExample, "Created the hello object\n");
}
We also must define the implementation of the process function. In this case, we’ll simply print something if we are debugging.
void
HelloObject::processEvent()
{
DPRINTF(HelloExample, "Hello world! Processing the event!\n");
}
Scheduling events
Finally, for the event to be processed, we first have to schedule the
event. For this we use the :cppschedule function. This function
schedules some instance of an Event
for some time in the future
(event-driven simulation does not allow events to execute in the past).
We will initially schedule the event in the startup()
function we
added to the HelloObject
class. The startup()
function is where
SimObjects are allowed to schedule internal events. It does not get
executed until the simulation begins for the first time (i.e. the
simulate()
function is called from a Python config file).
void
HelloObject::startup()
{
schedule(event, 100);
}
Here, we simply schedule the event to execute at tick 100. Normally, you
would use some offset from curTick()
, but since we know the startup()
function is called when the time is currently 0, we can use an explicit
tick value.
The output when you run gem5 with the “HelloExample” debug flag is now
gem5 Simulator System. http://gem5.org
gem5 is copyrighted software; use the --copyright option for details.
gem5 compiled Jan 4 2017 11:01:46
gem5 started Jan 4 2017 13:41:38
gem5 executing on chinook, pid 1834
command line: build/X86/gem5.opt --debug-flags=Hello configs/learning_gem5/part2/run_hello.py
Global frequency set at 1000000000000 ticks per second
0: hello: Created the hello object
Beginning simulation!
info: Entering event queue @ 0. Starting simulation...
100: hello: Hello world! Processing the event!
Exiting @ tick 18446744073709551615 because simulate() limit reached
More event scheduling
We can also schedule new events within an event process action. For
instance, we are going to add a latency parameter to the HelloObject
and a parameter for how many times to fire the event. In the next
chapter we will make these parameters accessible
from the Python config files.
To the HelloObject class declaration, add a member variable for the latency and number of times to fire.
class HelloObject : public SimObject
{
private:
void processEvent();
EventFunctionWrapper event;
const Tick latency;
int timesLeft;
public:
HelloObject(const HelloObjectParams &p);
void startup() override;
};
Then, in the constructor add default values for the latency
and
timesLeft
.
HelloObject::HelloObject(const HelloObjectParams ¶ms) :
SimObject(params), event([this]{processEvent();}, name()),
latency(100), timesLeft(10)
{
DPRINTF(HelloExample, "Created the hello object\n");
}
Finally, update startup()
and processEvent()
.
void
HelloObject::startup()
{
schedule(event, latency);
}
void
HelloObject::processEvent()
{
timesLeft--;
DPRINTF(HelloExample, "Hello world! Processing the event! %d left\n", timesLeft);
if (timesLeft <= 0) {
DPRINTF(HelloExample, "Done firing!\n");
} else {
schedule(event, curTick() + latency);
}
}
Now, when we run gem5, the event should fire 10 times, and the simulation will end after 1000 ticks. The output should now look like the following.
gem5 Simulator System. http://gem5.org
gem5 is copyrighted software; use the --copyright option for details.
gem5 compiled Jan 4 2017 13:53:35
gem5 started Jan 4 2017 13:54:11
gem5 executing on chinook, pid 2326
command line: build/X86/gem5.opt --debug-flags=Hello configs/learning_gem5/part2/run_hello.py
Global frequency set at 1000000000000 ticks per second
0: hello: Created the hello object
Beginning simulation!
info: Entering event queue @ 0. Starting simulation...
100: hello: Hello world! Processing the event! 9 left
200: hello: Hello world! Processing the event! 8 left
300: hello: Hello world! Processing the event! 7 left
400: hello: Hello world! Processing the event! 6 left
500: hello: Hello world! Processing the event! 5 left
600: hello: Hello world! Processing the event! 4 left
700: hello: Hello world! Processing the event! 3 left
800: hello: Hello world! Processing the event! 2 left
900: hello: Hello world! Processing the event! 1 left
1000: hello: Hello world! Processing the event! 0 left
1000: hello: Done firing!
Exiting @ tick 18446744073709551615 because simulate() limit reached
You can find the updated header file here and the implementation file here.