tut_write_simple_sequence - vnegnev/marcos_extras GitHub Wiki

Tutorial: write a simple MaRCoS sequence

Overview

In this tutorial, you’ll create a repository for your own experiments with MaRCoS, and run a few basic Git and Github commands. Next you will write a simple MaRCoS sequence, run it with the simulated server, and view the simulated behaviour of the Red Pitaya console.

It will take around 1–2 hours – please ask for help if you get stuck.

Prerequisites

You should have completed the setting up the MaRCoS software tutorial, or otherwise prepared a working MaRCoS environment, and successfully run test_marga_model.py.

You additionally need a GitHub account; follow the instructions at https://www.github.com to set one up.

Fork the marcos_experiments repository

First, you’ll prepare your own fork of the marcos_experiments repository, which you will then customise as you go through this tutorial. Go to https://www.github.com/vnegnev/marcos_experiments and click the Fork button:

You now have your own copy of the marcos_experiments repository. You should see a title somewhat like this:

except instead of vnegnev, you’ll see your Github username.

Clone your fork of marcos_experiments to your local machine

Go to your marcos_pack folder and clone your fork of marcos_experiments, replacing your_username with your Github user name:

[vlad@arch-ssd ~]$ cd ~/marcos_pack/
[vlad@arch-ssd marcos_pack]$ git clone https://github.com/your_username/marcos_experiments.git # REPLACE your_username
Cloning into 'marcos_experiments'...
remote: Enumerating objects: 7, done.
remote: Counting objects: 100% (7/7), done.
remote: Compressing objects: 100% (5/5), done.
remote: Total 7 (delta 0), reused 4 (delta 0), pack-reused 0
Receiving objects: 100% (7/7), 12.80 KiB | 6.40 MiB/s, done.
[vlad@arch-ssd marcos_pack]$

Create your first experiment

Make a copy of marcos_experiments/external.py.example and name it external.py:

[vlad@arch-ssd marcos_pack]$ cd marcos_experiments/
[vlad@arch-ssd marcos_experiments]$ cp external.py.example external.py

Open marcos_experiments/external.py and change the path to match your local path to marcos_client (unless your username is vlad!).

Create a new file in marcos_experiments, and name it my_first_experiment.py. Fill the top few lines as follows:

import numpy as np
import matplotlib.pyplot as plt
import pdb

st = pdb.set_trace


import external  # imports external.py
import experiment as ex  #


def my_first_experiment():
    exp = ex.Experiment(lo_freq=5, rx_t=3.125)


if __name__ == "__main__":
    my_first_experiment()

The my_first_experiment() function creates a new Experiment object. You will control and interact with the SDRLab console (or a simulated version of it) through this object, by configuring it as you wish and calling its various functions.

You have begun by setting the local-oscillator (LO) frequency to 5 MHz; this is the default frequency at which RF pulses will be modulated, and at which incoming receiver data will be demodulated. You have also set the RX time interval to 3.125, which means that during acquisition events, a sample will be acquired every 3.125 microseconds.

Commit your first experiment, and push to Github

Although you have not yet done much, it’s a good start, and you should commit your changes to your local repository, then push them to your remote repository on GitHub where the changes will be saved. In the terminal, run the following commands:

[vlad@arch-ssd marcos_experiments]$ git add my_first_experiment.py
[vlad@arch-ssd marcos_experiments]$ git commit -am "Add my first experiment"
[main bfc7f0b] Add my first experiment
1 file changed, 13 insertions(+)
create mode 100644 my_first_experiment.py
[vlad@arch-ssd marcos_experiments]$ git push
Enumerating objects: 4, done.
Counting objects: 100% (4/4), done.
Delta compression using up to 8 threads
Compressing objects: 100% (3/3), done.
Writing objects: 100% (3/3), 452 bytes | 452.00 KiB/s, done.
Total 3 (delta 1), reused 0 (delta 0), pack-reused 0
remote: Resolving deltas: 100% (1/1), completed with 1 local object.
To github.com:vnegnev/marcos_experiments.git
20d5456..bfc7f0b  main -> main

