tut_write_simple_sequence - vnegnev/marcos_extras GitHub Wiki
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.
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.
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.
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]$
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.
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!)
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()
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.
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 .
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):
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.