Subsections

From Conception to Measurement

Of course the main idea behind the MSR is that you're able to write new modules for it. This chapter will give an introduction with an actual example of a module as well as an implementation of a radio-transmission. After this you should be able to create your own modules and put them into use. An important introduction can be found in chapter [*]. You should also already have run the example in chapter [*]. This part is a bit heavy on coding, but you won't be able to write modules without a good knowledge of C.

Defining New Modules

In here you will learn the most important things about a module, how it works, how to use it, and how to extend it. This example is already present in the tree, but you can read this section to get a feel of it.

The goal of this module will be to measure the SNR of the signal. Even though this functionality is already implemented in a module, it is a nice idea to have a possibility to compare the results of the two approaches. The existing module compares the received training sequence with the original in order to calculate the SNR. As the training sequence is only part of a transmitted slot, it is a good idea to compare this SNR with the SNR computed in here.

Figure 24.1: The example slot
\includegraphics{figures/example_slot.eps}

In order to calculate the SNR differently, we will transmit a random sequence and then compare it after the transmission. This is depicted in fig. 24.1. In order to know the exact amplitude, it is important to know the random sequence in advance. This is done by setting the seed parameter of the random-module.

Once we have the received signal $y=y_{r}+iy_{i}=[y_{0r}y_{1r}...y_{n-1r}]+i[y_{0i}y_{1i}...y_{n-1i}]$ and the transmitted signal $x$, we can calculate the amplitude:


\begin{displaymath}
a=\frac{\sum_{l=0}^{n-1}y_{lr}sign(x_{lr})+y_{li}sign(x_{li})}{n}\end{displaymath}

and also the variance:


\begin{displaymath}
v=\frac{\sum_{l=0}^{n-1}(y_{lr}-a  sign(x_{lr}))^{2}+(y_{li}-a  sign(x_{li}))^{2}}{2n}\end{displaymath}

and the signal to noise ratio is then


\begin{displaymath}
SNR=10*\log(\frac{a^{2}}{v})\end{displaymath}

The correctness of this assertion is left as an exercise to the reader.

The Files

Now that we know what it is about, let's have a look at the written files. For your information you will learn where the templates for the files come from in every section. The discussion then is only about the parts that have been added. In the directory Modules/Signals, there is a directory called SNR. in there you find the code for the SNR-module. The MSR knows about this directory because of the file Modules/Signals/Makefile that has an entry SNR in the list of DIRS. You can have a look at this Makefile to see it.

The template files come from the Conventions directory and are called:

Conventions/multi_*.c

Conventions/Makefile.module 

Most of the modules come in two parts: one sending part and one receiving part. But as they usually are used in a pair, they are put together into one module. So as not to be confused with different modules, they are renamed to:

multi_template.c -> snr.c

multi_template_send.c -> snr_send.c

multi_template_rcv.c -> snr_rcv.c

Makefile.module -> Makefile 

snr.c

Now look first at the file snr.c and look at the places that contain some documentation. It is really important to keep this documentation up-to-date, so that other people know what it's about:

SNR - measure the signal to noise ratio of a transmitted slot.   

In order to do this, we send a slot of known random data that

is measured on the other side. 

This is all that is needed. Not a big description, just enough to know what it's about.

What it does is the following: once the module snr is loaded into the memory, the function spc_module_init is called, which in turn calls rcv_module_init from snr_rcv.c and send_module_init from snr_send.c. These two functions are responsible to tell the CDB about their name, their input and output as well as their paramters.

snr_send.c

This is the part that prepares the slot.

documentation.

At the top of the file, you see again a short description of the module

snr_send.c - sends a slot of random symbols 
and a bit further down (after the copyright message) a bit more of description.

This module expects some random input that is then modulated

using QPSK modulation so that the noise can be mesured. It can

send the QPSK symbols either on the axis or in the corners. 

config-structure

We want the user to be able to change the amplitude of what we send over the channel. So, edit the config-structure, and make it something like:

