# 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 ''. 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 : {{ :tuto_fpga.png?200 |}} ## 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 OFDM_6_6_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 [[exp_upload|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 [[access|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 [[reserve|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. * ''impact_instr.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 !