See also

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

In-situ Mixer Calibration#

The Cluster RF Modules (QCM-RF and QRM-RF) have integrated IQ mixers that handle both upconversion and downconversion of RF signals. These mixers use a local oscillator (LO) with a frequency of \(\omega_{LO}\). During upconversion, they combine signals from the I and Q paths, which have a frequency of \(\omega_{NCO}\), resulting in an output signal at \(\omega_{LO} + \omega_{NCO}\). However, due to the inherent mathematical imperfections of IQ mixers, the output also includes an unwanted signal at \(\omega_{LO}\) (known as leakage) and another at \(\omega_{LO} - \omega_{NCO}\) (called the undesired sideband).

In this tutorial, we are going to look at ways to suppress the LO leakage and undesired sideband. This process is called mixer calibration.

To run this tutorial optimally, you will need:

  • A QCM-RF or QRM-RF module

  • Spectrum analyzer

  • Two SMA-cables

Setup#

First, we are going to import the required packages.

[1]:
from __future__ import annotations

import json

from qcodes.instrument import find_or_create_instrument

from qblox_instruments import Cluster, ClusterType

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).

!qblox-pnp list

[2]:
cluster_ip = "10.10.200.42"
cluster_name = "cluster0"

Connect to Cluster#

We now make a connection with the Cluster.

[3]:
cluster: Cluster = find_or_create_instrument(
    Cluster,
    recreate=True,
    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,
            10: ClusterType.CLUSTER_QTM,
            12: ClusterType.CLUSTER_QRC,
        }
        if cluster_ip is None
        else None
    ),
)

cluster.reset()
print(cluster.get_system_status())
Status: OKAY, Flags: NONE, Slot flags: NONE

Get connected modules#

[4]:
# QRM-RF modules
modules = cluster.get_connected_modules(lambda mod: mod.is_qrm_type and mod.is_rf_type)
[5]:
# This uses the module of the correct type with the lowest slot index
module = list(modules.values())[0]

We upload a simple sequence program that keeps playing the DC waveform at 30 percent IF power (waveform amplitude = 0.3). This will be modulated and upconverted within the QRM-RF before outputting.

[6]:
# Sequence program.
seq_prog = """
      wait_sync 4

loop: play    0,0,1200
      jmp     @loop
"""
waveforms = {"dc": {"data": [0.3 for i in range(0, 1200)], "index": 0}}

# 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()

module.sequencer0.sequence("sequence.json")

Let’s configure the sequencer to generate an IF frequency of \(100\) MHz. To get an output frequency of \(5.0\) GHz, we then have to configure the LO to run at \(4.9\) GHz.

[7]:
# Configure the Local oscillator

lo_freq = 4.9e9
nco_freq = 100e6
module.disconnect_outputs()
[8]:
module.disconnect_inputs()
[9]:
# Configure channel map
module.sequencer0.connect_sequencer("io0")
module.out0_in0_lo_freq(lo_freq)
[10]:
module.sequencer0.marker_ovr_en(True)
module.sequencer0.marker_ovr_value(3)  # Enables output on QRM-RF

# Configure the sequencer
module.sequencer0.mod_en_awg(True)
module.sequencer0.nco_freq(nco_freq)
module.sequencer0.sync_en(True)

module.arm_sequencer(0)
module.start_sequencer(0)

print(module.get_sequencer_status(0))
Status: OKAY, State: RUNNING, Info Flags: NONE, Warning Flags: NONE, Error Flags: NONE, Log: []

Connect the output of the QRM-RF (O1) to the spectrum analyzer. This is what the output looks like on the spectrum analyzer (center frequency at 4.85 GHz with 600 MHz bandwidth). We see three peaks which correspond to (from the right) 5 GHz (desired signal), 4.9 GHz (LO Leakage) and 4.8 GHz (unwanted sideband).

IQ_Mixer_Calib_before.png

We will use the hardware mixer calibration capabilities of the Cluster RF modules to suppress the LO and unwanted sideband. This calibration routine is carried out by the internal circuitry of the modules hence requiring no cabling on the output channels.

PLEASE NOTE:

  1. The output switches are turned OFF when the modules are performing calibration and are turned back ON after calibration.

  2. Calibration also interrupts other sequencers within the module. Consequently, please restart the necessary sequencers (arm_sequencer and start_sequencer) that should be operational after completing the calibration process. Sequencers in other modules will continue to run as usual.

  3. We expect a typical suppression of 35 dBc for LO and sideband tones when calibrating a (signal) tone with 30 percent of the maximum IF amplitude (AWG Offset). It is possible to achieve more than 55 dBc of spurious free dynamic range (SFDR) when calibrating the mixers manually. Please follow the ‘Manual Mixer calibration’ tutorial for manually calibrating the mixer.

  4. To achieve the best results for the automated mixer calibration, please meet the following conditions:

    • Screw in all modules at the top and bottom before running the calibration to ensure proper grounding.

    • All empty slots in the cluster should be filled in with metal flowblockers, and the flowblockers should be screwed in at the top and bottom as well.

Suppression of LO Leakage#

The module.out0_in0_lo_cal() or module.out{x}_lo_cal() functions can be called to calibrate the LO leakage as shown below.

[11]:
module.out0_in0_lo_cal()

Suppression of Undesired Sideband#

Similar to the LO suppression scheme, undesired sideband can also be suppressed using the sequencer.sideband_cal() function.

[12]:
module.sequencer0.sideband_cal()
module.arm_sequencer(0)
module.start_sequencer()

NOTE : If you would like to achieve better SFDR for your experiments, please calibrate the mixer manually.

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.

[13]:
# Stop all sequencers.
module.stop_sequencer()

# Print status of sequencers 0 and 1 (should now say it is stopped).
print(module.get_sequencer_status(0))
print(module.get_sequencer_status(1))
print()
Status: OKAY, State: STOPPED, Info Flags: FORCED_STOP, Warning Flags: NONE, Error Flags: NONE, Log: []
Status: OKAY, State: STOPPED, Info Flags: NONE, Warning Flags: NONE, Error Flags: NONE, Log: []