See also

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

Numerically Controlled Oscillator - Additional Features#

In this tutorial we will demonstrate the use of the NCO to carry out some novel experiments. This will be a review of some of the more advanced protocols one can carry out with the Q1 sequencer and the NCO. We will demonstrate the following experiments:

  1. Chirping the frequency of a pulse e.g for rapid spectroscopy measurements

  2. Phase updates e.g for virtual \(Z\) gates

We will show these functionalities using a QRM and directly connecting outputs \(\text{O}^{[1-2]}\) to inputs \(\text{I}^{[1-2]}\) respectively. We will then use the QRM’s sequencers to sequence waveforms on the outputs and simultaneously acquire the resulting waveforms on the inputs.

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 and connect to the instrument.

[1]:
# Import ipython widgets
import contextlib
import json

import ipywidgets as widgets
import matplotlib.pyplot as plt
import numpy as np

# Set up the environment.
from IPython.display import display
from qcodes import Instrument
from scipy.signal import spectrogram, welch
from scipy.signal.windows import gaussian

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]:
!qblox-pnp list
No devices found
[3]:
cluster_ip = "10.10.200.42"
cluster_name = "cluster0"

Connect to Cluster#

We now make a connection with the Cluster.

[4]:
from qblox_instruments import Cluster, ClusterType

try:  # Close the chosen QCodes instrument to prevent name clash
    Cluster.find_instrument(cluster_name).close()
except KeyError:
    pass

cluster = Cluster(
    name=cluster_name,
    identifier=cluster_ip,
    dummy_cfg={
        2: ClusterType.CLUSTER_QCM,
        4: ClusterType.CLUSTER_QRM,
        6: ClusterType.CLUSTER_QCM_RF,
        8: ClusterType.CLUSTER_QRM_RF,
    }
    if cluster_ip is None
    else None,
)

Get connected modules#

[5]:
def get_connected_modules(cluster, filter_fn=None):
    def checked_filter_fn(mod):
        if filter_fn is not None:
            return filter_fn(mod)
        return True

    return {
        mod.slot_idx: mod for mod in cluster.modules if mod.present() and checked_filter_fn(mod)
    }
[6]:
# QRM baseband modules
readout_modules = get_connected_modules(cluster, lambda mod: mod.is_qrm_type and not mod.is_rf_type)
readout_modules
[6]:
{4: <QcmQrm: cluster0_module4 of Cluster: cluster0>}
[7]:
readout_module = readout_modules[4]

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.

[8]:
cluster.reset()
print(cluster.get_system_state())
Status: OKAY, Flags: NONE, Slot flags: NONE

Frequency chirps#

Frequency chirps consitute a fast frequency sweep using the Q1ASM to change the NCO frequency in real time during playback. To do this, we first set up the QRM for continuous wave output and a single scope acquisition. This is significantly faster than a spectroscopy measurement but also limits the maximum number of points we can measure in a single program. The total duration of the sweep must be 16384 ns or less, as that is the maximum length of a scope acquisition.

In this tutorial, we will analyze the raw data measured by the scope acquisition of the QRM. For this we will define a simple helper function using scipy.signal.spectrogram and scipy.signal.welch. The spectrogram shows the frequency spectrum of the QRM output as a function of time, to visualize the frequency sweeps we are doing. Welch’s method is used to compute the input power as a function of frequency (power spectral density). This way we obtain the response of the system to find features of interest, e.g. a resonance.

[9]:
# Power as function of frequency and time by chunking the data
def plot_spectrogram(time_series: np.ndarray) -> None:
    f_sample = 1e9  # All devices have 1 GSPS sample rate
    fig, ax = plt.subplots(1, 2)

    f, t, Sxx = spectrogram(time_series, f_sample, return_onesided=False, detrend=False)

    idx = np.argsort(f)
    f = f[idx] / 1e6
    Sxx = Sxx[idx]

    spec = ax[0].pcolormesh(t, f, Sxx, shading="auto", cmap="YlOrRd")
    cb = fig.colorbar(spec)
    cb.set_label("Power Spectral Density [V$^2$/Hz]")
    ax[0].set_ylabel("Frequency [MHz]")
    ax[0].set_xlabel("Time [s]")

    f, Pxx = welch(time_series, f_sample, return_onesided=False, detrend=False)

    idx = np.argsort(f)
    f = f[idx] / 1e6
    Pxx = Pxx[idx]

    ax[1].semilogy(f, Pxx)
    ax[1].set_xlabel("Frequency [MHz]")
    ax[1].set_ylabel("Power Spectral Density [V$^2$/Hz]")
    fig.tight_layout()
    plt.show()

