next up previous contents index
Next: 2.7 Reference Up: 2. Application Programmer's Guide Previous: 2.5 Visualization of Process   Contents   Index

Subsections


2.6 Programming Guidelines


2.6.1 Coding Style

2.6.1.1 Processes

The preferred coding style for the definition of process types is indicated in the template listed below. This template is also part of the release in the subdirectory containing the examples. The template for user-defined processes is listed in the files myprocess.h and myprocess.cc. A user-defined process consists of a public part, that is accessible to the environment of a process instance, and of a private part, that is only accessible to the process instances themselves. The public part consists of four consecutive sections, namely constructors, destructor, the type member function, and the main member function. The private part consists of four consecutive sections, namely input ports, output ports, member functions, and member variables.


Program 2.6.1   Process Template
class MyProcess : public Process
{
public:
        // constructors 
        MyProcess(const Id& n, In<int>& i, 
                  Out<int>& o, int x);

        // destructor 
        ~MyProcess();

        // type member function 
        const char* type() const;

        // main member function
        void main();
private:
        // input ports 
        InPort<int>     in;

        // output ports 
        OutPort<int>    out;

        // member functions 
        int             f(int);

        // member variables
        int             a;
};


The constructor section contains one or more constructors. Each constructor has one or more arguments. The first argument is an instance name that is given when the programmer instantiates a process. The next arguments are optional; they are inputs, outputs, and parameters, respectively. More specically, the subsequent zero or more arguments are inputs that are bound to the input ports. The number, the type, and the order of inputs must correspond to the number, the type, and the order of input ports in the input port section. The next zero or more arguments are outputs that are bound to the output ports. The number, the type, and the order of outputs must correspond to the number, the type, and the order of output ports in the output port section. The final zero or more arguments are parameters that are used to initialize the member variables of the member variable section.

The destructor section contains an optional destructor. The purpose of the destructor is to deinitialize the member variables and to deallocate memory that has been allocated in the constructor. The type member function section contains one function that must return the type of the process, i.e., the class name which is in this case ``MyProcess''. The input port section contains zero or more input ports. The number, the type, and the order must correspond to those of the inputs in the constructors. The output port section contains zero or more output ports. The number, the type, and the order must correspond to those of the outputs in the constructors. The member function section contains zero or more functions which can be called from the main function. The member variable section contains zero or more member variables that can be accessed by the member functions including the main function.

The initialization of process instances is done in a constructor of the corresponding process type. The constructor of a process type contains a member initialization section and a member assignment section. The member assignment section may be used to assign a value to the member variables that are not initialized via the initializer list. The constructor cannot contain calls to member functions.


Program 2.6.2   Process Constructor Template
MyProcess::MyProcess(const Id& n, In<int>& i, 
                     Out<int>& o, int x) :
        // member initialization 
        Process(n),
        in(id("in"), i),
        out(id("out"), o)
{
        // member assignment
        a = x;
}


The initialization list passes the instance name of the user-defined process to the predefined base class Process. Furthermore the list initializes the input ports and output ports. To this end one must supply instance names as well as the binding of the ports to the inputs and outputs.

The functionality of a process type is defined in the main member function. Each process instance executes the main member function using its own state space, i.e., each process instance has its own program counter, its own memory space containing the variables as defined in the process type, its own ports, etcetera. The functionality in the main member function as well as the other member functions is described in terms of regular C or C++ constructs and the predefined YAPI functions read, write and select. Variables that are used in only one member function of a process type have to be declared locally in the scope of the specific member function. Variables that are used in more than one member function and that are not passed as arguments of the member functions have to be declared as member variables. It is important to note that one must not declare global or static variables other than constants because these variables are shared by all process instances of a process type. Consequently, all process instances operate in parallel on the same global or static variable which will result in unpredictable behavior because the accesses on this variable are not synchronized.

2.6.1.2 Process Networks

The preferred coding style for the definition of process network types is indicated in the template listed below. This template is also part of the release in the subdirectory containing the examples. The template for user-defined process networks is listed in the files mynetwork.h and mynetwork.cc. A user-defined process network consists of a public part, that is accessible to the environment of a process network instance, and of a private part, that is only accessible to the process network instances themselves. The public part consists of two consecutive sections, namely constructors and the type member function. The private part consists of five consecutive sections, namely input ports, output ports, fifos, processes, and process networks.


Program 2.6.3   Process Network Template
class MyNetwork : public ProcessNetwork
{
public:
        // constructors
        MyNetwork(const Id& n, In<int>& i, 
                  Out<int>& o, int x);

        // type member function
        const char* type() const;
private:
        // input ports 
        InPort<int>     in;

        // output ports 
        OutPort<int>    out;

        // fifos 
        Fifo<int>       fifo;

        // processes 
        MyProcess       myprocess;

        // process networks 
        MyOtherNetwork  mynetwork;
};


The constructor section is the same as the constructor section of processes. It contains one or more constructors. Each constructor has one or more arguments. The first argument is an instance name that is given when the programmer instantiates a process network. The next arguments are inputs, outputs, and parameters, respectively. More specifically, the subsequent zero or more arguments are inputs that are bound to the input ports. The number, the type, and the order of inputs must correspond to the number, the type, and the order of input ports in the input port section. The next zero or more arguments are outputs that are bound to the output ports. The number, the type, and the order of outputs must correspond to the number, the type, and the order of output ports in the output port section. The final zero or more arguments are parameters that are passed to processes and process networks in the process and process network sections, respectively.

