Process Source Code
This page describes how to implement the processes of a DAL application using C/C++. The key properties of the coding style are:
- The internal state of a process is stored in a structure.
- The behavior of a process is abstracted into three methods, namely
,
fire
, andfinish
. Roughly speaking, theinit
procedure is responsible for the initialization and is executed once at the startup of the application. Afterwards, the execution of a process is split into individual executions of the imperativefire
procedure, which is repeatedly invoked.
Once an application is stopped, the finish
procedure is called for cleanup.
- The communication between processes is enabled by calling high-level
read
andwrite
functions. - The finite state machine is triggered by events generated by invoking the function
DAL_send_event
.
Contents
Process Structure
The interface to the internal state and the behavioral functions of a process is abstracted in the structure DALProcess
. This structure DALProcess
can be seen as an interface in the object-oriented sense that allows a unified access to any process, independent of the actual process-specific implementation.
DALProcess
contains five pointers, namely local
, init
, fire
, finish
, and wptr
and is defined in the DAL header file dal.h
.
- The
local
member is a pointer to a structure that will store local information of a process. "Local" means that the data of the referenced structure is visible for just one process instance. If a process is iterated, each instance will have its own local structure. - The
init
,fire
, andfinish
function pointers point to the corresponding behavior functions. With these three functions, each process can have its own behavior. - The
wptr
pointer is a place holder for tool-chain specific process information. In the DAL functional simulation, for instance,wptr
is used to point to an instance of the C process class wrapper.
Process Header File
Each process has to be provided with a header file:
#ifndef SQUARE_H
#define SQUARE_H
#include <dal.h>
#define PORT_in "in"
#define PORT_out "out"
#define EVENT_1 "square_done"
/* Only required when using Windowed FIFOs */
typedef float TOKEN_in_t;
typedef float TOKEN_out_t;
/* Only required when using Windowed FIFOs */
#define TOKEN_in_RATE 1
#define TOKEN_out_RATE 1
#define BLOCK_out_SIZE 1
#define BLOCK_out_COUNT (TOKEN_out_RATE / BLOCK_out_SIZE)
typedef struct _local_states {
int currentNumber;
} Square_State;
void square_init(DALProcess *);
int square_fire(DALProcess *);
void square_finish(DALProcess *);
#endif
The properties of the header file are as follows:
-
It has to include the
dal.h
header file which declares some of the data types and functions used in a process. Other header files can be included, as well. -
For each port which is accessed within a process, a port macro must be declared in the header file of the process. This declaration must take the following form
#define PORT_X portname
. The name of the port must be the same as defined in the process network XML file. If the name of the port is an integer,portname
is simply the corresponding integer. If the name of the port is a string,portname
is the string in quotes. The name of the defined macro must have the prefix "PORT_
". It is prohibited to define any other macro having "PORT_" as its prefix. -
Each event that can eventually be triggered by the process has to be defined as
EVENT_X "<name>"
where<name>
represents the name of the event used in the XML specification of the finite state machine. The name of the defined macro must have the prefix "EVENT_". It is prohibited to define any other macro having "EVENT_" as its prefix. -
If the process is not intended to be used as an OpenCL kernel, it can store some internal state variables inside the
_local_states
structure. Its name should be<Name>_State
with<Name>
being the camel case variant of the process name. -
Finally, the header should contain the declaration of the three process functions
<name>_init
,<name>_fire
and<name>_finish
where<name>
is the process name. All three processes require a pointer to aDALProcess struct
as an argument. Theinit
andfinish
functions are called on creation and destruction of the process and should not return any value. Thefire
function is called repeatedly and contains the main functionality of the process. It has to return an integer number indicating whether it is able to process more data (0
) or if it should not be called anymore (1
). This is only applicable for processes later used as POSIX threads and the return value is ignored in OpenCL kernels, because they cannot return any value at all. For compatibility reasons though, the return value is still required.
In addition, if a FIFO channel is implemented as a Windowed FIFO (as requested by the OpenCL back-end), the following information must be provided in the header file:
-
The tokens sent and received by a process have to be defined as a new type
TOKEN_<name>_t
with<name>
again matching the port name for this token. The size of this data type should match the token size specified in the XML file. -
There need to be some additional parameters defined, depending on the port type. Input ports only have to set its token rate as
TOKEN_<name>_RATE
(<name>
always being the port name) and output ports have to define its token rate as well as its block size (BLOCK_<name>_SIZE
) and the number of blocks (BLOCK_<name>_COUNT
) which is usually simply (TOKEN_<name>_RATE
/BLOCK_<name>_SIZE
).
Process Source File
Overview and Example
The actual source file of a process defines its behavior:
#include "square.h"
void square_init(DALProcess *p) {
}
int square_fire(DALProcess *p) {
TOKEN_in_t *rbuf = (TOKEN_in_t *)DAL_read_begin(PORT_in, sizeof(TOKEN_in_t), TOKEN_in_RATE, p);
DAL_foreach (blk : PORT_out)
{
TOKEN_out_t *wbuf = (TOKEN_out_t *)DAL_write_begin(PORT_out, sizeof(TOKEN_out_t), TOKEN_out_RATE, BLOCK_out_SIZE, blk, p);
*wbuf = rbuf[blk] * rbuf[blk];
DAL_write_end(PORT_out, wbuf, p);
}
DAL_read_end(PORT_in, rbuf, p);
return 0;
}
void square_finish(DALProcess *p) {
}
Source files have to include their header file at the beginning and may also include other header files. System headers can be included too, but they are ignored with certain back-ends.
The init
, fire
, and finish
functions declared in the header file need to be defined here. The init()
, fire()
, and finish()
must not contain infinite loops. For inter-process communication, the communication functions described below can be used inside the init()
, fire()
, and finish()
functions. In particular, we differ between the communication interface for transmitting application data from one process to another one and between the communication interface to interact with the finite state machine.
Apart from these functions, there exists an other construct: the DAL_foreach
loop. It is used to iterate through all output blocks using the method specified in the mapping XML file. The syntax is as follows:
DAL_foreach (<block_id> : <port>) { }
-
<block_id>
has to be an unused variable name, because it is automatically transformed into a proper variable type. -
<port>
has to be one of thePORT_<name>
output ports defined in the header file.
Application Data Communication Interface
There are several functions provided by DAL to interact with the other processes via the corresponding ports. We different between accessing ordinary FIFOs and Windowed FIFOs.
Ordinary FIFO Communication Interface
-
int DAL_write(void *port, void *buf, int len, DALProcess *p)
attempts to write up tolen
bytes from port descriptorport
into the buffer starting atbuf
. -
int DAL_read(void *port, void *buf, int len, DALProcess *p)
attempts to read up tolen
bytes from port descriptorport
into the buffer starting atbuf
.
Windowed FIFO Communication Interface
-
void *DAL_read_begin(int port, int tokensize, int tokenrate, DALProcess *p)
This function is used to read data of sizetokensize
from aport
with a giventokenrate
.-
int port
: must be one of thePORT_<name>
constants defined in the header file -
int tokensize
: usually simply uses thesizeof
operation on theTOKEN_<name>_t
type -
int tokenrate
: should be one of theTOKEN_<name>_RATE
constants matching the used port -
DALProcess *p
: used for compatibility reasons and equals theDALProcess
pointer given to the calling function
The return value of
DAL_read_begin
should be assigned to a variable of typeTOKEN_<name>_t
according to the port used. -
-
void DAL_read_end(int port, void *buf, DALProcess *p)
This function ends the read procedure forport
and renders thebuf
pointer useless, therefore it should be called near the end of the function.-
int port
: has to be the samePORT_<name>
constant as used for the correspondingDAL_read_begin
-
void *buf
: the pointer which stored the data returned byDAL_read_begin
-
DALProcess *p
: used for compatibility reasons and equals theDALProcess
pointer given to the calling function
-
-
void *DAL_write_begin(int port, int tokensize, int tokenrate, int blocksize, int blk_ptr, DALProcess *p)
This function is used to write data of sizetokensize
to aport
with a giventokenrate
and a givenblocksize
.-
int port
: should be one of thePORT_<name>
constants defined in the header file -
int tokensize
: usually simply uses thesizeof
operation on theTOKEN_<name>_t
type -
int tokenrate
: should be one of theTOKEN_<name>_RATE
constants matching the used port -
int blocksize
: normally uses theBLOCK_<name>_SIZE
constant matching the used port -
int blk_ptr
: used to define which block of the output port is being written -
DALProcess *p
: used for compatibility reasons and equals theDALProcess
pointer given to the calling function
The return value of
DAL_write_begin
should be assigned to a variable of typeTOKEN_<name>_t
according to the port used and is a pointer to the write buffer. -
-
void DAL_write_end(int port, void *buf, DALProcess *p)
This function actually writes the data from thebuf
pointer to theport
.-
int port
: has to be the samePORT_<name>
constant as used for the correspondingDAL_write_begin
-
void *buf
: the pointer to the write buffer returned byDAL_write_begin
-
DALProcess *p
: used for compatibility reasons and equals theDALProcess
pointer given to the calling function
-
Event Communication Interface
This set of communication interfaces is used to send events to the finite state machine, i.e., to request behavior changes in the set of running or paused applications. In the current version, we define one primitive, namely DAL_send_event()
:
-
int DAL_send_event(void *event, DALProcess *p)
attempts to send an event encoded as an integer to the finite state machine.
We assume that events are always sent asynchronously and the sent events can be queued up in a first-come first-serve event queue. In the case that the event queue is full, the function DAL_send_event()
will be blocked until there is empty place in the event queue.
The finite state machine reacts to the events in the event queue. It repetitively checks the event queue, always gets the first event in the queue, and enables the corresponding transition. Only after the transition completes, i.e., the system is in the final state associated with this transition, the next event will be fetched. Undefined events and events that are not acceptable in the current state of the finite state machine, will be discarded and the finite state machine will simply remain in the current state.