See also

A Jupyter notebook version of this tutorial can be downloaded here.

Basic sequencing

In this tutorial we will demonstrate basic sequencer based operations (see Sequencer Operation) for programming a qblox instrument. This includes creating a sequence consisting of waveforms and a simple Q1ASM program, and executing this sequence synchronously on multiple sequencers.

The general process for setting up and executing a program on a Q1 sequencer is as follows: #. Connect to instrument #. Prepare a sequence (JSON formatted file) which consists of * Waveforms for playback * Weights for weighted integration * Acquisitions for capture * Q1ASM program to be executed by the sequencer #. This sequence is then loaded onto a sequencer on the connected instrument using the method instrument_variable.sequencerX.sequence("SequenceFile.json") #. The sequencer is then setup over its API as necessary #. Seqeuncer is then armed and started to commence the experiment #. Stop the sequencer and close down all instruments

This tutorial will give a basic introduction on how to work with the waveforms and Q1ASM segments of a sequence.

In the Tutorial the sequence is going to consecutively play two waveforms, a gaussian and block with a duration of 20ns each, with an increasing wait period in between them. We will increase the wait period 20ns 100 times, after which the sequence is stopped. The sequence will also trigger marker output 1 at every interval, so that the sequence can be easily monitored on an oscilloscope.

We can perform this tutorial with either a Pulsar or a Cluster QCM/QRM . We use the term ‘QxM’ encompassing both QCM and QRM modules.

To run this tutorial please make sure you have installed and enabled ipywidgets:

pip install ipywidgets
jupyter nbextension enable --py widgetsnbextension

Setup

First, we are going to import the required packages.

[1]:
# Import ipython widgets
import json
import math
import os

import ipywidgets as widgets
import matplotlib.pyplot
import numpy

# Set up the environment.
import scipy.signal
from IPython.display import display
from ipywidgets import fixed, interact, interact_manual, interactive
from qcodes import Instrument
from qblox_instruments import Cluster, PlugAndPlay

Scan For Clusters

We scan for the available devices connected via ethernet using the Plug & Play functionality of the Qblox Instruments package (see Plug & Play for more info).

[2]:
with PlugAndPlay() as p:
    # get info of all devices
    device_list = p.list_devices()

# Scan for available devices and display
names = {
    dev_id: dev_info["description"]["name"] for dev_id, dev_info in device_list.items()
}
ip_addresses = {
    dev_id: dev_info["identity"]["ip"] for dev_id, dev_info in device_list.items()
}

# create widget for names and ip addresses
connect = widgets.Dropdown(
    options=[(names[dev_id] + " @" + ip_addresses[dev_id], dev_id)
             for dev_id in device_list.keys()],
    description="Select Device",
)
display(connect)

Connect to Cluster

We now make a connection with the Cluster selected in the dropdown widget. We also define a function to find the modules we’re interested in. We select the readout and control module we want to use. Note that you need to set select_qrm_type to False is you are using a qcm.

[ ]:
# Connect to device
dev_id = connect.value
# Close the chosen QCodes instrument as to prevent name clash.
try:
    Instrument.find_instrument(names[dev_id]).close()
except KeyError:
    pass

print(ip_addresses)
print(dev_id)
cluster = Cluster(name=names[dev_id], identifier=ip_addresses[dev_id])

print(f"{connect.label} connected")
print(cluster.get_system_state())
[4]:
def select_module_widget(device, select_all=False, select_qrm_type: bool=True, select_rf_type: bool=False):
    """Create a widget to select modules of a certain type

    default is to show only QRM baseband

    Args:
        devices : Cluster we are currently using
        select_all (bool): ignore filters and show all modules
        select_qrm_type (bool): filter QRM/QCM
        select_rf_type (bool): filter RF/baseband
    """
    options = [[None, None]]


    for module in device.modules:
        if module.present():
            if select_all or (module.is_qrm_type == select_qrm_type and module.is_rf_type == select_rf_type):
                options.append(
                    [
                        f"{device.name} "
                        f"{module.short_name} "
                        f"({module.module_type}{'_RF' if module.is_rf_type else ''})",
                        module,
                    ]
                )
    widget = widgets.Dropdown(options=options)
    display(widget)

    return widget
[5]:
print("Select the readout module from the available modules:")
select_module = select_module_widget(cluster, select_qrm_type=True, select_rf_type=False)
Select the readout module from the available modules:
[ ]:
module = select_module.value
print(f"{module} connected")

Reset the Cluster

We reset the Cluster to enter a well-defined state. Note that resetting will clear all stored parameters, so resetting between experiments is usually not desirable.

[ ]:
cluster.reset()
print(cluster.get_system_state())

Generate waveforms

Next, we need to create the gaussian and block waveforms for the sequence. The waveforms constructed here will be referenced by the Q1ASM program for playback. See section Sequencer for details on how waveform dictionary is structured.

[10]:
# Waveform parameters
waveform_length = 22  # nanoseconds

# Waveform dictionary (data will hold the samples and index will be used to select the waveforms in the instrument).
waveforms = {
    "gaussian": {
        "data": scipy.signal.gaussian(
            waveform_length, std=0.12 * waveform_length
        ).tolist(),
        "index": 0,
    },
    "block": {"data": [1.0 for i in range(0, waveform_length)], "index": 1},
}