And two more helper functions for plotting the amplitude of an array of I, Q values and a scope acquisition:

[10]:
def plot_amplitude(x, I_data, Q_data):
    amplitude = np.abs(I_data + 1j * Q_data)

    plt.plot(x / 1e6, amplitude)
    plt.xlabel("Frequency [MHz]")
    plt.ylabel("Integration [V]")
    plt.show()


def plot_scope(trace, t_min: int, t_max: int):
    x = np.arange(t_min, t_max)
    plt.plot(x, np.real(trace[t_min:t_max]))
    plt.plot(x, np.imag(trace[t_min:t_max]))
    plt.ylabel("Scope [V]")
    plt.xlabel("Time [ns]")
    plt.show()
[11]:
start_freq = -500e6
stop_freq = 500e6

n_averages = 10
MAXIMUM_SCOPE_ACQUISITION_LENGTH = 16384

Setting up the QRM#

We set up a modulated DC offset:

[12]:
readout_module.disconnect_outputs()
readout_module.disconnect_inputs()

# Configure channel map
readout_module.sequencer0.connect_sequencer("io0_1")

# Set DC Offset
readout_module.sequencer0.offset_awg_path0(1)
readout_module.sequencer0.offset_awg_path1(1)

# Enable modulation and demodulation. Note that the scope is not demodulated
readout_module.sequencer0.mod_en_awg(True)
readout_module.sequencer0.demod_en_acq(True)

# Enable hardware averaging for the scope
readout_module.scope_acq_avg_mode_en_path0(True)
readout_module.scope_acq_avg_mode_en_path1(True)

readout_module.sequencer0.integration_length_acq(MAXIMUM_SCOPE_ACQUISITION_LENGTH)
readout_module.sequencer0.nco_prop_delay_comp_en(True)

The sequencer program can fundamentally only support integer values. However, the NCO has a frequency resolution of 0.25 Hz and supports 10^9 phase values.

Therefore, frequencies in the sequencer program must be given as an integer multiple of 0.25 Hz, and phases as an integer multiple of \(360/10^9\) degrees.

[13]:
step_time = 44  # Time per frequency step in ns. We can reduce this, but the program needs to be changed. See next section
n_steps = int(16384 / step_time)
n_averages = 10

step_freq = (stop_freq - start_freq) / n_steps
print(f"{n_steps} steps with step size {step_freq/1e6} MHz")

# Convert frequencies to multiples of 0.25 Hz
nco_int_start_freq = int(4 * start_freq)
nco_int_step_freq = int(4 * step_freq)

# For plotting, convert the NCO integer values back to frequencies
nco_sweep_range = np.arange(nco_int_start_freq, 4 * stop_freq, nco_int_step_freq) / 4.0
372 steps with step size 2.6881720430107525 MHz

Now, we write a Q1ASM program that quickly changes the NCO’s frequency, converting the continuous sine output into a chirp.

Internally, the processor stores negative values using two’s complement. This has some implications for our program: - We cannot directly store a negative value in a register. Substracting a larger value from a smaller one works as expected though. - Immediate values are handled by the compiler, i.e. set_freq -100 gives the expected result of -25 Hz. - Comparisons (jlt, jge) with registers storing a negative value do not work as expected, as the smallest negative number is larger than the largest positive number. To keep the program general we should therefore use loop instead.

[14]:
acquisitions = {"acq": {"num_bins": 1, "index": 0}}

setup = f"""
    move {n_averages}, R2

avg_loop:
    move    0, R0          # frequency
    move {n_steps}, R1     # n_steps
"""

# To get a negative starting frequency, we substract a positive number from 0
if start_freq <= 0:
    setup += f"""
    sub R0, {-nco_int_start_freq}, R0
    """
else:
    setup += f"""
    add R0, {nco_int_start_freq}, R0
    """

