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.