{ "cells": [ { "attachments": {}, "cell_type": "markdown", "id": "4597afb4", "metadata": {}, "source": [ "# Mixer correction\n", "In this tutorial we will demonstrate the ability to compensate for output mixer non-idealities and observe the changes using an oscilloscope.\n", "\n", "Mixer non-idealities can lead to unwanted spurs on the output (LO/RF/IF feedthrough and other spurious products) and they can be compensated by applying adjustments to the I/Q outputs: phase offset, gain ratio and DC offset. This solution applies to both baseband QCM/QRM products using external mixers as well as QCM-RF and QRM-RF products.\n", "\n", "The tutorial is designed for Cluster QRM/QCM baseband. We will adjust all the parameters listed above and observe the changes to the I/Q outputs directly on an oscilloscope.\n", "\n", "For QCM-RF and QRM-RF products, one can also refer to the 'mixer calibration' section of the tutorial on [RF-control](https://qblox-qblox-instruments.readthedocs-hosted.com/en/master/tutorials/q1asm_tutorials/basic/rf/rf_control.html#Mixer-calibration).\n", "\n", "To run this tutorial please make sure you have installed and enabled ipywidgets:\n", "```\n", "pip install ipywidgets\n", "jupyter nbextension enable --py widgetsnbextension\n", "```" ] }, { "attachments": {}, "cell_type": "markdown", "id": "be0fecb4", "metadata": {}, "source": [ "Setup\n", "-----\n", "\n", "First, we are going to import the required packages." ] }, { "cell_type": "code", "execution_count": 1, "id": "0c45669a", "metadata": {}, "outputs": [], "source": [ "# Import ipython widgets\n", "import contextlib\n", "import json\n", "\n", "import ipywidgets as widgets\n", "\n", "# Set up the environment.\n", "from IPython.display import display\n", "from ipywidgets import interact\n", "from qcodes import Instrument\n", "\n", "from qblox_instruments import Cluster, PlugAndPlay" ] }, { "attachments": {}, "cell_type": "markdown", "id": "34b709b2", "metadata": {}, "source": [ "### Scan For Clusters\n", "\n", "We scan for the available clusters on our network using the Plug & Play functionality of the Qblox Instruments package (see [Plug & Play](https://qblox-qblox-instruments.readthedocs-hosted.com/en/master/api_reference/tools.html#api-pnp) for more info)." ] }, { "cell_type": "code", "execution_count": 2, "id": "aac181cd", "metadata": {}, "outputs": [ { "data": { "application/vnd.jupyter.widget-view+json": { "model_id": "3228ff1b49ba4185996426f12d51a797", "version_major": 2, "version_minor": 0 }, "text/plain": [ "Dropdown(description='Select Device', options=(('Marketing-Cluster @10.10.200.99', '00015_2247_002'), ('cluste…" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "# Scan for available devices and display\n", "with PlugAndPlay() as p:\n", " # get info of all devices\n", " device_list = p.list_devices()\n", "\n", "names = {dev_id: dev_info[\"description\"][\"name\"] for dev_id, dev_info in device_list.items()}\n", "ip_addresses = {dev_id: dev_info[\"identity\"][\"ip\"] for dev_id, dev_info in device_list.items()}\n", "\n", "# create widget for names and ip addresses\n", "connect = widgets.Dropdown(\n", " options=[(names[dev_id] + \" @\" + ip_addresses[dev_id], dev_id) for dev_id in device_list],\n", " description=\"Select Device\",\n", ")\n", "display(connect)" ] }, { "attachments": {}, "cell_type": "markdown", "id": "880d4c02", "metadata": {}, "source": [ "### Connect to Cluster\n", "\n", "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." ] }, { "cell_type": "code", "execution_count": 3, "id": "7ed5db70", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Marketing-Cluster @10.10.200.99 connected\n", "Status: OKAY, Flags: NONE, Slot flags: NONE\n" ] } ], "source": [ "# Connect to device\n", "dev_id = connect.value\n", "# Close the chosen QCodes instrument as to prevent name clash.\n", "\n", "# PlugAndPlay assigns an instrument name with a hyphen. For example 'pulsar-qrm' as a string for instrument name.\n", "# QCodes instrument class cannot handle hyphens, and changes them to underscore '_'.\n", "# However, this happens only once with a warning and never again, so code does not work. This line does the automation for it.\n", "names[dev_id] = names[dev_id].replace(\"-\", \"_\")\n", "\n", "with contextlib.suppress(KeyError):\n", " Instrument.find_instrument(names[dev_id]).close()\n", "\n", "\n", "cluster = Cluster(name=names[dev_id], identifier=ip_addresses[dev_id])\n", "\n", "print(f\"{connect.label} connected\")\n", "print(cluster.get_system_state())" ] }, { "cell_type": "code", "execution_count": 5, "id": "55248959", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Select QCM or QRM module from the available ones:\n" ] }, { "data": { "application/vnd.jupyter.widget-view+json": { "model_id": "7a7a567d31b8429980105f27c5aede0a", "version_major": 2, "version_minor": 0 }, "text/plain": [ "Dropdown(options=([None, None], ['Marketing-Cluster module3 (QCM)', connected\n", "Status: OKAY, Flags: NONE, Slot flags: NONE\n" ] } ], "source": [ "# Connect to the cluster QCM/QRM module\n", "module = select_module.value\n", "print(f\"{module} connected\")\n", "print(cluster.get_system_state())" ] }, { "attachments": {}, "cell_type": "markdown", "id": "1934495b", "metadata": {}, "source": [ "### Reset the Cluster\n", "\n", "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." ] }, { "cell_type": "code", "execution_count": null, "id": "dd9e8d3e", "metadata": {}, "outputs": [], "source": [ "cluster.reset()\n", "print(cluster.get_system_state())" ] }, { "attachments": {}, "cell_type": "markdown", "id": "cef9189f", "metadata": {}, "source": [ "### Setup Sequencer \n", "\n", "\n", "The easiest way to view the influence of the mixer correction is to mix the NCO sin and cos with I and Q values of 1 (fullscale). The instrument output would be simple sinusoids with a 90[deg] phase offset and identical amplitude.\n", "\n", "We use sequencer 0 to set I and Q values of 1 (fullscale) using DC offset and we mix those with the NCO signals." ] }, { "cell_type": "code", "execution_count": 7, "id": "d976eb31", "metadata": { "lines_to_next_cell": 2 }, "outputs": [], "source": [ "# Program sequence we will not use.\n", "sequence = {\"waveforms\": {}, \"weights\": {}, \"acquisitions\": {}, \"program\": \"stop\"}\n", "with open(\"sequence.json\", \"w\", encoding=\"utf-8\") as file:\n", " json.dump(sequence, file, indent=4)\n", " file.close()\n", "module.sequencer0.sequence(sequence)\n", "\n", "# Program fullscale DC offset on I & Q, turn on NCO and enable modulation.\n", "module.sequencer0.offset_awg_path0(1.0)\n", "module.sequencer0.offset_awg_path1(1.0)\n", "module.sequencer0.nco_freq(10e6)\n", "module.sequencer0.mod_en_awg(True)" ] }, { "attachments": {}, "cell_type": "markdown", "id": "43218fe1", "metadata": { "lines_to_next_cell": 2 }, "source": [ "Control sliders\n", "-----\n", "\n", "Create control sliders for the parameters described in the introduction. Each time the value of a parameter is updated, the sequencer is automatically stopped from the embedded firmware for safety reasons and has to be manually restarted.\n", "\n", "The sliders cover the valid parameter range. If the code below is modified to input invalid values, the firmware will not program the values.\n", "\n", "Please connect the I/Q outputs ($\\text{O}^{[1-2]}$) to an oscilloscope and set to trigger continuously on the I channel at 0V. Execute the code below, move the sliders and observe the result on the oscilloscope." ] }, { "cell_type": "code", "execution_count": 8, "id": "e141f69a", "metadata": {}, "outputs": [ { "data": { "application/vnd.jupyter.widget-view+json": { "model_id": "4d28bf10bf604af6a0ba8a728004e1a1", "version_major": 2, "version_minor": 0 }, "text/plain": [ "interactive(children=(FloatSlider(value=0.0, description='Offset I:', max=0.5, min=-0.5, step=0.01), Output())…" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "application/vnd.jupyter.widget-view+json": { "model_id": "ee28dda0e2434c2b8f298b1376bc717f", "version_major": 2, "version_minor": 0 }, "text/plain": [ "interactive(children=(FloatSlider(value=0.0, description='Offset Q:', max=0.5, min=-0.5, step=0.01), Output())…" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "application/vnd.jupyter.widget-view+json": { "model_id": "c949299334fa45c5a5f4319fad5d2092", "version_major": 2, "version_minor": 0 }, "text/plain": [ "interactive(children=(FloatLogSlider(value=1.0, base=2.0, description='Gain ratio:', max=1.0, min=-1.0), Outpu…" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "application/vnd.jupyter.widget-view+json": { "model_id": "4f14de754d4647e8a4acc93c66c215dd", "version_major": 2, "version_minor": 0 }, "text/plain": [ "interactive(children=(FloatSlider(value=0.0, description='Phase offset:', max=45.0, min=-45.0, step=1.0), Outp…" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "text/plain": [ "" ] }, "execution_count": 8, "metadata": {}, "output_type": "execute_result" } ], "source": [ "def set_offset_I(offset_I):\n", " module.out0_offset(offset_I)\n", " module.arm_sequencer(0)\n", " module.start_sequencer(0)\n", "\n", "\n", "def set_offset_Q(offset_Q):\n", " module.out1_offset(offset_Q)\n", " module.arm_sequencer(0)\n", " module.start_sequencer(0)\n", "\n", "\n", "def set_gain_ratio(gain_ratio):\n", " module.sequencer0.mixer_corr_gain_ratio(gain_ratio)\n", " module.arm_sequencer(0)\n", " module.start_sequencer(0)\n", "\n", "\n", "def set_phase_offset(phase_offset):\n", " module.sequencer0.mixer_corr_phase_offset_degree(phase_offset)\n", " module.arm_sequencer(0)\n", " module.start_sequencer(0)\n", "\n", "\n", "I_bounds = module.out0_offset.vals.valid_values\n", "interact(\n", " set_offset_I,\n", " offset_I=widgets.FloatSlider(\n", " min=I_bounds[0], max=I_bounds[1], step=0.01, value=0.0, description=\"Offset I:\"\n", " ),\n", ")\n", "\n", "Q_bounds = module.out1_offset.vals.valid_values\n", "interact(\n", " set_offset_Q,\n", " offset_Q=widgets.FloatSlider(\n", " min=Q_bounds[0], max=Q_bounds[1], step=0.01, value=0.0, description=\"Offset Q:\"\n", " ),\n", ")\n", "\n", "# The gain ratio correction is bounded between 1/2 and 2\n", "interact(\n", " set_gain_ratio,\n", " gain_ratio=widgets.FloatLogSlider(\n", " min=-1, max=1, step=0.1, value=1.0, base=2, description=\"Gain ratio:\"\n", " ),\n", ")\n", "\n", "ph_bounds = module.sequencer0.mixer_corr_phase_offset_degree.vals.valid_values\n", "interact(\n", " set_phase_offset,\n", " phase_offset=widgets.FloatSlider(\n", " min=ph_bounds[0], max=ph_bounds[1], step=1.0, value=0.0, description=\"Phase offset:\"\n", " ),\n", ")" ] }, { "attachments": {}, "cell_type": "markdown", "id": "889722c2", "metadata": {}, "source": [ "When tuning the DC offset you might notice that the signal starts \"clipping\". This is caused by the fact that we are already at full-scale, thus any offset takes our signal out of its dynamic range.\n", "\n", "When this happens, the output LEDs on the module turn orange. This, and other LEDs states, are explained in the [troubleshooting](https://qblox-qblox-instruments.readthedocs-hosted.com/en/master/cluster/troubleshooting.html) guide." ] }, { "attachments": {}, "cell_type": "markdown", "id": "8921a04c", "metadata": {}, "source": [ "### Reset the Cluster\n", "\n", "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." ] }, { "cell_type": "code", "execution_count": 9, "id": "bc86696b", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Status: OKAY, Flags: NONE, Slot flags: NONE\n" ] } ], "source": [ "cluster.reset()\n", "print(cluster.get_system_state())" ] } ], "metadata": { "jupytext": { "formats": "ipynb,py:percent" }, "kernelspec": { "display_name": "Python 3 (ipykernel)", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.9.12" }, "vscode": { "interpreter": { "hash": "065ea129c2b103553c36b444f5acc29b8b9963475a3386776c0b4dd228200cd5" } } }, "nbformat": 4, "nbformat_minor": 5 }