# Play a chirped pulse
chirp = (
    setup
    + f"""
    reset_ph
    set_freq 0
    upd_param 200
    acquire 0,0,4                         # Start acquisition. This is not blocking

nco_set:
    set_freq        R0                    # Set the frequency
    add             R0,{nco_int_step_freq}, R0  # Update the frequency register
    upd_param       {step_time}
    loop            R1, @nco_set          # Loop over all frequencies

    wait            10000
    loop            R2, @avg_loop

    stop                                  # Stop
"""
)

# Add sequence to single dictionary and write to JSON file.
sequence = {
    "waveforms": {},
    "weights": {},
    "acquisitions": acquisitions,
    "program": chirp,
}
with open("sequence.json", "w", encoding="utf-8") as file:
    json.dump(sequence, file, indent=4)
[15]:
readout_module.sequencer0.sequence("sequence.json")
# readout_module.sequencer0.integration_length_acq(MAXIMUM_SCOPE_ACQUISITION_LENGTH)
[16]:
%%time
readout_module.sequencer0.nco_freq(0)
readout_module.arm_sequencer(0)
readout_module.start_sequencer()
print(readout_module.get_sequencer_state(0))


# Wait for the sequencer to stop with a timeout period of one minute.
readout_module.get_acquisition_state(0, timeout=1)

# Move acquisition data from temporary memory to acquisition list.
readout_module.store_scope_acquisition(0, "acq")
data = readout_module.get_acquisitions(0)["acq"]
Status: STOPPED, Flags: ACQ_SCOPE_DONE_PATH_0, ACQ_SCOPE_OVERWRITTEN_PATH_0, ACQ_SCOPE_DONE_PATH_1, ACQ_SCOPE_OVERWRITTEN_PATH_1, ACQ_BINNING_DONE
CPU times: user 2 ms, sys: 3 ms, total: 5 ms
Wall time: 239 ms

Note that this is significantly faster than standard spectroscopy with Q1ASM with a larger number of points. For this measurement, we only use the scope acquisition data. We can analyze it again with a spectrogram.

[17]:
trace = np.array(data["acquisition"]["scope"]["path0"]["data"]) + 1j * np.array(
    data["acquisition"]["scope"]["path1"]["data"]
)
plot_spectrogram(trace)
../../../_images/tutorials_q1asm_tutorials_intermediate_nco_control_adv_29_0.png

Note the difference in timescale to before. In the spectrogram we can see the intended spectrum of the chirp. And finally, we can visualize the chirp pulse. For better clarity, we show the low frequency parts around 8400ns.

[18]:
plot_scope(trace, 8350, 9000)
../../../_images/tutorials_q1asm_tutorials_intermediate_nco_control_adv_31_0.png

Timings of the Q1 and realtime processors#

The sequencer has a real-time pipeline that generates the output and a classical pipeline responsible for logic and filling the queue of the real-time pipeline. This queue is 32 instructions long and starts pre-filled when the sequencer is started. If the runtime of classical instructions is shorter than the corresponding real-time instructions, the sequencer will stop. See also in the documentation of the sequencer.

We can see this by running the same program as before, but with reduced time between frequency steps:

[19]:
step_time = 40  # this will cause stalling

n_steps = int(16384 / step_time)
step_freq = (stop_freq - start_freq) / n_steps
print(f"{n_steps} steps with step size {step_freq/1e6} MHz")

# Convert frequencies to multiples of 0.25 Hz
nco_int_start_freq = int(4 * start_freq)
nco_int_step_freq = int(4 * step_freq)

# For plotting, convert the NCO integer values back to frequencies
nco_sweep_range = np.arange(nco_int_start_freq, 4 * stop_freq, nco_int_step_freq) / 4.0
409 steps with step size 2.4449877750611244 MHz
[20]:
acquisitions = {"acq": {"num_bins": 1, "index": 0}}

setup = f"""
    move {n_averages}, R2

avg_loop:
    move {n_steps}, R1  # n_steps
    move    0, R0          # frequency
    nop
"""

# To get a negative starting frequency, we substract a positive number from 0
if start_freq <= 0:
    setup += f"""
    sub R0, {-nco_int_start_freq}, R0
    """
else:
    setup += f"""
    add R0, {nco_int_start_freq}, R0
    """