[vlad@arch-ssd marcos_experiments]$

Now, to verify that you have pushed successfully, go to your repository on GitHub, and refresh the page. You should see something like:

in particular, showing the commit name. Now your work is saved online. You can continue without fear of data loss, and later share your work with others to get help or information. When in doubt, remember to git add your new files, git commit your changes, and git push them to your remote repository.

This is all the Git knowledge you will need for now. If this is new to you, I highly recommend you do some Git tutorials. Git and the ideas behind it and other version control software are central to modern open-source development, and even as an end user, a working knowledge of Git will help you a lot. Over time, a good understanding of Git will also change your mindset for the better when working with and developing software.

(Pep talk over – the tutorial resumes below!)

Write your first simple sequence

The MaRCoS system can be programmed in several different ways; in this tutorial we will focus on the dictionary way.

We will create an RF pulse, on the first channel of the SDRLab, with an amplitude of 50% of full-scale. The pulse will start 50us after the beginning of our experiment, and will turn off 80us later (130us from the beginning of our experiment).

In my_first_experiment.py, after the Experiment constructor, add two lines with the following:

tx0_times = np.array([50, 130])
tx0_amps = np.array([0.5, 0])

We have put the absolute times where the TX0 output will change into a Numpy array called tx0_times, and the values of the TX0 output at these times into the tx0_amps array, where 1 means 100% of full-scale. Now we feed these arrays into the Experiment object, by creating a dictionary and using the add_flodict() function, as follows:

event_dict = {'tx0': (tx0_times, tx0_amps)}
exp.add_flodict(event_dict)

We have created a dictionary with one element, whose key is tx0 and whose value is a 2-element tuple, containing the two arrays we created earlier.

Now let’s run a readout event, starting at 200us and running for 200us (i.e. ending at 400us). We can write this in a single line:

exp.add_flodict({'rx0_en': (np.array([200, 400]), np.array([1, 0]))})

Finally, we wish to plot our planned sequence, which our exp object is now aware of:

exp.plot_sequence()
plt.show()

Your entire my_first_experiment() function should look like:

def my_first_experiment():
    exp = ex.Experiment(lo_freq=5, rx_t=3.125)
    tx0_times = np.array([50, 130])
    tx0_amps = np.array([0.5, 0])

    event_dict = {"tx0": (tx0_times, tx0_amps)}
    exp.add_flodict(event_dict)
    exp.add_flodict({"rx0_en": (np.array([200, 400]), np.array([1, 0]))})

    exp.plot_sequence()
    plt.show()

Plot the sequence you have created

Either run the test_marga_model.py unit tests (described in the previous tutorial) or run the command:

[vlad@arch-ssd ~]$ fallocate -l 516KiB /tmp/marcos_server_mem

In a new terminal window, start the marga emulator (we will use it in the next step - for now it just needs to be started):

[vlad@arch-ssd ~]$ cd marcos_pack/marga/build/
[vlad@arch-ssd build]$ ./marga_sim csv
MaRCoS server + MARGA sim model, May 11 2021 02:02:59
Server version 1.0.5
Dumping event output to ./marga_sim.csv

In your original window, run my_first_experiment.py:

[vlad@arch-ssd marcos_experiments]$ python my_first_experiment.py

You should see something like the following plot:

Note the four panels, showing different console outputs (the gradient panel is empty). In the first panel, you can see the RF pulse, rising at 50us and falling at 130us. You can also see the receiver turn on at 200us and off at 400us.

The LEDs by default just run through a ramp, which is most useful when running multi-second experiments since it gives a visual indication of experimental progress.

[Advanced technique] Emulate the sequence, and plot the results

Comment out the two plotting commands in your my_first_experiment() function. Extend it with two more commands, that will actually run the experiment and then close the emulator (but won’t close a server running on the hardware):

exp = ex.Experiment(lo_freq=5, rx_t=3.125)

tx0_times = np.array([50, 130])
tx0_amps = np.array([0.5, 0])

event_dict = {'tx0': (tx0_times, tx0_amps)}
exp.add_flodict(event_dict)

