24.22.4.1.1. AtomicSimpleCPU initial events

Let’s have a closer look at the initial magically scheduled events of the simulation.

Most events come from other events, but at least one initial event must be scheduled somehow from elsewhere to kick things off.

The initial tick event:

0: Event: AtomicSimpleCPU tick.wrapped_function_event: EventFunctionWrapped 39 scheduled @ 0

we’ll study by breaking at at the point that prints messages: b Trace::OstreamLogger::logMessage() to see where events are being scheduled from:

Trace::OstreamLogger::logMessage() at trace.cc:149 0x5555593b3b1e
void Trace::Logger::dprintf_flag<char const*, char const*, unsigned long>() at 0x55555949e603
void Trace::Logger::dprintf<char const*, char const*, unsigned long>() at 0x55555949de58
Event::trace() at eventq.cc:395 0x55555946d109
EventQueue::schedule() at eventq_impl.hh:65 0x555557195441
EventManager::schedule() at eventq.hh:746 0x555557194aa2
AtomicSimpleCPU::activateContext() at atomic.cc:239 0x555559075531
SimpleThread::activate() at simple_thread.cc:177 0x555559545a63
Process::initState() at process.cc:283 0x555559484011
ArmProcess64::initState() at process.cc:126 0x55555730827a
ArmLinuxProcess64::initState() at process.cc:1,777 0x5555572d5e5e

The interesting call is at AtomicSimpleCPU::activateContext:

schedule(tickEvent, clockEdge(Cycles(0)));

which calls EventManager::schedule.

AtomicSimpleCPU is an EventManager because SimObject inherits from it.

tickEvent is an EventFunctionWrapper which contains a std::function<void(void)> callback;, and is initialized in the constructor as:

tickEvent([this]{ tick(); }, "AtomicSimpleCPU tick",
        false, Event::CPU_Tick_Pri),

The call stack above ArmLinuxProcess64::initState is pybind11 fuzziness, but if we grep a bit we find the Python call point:

src/python/m5/simulate.py

def instantiate(ckpt_dir=None):

    ...

    # Create the C++ sim objects and connect ports
    for obj in root.descendants(): obj.createCCObject()
    for obj in root.descendants(): obj.connectPorts()

    # Do a second pass to finish initializing the sim objects
    for obj in root.descendants(): obj.init()

    ...

    # Restore checkpoint (if any)
    if ckpt_dir:
        ...
    else:
        for obj in root.descendants(): obj.initState()

and this gets called from the toplevel Python scripts e.g. se.py configs/common/Simulation.py does:

m5.instantiate(checkpoint_dir)

As we can see, initState is just one stage of generic SimObject initialization. root.descendants() goes over the entire SimObject tree calling initState().

Finally, we see that initState is part of the SimObject C++ API:

src/sim/sim_object.hh

class SimObject : public EventManager, public Serializable, public Drainable,
                  public Stats::Group
{

    ...

    /**
     * initState() is called on each SimObject when *not* restoring
     * from a checkpoint.  This provides a hook for state
     * initializations that are only required for a "cold start".
     */
    virtual void initState();

Finally, we see that initState is exposed to the Python API at:

build/ARM/python/_m5/param_SimObject.cc

module_init(py::module &m_internal)
{
    py::module m = m_internal.def_submodule("param_SimObject");
    py::class_<SimObjectParams, std::unique_ptr<SimObjectParams, py::nodelete>>(m, "SimObjectParams")
        .def_readwrite("name", &SimObjectParams::name)
        .def_readwrite("eventq_index", &SimObjectParams::eventq_index)
        ;

    py::class_<SimObject, Drainable, Serializable, Stats::Group, std::unique_ptr<SimObject, py::nodelete>>(m, "SimObject")
        .def("init", &SimObject::init)
        .def("initState", &SimObject::initState)
        .def("memInvalidate", &SimObject::memInvalidate)
        .def("memWriteback", &SimObject::memWriteback)
        .def("regProbePoints", &SimObject::regProbePoints)
        .def("regProbeListeners", &SimObject::regProbeListeners)
        .def("startup", &SimObject::startup)
        .def("loadState", &SimObject::loadState, py::arg("cp"))
        .def("getPort", &SimObject::getPort, pybind11::return_value_policy::reference, py::arg("if_name"), py::arg("idx"))
        ;

}

which is more magical than the other param classes since py::class_<SimObject has non-trivial methods, those are auto-generated by the cxx_exports code generation mechanism:

class SimObject(object):

    ...

    cxx_exports = [
        PyBindMethod("init"),
        PyBindMethod("initState"),
        PyBindMethod("memInvalidate"),
        PyBindMethod("memWriteback"),
        PyBindMethod("regProbePoints"),
        PyBindMethod("regProbeListeners"),
        PyBindMethod("startup"),
    ]

And the second magically scheduled event is the exit event:

0: Event: Event_70: generic 70 scheduled @ 0
0: Event: Event_70: generic 70 rescheduled @ 18446744073709551615

which is scheduled with backtrace:

Trace::OstreamLogger::logMessage() at trace.cc:149 0x5555593b3b1e
void Trace::Logger::dprintf_flag<char const*, char const*, unsigned long>() at 0x55555949e603
void Trace::Logger::dprintf<char const*, char const*, unsigned long>() at 0x55555949de58
Event::trace() at eventq.cc:395 0x55555946d109
EventQueue::schedule() at eventq_impl.hh:65 0x555557195441
BaseGlobalEvent::schedule() at global_event.cc:78 0x55555946d6f1
GlobalEvent::GlobalEvent() at 0x55555949d177
GlobalSimLoopExitEvent::GlobalSimLoopExitEvent() at sim_events.cc:61 0x555559474470
simulate() at simulate.cc:104 0x555559476d6f

which comes at object creation inside simulate() through the GlobalEvent() constructor:

simulate_limit_event =
    new GlobalSimLoopExitEvent(mainEventQueue[0]->getCurTick(),
                                "simulate() limit reached", 0);

This event indicates that the simulation should finish by overriding bool isExitEvent() which gets checked in the main simulation at EventQueue::serviceOne:

if (event->isExitEvent()) {
    assert(!event->flags.isSet(Event::Managed) ||
            !event->flags.isSet(Event::IsMainQueue)); // would be silly
    return event;

Tested in gem5 12c917de54145d2d50260035ba7fa614e25317a3.