Creating a `SimObject`

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

Setting up your development environment

This is going to talk about getting started developing gem5.

gem5-style guidelines

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:

  • Use 4 spaces, not tabs
  • Sort the includes
  • Use capitalized camel case for class names, camel case for member variables and functions, and snake case for local variables.
  • Document your code

git branches

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.

Creating a very simple 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

Step 1: Create a Python class for your new 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.

Step 2: Implement your SimObject in C++

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 &params) :
    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.

Step 3: Register the SimObject and C++ file

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')

Step 4: (Re)-build gem5

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

Step 5: Create the config scripts to use your new SimObject

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.

Debugging in gem5

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.

Using debug flags

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

Adding a new debug flag

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.

Debug output

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.

Using functions other than DPRINTF

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”.

Adding parameters to SimObjects and more events

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.

Simple parameters

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 &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);
}

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.

Other SimObjects as parameters

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 &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);
}

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.

Adding the GoodbyeObject as a parameter to the HelloObject

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 &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");
}

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.

Updating the config script

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.