The type member function section contains one function that must return the type of the process network, i.e., the class name which is in this case ``MyNetwork''. The input port section contains one or more input ports. The number, the type, and the order must correspond to those of the inputs in the constructors. The output port section contains one or more output ports. The number, the type, and the order must correspond to those of the outputs in the constructors. The fifo section contains zero or more fifos. The process section contains zero or more processes. The process network section contains zero or more process networks.

The initialization of process network instances is done in a constructor of the corresponding process network type. The constructor of a process network type only contains a member initialization section. The member assignment section is obsolete because process network types only contain member variables that have a constructor.


Program 2.6.4   Process Network Constructor Template
MyNetwork::MyNetwork(const Id& n, In<int>& i, 
                     Out<int>& o, int x) :
        // member initialization 
        ProcessNetwork(n),    
        in(id("in"), i),
        out(id("out"), o),
        fifo(id("fifo")),
        myprocess(id("myprocess"), in, fifo, x),
        mynetwork(id("mynetwork"), fifo, out)
{ }


The initialization list passes the instance name of the user-defined process network to the predefined base class ProcessNetwork. Furthermore the list initializes the input ports, output ports, fifos, processes, and process networks. To this end one must supply instance names, the binding of the ports to the inputs and outputs, and the binding of processes and process networks to the ports and fifos. Note that it is not allowed to pass the inputs and outputs directly to the processes and process networks.

2.6.2 Design Rules

In this section we present design rules to develop reusable YAPI applications. By reuse we mean two things. Firstly, to reuse applications in the design of alternative applications. Secondly, to reuse applications in the design of alternative architectures. To illustrate these design rules we use examples from VYA (Video YAPI) [23], which is a set of design rules for applying YAPI to model video applications.

Rule 1. Introduce functional data types

The introduction of functional data types improves reusability because it makes data types explicit. As an example we mention the data type VYApixel that is used to represent R, G, and B as well as Y, U, and V values. One advantage is that this data type abstracts from the representation of these values in an implementation, which can be for instance int or short. Another advantage is that it is clear what data type is used such that type checking at compile-time is feasible. For example, the data types VYAlineLength and VYAimageWidth are both defined as unsigned int, but the former is associated with lines while the latter is associated with images. Hence, functional data types are important, especially for the definition of the interfaces of processes to increase their reusability in alternative applications.

Rule 2. Minimize latency

The latency of a process is the difference between the number of values that it has read and the number of values that it has written. In general each output value functionally depends on one or more input values, such that these input values need to be read before the output value can be computed and written. By reading the input values as late as possible, i.e., only when you need them, and by writing the output values as soon as possible, i.e., immediately after you compute them, the number of living values in the process is minimal. This minimizes the memory requirements of the process and of its environment (i.e., the fifos of the process network) if its output needs to be synchronized with other streams. Here synchronization means to match the latency of the process and the latency of the other streams by storing these streams in fifos. By minimizing the latency of the processes also the fifo sizes that are minimally needed to avoid deadlock are reduced. This increases the reusability of the process in alternative applications.

Figure 2.5: The relation between process latencies and fifo sizes.
\begin{figure}\centerline{\psfig{figure=Figures/latency.eps,width=0.6\textwidth}}\end{figure}

To illustrate the relation between latency and fifo sizes we take the example of Figure 2.5. We assume that the producer alternatingly writes one value to its two ports. Similarly the consumer alternatingly reads one value from its two ports. We assume that they both start with the upper port. Due to latency $x$ of the filter, the first read action of the consumer cannot be completed until the producer has written $x$ values to its upper port. Hence, the fifo between the producer and the consumer must be able to store at least $x-1$ values to prevent deadlock.

Rule 3. Use vector operations

Vector operations have been introduced to efficiently support the reading and writing of composite data types. Suppose for example that we have a process that internally has a data type for video lines which is composed out of video pixels. In VYA this would typically be written as follows

typedef Line VYApixel[n];

where variable n is of type VYAlineLength, which is defined as unsigned int. The value of a variable line of type Line can be read using vector operation read(in, line, n) or written using vector operation write(out, line, n) in a single operation. Hence, the vector operations abstract from how the video line is communicated in the implementation. The implementation can communicate the line pixel by pixel, in groups of pixels, or in its entirety. So the abstraction provides more implementation freedom which increases the reusabilty in alternative architectures.

Rule 4. Avoid ``resource'' sharing

Ports, fifos, processes, and process networks are functional entities, they are not resources. Therefore, they must not be shared for multiple purposes. As an example we mention the interface of VYA processes and process networks which always have separate ports for Y, U, and V values of type VYApixel. In 4:2:2 format these streams are often multiplexed in one YUYV stream, but this is a design choice which must be made explicit in a mapping step if one maps the three YAPI ports for Y, U, and V, onto a single port in the architecture.

Another example of ``resource'' sharing is RGB to YUV conversion and vice versa. Although the functions for both cases might be quite similar apart from the coefficients, the interfaces for both cases are different. For RGB to YUV conversion we have R, G, and B inputs and Y, U, and V outputs. For YUV to RGB we have Y, U, and V inputs and R, G, and B outputs. Do not try to model these two functions in a single process because the resulting process is not reusable for others that need only one of the two functions. Moreover, the resulting process lacks the concurrency that two separate processes would have had. If one uses both processes in the same process network, then it is possible in the mapping step to map both processes onto a single processor mutually exclusive in time. One should not attempt to model this design decision in YAPI.


next up previous contents index
Next: 2.7 Reference Up: 2. Application Programmer's Guide Previous: 2.5 Visualization of Process   Contents   Index
© Copyright Koninklijke Philips Electronics NV 2006