Welcome to Ars-Informatica  

 
 
 
 
 
 

If you want to build a ship don't herd people together to collect wood and don't assign them tasks and work, but rather teach them to long for the endless immensity of the sea. (Antoine-Marie-Roger de Saint-Exupéry)

The mm_process


In Metropolis a process is an active element with its own control thread. The declaration of a process involves four steps:

  • a name used to identify the process
  • a process header (enum_static_events) where the list of actions that a process can perform are listed in form of a pre-defined list of events (an action is executed between the begin and end of an event)
  • the main thread that is the entry point of the process
  • the actions along with the statements that belong to each action

When the process hits a synchronization point (label annotation, await statement, action statement) the simulator core-lib schedules the process according to the following figure

The process state chart follows the definition in the Tagged Signal Model. Each process has a set of behaviors. A system is determinate if it exposes exactly one (RUNNING) or zero (DONTRUN) behaviors. The way in which the set of possible behaviors (CANRUN) are restricted is determined by input signals, which are collections of events. It is up to the fire_mgr in the core-lib intersects the events and selects the behavior.

A process skeleton is depicted in figure

The name of the process is defined by the user and it is useful during the mapping process. The so-called process header enum_static_events is a pure virtual function that requires user implementation. The following snippet of code shows an example:

        
        void myprocessclass::enum_static_events() {
            // if the process does not derive directly from mm_process
            // call the base class ! (i.e. xGiotto)
            baseclass::enum_static_events();
            // list of events/actions
            add_static_event(eventname, &actionfunction);
        }
        
        

where eventname is a string with the name of the event and actionfunction is the address of the action to be executed when the event is fired. The actionfunction may also be a NULL pointer. There are at least two cases in which this may be useful:

  • the action is mapped on a co-simulation engine
  • the action represents a quantity constraint (time delay, etc ...)

In the former case all the statements belonging to the action are actually executed in the co-simulation engine. In the later case the quantity annotation requests the corresponding quantity manager to schedule the execution and the statements of the action follow the request without the need of a stand-alone action (conceptually are the same but the simulator allows both representations for a sake of simplicity from user perspective).

The following code shows a simple example for the first case:

        
        ...
        // call the co-simulation for decoding a stream
        mm_action decode(MM_COMPOSITION_SERIAL, 1,
            new mm_code("decode", NULL, this, "decoder.xml"));
        decode.execute();
        
        

For the quantity case let's imagine to model a RTOS. A taskSwitch action may be defined as:

        
        void RTOS::enum_static_events() {
            add_static_event("taskSwitch", NULL);
            add_static_event("taskRun", &taskRun);
            add_static_event("checkWaitingProcesses",
                            &checkWaitingProcesses);
        }
        
        

If we want to model the fact that it takes one cycle:

        
        mm_action action(get_action("taskSwitch", this));
        xi = (mt_unsigned_long_t *)
                   get_quantity("_GTIME_")->A();
        mm_event *event = get_static_event(MM_BEGIN_EVENT,
                                           "taskSwitch");
        get_quantity("_GTIME_")->get_mgr()->
            request(new mm_request_class_gtime(event, xi));
        get_quantity("_GTIME_")->get_mgr()->
            request(new mm_request_class_gtime(
                       event->get_other(),
                       new mt_unsigned_long_t(*xi + 1)));
        action.execute();
        
        

The code above does the following:

  • create the action taskSwitch by referring to its static event (the static event is no more static since it is created as a new instance, copy of the static one, dynamically fire-able by the simulation platform)
  • the quantity GTIME is called to get the current value
  • the static begin event corresponding to the taskSwitch action is retrieved in order to create a request for the quantity manager
  • first request requires that the begin event being executed now
  • second request requires that the end event event->get_other() returns the end/begin events for begin/end events respectively) being executed now plus one tick
  • request to the simulation library is performed by calling the execute of the action

The actions are handled by mm_code class. An action can correspond to one statement, a block of statements or even an entire function.

Any event a process can fire must be registered in the enum_static_events} function. The simulator forces the user specify all the events a-priori in order to build an event set structure before starting the simulation. This phase is called elaboration and it is used by the simulation engine to create relationships among different events. For example let's imagine a writeint action in process dummy on a functional side is mapped onto cpuwriteint on a process called CPU. The mapping netlist called mymap implements the following function:

        
        void mymap::intersect(mm_synch &synch) {
            synch.add("dummy", "writeint", "CPU", "cpuwriteint");
            // ...
        }
        
        

Again the intersect function is a pure virtual function used by the simulation elaboration phase to create events relationships. The mechanism is hidden underneath the mm_synch class.

If a named event is not registered in enum_static_event} the elaboration phase raises an error.