# Play a chirped pulse
chirp = (
    setup
    + f"""
    reset_ph
    set_freq 0
    upd_param 200
    acquire 0,0,4                         # Start acquisition. This is not blocking

nco_set:
    set_freq        R0                    # Set the frequency
    add             R0,{nco_int_step_freq}, R0  # Update the frequency register
    upd_param       {step_time}
    loop            R1, @nco_set          # Loop over all frequencies

    wait            10000
    loop            R2, @avg_loop

    stop                                  # Stop
"""
)

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

readout_module.arm_sequencer(0)
readout_module.start_sequencer()

# Wait for the sequencer to stop with a timeout period of one minute.
readout_module.get_acquisition_state(0, timeout=1)

# Move acquisition data from temporary memory to acquisition list.
readout_module.store_scope_acquisition(0, "acq")
data = readout_module.get_acquisitions(0)["acq"]

As can be seen from the red LEDs on the front of your Cluster, the Q1 processor has stalled, and the program stopped.

[22]:
print(readout_module.sequencer0.get_sequencer_state())
Status: STOPPED, Flags: FORCED_STOP, SEQUENCE_PROCESSOR_RT_EXEC_COMMAND_UNDERFLOW, ACQ_SCOPE_DONE_PATH_0, ACQ_SCOPE_DONE_PATH_1, ACQ_BINNING_DONE

We can also see on the scope that the chirp has been stopped prematurely.

[23]:
trace = np.array(data["acquisition"]["scope"]["path0"]["data"]) + 1j * np.array(
    data["acquisition"]["scope"]["path1"]["data"]
)
plot_spectrogram(trace)
../../../_images/tutorials_q1asm_tutorials_intermediate_nco_control_adv_39_0.png

Before continuing, we clear the flags on the qrm:

[24]:
cluster.clear()

If we want to the chirp to use as many updates as possible, we need to unroll the loop. This way, we can reduce the time per frequency step down to 8ns: set_freq, upd_param both take 4ns on the Q1 processor.

[25]:
step_time = 8

n_steps = int(16384 / step_time)
step_freq = (stop_freq - start_freq) / n_steps
print(f"{n_steps} steps with step size {step_freq/1e6} MHz")

# Convert frequencies to multiples of 0.25 Hz
nco_int_start_freq = int(4 * start_freq)
nco_int_step_freq = int(4 * step_freq)

# For plotting, convert the NCO integer values back to frequencies
nco_sweep_range = np.arange(nco_int_start_freq, 4 * stop_freq, nco_int_step_freq) / 4.0
2048 steps with step size 0.48828125 MHz

This does not leave room for a loop, which would take an additional 24ns. However, we can use the instruction memory of the Q1 processor:

[26]:
chirp = f"""
    move {n_averages}, R2

avg_loop:
    move    0, R0          # frequency
    nop
"""

# To get a negative starting frequency, we substract a positive number from 0
if start_freq <= 0:
    chirp += f"""
    sub R0, {-nco_int_start_freq}, R0
    """
else:
    chirp += f"""
    add R0, {nco_int_start_freq}, R0
    """

chirp += """
    reset_ph
    set_freq 0
    upd_param 200
    acquire 0,0,4                         # Start acquisition. This is not blocking

"""
# unroll the loop into individual commands

for frequency in nco_sweep_range:
    chirp += f"""
    set_freq        {int(4*frequency)}                    # Set the frequency
    upd_param       {step_time}
"""