exp.add_flodict({'rx0_en': (np.array([200, 400]), np.array([1, 0]))})
# exp.plot_sequence()
# plt.show()

rxd, msgs = exp.run()
exp.close_server(only_if_sim=True)

Run the script as before:

[vlad@arch-ssd marcos_experiments]$ python my_first_experiment.py

The script should just exit. In your other terminal window, the emulator should have also exited, and created a marga_sim.csv file:

[vlad@arch-ssd build]$ ls
CMakeCache.txt  CMakeFiles  cmake_install.cmake  marga_sim  marga_sim.csv  Makefile
[vlad@arch-ssd build]$

Unlike the plot from before, which is just generated by the Experiment class for debugging purposes, the marga_sim.csv file is actually the output of a detailed, cycle-accurate simulation of the marga FPGA firmware and the MaRCoS server’s behaviour. The effect of bugs or new features in the firmware or server code will be reflected in this CSV file, and it is a powerful tool for developing and debugging the entire MaRCoS system without needing to actually run the hardware. The unit tests you ran in the first tutorial were actually comparing a set of CSVs generated by the emulator against a corresponding set of reference CSVs.

You can plot the simulated CSV as follows:

[vlad@arch-ssd ~]$ cd marcos_pack/marcos_client/
[vlad@arch-ssd marcos_client]$ python plot_csv.py ~/marcos_pack/marga/build/marga_sim.csv

You should see:

It’s slightly different from the earlier plot – the transitions on tx0_i and rx0 en occur a few microseconds later than specified in the Python script. This is because the simulation includes the delays while data is written to the marga memory, and extra padding times hidden from the user.

Later when you see unusual behaviour from the Red Pitaya’s outputs, such as strange event timing or other problems, you can simulate your sequence and see whether it’s a bug in MaRCoS or a true hardware issue.

The CSV format itself is documented in TODO TODO .

Create a more advanced sequence

Now, let’s write a more advanced sequence, using both of the TX and RX channels. Modify the my_first_experiment() function as follows:

def my_first_experiment():
    exp = ex.Experiment(lo_freq=5, rx_t=3.125)

    event_dict = {
	  "tx0": (np.array([50, 130, 200, 360]), np.array([0.5, 0, 0.5j, 0])),
	  "tx1": (np.array([500, 700]), np.array([0.2, 0])),
	  "rx0_en": (np.array([400, 800]), np.array([1, 0])),
	  "rx1_en": (np.array([400, 800]), np.array([1, 0])),
    }
    exp.add_flodict(event_dict)
    exp.plot_sequence()

    rxd, msgs = exp.run()
    exp.close_server(only_if_sim=True)

    plt.figure()
    plt.plot(np.abs(rxd["rx1"]))
    plt.show()

Now, tx0 has an extra pulse at 90 degrees to the first from 200-360us, tx1 has a weak pulse from 500–700us, and rx1 is on over the same interval as rx0.

The sequence plot is more or less as expected:

Note the i and q channels are plotted separately.

The rxd dictionary received back from the Experiment.run() function contains the raw readout data. In this case it has two elements, rx0 and rx1, corresponding to the two RX channels that were run as part of the sequence. The emulator mimics a simple loopback, i.e. any RF pulse happening on TX0 during an RX0 readout will be measured there, and the same for TX1/RX1. Thus, the rx1 plot shows:

There are 128 samples, which matches the receiver window duration (400us) divided by the RX sampling period (3.125us). In the middle area is a pulse, corresponding to the tx1 pulse from 500–700us.

We can modify rx_t to be 1 instead of 3.125, and now we see 400 samples (400us / 1us):

Wrapping up

If you have completed this, well done - you have now tried out many of the MaRCoS features, and seen how to write your own experimental sequences! Don’t forget to git add, git commit and git push – you can also look at the final my_first_experiment.py file on Github on the tutorial2 branch.

If you want to play around further, inside marcos_client/examples.py you can see some more advanced usage examples of the dictionary interface.

⚠️ **GitHub.com Fallback** ⚠️