typedef struct {

  // The amplitude of the generated QPSK-signal

  int amplitude;

  // The QPSK-type, 0->in the corner, 1->on the axes

  int type;

} config_t; 

It may seem strange that we take an integer for the amplitude, but you have to know that the signals are all in 16-bit integers, so what usually is between $+1$ and $-1$ is now between $+32767$ and $-32768$.

private-structure

Once the user changed the configuration, we will store it in our private variable. This is more for convenience than anything else:

typedef struct {

  int amplitude;

  int type;

} private_t; 

The other structure may be left empty, we don't need it for this example. Just after the structures is a function called

send_init

Why another initialisation function, you might ask. Well, remember from chapter [*] that first the module is registered with the CDB, before it is possible to instantiate it. So, this function is what is called each time this module is instantiated. In our example, we just want to put a default-value in the amplitude-part of the configuration, so add this line:

config->amplitude = 32767;

config->type = 0; 

32767 is the maximum number that we can have in a 16-bit signed variable.

send_configure_input

This function is called whenever the MSR wants to know what the size of the input should be, given the size of the output. So, for each 2 bit of input, we create one symbol with the QPSK representation. This means that two bits of input create one symbol of output, at least for an even number of symbol-outputs. The only tricky part here is that the input is not counted in bits, but rather in bytes. So $size_{input}=\frac{2*size_{output}}{8}$, which is written in this function as

size_in(0) = ( size_out(0) + 7 ) * 2 / 8; 
This assures that we always have enough bits to write to the output.

send_configure_output

The same as before, but this time the opposite direction:

size_out(0) = size_in(0) * 8 / 2; 

send_reconfig

We use this function to copy the configuration-data to our private structure:

private->amplitude = config->amplitude;

private->type = config->type; 

send_pdata

Now comes finally the processing function. This is where the main action takes place. Let's first define some variables:

// Definition of variables - don't touch

stats_t *stats;

int i, amp;

U8 *in;

SYMBOL_COMPLEX *out; 

To get to the input and output-buffers, we have to do the following:

in = buffer_in(0);

out = buffer_out(0); 

The first line deletes the data bit on the input-port, signaling the MSR that the input-port is free again to receive some data. Furthermore it returns a pointer to the input-buffer of this module. The second line gets the output-buffer of this module and sets the data bit on the output-port. Once this function returns, the MSR checks for the data-bit in the output-port and, if it is set, handles the processing to the next module.

OK, now we just have to process the data:

// Fill the slot with random QPSK symbols

  

for ( i=0; i<size_out(0); i++ ){

  switch( 2 ){

  case 1:

    // The amplitude in this case is

    // Sqrt( Re^2 + Im^2 ) and thus the

    // desired amplitude has to be divided by

    // sqrt(2)

    amp = private->amplitude / sqrt( 2 );

    out[i].real = ( 2 * ( *in & 1 ) - 1 ) * amp;

    *in = *in >> 1;

    out[i].imag = ( 2 * ( *in & 1 ) - 1 ) * amp;

    *in = *in >> 1;

    break;

  case 2:

    amp = private->amplitude;

    if ( *in & 1 ){

      *in = *in >> 1;

      out[i].real = ( 2 * ( *in & 1 ) - 1 ) * amp;

      out[i].imag = 0;

    } else {

      *in = *in >> 1;

      out[i].imag = ( 2 * ( *in & 1 ) - 1 ) * amp;

      out[i].real = 0;

    }

    *in = *in >> 1;

    break;

  }

  // Get the next input-byte of random

  if ( i && !( i % 4 ) ){

    in++;

  }

}

You might not be completely fond of this example, but it works (yet to check).

send_custom_message

This function is not needed and can be deleted

send_module_init

Registers this module with the CDB. The CDB first wants to be informed about the type of module to be attached24.1. In our case, we have one input, one output, one config-parameter and 0 stats-parameter:

desc = swr_spc_get_new_desc( 1, 1, 2, 0 ); 
Then we have to tell about the config-parameter, the input-type and the output-type:

UM_CONFIG_INT( ''amplitude'' );

UM_CONFIG_INT( ''type'' );