Let’s plot the waveforms to see what we have created.

[11]:
time = numpy.arange(0, max(map(lambda d: len(d["data"]), waveforms.values())), 1)
fig, ax = matplotlib.pyplot.subplots(1, 1, figsize=(10, 10 / 1.61))

for wf, d in waveforms.items():
    ax.plot(time[: len(d["data"])], d["data"], ".-", linewidth=0.5, label=wf)

ax.legend(loc=4)
ax.yaxis.grid()
ax.xaxis.grid()
ax.set_ylabel("Waveform primitive amplitude")
ax.set_xlabel("Time (ns)")

matplotlib.pyplot.draw()
matplotlib.pyplot.show()
../../_images/tutorials_q1asm_tutorials_basic_sequencing_15_0.png

Create Q1ASM program

Now that we have the waveforms for the sequence, we need a Q1ASM program that sequences the waveforms as previously described. The Q1ASM program can address the memory in the sequences waveforms and acquisitions to construct a program for playback. View Q1 Programming for a break down of available instructions in the Q1ASM language.

[12]:
# Sequence program.
seq_prog = """
       move      100,R0   #Loop iterator.
       move      20,R1    #Initial wait period in ns.
       wait_sync 4        #Wait for sequencers to synchronize and then wait another 4 ns.

loop:  set_mrk   1        #Set marker output 1.
       play      0,1,4    #Play a gaussian and a block on output path 0 and 1 respectively and wait 4 ns.
       set_mrk   0        #Reset marker output 1.
       upd_param 16       #Update parameters and wait the remaining 16 ns of the waveforms.

       wait      R1       #Wait period.

       play      1,0,20   #Play a block and a gaussian on output path 0 and 1 respectively and wait 22 ns.
       wait      1000     #Wait a 1us in between iterations.
       add       R1,20,R1 #Increase wait period by 20 ns.
       loop      R0,@loop #Subtract one from loop iterator.

       stop               #Stop the sequence after the last iteration.
"""

Prepare and Upload sequence

Now that we have the waveforms and Q1ASM program, we can combine them in a sequence stored in a JSON file.

[13]:
# Add sequence to single dictionary and write to JSON file.
sequence = {
    "waveforms": waveforms,
    "weights": {},
    "acquisitions": {},
    "program": seq_prog,
}
with open("sequence.json", "w", encoding="utf-8") as file:
    json.dump(sequence, file, indent=4)
    file.close()

Let’s write the JSON file to the instruments. We will use sequencer 0 and 1, which will drive outputs \(\text{O}^{[1-2]}\) and \(\text{O}^{[3-4]}\) respectively.

[14]:
# Upload sequence.
module.sequencer0.sequence("sequence.json")
module.sequencer1.sequence("sequence.json")

Play sequence

The sequence has been uploaded to the instrument. Now we need to configure the sequencers in the instrument to use the wait_sync instruction at the start of the Q1ASM program to synchronize.

[15]:
# Configure the sequencers to synchronize.
module.sequencer0.sync_en(True)
module.sequencer1.sync_en(True)

# Map sequencers to specific outputs (but first disable all sequencer connections).
for sequencer in module.sequencers:
    for out in range(0, 4):
        if hasattr(sequencer, "channel_map_path{}_out{}_en".format(out % 2, out)):
            sequencer.set("channel_map_path{}_out{}_en".format(out % 2, out), False)

# If it is a QRM, we only map sequencer 0 to the outputs.
module.sequencer0.channel_map_path0_out0_en(True)
module.sequencer0.channel_map_path1_out1_en(True)
if module.is_qcm_type:
    module.sequencer1.channel_map_path0_out2_en(True)
    module.sequencer1.channel_map_path1_out3_en(True)

Now let’s start the sequence. If you want to observe the sequence, this is the time to connect an oscilloscope to marker output 1 and one or more of the four outputs. Configure the oscilloscope to trigger on the marker output 1.

[16]:
# Arm and start both sequencers.
module.arm_sequencer(0)
module.arm_sequencer(1)
module.start_sequencer()

# Print status of both sequencers.
print(module.get_sequencer_state(0))
print(module.get_sequencer_state(1))
Status: STOPPED, Flags: NONE
Status: STOPPED, Flags: NONE

Stop

Finally, let’s stop the sequencers if they haven’t already and close the instrument connection. One can also display a detailed snapshot containing the instrument parameters before closing the connection by uncommenting the corresponding lines.

[17]:
# Stop both sequencers.
module.stop_sequencer()

# Print status of both sequencers (should now say it is stopped).
print(module.get_sequencer_state(0))
print(module.get_sequencer_state(1))
print()

# Uncomment the following to print an overview of the instrument parameters.
# Print an overview of the instrument parameters.
# print("Snapshot:")
# module.print_readable_snapshot(update=True)

# Close the instrument connection.
cluster.reset()
print(cluster.get_system_state())
Cluster.close_all()
Status: STOPPED, Flags: FORCED_STOP
Status: STOPPED, Flags: FORCED_STOP

Status: OKAY, Flags: NONE, Slot flags: NONE