Version 17 (modified by ismael, 16 years ago) |
---|
Object 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 Object" 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 an Object
Given the previous definition of an object, we will organize object files following this directory structure:
Directory | Files | Comment |
myobj/interfaces/ | inputs.h outputs.h stats.h | Definition of input/output interfaces, statistics variables and initialization parameters |
myobj/src/ | myobj.c myobj_imp.c xxx.c | Implementation (myobj_imp.c + xxx.c) of the algorithm and main skeleton (myobj.c) See below for more information |
myobj/bin/ | myobj | Binaries after compilation and linking are placed here. |
Implementing the Object
Once your object 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 Object 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”
Figure 1 represents this concept. Notice how the object must announce its presence to ALOE prior to receive any order.
The first order the object will receive will be to initialize (INIT) itself. Then, the object 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 object.
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 object. In such case, the object must tell to ALOE to close all the allocated resources and finish its execution.
Note how the object 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 object before returning to the STATUS point.
Coding an Object
The programming language used to describe an object 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 ALOE SW-API Documentation to get an insight on the available functions and its behaviour.
An Object 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 objects 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 object is executed (every time slot) will be obtained using the following function:
int GetTempo(float freq);
This function receive as parameter the frequency the object 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 object 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 objects 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 myobj.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 */ };
You can read more of how to use these structures in the example objects. If you downloaded the source, they are in the example directory.
Finally, the following figure shows the file where the processing code should be written. Note that in the configuration of the structure we have provided a function to process (or generate) data for every interface. Now, the only thing we have to do is to code these functions:
Figure 3 – Object implementation (myobj_imp.c)
/** ALOE headers */ #include <phal_sw_api.h> #include <math.h> #include <swapi_utils.h> #include <phal_hw_api.h> #include <sys/resource.h> #define PI 3.1415 #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 */ for (i=0;i<len;i++) { output_data[i] = (int) ((float) 1000.0*cosf((double) 2.0*PI*fsin*i/freq)); } 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 an object 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.
Compiling and Linking
Compiling an ALOE objects does not need any other consideration rather than linking with the ALOE SW Library and HW Library. Obviously, we have to pick the HW library of our system. Under linux, and once we have installed ALOE (binary or source), the libraries should appear at /usr/local/lib with the names libsw_api.a and libhw_api.a.
If your prefer to use autoconf/automake tools the following attached files might be useful:
- sample configure.in
- sample Makefile.am