NOTE: This text is not necessarily up to date!
TO DO: Please update with PARAMS(<SimObject>)
TO DO: Update the parameters to not depend on the events
This is going to talk about getting started developing gem5.
When modifying any open source project, it is important to follow the project’s style guidelines. Details on gem5 style can be found on the gem5 Coding Style page.
To help you conform to the style guidelines, gem5 includes a script
which runs whenever you commit a changeset in git. This script should be
automatically added to your .git/config file by SCons the first time you
build gem5. Please do not ignore these warnings/errors. However, in the
rare case where you are trying to commit a file that doesn’t conform to
the gem5 style guidelines (e.g., something from outside the gem5 source
tree) you can use the git option --no-verify
to skip running the style
checker.
The key takeaways from the style guide are:
Most people developing with gem5 use the branch feature of git to track their changes. This makes it quite simple to commit your changes back to gem5. Additionally, using branches can make it easier to update gem5 with new changes that other people make while keeping your own changes separate. The Git book has a great chapter describing the details of how to use branches.
SimObject
Note: gem5 has SimObject named SimpleObject
. Implementing another
SimpleObject
SimObject will result in confusing compiler issues.
Almost all objects in gem5 inherit from the base SimObject type.
SimObjects export the main interfaces to all objects in gem5. SimObjects
are wrapped C++
objects that are accessible from the Python
configuration scripts.
SimObjects can have many parameters, which are set via the Python
configuration files. In addition to simple parameters like integers and
floating point numbers, they can also have other SimObjects as
parameters. This allows you to create complex system hierarchies, like
real machines.
In this chapter, we will walk through creating a simple “HelloWorld”
SimObject. The goal is to introduce you to how SimObjects are created
and the required boilerplate code for all SimObjects. We will also
create a simple Python
configuration script which instantiates our
SimObject.
In the next few chapters, we will take this simple SimObject and expand on it to include debugging support, dynamic events, and parameters.
Using git branches
It is common to use a new git branch for each new feature you add to gem5.
The first step when adding a new feature or modifying something in gem5, is to create a new branch to store your changes. Details on git branches can be found in the Git book.
git checkout -b hello-simobject
Each SimObject has a Python class which is associated with it. This Python class describes the parameters of your SimObject that can be controlled from the Python configuration files. For our simple SimObject, we are just going to start out with no parameters. Thus, we simply need to declare a new class for our SimObject and set it’s name and the C++ header that will define the C++ class for the SimObject.
We can create a file, HelloObject.py
, in src/learning_gem5/part2
.
If you have cloned the gem5 repository you’ll have the files mentioned
in this tutorial completed under src/learning_gem5/part2
and
configs/learning_gem5/part2
. You can delete these or move them
elsewhere to follow this tutorial.
from m5.params import *
from m5.SimObject import SimObject
class HelloObject(SimObject):
type = 'HelloObject'
cxx_header = "learning_gem5/part2/hello_object.hh"
It is not required that the type
be the same as the name of the class,
but it is convention. The type
is the C++ class that you are wrapping
with this Python SimObject. Only in special circumstances should the
type
and the class name be different.
The cxx_header
is the file that contains the declaration of the class
used as the type
parameter. Again, the convention is to use the name
of the SimObject with all lowercase and underscores, but this is only
convention. You can specify any header file here.
Next, we need to create hello_object.hh
and hello_object.cc
in
src/learning_gem5/part2/
directory which will implement the HelloObject
.
We’ll start with the header file for our C++
object. By convention,
gem5 wraps all header files in #ifndef/#endif
with the name of the
file and the directory its in so there are no circular includes.
The only thing we need to do in the file is to declare our class. Since
HelloObject
is a SimObject, it must inherit from the C++ SimObject
class. Most of the time, your SimObject’s parent will be a subclass of
SimObject, not SimObject itself.
The SimObject class specifies many virtual functions. However, none of these functions are pure virtual, so in the simplest case, there is no need to implement any functions except for the constructor.
The constructor for all SimObjects assumes it will take a parameter
object. This parameter object is automatically created by the build
system and is based on the Python
class for the SimObject, like the
one we created above. The name for this parameter type is generated
automatically from the name of your object. For our “HelloObject” the
parameter type’s name is “HelloObjectParams”.
The code required for our simple header file is listed below.
#ifndef __LEARNING_GEM5_HELLO_OBJECT_HH__
#define __LEARNING_GEM5_HELLO_OBJECT_HH__
#include "params/HelloObject.hh"
#include "sim/sim_object.hh"
class HelloObject : public SimObject
{
public:
HelloObject(const HelloObjectParams &p);
};
#endif // __LEARNING_GEM5_HELLO_OBJECT_HH__
Next, we need to implement two functions in the .cc
file, not just
one. The first function, is the constructor for the HelloObject
. Here
we simply pass the parameter object to the SimObject parent and print
“Hello world!”
Normally, you would never use std::cout
in gem5. Instead, you
should use debug flags. In the next chapter, we
will modify this to use debug flags instead. However, for now, we’ll
simply use std::cout
because it is simple.
#include "learning_gem5/part2/hello_object.hh"
#include <iostream>
HelloObject::HelloObject(const HelloObjectParams ¶ms) :
SimObject(params)
{
std::cout << "Hello World! From a SimObject!" << std::endl;
}
Note: If the constructor of your SimObject follows the following signature,
Foo(const FooParams &)
then a FooParams::create()
method will be automatically defined. The purpose
of the create()
method is to call the SimObject constructor and return an
instance of the SimObject. Most SimObject will follow this pattern; however,
if your SimObject does not follow this pattern,
the gem5 SimObject documetation
provides more information about manually implementing the create()
method.
In order for the C++
file to be compiled and the Python
file to be
parsed we need to tell the build system about these files. gem5 uses
SCons as the build system, so you simply have to create a SConscript
file in the directory with the code for the SimObject. If there is
already a SConscript file for that directory, simply add the following
declarations to that file.
This file is simply a normal Python
file, so you can write any
Python
code you want in this file. Some of the scripting can become
quite complicated. gem5 leverages this to automatically create code for
SimObjects and to compile the domain-specific languages like SLICC and
the ISA language.
In the SConscript file, there are a number of functions automatically defined after you import them. See the section on that…
To get your new SimObject to compile, you simply need to create a new
file with the name “SConscript” in the src/learning_gem5/part2
directory. In
this file, you have to declare the SimObject and the .cc
file. Below
is the required code.
Import('*')
SimObject('HelloObject.py')
Source('hello_object.cc')
To compile and link your new files you simply need to recompile gem5. The below example assumes you are using the x86 ISA, but nothing in our object requires an ISA so, this will work with any of gem5’s ISAs.
scons build/X86/gem5.opt
Now that you have implemented a SimObject, and it has been compiled into
gem5, you need to create or modify a Python
config file run_hello.py
in
configs/learning_gem5/part2
to instantiate your object. Since your object
is very simple a system object is not required! CPUs are not needed, or
caches, or anything, except a Root
object. All gem5 instances require a
Root
object.
Walking through creating a very simple configuration script, first, import m5 and all of the objects you have compiled.
import m5
from m5.objects import *
Next, you have to instantiate the Root
object, as required by all gem5
instances.
root = Root(full_system = False)
Now, you can instantiate the HelloObject
you created. All you need to
do is call the Python
“constructor”. Later, we will look at how to
specify parameters via the Python
constructor. In addition to creating
an instantiation of your object, you need to make sure that it is a
child of the root object. Only SimObjects that are children of the
Root
object are instantiated in C++
.
root.hello = HelloObject()
Finally, you need to call instantiate
on the m5
module and actually
run the simulation!
m5.instantiate()
print("Beginning simulation!")
exit_event = m5.simulate()
print('Exiting @ tick {} because {}'
.format(m5.curTick(), exit_event.getCause()))
Remember to rebuild gem5 after modifying files in the src/ directory. The command line to run the config file is in the output below after ‘command line:’. The output should look something like the following:
Note: If the code for the future section “Adding parameters to SimObjects
and more events”, (goodbye_object) is in your src/learning_gem5/part2
directory, run_hello.py will cause an error. If you delete those files or
move them outside of the gem5 directory run_hello.py
should give the output
below.
gem5 Simulator System. http://gem5.org
gem5 is copyrighted software; use the --copyright option for details.
gem5 compiled May 4 2016 11:37:41
gem5 started May 4 2016 11:44:28
gem5 executing on mustardseed.cs.wisc.edu, pid 22480
command line: build/X86/gem5.opt configs/learning_gem5/part2/run_hello.py
Global frequency set at 1000000000000 ticks per second
Hello World! From a SimObject!
Beginning simulation!
info: Entering event queue @ 0. Starting simulation...
Exiting @ tick 18446744073709551615 because simulate() limit reached
Congrats! You have written your first SimObject. In the next chapters, we will extend this SimObject and explore what you can do with SimObjects.
Above, we covered how to
create a very simple SimObject. In this chapter, we will replace the
simple print to stdout
with gem5’s debugging support.
gem5 provides support for printf
-style tracing/debugging of your code
via debug flags. These flags allow every component to have many
debug-print statements, without all of them enabled at the same time.
When running gem5, you can specify which debug flags to enable from the
command line.
For instance, when running the first simple.py script from
simple-config-chapter, if you enable the DRAM
debug flag, you get the
following output. Note that this generates a lot of output to the
console (about 7 MB).
build/X86/gem5.opt --debug-flags=DRAM configs/learning_gem5/part1/simple.py | head -n 50
gem5 Simulator System. http://gem5.org
DRAM device capacity (gem5 is copyrighted software; use the --copyright option for details.
gem5 compiled Jan 3 2017 16:03:38
gem5 started Jan 3 2017 16:09:53
gem5 executing on chinook, pid 19223
command line: build/X86/gem5.opt --debug-flags=DRAM configs/learning_gem5/part1/simple.py
Global frequency set at 1000000000000 ticks per second
0: system.mem_ctrl: Memory capacity 536870912 (536870912) bytes
0: system.mem_ctrl: Row buffer size 8192 bytes with 128 columns per row buffer
0: system.remote_gdb.listener: listening for remote gdb #0 on port 7000
Beginning simulation!
info: Entering event queue @ 0. Starting simulation...
0: system.mem_ctrl: recvTimingReq: request ReadReq addr 400 size 8
0: system.mem_ctrl: Read queue limit 32, current size 0, entries needed 1
0: system.mem_ctrl: Address: 400 Rank 0 Bank 0 Row 0
0: system.mem_ctrl: Read queue limit 32, current size 0, entries needed 1
0: system.mem_ctrl: Adding to read queue
0: system.mem_ctrl: Request scheduled immediately
0: system.mem_ctrl: Single request, going to a free rank
0: system.mem_ctrl: Timing access to addr 400, rank/bank/row 0 0 0
0: system.mem_ctrl: Activate at tick 0
0: system.mem_ctrl: Activate bank 0, rank 0 at tick 0, now got 1 active
0: system.mem_ctrl: Access to 400, ready at 46250 bus busy until 46250.
46250: system.mem_ctrl: processRespondEvent(): Some req has reached its readyTime
46250: system.mem_ctrl: number of read entries for rank 0 is 0
46250: system.mem_ctrl: Responding to Address 400.. 46250: system.mem_ctrl: Done
77000: system.mem_ctrl: recvTimingReq: request ReadReq addr 400 size 8
77000: system.mem_ctrl: Read queue limit 32, current size 0, entries needed 1
77000: system.mem_ctrl: Address: 400 Rank 0 Bank 0 Row 0
77000: system.mem_ctrl: Read queue limit 32, current size 0, entries needed 1
77000: system.mem_ctrl: Adding to read queue
77000: system.mem_ctrl: Request scheduled immediately
77000: system.mem_ctrl: Single request, going to a free rank
77000: system.mem_ctrl: Timing access to addr 400, rank/bank/row 0 0 0
77000: system.mem_ctrl: Access to 400, ready at 101750 bus busy until 101750.
101750: system.mem_ctrl: processRespondEvent(): Some req has reached its readyTime
101750: system.mem_ctrl: number of read entries for rank 0 is 0
101750: system.mem_ctrl: Responding to Address 400.. 101750: system.mem_ctrl: Done
132000: system.mem_ctrl: recvTimingReq: request ReadReq addr 400 size 8
132000: system.mem_ctrl: Read queue limit 32, current size 0, entries needed 1
132000: system.mem_ctrl: Address: 400 Rank 0 Bank 0 Row 0
132000: system.mem_ctrl: Read queue limit 32, current size 0, entries needed 1
132000: system.mem_ctrl: Adding to read queue
132000: system.mem_ctrl: Request scheduled immediately
132000: system.mem_ctrl: Single request, going to a free rank
132000: system.mem_ctrl: Timing access to addr 400, rank/bank/row 0 0 0
132000: system.mem_ctrl: Access to 400, ready at 156750 bus busy until 156750.
156750: system.mem_ctrl: processRespondEvent(): Some req has reached its readyTime
156750: system.mem_ctrl: number of read entries for rank 0 is 0
Or, you may want to debug based on the exact instruction the CPU is
executing. For this, the Exec
debug flag may be useful. This debug
flags shows details of how each instruction is executed by the simulated
CPU.
build/X86/gem5.opt --debug-flags=Exec configs/learning_gem5/part1/simple.py | head -n 50
gem5 Simulator System. http://gem5.org
gem5 is copyrighted software; use the --copyright option for details.
gem5 compiled Jan 3 2017 16:03:38
gem5 started Jan 3 2017 16:11:47
gem5 executing on chinook, pid 19234
command line: build/X86/gem5.opt --debug-flags=Exec configs/learning_gem5/part1/simple.py
Global frequency set at 1000000000000 ticks per second
0: system.remote_gdb.listener: listening for remote gdb #0 on port 7000
warn: ClockedObject: More than one power state change request encountered within the same simulation tick
Beginning simulation!
info: Entering event queue @ 0. Starting simulation...
77000: system.cpu T0 : @_start : xor rbp, rbp
77000: system.cpu T0 : @_start.0 : XOR_R_R : xor rbp, rbp, rbp : IntAlu : D=0x0000000000000000
132000: system.cpu T0 : @_start+3 : mov r9, rdx
132000: system.cpu T0 : @_start+3.0 : MOV_R_R : mov r9, r9, rdx : IntAlu : D=0x0000000000000000
187000: system.cpu T0 : @_start+6 : pop rsi
187000: system.cpu T0 : @_start+6.0 : POP_R : ld t1, SS:[rsp] : MemRead : D=0x0000000000000001 A=0x7fffffffee30
250000: system.cpu T0 : @_start+6.1 : POP_R : addi rsp, rsp, 0x8 : IntAlu : D=0x00007fffffffee38
250000: system.cpu T0 : @_start+6.2 : POP_R : mov rsi, rsi, t1 : IntAlu : D=0x0000000000000001
360000: system.cpu T0 : @_start+7 : mov rdx, rsp
360000: system.cpu T0 : @_start+7.0 : MOV_R_R : mov rdx, rdx, rsp : IntAlu : D=0x00007fffffffee38
415000: system.cpu T0 : @_start+10 : and rax, 0xfffffffffffffff0
415000: system.cpu T0 : @_start+10.0 : AND_R_I : limm t1, 0xfffffffffffffff0 : IntAlu : D=0xfffffffffffffff0
415000: system.cpu T0 : @_start+10.1 : AND_R_I : and rsp, rsp, t1 : IntAlu : D=0x0000000000000000
470000: system.cpu T0 : @_start+14 : push rax
470000: system.cpu T0 : @_start+14.0 : PUSH_R : st rax, SS:[rsp + 0xfffffffffffffff8] : MemWrite : D=0x0000000000000000 A=0x7fffffffee28
491000: system.cpu T0 : @_start+14.1 : PUSH_R : subi rsp, rsp, 0x8 : IntAlu : D=0x00007fffffffee28
546000: system.cpu T0 : @_start+15 : push rsp
546000: system.cpu T0 : @_start+15.0 : PUSH_R : st rsp, SS:[rsp + 0xfffffffffffffff8] : MemWrite : D=0x00007fffffffee28 A=0x7fffffffee20
567000: system.cpu T0 : @_start+15.1 : PUSH_R : subi rsp, rsp, 0x8 : IntAlu : D=0x00007fffffffee20
622000: system.cpu T0 : @_start+16 : mov r15, 0x40a060
622000: system.cpu T0 : @_start+16.0 : MOV_R_I : limm r8, 0x40a060 : IntAlu : D=0x000000000040a060
732000: system.cpu T0 : @_start+23 : mov rdi, 0x409ff0
732000: system.cpu T0 : @_start+23.0 : MOV_R_I : limm rcx, 0x409ff0 : IntAlu : D=0x0000000000409ff0
842000: system.cpu T0 : @_start+30 : mov rdi, 0x400274
842000: system.cpu T0 : @_start+30.0 : MOV_R_I : limm rdi, 0x400274 : IntAlu : D=0x0000000000400274
952000: system.cpu T0 : @_start+37 : call 0x9846
952000: system.cpu T0 : @_start+37.0 : CALL_NEAR_I : limm t1, 0x9846 : IntAlu : D=0x0000000000009846
952000: system.cpu T0 : @_start+37.1 : CALL_NEAR_I : rdip t7, %ctrl153, : IntAlu : D=0x00000000004001ba
952000: system.cpu T0 : @_start+37.2 : CALL_NEAR_I : st t7, SS:[rsp + 0xfffffffffffffff8] : MemWrite : D=0x00000000004001ba A=0x7fffffffee18
973000: system.cpu T0 : @_start+37.3 : CALL_NEAR_I : subi rsp, rsp, 0x8 : IntAlu : D=0x00007fffffffee18
973000: system.cpu T0 : @_start+37.4 : CALL_NEAR_I : wrip , t7, t1 : IntAlu :
1042000: system.cpu T0 : @__libc_start_main : push r15
1042000: system.cpu T0 : @__libc_start_main.0 : PUSH_R : st r15, SS:[rsp + 0xfffffffffffffff8] : MemWrite : D=0x0000000000000000 A=0x7fffffffee10
1063000: system.cpu T0 : @__libc_start_main.1 : PUSH_R : subi rsp, rsp, 0x8 : IntAlu : D=0x00007fffffffee10
1118000: system.cpu T0 : @__libc_start_main+2 : movsxd rax, rsi
1118000: system.cpu T0 : @__libc_start_main+2.0 : MOVSXD_R_R : sexti rax, rsi, 0x1f : IntAlu : D=0x0000000000000001
1173000: system.cpu T0 : @__libc_start_main+5 : mov r15, r9
1173000: system.cpu T0 : @__libc_start_main+5.0 : MOV_R_R : mov r15, r15, r9 : IntAlu : D=0x0000000000000000
1228000: system.cpu T0 : @__libc_start_main+8 : push r14
In fact, the Exec
flag is actually an agglomeration of multiple debug
flags. You can see this, and all of the available debug flags, by
running gem5 with the --debug-help
parameter.
build/X86/gem5.opt --debug-help
Base Flags:
Activity: None
AddrRanges: None
Annotate: State machine annotation debugging
AnnotateQ: State machine annotation queue debugging
AnnotateVerbose: Dump all state machine annotation details
BaseXBar: None
Branch: None
Bridge: None
CCRegs: None
CMOS: Accesses to CMOS devices
Cache: None
CacheComp: None
CachePort: None
CacheRepl: None
CacheTags: None
CacheVerbose: None
Checker: None
Checkpoint: None
ClockDomain: None
...
Compound Flags:
All: Controls all debug flags. It should not be used within C++ code.
All Base Flags
AnnotateAll: All Annotation flags
Annotate, AnnotateQ, AnnotateVerbose
CacheAll: None
Cache, CacheComp, CachePort, CacheRepl, CacheVerbose, HWPrefetch
DiskImageAll: None
DiskImageRead, DiskImageWrite
...
XBar: None
BaseXBar, CoherentXBar, NoncoherentXBar, SnoopFilter
In the previous chapters, we used a simple
std::cout
to print from our SimObject. While it is possible to use the
normal C/C++ I/O in gem5, it is highly discouraged. So, we are now going
to replace this and use gem5’s debugging facilities instead.
When creating a new debug flag, we first have to declare it in a SConscript file. Add the following to the SConscript file in the directory with your hello object code (src/learning_gem5/).
DebugFlag('HelloExample')
This declares a debug flag of “HelloExample”. Now, we can use this in debug statements in our SimObject.
By declaring the flag in the SConscript file, a debug header is
automatically generated that allows us to use the debug flag. The header
file is in the debug
directory and has the same name (and
capitalization) as what we declare in the SConscript file. Therefore, we
need to include the automatically generated header file in any files
where we plan to use the debug flag.
In the hello_object.cc
file, we need to include the header file.
#include "debug/HelloExample.hh"
Now that we have included the necessary header file, let’s replace the
std::cout
call with a debug statement like so.
DPRINTF(HelloExample, "Created the hello object\n");
DPRINTF
is a C++ macro. The first parameter is a debug flag that has
been declared in a SConscript file. We can use the flag Hello
since we
declared it in the src/learning_gem5/SConscript
file. The rest of the
arguments are variable and can be anything you would pass to a printf
statement.
Now, if you recompile gem5 and run it with the “Hello” debug flag, you get the following result.
build/X86/gem5.opt --debug-flags=Hello configs/learning_gem5/part2/run_hello.py
gem5 Simulator System. http://gem5.org
gem5 is copyrighted software; use the --copyright option for details.
gem5 compiled Jan 4 2017 09:40:10
gem5 started Jan 4 2017 09:41:01
gem5 executing on chinook, pid 29078
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...
Exiting @ tick 18446744073709551615 because simulate() limit reached
You can find the updated SConcript file here and the updated hello object code here.
For each dynamic DPRINTF
execution, three things are printed to
stdout
. First, the current tick when the DPRINTF
is executed.
Second, the name of the SimObject that called DPRINTF
. This name is
usually the Python variable name from the Python config file. However,
the name is whatever the SimObject name()
function returns. Finally,
you see whatever format string you passed to the DPRINTF
function.
You can control where the debug output goes with the --debug-file
parameter. By default, all of the debugging output is printed to
stdout
. However, you can redirect the output to any file. The file is
stored relative to the main gem5 output directory, not the current
working directory.
DPRINTF
is the most commonly used debugging function in gem5. However,
gem5 provides a number of other functions that are useful in specific
circumstances.
These functions are like the previous functions
:cppDDUMP
,:cppDPRINTF
, and:cppDPRINTFR
except they do not take a flag as a parameter. Therefore, these statements will always print whenever debugging is enabled.
All of these functions are only enabled if you compile gem5 in “opt” or “debug” mode. All other modes use empty placeholder macros for the above functions. Therefore, if you want to use debug flags, you must use either “gem5.opt” or “gem5.debug”.
One of the most powerful parts of gem5’s Python interface is the ability
to pass parameters from Python to the C++ objects in gem5. In this
chapter, we will explore some of the kinds of parameters for SimObjects
and how to use them building off of the simple HelloObject
from the
previous chapters.
First, we will add parameters for the latency and number of times to
fire the event in the HelloObject
. To add a parameter, modify the
HelloObject
class in the SimObject Python file
(src/learning_gem5/part2/HelloObject.py
). Parameters are set by adding new
statements to the Python class that include a Param
type.
For instance, the following code has a parameter time_to_wait
which is
a “Latency” parameter and number_of_fires
which is an integer
parameter.
class HelloObject(SimObject):
type = 'HelloObject'
cxx_header = "learning_gem5/part2/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.<TypeName>
declares a parameter of type TypeName
. Common types
are Int
for integers, Float
for floats, etc. These types act like
regular Python classes.
Each parameter declaration takes one or two parameters. When given two
parameters (like number_of_fires
above), the first parameter is the
default value for the parameter. In this case, if you instantiate a
HelloObject
in your Python config file without specifying any value
for number_of_fires, it will take the default value of 1.
The second parameter to the parameter declaration is a short description
of the parameter. This must be a Python string. If you only specify a
single parameter to the parameter declaration, it is the description (as
for time_to_wait
).
gem5 also supports many complex parameter types that are not just
builtin types. For instance, time_to_wait
is a Latency
. Latency
takes a value as a time value as a string and converts it into simulator
ticks. For instance, with a default tick rate of 1 picosecond
(10\^12 ticks per second or 1 THz), "1ns"
is automatically converted
to 1000. There are other convenience parameters like Percent
,
Cycles
, MemorySize
and many more.
Once you have declared these parameters in the SimObject file, you need
to copy their values to your C++ class in its constructor. The following
code shows the changes to the HelloObject
constructor.
HelloObject::HelloObject(const HelloObjectParams ¶ms) :
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);
}
Here, we use the parameter’s values for the default values of latency
and timesLeft. Additionally, we store the name
from the parameter
object to use it later in the member variable myName
. Each params
instantiation has a name which comes from the Python config file when it
is instantiated.
However, assigning the name here is just an example of using the params
object. For all SimObjects, there is a name()
function that always
returns the name. Thus, there is never a need to store the name like
above.
To the HelloObject class declaration, add a member variable for the name.
class HelloObject : public SimObject
{
private:
void processEvent();
EventWrapper event;
const std::string myName;
const Tick latency;
int timesLeft;
public:
HelloObject(HelloObjectParams *p);
void startup();
};
When we run gem5 with the above, we get the following error:
gem5 Simulator System. http://gem5.org
gem5 is copyrighted software; use the --copyright option for details.
gem5 compiled Jan 4 2017 14:46:36
gem5 started Jan 4 2017 14:46:52
gem5 executing on chinook, pid 3422
command line: build/X86/gem5.opt --debug-flags=Hello configs/learning_gem5/part2/run_hello.py
Global frequency set at 1000000000000 ticks per second
fatal: hello.time_to_wait without default or user set value
This is because the time_to_wait
parameter does not have a default
value. Therefore, we need to update the Python config file
(run_hello.py
) to specify this value.
root.hello = HelloObject(time_to_wait = '2us')
Or, we can specify time_to_wait
as a member variable. Either option is
exactly the same because the C++ objects are not created until
m5.instantiate()
is called.
root.hello = HelloObject()
root.hello.time_to_wait = '2us'
The output of this simple script is the following when running the the
Hello
debug flag.
gem5 Simulator System. http://gem5.org
gem5 is copyrighted software; use the --copyright option for details.
gem5 compiled Jan 4 2017 14:46:36
gem5 started Jan 4 2017 14:50:08
gem5 executing on chinook, pid 3455
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 with the name hello
Beginning simulation!
info: Entering event queue @ 0. Starting simulation...
2000000: hello: Hello world! Processing the event! 0 left
2000000: hello: Done firing!
Exiting @ tick 18446744073709551615 because simulate() limit reached
You can also modify the config script to fire the event multiple times.
You can also specify other SimObjects as parameters. To demonstrate
this, we are going to create a new SimObject, GoodbyeObject
. This
object is going to have a simple function that says “Goodbye” to another
SimObject. To make it a little more interesting, the GoodbyeObject
is
going to have a buffer to write the message, and a limited bandwidth to
write the message.
First, declare the SimObject in the SConscript file:
Import('*')
SimObject('HelloObject.py')
Source('hello_object.cc')
Source('goodbye_object.cc')
DebugFlag('Hello')
The new SConscript file can be downloaded here.
Next, you need to declare the new SimObject in a SimObject Python file.
Since the GoodbyeObject
is highly related to the HelloObject
, we
will use the same file. You can add the following code to
HelloObject.py
.
This object has two parameters, both with default values. The first
parameter is the size of a buffer and is a MemorySize
parameter.
Second is the write_bandwidth
which specifies the speed to fill the
buffer. Once the buffer is full, the simulation will exit.
class GoodbyeObject(SimObject):
type = 'GoodbyeObject'
cxx_header = "learning_gem5/part2/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")
The updated HelloObject.py
file can be downloaded
here.
Now, we need to implement the 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/part2/goodbye_object.hh"
#include "debug/Hello.hh"
#include "sim/sim_exit.hh"
GoodbyeObject::GoodbyeObject(const GoodbyeObjectParams ¶ms) :
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);
}
The header file can be downloaded here and the implementation can be downloaded here.
The interface to this GoodbyeObject
is simple a function sayGoodbye
which takes a string as a parameter. When this function is called, the
simulator builds the message and saves it in a member variable. Then, we
begin filling the buffer.
To model the limited bandwidth, each time we write the message to the buffer, we pause for the latency it takes to write the message. We use a simple event to model this pause.
Since we used a MemoryBandwidth
parameter in the SimObject
declaration, the bandwidth
variable is automatically converted into
ticks per byte, so calculating the latency is simply the bandwidth times
the bytes we want to write the buffer.
Finally, when the buffer is full, we call the function exitSimLoop
,
which will exit the simulation. This function takes three parameters,
the first is the message to return to the Python config script
(exit_event.getCause()
), the second is the exit code, and the third is
when to exit.
First, we will also add a GoodbyeObject
as a parameter to the
HelloObject
. To do this, you simply specify the SimObject class name
as the TypeName
of the Param
. You can have a default, or not, just
like a normal parameter.
class HelloObject(SimObject):
type = 'HelloObject'
cxx_header = "learning_gem5/part2/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")
The updated HelloObject.py
file can be downloaded
here.
Second, we will add a reference to a GoodbyeObject
to the
HelloObject
class.
Don’t forget to include goodbye_object.hh at the top of the hello_object.hh file!
#include <string>
#include "learning_gem5/part2/goodbye_object.hh"
#include "params/HelloObject.hh"
#include "sim/sim_object.hh"
class HelloObject : public SimObject
{
private:
void processEvent();
EventWrapper event;
/// Pointer to the corresponding GoodbyeObject. Set via Python
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();
};
Then, we need to update the constructor and the process event function
of the HelloObject
. We also add a check in the constructor to make
sure the goodbye
pointer is valid. It is possible to pass a null
pointer as a SimObject via the parameters by using the NULL
special
Python SimObject. We should panic when this happens since it is not a
case this object has been coded to accept.
#include "learning_gem5/part2/hello_object.hh"
#include "base/misc.hh"
#include "debug/Hello.hh"
HelloObject::HelloObject(HelloObjectParams ¶ms) :
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");
}
Once we have processed the number of event specified by the parameter,
we should call the sayGoodbye
function in the GoodbyeObject
.
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);
}
}
You can find the updated header file here and the implementation file here.
Lastly, we need to add the GoodbyeObject
to the config script. Create
a new config script, hello_goodbye.py
and instantiate both the hello
and the goodbye objects. For instance, one possible script is the
following.
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()))
You can download this script here.
Running this script generates the following output.
gem5 Simulator System. http://gem5.org
gem5 is copyrighted software; use the --copyright option for details.
gem5 compiled Jan 4 2017 15:17:14
gem5 started Jan 4 2017 15:18:41
gem5 executing on chinook, pid 3838
command line: build/X86/gem5.opt --debug-flags=Hello configs/learning_gem5/part2/hello_goodbye.py
Global frequency set at 1000000000000 ticks per second
0: hello.goodbye_object: Created the goodbye object
0: hello: Created the hello object
Beginning simulation!
info: Entering event queue @ 0. Starting simulation...
2000000: hello: Hello world! Processing the event! 4 left
4000000: hello: Hello world! Processing the event! 3 left
6000000: hello: Hello world! Processing the event! 2 left
8000000: hello: Hello world! Processing the event! 1 left
10000000: hello: Hello world! Processing the event! 0 left
10000000: hello: Done firing!
10000000: hello.goodbye_object: Saying goodbye to hello
10000000: hello.goodbye_object: Scheduling another fillBuffer in 152592 ticks
10152592: hello.goodbye_object: Processing the event!
10152592: hello.goodbye_object: Scheduling another fillBuffer in 152592 ticks
10305184: hello.goodbye_object: Processing the event!
10305184: hello.goodbye_object: Scheduling another fillBuffer in 152592 ticks
10457776: hello.goodbye_object: Processing the event!
10457776: hello.goodbye_object: Scheduling another fillBuffer in 152592 ticks
10610368: hello.goodbye_object: Processing the event!
10610368: hello.goodbye_object: Scheduling another fillBuffer in 152592 ticks
10762960: hello.goodbye_object: Processing the event!
10762960: hello.goodbye_object: Scheduling another fillBuffer in 152592 ticks
10915552: hello.goodbye_object: Processing the event!
10915552: hello.goodbye_object: Goodbye done copying!
Exiting @ tick 10944163 because Goodbye hello!! Goodbye hello!! Goodbye hello!! Goodbye hello!! Goodbye hello!! Goodbye hello!! Goo
You can modify the parameters to these two SimObjects and see how the overall execution time (Exiting @ tick 10944163) changes. To run these tests, you may want to remove the debug flag so there is less output to the terminal.
In the next chapters, we will create a more complex and more useful SimObject, culminating with a simple blocking uniprocessor cache implementation.