Skip to content

Multiplexed Qubit Readout


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,


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 Quantum Analyzer channel channel output (input) to the readout input (output) line, see SHFQA connection.

Figure 1: SHFQA connection.


  1. Connect the instrument

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

    from zhinst.toolkit import Session, SHFQAChannelMode, Waveforms
    from import gaussian
    import numpy as np
    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)
    readout_pulses = Waveforms()
    for i, f in enumerate(FREQUENCIES):
            wave1=flat_top_gaussian * np.exp(2j * np.pi * f * time_vec)
    # generate integration weights
    weights =  Waveforms()
    for waveform_slot, pulse in readout_pulses.items():
            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 1 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 = 0 # physical Channel 1
    NUM_READOUTS = 100
    NUM_AVERAGES = 100
    MODE_AVERAGES = 0 # 0: cyclic; 1: sequential;
    INTEGRATION_TIME = PULSE_DURATION # in units of second
        center_frequency=5e9, # in units of Hz
        input_range=-30, # in units of dBm
        output_range=-30, # in units of dBm
    # upload readout pulses and integration weights to waveform memory
    device.qachannels[CHANNEL_INDEX].generator.clearwave() # clear all readout waveforms
    device.qachannels[CHANNEL_INDEX].readout.integration.clearweight() # clear all integration weights
        # compensation for the delay between generator output and input of the integration unit
    # configure sequencer
        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}) {{
            repeat({NUM_READOUTS}) {{
    if MODE_AVERAGES == 1:
        loop_string = f"""
        repeat({NUM_READOUTS}) {{
            repeat({NUM_AVERAGES}) {{
    seqc_program = f"""
                startQA(QA_GEN_ALL, QA_INT_ALL, true, 0, 0x0);
    # configure QA setup and QA result logger
    device.qachannels[CHANNEL_INDEX].readout.integration.length(int(INTEGRATION_TIME * SAMPLING_RATE))
        result_source='result_of_integration', # “result_of_integration” or “result_of_discrimination”.
  4. Run the measurement and download the result

    Before starting the measurement, the QA Result Logger is enabled to be ready to get the result, and the Sequencer is enabled to be ready to run the sequence once receive a software trigger. The software trigger is configured to be repeated 100 times with a cycle duration of 2 ms. The measurement result can be downloaded and processed for different purposes, and it can also be displayed in QA Result Logger Tab see Figure 2.

    device.qachannels[CHANNEL_INDEX] # enable QA Result Logger
        num_triggers=NUM_READOUTS * NUM_AVERAGES, wait_time=2e-3
    readout_results = device.qachannels[CHANNEL_INDEX]

    Figure 2: Multiplexed readout of 8 qubits in loopback configuration.