User Tools

Site Tools


tuto_fpga_pico

Using a custom bitstream with a PicoSDR

This tutorial shows how to use a custom bitstream when you are making experiments with PicoSDR. For this we will be using a Nutaq example about OFDM transmission.

This example provides a bitstream and a GNU Radio Companion (GRC) file interacting with the bitstream. All the OFDM work is done within the FPGA. Basically you send raw data to the PicoSDR, the FPGA makes the OFDM modulation, TX antenna of card 2 makes the emission and RX antenna of card 2 receives the signal, the FPGA makes the OFDM demodulation, and data is sent back to the computer. As you can see all the work is done with only 1 PicoSDR but using the antennas, so this is really for demonstration purpose only. (Pro tip : when modulation and demodulation are made by the same FPGA you don't have to take care of the synchronization because both share the same clock).

The GRC file can be found at /Path/to/toolchain/opt/Nutaq/ADP6/ADP_MicroTCA/sdk/gnuradio/gr-nutaq/examples/ofdm.grc and the bitstream at /Path/to/toolchain/opt/Nutaq/ADP6/ADP_MicroTCA/sdk/fpga/bin/OFDM_6_6_0_sx315.bit. Copy both to a new folder named fpga-pico. For example, on airlock, the toolchain is located in /cortexlab/toolchains/current.

Edit the .grc file

Let's take a look at ofdm.grc: On airlock, run $ gnuradio-companion <path-to-ofdm.grc>.

First, what it is intended to do: we send a video file to RTDEX Sink, and from RTDEX Source we get the constellation points of the OFDM demodulation and the video file is sent to a UDP Sink (in order to open it with VLC for example). We have also a lot of parameter blocks we don't care about in this tutorials (all the custom register blocks are specific to the bitstream).

This example can be used straight away when you are working on your setup, but not in CorteXlab because of the video file over UDP thing. Instead of this, we are going to do something a lot easier, which is sending a constant integer to the PicoSDR, and see if we get it back.

First of all, we need to change the generate option of the graph to 'No GUI'. Thus we can remove the QT GUI Constellation Sink block and all the other blocks leading to it. You can also remove the File Source, Throttle and UDP Sink blocks, we don't need them anymore.

Let's get back to the idea of sending a constant number to the PicoSDR and see if we get it back. First thing to do is to add a Constant Source block (with Int as Output Type, and whatever-you-want as the constant) (block can be found in category Waveform Generator), and connect it to RTDEX Sink. Now, how can we check that we receive the right number ? We could send it to a file and then check the file, but this ain't funny. Instead of this, we're gonna use a super cool new type of block added to GNURadio lately (As I am writing this tutorial). I'm talking about Python Block. With this block, you can create a custom block, with its associated python code easily, without going through an OOT module.

Add a Python Block (from category Misc), open it, and click on 'Open in Editor'. You can now edit the python code behind the block, and as soon as you save it, changes are repercuted to the block in GRC. You can either write your own code comparing two inputs or use mine :

"""
Embedded Python Blocks:

Each time this file is saved, GRC will instantiate the first class it finds to get
the ports and parameters of your block. The arguments to __init__  will be the
parameters. All of them are required to have default values!
"""
import numpy as np
from gnuradio import gr
import sys
class blk(gr.sync_block):
    def __init__(self, num_samples=100000):  # only default arguments here
        gr.sync_block.__init__(
            self,
            name='Error rate',
            in_sig=[np.int32, np.int32],
            out_sig=None
        )

	self._num_samples = num_samples
	self._idx_sample = 0
	self._valid = 0
	self._bad = 0

    def work(self, input_items, output_items):
        in0 = input_items[0]
        in1 = input_items[1]

	for i in range(len(in0)):		
		if (in0[i] == in1[i]):
		    self._valid += 1
		else:
		    self._bad += 1

        	self._idx_sample += 1

		if (self._idx_sample >= self._num_samples):
			rate = self._valid*100/float(self._num_samples)
			print "----------------------------------"
			print "%.3f percent valid over %s samples " % (rate, self._num_samples)
			print "----------------------------------"
			return -1

        return len(input_items[0])

The code is quite simple : you take two input streams of int, you compare them over a number of samples defined by the paramter num_samples, and when it's done you print the result of matching sample in percentage and you stop the block.

Save the file in editor and go back to the graph. You now have your new block named 'Error Rate' with two inputs. Connect the constant source and the RTDEX Source blocks to it.

We are now done with the .grc file, don't forget to generate the flow graph. Your graph should look like this :

Create the scenario

In order to run the experiment in CorteXlab we need a description called scenario. Create a file named scenario.yaml and copy the following in it :

# Example scenario description file
#
#   All lines starting with "#" and empy lines are ignored


# Scenario textual description
#   simple string (a one liner)
description: base scenario for CorteXlab

# Experiment maximum duration
#   Time after which the experiment is forced to stop
#   integer (seconds)
duration: 300

# Node list
#
#   format:
#
#   nodes:
#     (machine):
#       command: (entry point script relative to the task root)

nodes:
  node31:
    command: ./ofdm.py
    bitstreamA: OFDM_6_6_0_sx315.bit

If you translate this file it says “The experiment will run for 5 minutes. The script ofdm.py will be executed on node 31 using the bitstream OFDM66_0_sx315.bit”.

Launch the experiment into CorteXlab

The file tree should now look like that:

...
├── fpga-pico
│   ├── ofdm.py
│   ├── epy_block_0.py
│   ├── scenario.yaml
│   ├── ofdm.grc
│   └── OFDM_6_6_0_sx315.bit
...

The .grc will not be used by CorteXlab but you can let it in the same directory. epy_block_0.py correspond to the python code of your custom block error rate.

Upload the files on airlock

Upload the fpga-pico directory on Airlock as it is explained here. For example for Linux it will look like this :

you@your-pc:~$ scp -P 2269 -r path/to/fpga-pico username@gw.cortexlab.fr:~ 

Create the task

Connect to Airlock through ssh as it is describe here. For example for Linux it will look like this :

you@yourpc:~$ ssh -p 2269 username@gw.cortexlab.fr

You can find in your home directory the fpga-pico directory that we have uploaded previously.

Now, use the Minus CLI to create the task file:

you@srvairlock:~$ minus task create fpga-pico

The success (or failure) of the creation will be printed on screen. The task file will be created at the same level and name than the targeted experiment folder but with a .task suffix, in our case fpga-pico.task.

Note: You can get help on the Minus CLI at anytime with minus -h

Book the testbed with OAR

As it is explain here, we need to book the testbed with OAR in order to run our experiment. Once you have booked the platform, you will be the exclusive user of the testbed, avoiding experimentation interference.

A basic example to submit an OAR interactive job requesting all available nodes, for 30 minutes:

$ oarsub -I -l nodes=BEST,walltime=0:30:00

Submit the task

Now, we have booked the testbed and we have a .task file containing our experiment. In order to run it, we need to submit it to the testbed scheduler.

For now, the scheduler is a simple FIFO queue, but a more advanced scheduling mechanism will be implemented in next versions of CorteXlab.

To submit a task to the scheduler, use the Minus CLI:

you@srvairlock:~$ minus task submit fpga-pico.task

On screen will be prompted the id of your task in the scheduler. Note it down so that you can easily retrieve your results or monitor the progress of the experiment.

Observe the result

You can check the status of your experiment through the testbed scheduler. To do so, use the Minus CLI:

you@srvairlock:~$ minus testbed status
Testbed status:
ID count so far: 15
Number of awaiting jobs: 0
ID of the running job: None (None means server is idle)

The information given by this command enables you to deduce your experiment status. It's quite self-explanatory.

Once it's finished (we have set the duration to 5 minutes), Minus will take care of copying the results and output messages back to your home folder in srvairlock, such that you can analyse it.

All results are stored by task number in the results folder, inside your home folder.

Go in this folder :

you@srvairlock:~/fpga-pico$ cd ~/results
you@srvairlock:~/results$ ls
task_15
you@srvairlock:~/results$ cd task_15
you@srvairlock:~/results/task_15$ ls
node31.tgz

So we see that we have a folder for each task and inside each folder one compressed file per participant node. Let's extract node31.tgz :

you@srvairlock:~/results/task_15$ tar -zxf node31.tgz
you@srvairlock:~/results/task_15$ ls 
node31 node31.tgz
you@srvairlock:~/results/task_15$ cd node31
you@srvairlock:~/results/task_15/node31$ ls
epy_block_0.py    impact_instr.txt      ofdm.py        stdout.txt
epy_block_0.pyc   OFDM_6_6_0_sx315.bit  scenario.yaml
_impactbatch.log  ofdm.grc              stderr.txt

We see that all of the files we used to create the task are inside. The others are:

  • stdout.txt: all output messages from your GNU Radio python script are written here. These include GNU Radio messages as well as all “print”s you include in your code. Seeing the contents of this file is useful to assert its correct operation.
  • stderr.txt: all error messages are printed here. If you see strange things on the stdout.txt or nothing at all, it might be interesting to take a look at the stderr.txt to debug your code.
  • impactinstr.txt: The impact instructions used to flash the bitstream (this file is automatically generated) * impactbatch.log: Impact execution log

Let's take a look inside stdout.txt:

you@srvairlock:~/results/task_15/node31$ less stdout.txt

You should get a file looking like this:

set_min_output_buffer on block 1 to 2048
nutaq_carrier_perseus_TX connected to 192.168.0.101
Registering carrier nutaq_carrier_perseus_TX
Global init begin
radio420_tx::InitRadio420x: 1
    Powering up hardware
    Resetting hardware
    Configuring PLL
      - Reference frequency 30720000 Hz from internal oscillator
      - Acquisition frequency 76800000 Hz
      - Lime frequency 30720000 Hz 
    PLL is locked
    Configuring Gains
    Automatic calibration
radio420_rx::InitRadio420x: 1
    Automatic calibration
rx ended
radio420_tx::InitRadio420x: 2
    Resetting hardware
    Configuring PLL
      - Reference frequency 30720000 Hz from external connector
      - Acquisition frequency 76800000 Hz
      - Lime frequency 30720000 Hz 
    PLL is locked
    Configuring Gains
    Automatic calibration
radio420_rx::InitRadio420x: 2
    Automatic calibration
rx ended
rtdex_sink: Init 
rtdex_sink: Device MAC Address: 00:d0:cc:0a:01:97
rtdex_sink: Host MAC Address: 78:d0:04:20:74:69
rtdex_source: Init 
rtdex_source: Device MAC Address: 00:d0:cc:0a:01:97
rtdex_source: Host MAC Address: 78:d0:04:20:74:69
Global init end successfully
rtdex_sink: End Good
rtdex_source: End Good
----------------------------------
98.028 percent valid over 100000 samples 
----------------------------------
rtdex_sink: stopped
rtdex_source: stopped
Unregistering carrier nutaq_carrier_perseus_TX

Here we have 98% valid received numbers, which is what we expected because the two antennas are close. You should have a result at least superior to 90%.

What's next

Obviously, the next step for you is to try an experiment with your own bitstream. But you can also try to adapt other Nutaq example bistream !

tuto_fpga_pico.txt · Last modified: 2017/11/15 16:22 by onicolas