= Module Developer Guide = ALOE generates waveforms by linking a set of Signal Processing blocks following a defined graph. The concatenation of each processing function, applied to a flow of data produces the desired signal. Each of these components must be written (in ANSI C) following a set of rules which enable it to interact with the rest of components and with the underlying Operating Environment. A Signal Processing Block or '''"ALOE Module"''' has the following properties: * A set of ''input ''interfaces * A set of ''output ''interfaces * A set of ''initialization parameters'' And performs the following behaviour: * Reads once the initialization parameters and configures itself. * Generates data for a set of output streams as a function of any, one or more input streams == Defining a Module == Given the previous definition of a module, we will organize module files following this directory structure: ||'''Directory'''||'''Files'''||'''Comment'''|| ||''mymod''/platform_make/||...||One directory for each platform it can be compiled. For example:[[BR]]''lnx_make'' shall contain the Makefile, ''c6000_make'' shall contain CCS' .pjt files, etc.|| ||''mymod''/interfaces/||inputs.h[[BR]]outputs.h[[BR]]stats.h||Definition of input/output interfaces, statistics variables and initialization parameters|| ||''mymod''/src/||module.c[[BR]]module_imp.c[[BR]]mymod.c||mymod.c: Standalone function implementing the algorithm.[[BR]]module.c and module_imp.c is the skeleton[[BR]]See below for more information|| ||''mymod''/bin/||mymod||Binaries after compilation and linking are placed here.|| == == == Implementing the Module == Once your module has been properly defined, you can begin coding it. Here we assume the reader is familiar with basic ALOE concepts, nevertheless, this section describes how to implement an ALOE Module and provides some examples. === Process Structure === All the tasks a radio application shall realize must follow the following simple function structure: {{{ “Check message, if any present -> Dispatch the task -> Return” }}} The first order the object will receive will be to initialize (INIT) itself. Then, the module will read a set of initialization parameters, activate the communications interfaces and declare a set of statistics variables (for monitoring purposes). This procedure is executed just once in the life-cycle of the module. If an execution order is received (RUN), the object will receive any pending message, dispatch the required task (for example process a block of data received from an interface and send the result through another interface) and return to the initial point. The last case is the reception of the order to close (STOP) the module. In such case, the module must tell to ALOE to close all the allocated resources and finish its execution. Note how the module owns the CPU during the time it process the message. After that, the object returns to the STATUS point handing the CPU over to ALOE again. In anomalous or time rules violation, ALOE may release the ownership from the module before returning to the STATUS point. === Coding a Module === The programming language used to describe a module may change between different processing devices, depending on the tools provided by the device manufacturer. However, it would be very interesting to use always the same language so the code can be reused. This is unrealistic, because currently programmable devices as GPP or DSP can use C/C++ while others like logic programmable devices as FPGA use totally different languages (VHDL). Nevertheless, these are the chosen standard programming language for the ALOE Framework: * C Language for programmable devices * VHDL Language for FPGA devices Not only the use of a standard language is a requirement to enable the whole set of functionalities provided by ALOE. Also the use of the ALOE Software Library API is a requirement as well as other considerations. At this point, we strongly recommend the reader to check [htdocs:swapi_doc/index.html ALOE SW-API Documentation] to get an insight on the available functions and its behaviour. A Module designer must be concerned about the misinformation of the final platform to be executed on. This means that very important concepts (in software radio) like sampling frequency, time slot duration, memory distribution or architecture, etc. are totally unknown when modules are designed. Thus, it would need to support such sort of frequency ranges and architectures under parameter reconfigurations. A special function provided by the ALOE Software Library API will serve for this purpose. The number of samples to be generated every time the module is executed (every time slot) will be obtained using the following function: {{{ int GetTempo(float freq); }}} This function receive as parameter the frequency the module desires to operate. As the environment (ALOE) is the only who knows the execution interval associated to the process, a simply operation will be done to calculate the number of samples to be generated at that precise moment. The amount of samples that the module needs at its input will depend on the algorithm, more precisely, in the rate relation between the input and output frequency. Anyway, the object will wait until all the data it needs is available. Note that this could lead to time violations if frequency relations between collateral objects are not defined with caution. In order to facilitate modules programming an skeleton implementing some common functionalities has been defined. In this sense, the user will only have to define the interfaces in a constant structure and provide some functions to process data. The initialization and buffer management will be done by the skeleton. This skeleton is provided in the module.c file and is exactly the same for any object (so you can copy anyone provided by the example objects). The skeleton needs three header files: inputs.h, outputs.h and stats.h. Interfaces and statistics are defined in structures (defined in swapi_utils.h) which name has to be mantained (input_itfs, output_itfs, params and stats). The following figure describe these structures: '''Figure 2 – Structure definitions (swapi_utils.h)''' {{{ /** Structure for logic interfaces * * Regardless the direction of the interface, the user must provide * a buffer for storing data of size max_buffer_len elements of size sample_sz (in bytes). * * For input interfaces. Set the variable name to input_itf: * process_fnc() will be called as soon as the required amount of * samples is available. This number is provided by the function get_block_sz() * which will be called at the beginning of every timeslot (if needed). * Setting get_block_sz() to NULL is equivalent to returning 0 from this function. * As soon as any data is available the processing function will be called. * * For output interfaces. Set the variable name to output_itf: * If data needs to be generated, point get_block_sz() to a function returning * the amount of samples to be generated (you can use GetTempo to convert from * frequency to number of samples). Then, process_fnc() will be called to * generate such data. * If data is being provided synchronously to any input interface, set get_block_sz * to NULL and use SendItf() function to send the data. * * Example: * * Configure an input interface which can process any amount of samples (not block oriented) * * typedef int input1_t; * * struct utils__itf input_itfs[] = { * {"myInputInterface", * sizeof(input1_t), * 1024, * input_buffer, * NULL, * process_input}, * * {NULL, 0, 0, 0, 0, 0}}; * * */ struct utils_itf { char *name; /**<< Name of the interface */ int sample_sz; /**<< Size of the sample, in bytes */ int max_buffer_len; /**<< Max buffer length in samples */ void *buffer; /**<< Buffer where to store data */ int (*get_block_sz) (); /**<< Returns number of samples to read/generate. Returning <0 interrupts execution*/ int (*process_fnc) (int); /**<< Function to process/generate data. Returning 0 interrupts execution */ }; struct utils_param { char *name; /**< Name of the parameter */ char type; /**< Type of the parameter*/ int size; /**< Size of the parameter value */ void *value; /**< Pointer to the value to obtain. Make sure the buffer is big enough (len) */ }; /** Structure for statistics * * You can automatically initialize a variable if you set the configuration * in this structure. * The id of the initialized variable is stored in the id pointer and you can * also initialize the variable with a value if the pointer value is set to any * non-NULL value. * * Set update_mode to READ to automatically read the contents of the variable * at the beggining of every timeslot (with the contents at value with fixed length size). * * Set update_mode to WRITE to automatically set the contents of the variable * at the end of every timeslot (with the contents at value with fixed length size). * * Set update_mode to OFF to disable automatic updates. */ struct utils_stat { char *name; /**< Name of the variable */ char type; /**< Type of the variable */ int size; /**< Size of the variable, in elements (of type) */ int *id; /**< Pointer to store the id for the stat */ void *value; /**< Initial value for stat */ enum stat_update update_mode; /**< Mode for automatically updating */ }; }}} The other skeleton file, module_imp.c, is the bridge between the skeleton and the algorithm functions. It implements de functions called after a packet is received or must be generated. Although the algorithm could be implemented here, good coding practices recommend to do it in another file so the implementation shall be used in other contexts. '''Figure 3 – Object implementation (module_imp.c)''' {{{ /** ALOE headers */ #include #include #include #include #include "inputs.h" #include "outputs.h" #include "stats.h" char str[128]; int get_output_length() { /* use gettempo to get number of samples */ return GetTempo(freq); } /** Run function. * @return 1 if ok, 0 if error */ int generate_output(int len) { int i; if (!len) return 1; sprintf(str, "Generating sampfreq %.1f sin freq %.1f\n", freq,fsin); WriteLog(1,str); /* generate sinusoid */ generate_sinusoid(output_data,fsin,fsamp); SetStatsValue(stat_signal, output_data, len); return 1; } int InitCustom() { return 1; } int RunCustom() { return 1; } }}} The last two functions ({{{InitCustom()}}} and {{{RunCustom()}}}) have also to be defined. The {{{InitCustom}}} function is used to implement custom initialization routines not related with interface or statistics initialization. For example, if a module has to compute a set of filter coeficients given an initialization parameter, it can be done in this function. The skeleton will get the parameter(s) value if we define it in the specific structure. After that, the {{{InitCustom}}} function will be called and we will perform the computations.  Analogously, the {{{RunCustom}}} function might be useful to perform some background tasks not related directly with data processing (more formally, asyncrhonous with data flow). An example of this may be updating an internal state. Finally, the actual algorithm will be implemented in a standalone file, for example, myobj.c: {{{ /** ALOE headers */ #include #include #define PI 3.1415 void generate_sinusoid(int *output_data, float fsin, float fsamp) { int i; for (i=0;i