chirp += """

    wait            10000
    loop            R2, @avg_loop

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

Now we execute the program and plot the spectrogram:

[28]:
%%time
readout_module.arm_sequencer(0)
readout_module.start_sequencer()

# Wait for the sequencer to stop with a timeout period of one minute.
readout_module.get_acquisition_state(0, timeout=1)

# Move acquisition data from temporary memory to acquisition list.
readout_module.store_scope_acquisition(0, "acq")
data = readout_module.get_acquisitions(0)["acq"]
CPU times: user 2.56 ms, sys: 641 µs, total: 3.2 ms
Wall time: 140 ms

Note that the execution time is comparable to the other chirped measurements, but with more than double the number of points.

[29]:
trace = np.array(data["acquisition"]["scope"]["path0"]["data"]) + 1j * np.array(
    data["acquisition"]["scope"]["path1"]["data"]
)
plot_spectrogram(trace)
../../../_images/tutorials_q1asm_tutorials_intermediate_nco_control_adv_50_0.png

Phase updates#

Virtual Z gates#

In addition to fast frequency updates, the sequencer also supports real-time changes of the NCO phase. In particular for superconducting qubits, this can be used for a so-called virtual \(Z\) gate, see McKay et al. (2016). The virtual \(Z\) gate is a change of reference frame rather than a physical operation. Therefore, it is instantaneous and near perfect - the dominant error being that the NCO has a finite resolution of \(10^9\) different phases. Below, we will demonstrate how to to use a virtual Z to use the same pulse for both \(X\) and \(Y\) rotations.

As the sequencer internally only supports integer values, we must first convert the phase into an integer multiple of \(360/10^{9}\) degree:

[30]:
int_90 = int(90 * (1e9 / 360))
int_270 = int(270 * (1e9 / 360))
[31]:
# Waveforms
waveform_len = 1000
waveforms = {
    "gaussian": {
        "data": gaussian(waveform_len, std=0.133 * waveform_len).tolist(),
        "index": 0,
    },
}

# Acquisitions
acquisitions = {"scope": {"num_bins": 1, "index": 0}}

# Program
virtual_z = f"""
acquire         0,0,4
reset_ph
play            0,0,{waveform_len}      # X90
# This is equivalent to Y90, but uses the same waveform as X90
set_ph_delta    {int_90}     # Z90
play            0,0,{waveform_len}      # X90
set_ph_delta    {int_270}    # Z-90
play            0,0,{waveform_len}      # X90
stop
"""

# Write sequence to file.
with open("sequence.json", "w", encoding="utf-8") as file:
    json.dump(
        {
            "waveforms": waveforms,
            "weights": {},
            "acquisitions": acquisitions,
            "program": virtual_z,
        },
        file,
        indent=4,
    )
    file.close()
[32]:
# Program sequencers. But first we reset the cluster
cluster.reset()

readout_module.sequencer0.sequence("sequence.json")

readout_module.disconnect_outputs()
readout_module.disconnect_inputs()

# Configure channel map
readout_module.sequencer0.connect_sequencer("io0_1")
readout_module.sequencer0.nco_freq(3e6)

# Enable modulation
readout_module.sequencer0.mod_en_awg(True)
readout_module.sequencer0.demod_en_acq(True)

# Enable hardware averaging for the scope
readout_module.scope_acq_avg_mode_en_path0(True)
readout_module.scope_acq_avg_mode_en_path1(True)

readout_module.sequencer0.integration_length_acq(MAXIMUM_SCOPE_ACQUISITION_LENGTH)

Now we can run the program and look at the scope acquisition.

[33]:
# Start the sequence
readout_module.arm_sequencer(0)
readout_module.start_sequencer()

# Wait for the sequencer to stop
readout_module.get_acquisition_state(0, timeout=1)

# Get acquisition data
readout_module.store_scope_acquisition(0, "scope")
acq = readout_module.get_acquisitions(0)

trace = np.asarray(acq["scope"]["acquisition"]["scope"]["path0"]["data"]) + 1j * np.asarray(
    acq["scope"]["acquisition"]["scope"]["path1"]["data"]
)

plot_scope(trace, 0, 4000)
../../../_images/tutorials_q1asm_tutorials_intermediate_nco_control_adv_56_0.png

Chirped pulses using the phase update#

As an exercise combining the concepts from this notebook, we can also create a chirp using only phase updates. This is purely educational and should not be used in an experiment. Again, we do not use a loop, and use the instruction memory instead to make the step size smaller.

[34]:
n_averages = 100
n_steps = int(16384 / (20))

acquisitions = {"acq": {"num_bins": 1, "index": 0}}


phase_chirp = f"""
    move {n_averages}, R2

avg_loop:
    move    0, R0                         # phase update step size
    reset_ph
    set_freq 0
    upd_param 200
    acquire 0,0,4                         # Start acquisition. This is not blocking

nco_set:
"""
# step the phase with increasing step size
for _ in range(n_steps):
    phase_chirp += f"""
    set_ph_delta    R0
    upd_param       20
    add             R0, {int(1e9/(4*n_steps))}, R0      # increase the 'frequency'
"""
phase_chirp += """
    wait            10000
    loop            R2, @avg_loop

    stop                                  # Stop
