Personal tools
User menu

Process Source Code

Jump to: navigation, search

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, and finish. Roughly speaking, the init 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 imperative fire 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 and write functions.
  • The finite state machine is triggered by events generated by invoking the function DAL_send_event.

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, and finish 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:

  1. #ifndef SQUARE_H
  2. #define SQUARE_H
  3.  
  4. #include <dal.h>
  5.  
  6. #define PORT_in  "in"
  7. #define PORT_out "out"
  8.  
  9. #define EVENT_1 "square_done"
  10.  
  11. /* Only required when using Windowed FIFOs */
  12. typedef float TOKEN_in_t;
  13. typedef float TOKEN_out_t;
  14.  
  15. /* Only required when using Windowed FIFOs */
  16. #define TOKEN_in_RATE  1
  17. #define TOKEN_out_RATE 1
  18. #define BLOCK_out_SIZE 1
  19. #define BLOCK_out_COUNT (TOKEN_out_RATE / BLOCK_out_SIZE)
  20.  
  21. typedef struct _local_states {
  22.     int currentNumber;
  23. } Square_State;
  24.  
  25. void square_init(DALProcess *);
  26. int square_fire(DALProcess *);
  27. void square_finish(DALProcess *);
  28.  
  29. #endif

The properties of the header file are as follows:

  1. 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.
  2. 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.
  3. 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.
  4. 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.
  5. 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 a DALProcess struct as an argument. The init and finish functions are called on creation and destruction of the process and should not return any value. The fire 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:

  1. 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.
  2. 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:

  1. #include "square.h"
  2.  
  3. void square_init(DALProcess *p) {
  4. }
  5.  
  6. int square_fire(DALProcess *p) {
  7. 	TOKEN_in_t *rbuf = (TOKEN_in_t *)DAL_read_begin(PORT_in, sizeof(TOKEN_in_t), TOKEN_in_RATE, p);
  8.  
  9. 	DAL_foreach (blk : PORT_out)
  10. 	{
  11. 		TOKEN_out_t *wbuf = (TOKEN_out_t *)DAL_write_begin(PORT_out, sizeof(TOKEN_out_t), TOKEN_out_RATE, BLOCK_out_SIZE, blk, p);
  12. 		*wbuf = rbuf[blk] * rbuf[blk];
  13. 		DAL_write_end(PORT_out, wbuf, p);
  14. 	}
  15.  
  16. 	DAL_read_end(PORT_in, rbuf, p);
  17.  
  18. 	return 0;
  19. }
  20.  
  21. void square_finish(DALProcess *p) {
  22. }

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>) {
}
  1. <block_id> has to be an unused variable name, because it is automatically transformed into a proper variable type.
  2. <port> has to be one of the PORT_<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 to len bytes from port descriptor port into the buffer starting at buf.
  • int DAL_read(void *port, void *buf, int len, DALProcess *p) attempts to read up to len bytes from port descriptor port into the buffer starting at buf.

Windowed FIFO Communication Interface

  1. void *DAL_read_begin(int port, int tokensize, int tokenrate, DALProcess *p) This function is used to read data of size tokensize from a port with a given tokenrate.
    1. int port: must be one of the PORT_<name> constants defined in the header file
    2. int tokensize: usually simply uses the sizeof operation on the TOKEN_<name>_t type
    3. int tokenrate: should be one of the TOKEN_<name>_RATE constants matching the used port
    4. DALProcess *p: used for compatibility reasons and equals the DALProcess pointer given to the calling function

    The return value of DAL_read_begin should be assigned to a variable of type TOKEN_<name>_t according to the port used.

  2. void DAL_read_end(int port, void *buf, DALProcess *p) This function ends the read procedure for port and renders the buf pointer useless, therefore it should be called near the end of the function.
    1. int port: has to be the same PORT_<name> constant as used for the corresponding DAL_read_begin
    2. void *buf: the pointer which stored the data returned by DAL_read_begin
    3. DALProcess *p: used for compatibility reasons and equals the DALProcess pointer given to the calling function
  3. 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 size tokensize to a port with a given tokenrate and a given blocksize.
    1. int port: should be one of the PORT_<name> constants defined in the header file
    2. int tokensize: usually simply uses the sizeof operation on the TOKEN_<name>_t type
    3. int tokenrate: should be one of the TOKEN_<name>_RATE constants matching the used port
    4. int blocksize: normally uses the BLOCK_<name>_SIZE constant matching the used port
    5. int blk_ptr: used to define which block of the output port is being written
    6. DALProcess *p: used for compatibility reasons and equals the DALProcess pointer given to the calling function

    The return value of DAL_write_begin should be assigned to a variable of type TOKEN_<name>_t according to the port used and is a pointer to the write buffer.

  4. void DAL_write_end(int port, void *buf, DALProcess *p) This function actually writes the data from the buf pointer to the port.
    1. int port: has to be the same PORT_<name> constant as used for the corresponding DAL_write_begin
    2. void *buf: the pointer to the write buffer returned by DAL_write_begin
    3. DALProcess *p: used for compatibility reasons and equals the DALProcess 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.

!!! Dieses Dokument stammt aus dem ETH Web-Archiv und wird nicht mehr gepflegt !!!
!!! This document is stored in the ETH Web archive and is no longer maintained !!!