UM_INPUT( SIG_U8, 0 );

UM_OUTPUT( SIG_SYMBOL_COMPLEX, 0 ); 

In order for the MSR to know what functions to call in what case, we have to define 'call-back functions'. As these are always the same, they are already pre-defined in the templates, and we only have to delete the send_custom_msg entry. Now the module-description is complete, save for the name:

send_id = swr_cdb_register_spc( &desc, "snr_send" ); 

snr_rcv.c

Let's start with the comment in the beginning of the file:

This module receives the stream from the matched filter  

and the stream of random-signals that are supposed to be

the same that have been used by the snr_send. It then

calculates the amplitude, the variance and the snr. 

This means that this module has two inputs: one from the channel, and another one from the random-module.

config-structure

Again we have the possibility to change the type:

typedef struct {

  // The QPSK-type, 0->in the corner, 1->on the axes   

  int type; 

} config_t; 

stats-structure

So we're able to retrieve the SNR from the outside, we have to write it in this structure:

typedef struct {

  double snr;

} stats_t; 

rcv_init

Let's just start with a SNR of -2.3:

stats->snr = -2.3;

config->type = 0; 

rcv_reconfigure

private->type = config->type; 
The functions rcv_configure_input and rcv_configure_output can be deleted, as this module is at the end of the chain.

rcv_pdata

Here goes the working function. It's just about implementing the above formula. Let's go through step by step. Definition of variables:

stats_t *stats;

SYMBOL_COMPLEX *in, *buf_rnd;

U8 *in_rnd;

double signal = 0., noise = 0.;

int i; 

Then we have to make sure that we have both the signal from the channel and the random-data:

if ( !data_available(0) || !data_available(1) ){

  PR_DBG( 4, "Not all data available yet\n" );

  return 0;

}

Now we reconstruct $x$ so that we can implement the formula given above. Instead of calculating $x$ and then taking the sign of it, we directly calculate $x$ with an amplitude of $\pm1$. Again, once for the QPSK-signals on the axes, and once for the signals tilted by $\frac{\pi}{4}$

in_rnd = buffer_in(1);

buf_rnd = swr_malloc( size_in(0) * sizeof( SYMBOL_COMPLEX ) );

for ( i=0; i<size_in(0); i++ ){

  switch( private->type ){

  case 0:

    buf_rnd[i].real = ( 2 * ( *in_rnd & 1 ) - 1 );

    *in_rnd = *in_rnd >> 1;

    buf_rnd[i].imag = ( 2 * ( *in_rnd & 1 ) - 1 );

    *in_rnd = *in_rnd >> 1;

    break;

  case 1:

    if ( *in_rnd & 1 ){

      *in_rnd = *in_rnd >> 1;

      buf_rnd[i].real = ( 2 * ( *in_rnd & 1 ) - 1 );

      buf_rnd[i].imag = 0;

    } else {

      *in_rnd = *in_rnd >> 1;

      buf_rnd[i].imag = ( 2 * ( *in_rnd & 1 ) - 1 );

      buf_rnd[i].real = 0;

    }

    *in_rnd = *in_rnd >> 1;

    break;

  }

  // Get the next input-byte of random

  if ( i && !( i % 4 ) ){

    in_rnd++;

  }

}

Now we can calculate the amplitude of the signal:

in = buffer_in(0);

// Calculate signal energy

for ( i=0; i<size_in(0); i++ ){

  signal += (double)( in[i].real ) * buf_rnd[i].real +

    (double)( in[i].imag ) * buf_rnd[i].imag;

}

signal = signal / size_in(0); 

And the noise-variance:

for ( i=0; i<size_in(0); i++ ){

  noise += pow( (double)in[i].real - signal * buf_rnd[i].real, 2 ) +

    pow( (double)in[i].imag - signal * buf_rnd[i].imag, 2 );

}

noise = noise / size_in(0) / 2; 

Finally we can calculate the snr:

PR_DBG( 2, "signal_amp: %i, noise_amp: %g\n", 

        (int)signal, noise );

  

// And write the snr

