Version 3 (modified by ismael, 16 years ago) |
---|
Object Developer Guide
WARNING: This page is under heavy construction, be careful when following instructions found here.
Introduction
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
- A set of resource demands as a function of initialization parameters (i.e. cpu time, bandwidth, etc.)
And produces the following behavior:
- The object reads once the initialization parameters and configures its behavior.
- Given a set of parameter values, the object produces an output set of streams as a function of an input set of streams within the defined resource utilization constraints.
Defining an Object
Given the previous definition of an object, we define a directory structure for its implementation in order to homogenize objects from several sources and to access easily to useful information. Generating a new object should follow this layout:
File/Directory | Comment |
myobj/interf/ | A file with name interface_name.h for each input and output interface defining the used data structure and in case of input ones, an explanation of the object behavior given that input. See example: input.h control.h |
myobj/src/ | The source code of your object. Among others, the principal one (where the main() is) with the name myobj.c |
myobj/bin/ | Binaries after compilation and linking are placed here. |
myobj/props/ | Under Work: A file with name resources.conf specifying demanded resource utilization given certain parameters and a file with name parameters.conf defining the set of initialization parameters and its behavior. |
myobj/Makefile | For compilation |
Implementing the Object
Once your object has been properly defined, you can begin implementing its behavior. 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 lifecycle 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.
Figure 3.1 – P-HAL Object Flow Diagram
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 P-HAL 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.
An Object writer 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 has a 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.
The following concepts should be taken into account when designing an object:
- Sampling frequency and time slot is unknown: the length of input and output packet (packet length over time slot duration equals sampling frequency) should be allowed to change and should be limited.
- Our goal is to write to the output interface certain number of samples (GetTempo()) each execution cycle (time-slot) ensuring a constant mean sampling frequency along time.
- Processing time over data unit (cycles/sample) should be taken into account when defining the required processing resources depending on the operating frequency.
- The format of the input and output data has to be well defined (integers, short, signed/unsigned, etc.).
- If control data must be received, another interface different from the data in/out interfaces should be used, in such case, an struct may be defined to receive such parameters.
- The values acquired from parameters initialization should always be checked, to ensure they follow our local limitations (i.e. buffer sizes).
- Always define constants as compiler constant definitions (#define …). This facilitates code reusing and maintenance.
The following example provides a template of the main object file named, conventionally, as myobj.c. Notice how we first include definition files for each interface we are going to use and the header files for ALOE API utilization. Then, we fall to an infinite loop where we continuously check for an status and execute it, then, we relinquish the cpu.
Figure 2 – Main example source (myobj.c)
/** ALOE headers */ #include <phal.h> #include <phal_sw_api.h> #include "input.h" /**< Definition of interface 'input' */ #include "control.h" /**< Definition of interface 'control' */ #include "output.h" /**< Definition of interface 'output' */ /** Main */ void main() { int status; switch(status) { case PHAL_STATUS_INIT: /** Call INITIALIZATION function */ if (!Init()) { ClosePHAL(); exit(0); } break; case PHAL_STATUS_RUN: /** Call RUN function */ if (!Run()) { ClosePHAL(); exit(0); } break; default: /** rest, is STOP */ ClosePHAL(); exit(0); break; } Relinquish(); }
Now following figures shows the content of input.h output.h and control.h here control is an interface to modify object behavior. NOTE the difference between this control variables and the initialization parameters: any the input we provide to this control interface will produce a change in the object behavior (reconfiguration) within the time/memory specifications. That means, for example, that certain reconfigurations, lets say, compute filter coeficients may not be realized inside the time window producing a real time failure, thus can not be performed during the execution (RUN) phase (should be realized during the initialization phase, as an initialization parameter).
Figure 3 – Interface example definitions (input.h)
/** Interface Name: input * * Data type: 32-bit signed integer * Max input size: 1024 elements * Min input size: 128 elements * * Description: Input stream of data */ #define INPUT_MAX_DATA 1024 #define INPUT_MIN_DATA 128 struct input_itf { int data[INPUT_MAX_DATA]; };
Figure 4 – Interface example definitions (output.h)
/** Interface Name: output * * Data type: 16-bit signed integer * Max output size: 2048 elements * Min output size: 256 elements * * Description: Output stream of data */ #define OUTPUT_MAX_DATA 2048 #define OUTPUT_MIN_DATA 256 struct output_itf { short data[OUTPUT_MAX_DATA]; };
Figure 5 – Interface example definitions (control.h)
/** Interface Name: control * * Data type: Structure (see below) * Max input size: 1 element * Min input size: 1 element * * Description: Configures the behavior of the object etc. etc. */ #define CONTROL_MAX_DATA 1 #define INPUT_MIN_DATA 1 struct control_itf { unsigned char modulation_type; short modulation_levels; int roll_factor; };
Finally, the following figure shows where actually the user code should be written. We provide here an skeleton to easy the deployment of new
#define DATA_OUT_SZ 128
#define FIR_COEF_SZ 12
/* frequency in/out rate */
#define DATA_INOUT_RATE 1
int data_in[DATA_IN_SZ],data_out[DATA_OUT_SZ];
int fir_coef[FIR_COEF];
int fdi,fdo;
int rcv_len;
int long_block;
int nof_coef;
float freq;
int stat_outsignal;
/** Init function
* @return 1 if ok, 0 if error
*/
int Init()
{
fdi = NewFlow(“input”, FLOW_READ_ONLY);
if (fdi < 0) {
WriteLog(“Error creating flow\n”);
return 0;
}
fdo = CreateFlow(“output”, FLOW_WRITE_ONLY);
if (fdo < 0) {
WriteLog(“Error creating flow\n”);
return 0;
}
n = InitParamFile();
if (n < 0) {
WriteLog (“Error initiating params file\n”);
return 0;
}
n = GetParameter(“freq”, &freq, 1);
if (n < 0) {
WriteLog (“Error getting parameter\n”);
return 0;
}
n = GetParameter(“nof_coef”, &nof_coef, 1);
if (n < 0) {
WriteLog (“Error getting parameter\n”);
return 0;
}
if (nof_coef > FIR_COEF_SZ) {
WriteLog (“Error getting parameter, too long\n”);
return 0;
}
memset(fir_coef, 0, FIR_COEF_SZ * 4);
n = GetParameter(“fir_coef”, fir_coef, nof_coef);
if (n < 0) {
WriteLog (“Error getting parameter\n”);
return 0;
}
n = InitStatsFile();
if (n < 0) {
WriteLog (“Error initiating stats\n”);
return 0;
}
stat_outsignal = InitStat(“out_signal”);
if (stat_outsignal < 0) {
WriteLog (“Error initiating stat\n”);
return 0;
}
rcv_len = 0;
return 1;
}
/** Run function.
* @return 1 if ok, 0 if error
*/
int Run()
{
/* Get number of samples to generate in current timeslot only if last block is already send*/
if (rcv_len == 0) {
long_block = GetTempo(freq);
if (long_block > DATA_OUT_SZ) {
WriteLog(“Error requested tempo exceeds output buffer.\n”);
return 0;
}
if (long_block * DATA_INOUT_RATE > DATA_IN_SZ) {
WriteLog(“Error requested tempo exceeds input buffer.\n”);
return 0;
}
}
/* receive a block of data */
do {
n = ReadFlow(fdi, data_in, long_block / DATA_INOUT_RATE - rcv_len);
if (n < 0) {
WriteLog(“Error reading from flow\n”);
return 0;
} else if (!n) {
break;
}
rcv_len += n;
} while(n && rcv_len < long_block / DATA_INOUT_RATE);
/* when enough data is received, process and send it */
if (rcv_len * DATA_INOUT_RATE == long_block) {
my_process_function(data_in, data_out, fir_coef, long_block);
/* write output data to output flow */
n = WriteFlow(fdo, data_out, long_block);
if (n < 0) {
LogWrite(“Error writing to flow\n”);
return 0;
} else if (!n) {
LogWrite(“Caution, missing packet due to full buffer\n”);
}
/* reset rcv counter */
rcv_len=0;
/* set stats variable */
SetStatsValue(stat_outsignal,data_out,long_block);
}
}
Coding Style
FlexNets Coding style is based on the Linux Kernel Coding Style and, although we strongly recommend its reading, you may first of all check the following document defining basic codying style conventions: Writing code for FlexNets?.
naming conventions per les interficies, els fitxers, etc.
Compiling and Linking
com compilar i linkar amb phal