This tutorial is applicable to all SHFQA Instruments.

## Goals and Requirements

The goal of this tutorial is to demonstrate how to use SHFQA to perform multiplexed qubit readout using Zurich Instruments Toolkit API.

For qubit control with the SHFSG Signal Generator, the SHFQC Qubit Controller and the HDAWG Arbitrary Wave Generator, and instrument synchronization and feedback with the PQSC Programmable Quantum System Controller, see tutorials in the Online Documentation.

 Users can download all zhinst-toolkit example files introduced in this tutorial from GitHub, https://github.com/zhinst/zhinst-toolkit/tree/main/examples.

## Preparation

The tutorial starts with the instrument in the default configuration (e.g., after a power cycle). For an optimal tutorial experience, please follow these preparation steps:

• ensure that the version of the LabOne Python API, LabOne and the Firmware of the SHFQA device, zhinst-toolkit (pip install zhinst-toolkit) and Python (3.7 or newer) are updated and compatible,

• make sure that the Instrument is powered on and connected by Ethernet to your local area network (LAN) where the host computer resides or by USB (Maintenance port) to the host computer,

• start LabOne and open the LabOne Graphical User Interface using the default web browser,

• connect the SHFQA channel 1 output (input) to the readout input (output) line, see SHFQA connection.

Figure 1. SHFQA connection.

## Tutorial

1. Connect the instrument

Create a toolkit session to the data server and connect the device with the device ID, e.g. 'DEV12001', see Connecting to the Instrument.

# Load the LabOne API and other necessary packages
from zhinst.toolkit import Session
from scipy.signal import gaussian
import numpy as np

DEVICE_ID = 'DEVXXXXX'
SERVER_HOST = 'localhost'

session = Session(SERVER_HOST)              ## connect to data server
device = session.connect_device(DEVICE_ID)  ## connect to device
2. Generate readout pulses and integration weights

To readout 8 qubits in parallel, 8 readout pulses with different amplitude, frequency and phase are summed up to generate a single output signal. Please note that the maximum amplitude of the sum of all readout pulses should not exceed 1.

In this tutorial, the envelope of all readout pulses is flat-top Gaussian with pulse length of 500 ns and rise and fall time of 10 ns, all amplitude are equally scaled by a factor of 0.9 and divided by the number of qubits, 8 readout frequencies span from 32 MHz to 100 MHz, and all phases are set to 0.

In Readout mode, the frequency down-converted signal is integrated with the integration weights. Tutorial Integration Weights Measurement. shows how to measure integration weights to improve the SNR. In this tutorial, conjugated readout pulses with the amplitude scaling factor of 1 are used and uploaded to the integration weight memory. The integration delay can be measured with the scope, see Integration Weights Measurement.

# generate readout pulses
NUM_QUBITS = 8
RISE_FALL_TIME = 10e-9 # in units of second
SAMPLING_RATE = 2e9 # in units of Hz
PULSE_DURATION = 500e-9 # in units of second
FREQUENCIES = np.linspace(32e6, 150e6, NUM_QUBITS) # in units of Hz
SCALING = 0.9 / NUM_QUBITS # amplitude scaling factor

rise_fall_len = int(RISE_FALL_TIME * SAMPLING_RATE)
pulse_len = int(PULSE_DURATION * SAMPLING_RATE)
std_dev = rise_fall_len // 10

gauss = gaussian(2 * rise_fall_len, std_dev)
flat_top_gaussian = np.ones(pulse_len)
flat_top_gaussian[0:rise_fall_len] = gauss[0:rise_fall_len]
flat_top_gaussian[-rise_fall_len:] = gauss[-rise_fall_len:]
flat_top_gaussian *= SCALING
time_vec = np.linspace(0, PULSE_DURATION, pulse_len)

for i, f in enumerate(FREQUENCIES):
slot=i,
wave1=flat_top_gaussian * np.exp(2j * np.pi * f * time_vec)
)

# generate integration weights
ROTATION_ANGLE = 0
weights =  Waveforms()
weights.assign_waveform(
slot=waveform_slot,
wave1=np.conj(pulse[0] * np.exp(1j * ROTATION_ANGLE)) / np.abs(pulse[0])
)
3. Configure the Channel

Configure the Channel such that the readout pulses are integrated with different integration weights in parallel, and the measurement is repeated 10000 times.

The input range and output range of the Channel 4 is set to -30 dBm, and the center frequency is 5 GHz. There are 2 application modes , see Quantum Analyzer Setup Tab. For multiplex readout, Readout mode is selected in order to use customized integration weights for different qubits.

The Readout pulses and integration weights with assigned waveform memory slots are uploaded to the waveform memory after clear all waveforms which may saved in the waveform memory previously.

The measurement sequence is defined by the SeqC program such that it sends out the readout pulse and integrates the signal for 500 ns with the integration delay of 220 ns after receiving a software trigger. The measurement is repeated 10000 times.

The result after integration and averaging will be saved to the QA Result Logger. This configuration can be used to calibrate control pulses and characterize the qubits, measure thresholds without averaging for state discrimination or readout fidelity if the result source is set to 'result_of_discrimination' and the thresholds are updated using device.qachannels[CHANNEL_INDEX].readout.discriminators[n].threshold (n is the qubit index).

# configure inputs and outputs
CHANNEL_INDEX = 3 # physical Channel 4
NUM_AVERAGES = 100
MODE_AVERAGES = 0 # 0: cyclic; 1: sequential;
INTEGRATION_TIME = PULSE_DURATION # in units of second

device.qachannels[CHANNEL_INDEX].configure_channel(
center_frequency=5e9, # in units of Hz
input_range=-30, # in units of dBm
output_range=-30, # in units of dBm
)
device.qachannels[CHANNEL_INDEX].input.on(1)
device.qachannels[CHANNEL_INDEX].output.on(1)

device.qachannels[CHANNEL_INDEX].generator.clearwave() # clear all readout waveforms
device.qachannels[CHANNEL_INDEX].readout.integration.clearweight() # clear all integration weights
weights=weights,
# compensation for the delay between generator output and input of the integration unit
integration_delay=220e-9
)

# configure sequencer
device.qachannels[CHANNEL_INDEX].generator.configure_sequencer_triggering(
aux_trigger="software_trigger0", # chanNtriginM, chanNseqtrigM, chanNrod
play_pulse_delay=0, # 0s delay between startQA trigger and the readout pulse
)
loop_string = f"""
repeat({NUM_AVERAGES}) {{
"""
if MODE_AVERAGES == 1:
loop_string = f"""
repeat({NUM_AVERAGES}) {{
"""
seqc_program = f"""
{loop_string}
waitDigTrigger(1);
startQA(QA_GEN_ALL, QA_INT_ALL, true, 0, 0x0);
}}
}}
"""

# configure QA setup and QA result logger
result_source='result_of_integration', # “result_of_integration” or “result_of_discrimination”.
num_averages=NUM_AVERAGES,
averaging_mode=MODE_AVERAGES,
)
device.qachannels[CHANNEL_INDEX].readout.run() # enable QA Result Logger
readout_results = device.qachannels[CHANNEL_INDEX].readout.read()