swr_sdb_get_stats_struct( context->id, (void**)&stats );

if ( noise > 0 ){

  stats->snr = 10 * ( log10( signal ) * 2 - log10( noise ) );

}

swr_free( buf_rnd );

Again, the rcv_user_msg is not used and can be deleted.

rcv_module_init

We have only 1 input, no output, 1 config-variable and 1 stats-variable, and lots of functions are not used:

desc = swr_spc_get_new_desc( 1, 0, 1, 1 );

UM_STATS_DOUBLE( ''snr'' );

UM_INPUT( SIG_SYMBOL_COMPLEX, 0 );

desc->fn_init              = rcv_init;

desc->fn_reconfigure       = rcv_reconfig;

desc->fn_process_data      = rcv_pdata;   

desc->fn_finalize          = rcv_finalize; 

rcv_id = swr_cdb_register_spc( &desc, "snr_rcv" ); 

Makefile

In the makefile we have to tell the final name of the module, as well that we use the math-library:

MODULE_NAME = snr

MATH = true 

Compile it

Now you can try to compile it by typing make on the command-line. If there are any errors, try to fix them, the above lines should work, they have been tested. In order to include this module even better in the MSR, you can add the name of the directory to the file Modules/Signals/Makefile in the line DIRS = . Like this a top-level make will also update the SNR-module.

Testing

Up to now only the module has been written. It is not yet in a usable state, as it is only registered with the CDB, but not yet instantiated. Theoretically we could write everything in the module to make an instance, but this would turn upside-down the idea of modules. So what we need is an own program that implements the chain and runs it, just to look how good it runs.

Perhaps as a surprise, this program will again be a module, but this time a module that does actually something. Implementing a simple chain. So there is a function called um_module_init that will be called upon inserting the module. This function itself creates a new thread that will be used to create the chain. In order to be compatible for further RTLinux implementation, we have to do this two-step calling.

The Directory

In the MSR, there is a directory called Test which holds already different tests. The test for the SNR is of course in a directory called Test/SNR. The templates for the test-module are in

Conventions/test_template.c 

Conventions/Makefile.module 

Again, for easier handling they are renamed:

test_template.c -> test_snr.c

Makefile.module -> Makefile 

Makefile

The makefile wants to know the name of the module, which is test_snr, as well as the modules to load in order for the MSR to function correctly. In our case, these are the modules random, snr, midamble, rrc and block:

MODULE_NAME = test_snr

DEPENDS = random snr midamble rrc block 

test_snr.c

Let's have a look at the documentation:

Make a simple chain:

random - snr_send - midamble - rrc - block - 

         matched_filter - snr_rcv

and additionally:

random - snr_rcv(2)

Then we can create our main-function, which is called start_it for the test-program.

start_it

The first thing we have to do is to create a chain of modules. A chain is a logical suit of signal-processing modules, that take some input and produce some output that is handled further down the chain.

When using the swr_chain_create functionwe give a list of all modules, that will be automatically connected together, and finish the list with END_CHAIN. In this call to swr_chain_create, you see three different kind of macros, NEW_SPC_VAR, NEW_SPC and OLD_SPC_IN all of which are described in [*]. In short, while the former allows you to give a variable where a reference to the module will be stored, the latter just creates the module and connects it, without giving the reference of the created module. The third takes an already defined module for further connections.

swr_sdb_id rnd, mafi, snr_rcv;

test_chain = swr_chain_create(

                      NEW_SPC_VAR( ''random'', rnd ),

                      NEW_SPC_VAR( ''snr_send'' ),

                      NEW_SPC( ''midamble'' ),

                      NEW_SPC( ''rrc'' ),

                      NEW_SPC_VAR( ''block'' ),

                      NEW_SPC_VAR( ''matched_filter'', mafi ),

                      NEW_SPC_VAR( ''snr_rcv'', snr_rcv ),

                      END_CHAIN );

test_chain_2 = swr_chain_create(

                      NEW_SPC_VAR( ''random'', rnd2 ),

                      OLD_SPC_IN( snr_rcv, 1 ),

                      END_CHAIN ); 