"""

# Write sequence to file.
with open("sequence.json", "w", encoding="utf-8") as file:
    json.dump(
        {
            "waveforms": waveforms,
            "weights": {},
            "acquisitions": acquisitions,
            "program": phase_chirp,
        },
        file,
        indent=4,
    )
    file.close()

We can run this with the same settings as the chirps before:

[35]:
readout_module.sequencer0.sequence("sequence.json")

# Set DC Offset
readout_module.sequencer0.offset_awg_path0(1)
readout_module.sequencer0.offset_awg_path1(1)

readout_module.arm_sequencer(0)
readout_module.start_sequencer()

# Wait for the sequencer to stop with a timeout period of one minute.
readout_module.get_acquisition_state(0, timeout=1)

# Move acquisition data from temporary memory to acquisition list.
readout_module.store_scope_acquisition(0, "acq")
data = readout_module.get_acquisitions(0)["acq"]

In the spectrogram we can see a slow frequency sweep - plus high frequency components. The reason for those is easily visible in the scope, the “sine” wave is not smooth, but instead made up of many square pulses (as expected).

[36]:
trace = np.array(data["acquisition"]["scope"]["path0"]["data"]) + 1j * np.array(
    data["acquisition"]["scope"]["path1"]["data"]
)
plot_spectrogram(trace)

plot_scope(trace, 0, 2000)
../../../_images/tutorials_q1asm_tutorials_intermediate_nco_control_adv_62_0.png
../../../_images/tutorials_q1asm_tutorials_intermediate_nco_control_adv_62_1.png

Using registers, we can also do negative phase steps:

[37]:
n_averages = 100
n_steps = int(16384 / (20))

acquisitions = {"acq": {"num_bins": 1, "index": 0}}


phase_chirp = f"""
    move {n_averages}, R2

avg_loop:
    move    0, R0                         # phase update step size
    reset_ph
    upd_param 200
    set_freq 0
    acquire 0,0,4                         # Start acquisition. This is not blocking

nco_set:
"""
# step the phase with increasing (negative) step size
for _ in range(n_steps):
    phase_chirp += f"""
    set_ph_delta    R0
    upd_param       20
    sub             R0, {int(1e9/(4*n_steps))}, R0      # increase the step size ('frequency')
"""

phase_chirp += """
    wait            10000
    loop            R2, @avg_loop

    stop                                  # Stop
"""

# Write sequence to file.
with open("sequence.json", "w", encoding="utf-8") as file:
    json.dump(
        {
            "waveforms": waveforms,
            "weights": {},
            "acquisitions": acquisitions,
            "program": phase_chirp,
        },
        file,
        indent=4,
    )
    file.close()

readout_module.sequencer0.sequence("sequence.json")

readout_module.arm_sequencer(0)
readout_module.start_sequencer()

# Wait for the sequencer to stop with a timeout period of one minute.
readout_module.get_acquisition_state(0, timeout=1)

# Move acquisition data from temporary memory to acquisition list.
readout_module.store_scope_acquisition(0, "acq")
data = readout_module.get_acquisitions(0)["acq"]

trace = np.array(data["acquisition"]["scope"]["path0"]["data"]) + 1j * np.array(
    data["acquisition"]["scope"]["path1"]["data"]
)
plot_spectrogram(trace)

plot_scope(trace, 0, 2500)
../../../_images/tutorials_q1asm_tutorials_intermediate_nco_control_adv_64_0.png
../../../_images/tutorials_q1asm_tutorials_intermediate_nco_control_adv_64_1.png

Stop#

Finally, let’s stop the playback 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.

[38]:
# Stop both sequencers.
readout_module.stop_sequencer()

# Print status of both sequencers (should now say it is stopped).
print(readout_module.get_sequencer_state(0))
print(readout_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:")
# readout_module.print_readable_snapshot(update=True)

# Reset the cluster
cluster.reset()
print(cluster.get_system_state())
Status: STOPPED, Flags: FORCED_STOP, ACQ_SCOPE_DONE_PATH_0, ACQ_SCOPE_OVERWRITTEN_PATH_0, ACQ_SCOPE_DONE_PATH_1, ACQ_SCOPE_OVERWRITTEN_PATH_1, ACQ_BINNING_DONE
Status: STOPPED, Flags: FORCED_STOP

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