So we have created a chain. The modules have the following function:

If we compile and test our module with make; make user, this chain will be created and the program will exit. What we forgot is to really use this chain. For this, the random module listens to user-messages, and begins creating a random-output whenever it receives such a user-message. But first we have to make sure that both random-modules create the same values:

swr_sdb_set_config_int( rnd, "seed", 0x1234 );

swr_sdb_set_config_int( rnd2, "seed", 0x1234 ); 

Then we adjust a bit the amplitudes, so that we don't run all the time on the edge:

swr_sdb_set_config_int( snr_send, "amplitude", 16384 / 4 );

swr_sdb_set_config_int( mid, "amplitude", 16384 / 4 ); 

To make it a bit more nice, we have a look at different values, using the sigma parameter of the block module:

for ( i=0; i<50; i+=5 ){

  swr_sdb_set_config_double( block, "sigma", i );

  swr_sdb_send_msg( rnd, SUBS_MSG_USER, NULL, -1 );

  swr_sdb_send_msg( rnd2, SUBS_MSG_USER, NULL, -1 );

  PR( "Amp: %2i:%2i, Noise: %3i:%3i  SNR : %5.5g - %5.5g = %5.5g\n",

      swr_sdb_get_stats_int( mafi, "mid_amp" ),

      swr_sdb_get_stats_int( snr_rcv, "amp" ),

      swr_sdb_get_stats_int( mafi, "noise_var" ),

      swr_sdb_get_stats_int( snr_rcv, "var" ),

      swr_sdb_get_stats_double( mafi, "snr" ),

      swr_sdb_get_stats_double( snr_rcv, "snr" ),

      swr_sdb_get_stats_double( mafi, "snr" ) -

      swr_sdb_get_stats_double( snr_rcv, "snr" ) );

}

And after a make; make user you should see something like:

Amp: 63:63, Noise:   1:1   SNR : 34.40 - 34.24 =  0.15426

Amp: 62:62, Noise:   5:6   SNR : 28.52 - 28.10 =  0.42007

Amp: 62:62, Noise:  27:26  SNR : 21.50 - 21.58 = -0.077267

Amp: 62:62, Noise:  66:63  SNR : 17.61 - 17.83 = -0.21601

Amp: 62:61, Noise: 122:111 SNR : 14.96 - 15.34 = -0.37944

Amp: 61:61, Noise: 190:187 SNR : 12.89 - 13.07 = -0.18071

Amp: 58:59, Noise: 179:219 SNR : 12.72 - 12.01 =  0.70692

Amp: 65:64, Noise: 317:322 SNR : 11.23 - 11.06 =  0.16854

Amp: 66:63, Noise: 421:393 SNR : 10.14 - 10.15 = -0.016712

Amp: 65:65, Noise: 528:555 SNR : 9.026 -  8.83 =  0.18867 

So you see that our method gives more or less the same results as the snr calculated in the matched_filter.

Going Over the Air

OK, now that the module is written, a simple test-case shows that our module works (NOT), we can go on and write a simple radio that transmits the SNR-slot and then receives it and shows the result. We will make a simple radio that has a master, the BaseStation, that transmits the synchronisation-signal, and a client, the MobileStation, that synchronises to it and sends a SNR-slot in return.

The Directories and Files

It will be a radio, so we find the code in the directory Radios/SNR. The base for this radio is the simple-radio that can be found at Radios/Simple and contains the following files:

simple_radio.h

Makefile

BS/Makefile

BS/radio_bs.c

MS/Makefile

MS/radio_ms.c 

To reflect the fact that it isn't the simple radio anymore, we have to adjust the names in the Makefiles. The first lines in BS/Makefile contains:

MODULE_NAME = radio_snr_bs 

DEPENDS = rrc synch energy mapper midamble random \

          rndstr spread sink cch macro_sch snr 

and in MS/Makefile reads:

MODULE_NAME = radio_snr_ms

DEPENDS = rrc synch energy mapper midamble random \

          rndstr spread sink macro_synch macro_sch snr 

README

This file also reflects the changes and has a very small documentation in it.

MS/radio_ms.c

There is a lot of things to say about the basic system. You can find an introduction in [*]. Here we just try to focus on the things necessary to run our SNR-module over a real channel.

A very short simplification: when the mobile synchronises for the first time to the base-station, it creates the necessary chains. This happens in the function synchronised. Near the end of this function, you have to replace the declaration of the UP-chain with the following chain:

// And setup a simple UP-chain...   

ch_up = swr_chain_create( 

                      NEW_SPC_VAR( "random", rnd ), 

                      NEW_SPC( "snr_send" ), 

                      NEW_SPC( "midamble" ), 

                      NEW_SPC( "rrc" ),  

                      OLD_SPC_IN( stfa, 1 ),

                      CHAIN_END ); 

swr_stfa_notice_sdb( stfa, 1, rnd );

PR( "Ready to go" ); 

You see a new macro in the function swr_chain_create here, which is called OLD_SPC_IN and uses the stfa module. The macro tells the function that this module has already been created before. The module stfa is quite important in the MSR: it creates the link between the modules and the actual hardware. So an input to the stfa is like a connection to the antenna.

As we don't know exactly when the mobile will be synchronised with the base-station, we set the seed of the random module every frame to the same value. Like this we're sure that both the BS and the MS have the same random-values. To achieve this, the function do_send_up is handy. It is called once in a frame, and we can put the following line in there:

swr_sdb_set_config_int( rnd, "seed", 0x1234 ); 
That's it for the mobile-station part.

radio_bs.c

This part of the radio sets up the chains for reception anyway and then just waits on what happens. As it gives the synchronisation and doesn't need to wait for it, it is much more simple than radio_ms.c. So we can directly change the construction of the UP-chain in the function start_it to:

PR( "Setting uplink in slot 1\n" );   

ch_rach = swr_chain_create(

                 OLD_SPC_OUT( stfa, 1 ),       

                 NEW_SPC_VAR( "matched_filter", mafi ), 

                 NEW_SPC( "snr_rcv" ),

                 CHAIN_END );

ch_rach_2 = swr_chain_create( NEW_SPC_VAR( "random", rnd ),

                              OLD_SPC_IN( snr_rcv, 1 ),

                              CHAIN_END );

swr_sdb_set_config_int( sch, "mafi0", mafi );

while ( looping++ ){

  usleep( 1000000 );

  PR( "mafi0: %g, mafi1: %g\n",

      swr_sdb_get_stats_double( mafi, "snr" ),

      swr_sdb_get_stats_double( snr_rcv, "snr" ) );

}

One last important thing we don't have to forget: once a frame we have to tell the random-module to generate some data. This is best done in the function called do_send_down, which is used once a slot. So we can insert there:

swr_sdb_set_config_int( rnd, "seed", 0x1234 );   

swr_sdb_send_msg( rnd, SUBS_MSG_USER, NULL, -1 ); 

Running it with the channel-simulation

Again, to help track down errors, it is more adviseable to run it first in simulation-mode, like this you can track down errors much more simple. In order to do so, change to the directory Radios/SNR and type make to compile it, and make server; make show_bsms which should bring up two windows, one showing the mobilestation and another showing the basestation. If something goes wrong with the compilation, fix it and run make again. If something goes wrong with the display, type make kill; make cleanproc which should clean-up the directories, and then you can try make server; make show_bsms again.

Running the real thing

Now that you did all this work and the modules returned some decent values, you can be pretty sure that it shouldn't run havoc in real-time mode. So let's try it. First, you have to run the radio on the basestation, issuing a make rf_show from the Radios/SNR/BS directory. Then, on the other machine, you can run make rf_show from the Radios/SNR/MS directory. If everything is set up correctly, the hardware is OK and all things are nicely connected and plugged in (this will give another chapter or two, installing the hardware), you should again see two windows, one from the basestation and one from the mobilestation, and the values this time are REAL values. If you come this far, congratulations!

Linus Gasser 2004-04-14