Source code for qblox_instruments.qcodes_drivers.module

# ----------------------------------------------------------------------------
# Description    : QCM/QRM QCoDeS interface
# Git repository : https://gitlab.com/qblox/packages/software/qblox_instruments.git
# Copyright (C) Qblox BV (2020)
# ----------------------------------------------------------------------------


# -- include -----------------------------------------------------------------

import math
import re
from collections.abc import Sequence
from functools import partial
from typing import Any, Callable, Optional, Union

from qcodes import Instrument, InstrumentChannel, Parameter
from qcodes import validators as vals

from qblox_instruments import InstrumentType
from qblox_instruments.docstring_helpers import partial_with_numpy_doc
from qblox_instruments.native.helpers import MultiplesNumbers, check_is_valid_type
from qblox_instruments.qcodes_drivers.io_channel_qsm import IOChannelQSM
from qblox_instruments.qcodes_drivers.io_channel_qtm import IOChannelQTM
from qblox_instruments.qcodes_drivers.io_pulse_channel import IOPulseChannel
from qblox_instruments.qcodes_drivers.quad import Quad
from qblox_instruments.qcodes_drivers.sequencer import Sequencer
from qblox_instruments.types import FrequencyParameter

RE_LO_EN_PARAMETER = re.compile(r"out\d+(_in\d+)?_lo_en")
"""Regex to match parameters that turn on/off local oscillators."""


# -- class -------------------------------------------------------------------
[docs] class Module(InstrumentChannel): """ This class represents a QCM/QRM/QTM/QSM module. It combines all module specific parameters and functions into a single QCoDes InstrumentChannel. """ # ------------------------------------------------------------------------
[docs] def __init__( self, parent: Instrument, name: str, slot_idx: int, ) -> None: """ Creates a QCM/QRM/QTM/QSM module class and adds all relevant parameters for the module. Parameters ---------- parent : Instrument The QCoDeS class to which this module belongs. name : str Name of this module channel slot_idx : int The index of this module in the parent instrument, representing which module is controlled by this class. """ # Initialize instrument channel super().__init__(parent, name) # Store sequencer index self._slot_idx = slot_idx MAX_NUM_LO = 2 MAX_NUM_IN_CHANNELS = 4 MAX_NUM_OUT_CHANNELS = 4 MAX_NUM_MARKERS = 4 for attr_name in Module._get_required_parent_qtm_attr_names(): self._register(attr_name) for attr_name in Module._get_required_parent_qrx_qcm_attr_names( num_lo=MAX_NUM_LO, num_in_channels=MAX_NUM_IN_CHANNELS, num_out_channels=MAX_NUM_OUT_CHANNELS, num_markers=MAX_NUM_MARKERS, ): self._register(attr_name) for attr_name in Module._get_required_parent_qsm_attr_names(): self._register(attr_name) for attr_name in Module._get_required_parent_qrc_attr_names(): self._register(attr_name) # Add required parent attributes for the QCoDeS parameters to function try: self.parent._present_at_init(self.slot_idx) except KeyError: pass else: # CONSTANTS # These are some defaults that apply to QCM modules # TO DO: Refactor add_qcodes_params to make num_seq and num_dio # a constant too NUM_MARKERS = 4 NUM_OUT_CHANNELS = 4 NUM_IN_CHANNELS = 0 NUM_SEQ = 6 NUM_DIO = 0 NUM_SM = 0 if self.is_qrm_type: NUM_OUT_CHANNELS = 2 NUM_IN_CHANNELS = 2 elif self.is_qrc_type: NUM_OUT_CHANNELS = 12 NUM_MARKERS = 1 NUM_IN_CHANNELS = 4 NUM_SEQ = 12 elif self.is_qtm_type: NUM_SEQ = 8 NUM_DIO = 8 elif self.is_qsm_type: NUM_MARKERS = 0 NUM_OUT_CHANNELS = 0 NUM_IN_CHANNELS = 0 NUM_SEQ = 0 NUM_DIO = 0 NUM_SM = 8 # Add QCM/QRM/QTM/QDM/LINQ/QRC QCoDeS parameters if ( self.is_qrm_type or self.is_qcm_type or self.is_qtm_type or self.is_qrc_type or self.is_qsm_type ): add_qcodes_params( self, num_seq=NUM_SEQ, num_dio=NUM_DIO, num_in_channels=NUM_IN_CHANNELS, num_out_channels=NUM_OUT_CHANNELS, num_markers=NUM_MARKERS, num_sm=NUM_SM, ) # Add module QCoDeS parameters self.add_parameter( "present", label="Module present status", docstring="Sets/gets module present status for slot {} in the Cluster.", unit="", vals=vals.Bool(), get_parser=bool, get_cmd=self._get_modules_present, ) self.add_parameter( "connected", label="Module connected status", docstring="Gets module connected status for slot {} in the Cluster.", unit="", vals=vals.Bool(), get_parser=bool, get_cmd=self._get_modules_connected, )
[docs] def snapshot_base( self, update: Optional[bool] = False, params_to_skip_update: Optional[Sequence[str]] = None, ) -> dict[Any, Any]: """ Override the module snapshot method to prevent it from sending unnecessary commands to poll its `connected` and `present` statuses if they've already present in the cache (likely because the cluster's snapshot was requested). If the parameter is not present in the QCoDeS cache, then we bypass the respective getter function and instead use the more optimized methods on the parent cluster. See :meth:`InstrumentBase.snapshot_base` for the function signature. """ if self.parameters["connected"].cache.get(get_if_invalid=False) is None: self.parameters["connected"].cache.set( self.slot_idx in self.parent._get_slots_with_module_connected() ) if self.parameters["present"].cache.get(get_if_invalid=False) is None: self.parameters["present"].cache.set( self.slot_idx in self.parent._get_slots_with_module_present() ) params_to_skip_update = set(params_to_skip_update or []) | {"connected", "present"} return super().snapshot_base(update, params_to_skip_update) # type: ignore
# ------------------------------------------------------------------------ def __repr__(self) -> str: try: if self.is_rf_type and not self.is_qrc_type: suffix = "-RF" elif self.is_eom_type: suffix = "-Pulse" else: suffix = "" return f"<{self.module_type}{suffix}, {self.name}>" except KeyError: # A KeyError may happen if there is no physical module in this slot. return f"<{self.name}>" # ------------------------------------------------------------------------ @property def slot_idx(self) -> int: """ Get slot index. Returns ---------- int Slot index """ return self._slot_idx # ------------------------------------------------------------------------ @property def module_type(self) -> InstrumentType: """ Get module type (e.g. QRM, QCM). Returns ---------- InstrumentType Module type Raises ---------- KeyError Module is not available. """ return self.parent._module_type(self.slot_idx) # ------------------------------------------------------------------------ @property def is_qcm_type(self) -> bool: """ Return if module is of type QCM. Returns ---------- bool True if module is of type QCM. Raises ---------- KeyError Module is not available. """ return self.parent._is_qcm_type(self.slot_idx) # ------------------------------------------------------------------------ @property def is_qrm_type(self) -> bool: """ Return if module is of type QRM. Returns ---------- bool: True if module is of type QRM. Raises ---------- KeyError Module is not available. """ return self.parent._is_qrm_type(self.slot_idx) # ------------------------------------------------------------------------ @property def is_qtm_type(self) -> bool: """ Return if module is of type QTM. Returns ---------- bool: True if module is of type QTM. Raises ---------- KeyError Module is not available. """ return self.parent._is_qtm_type(self.slot_idx) # ------------------------------------------------------------------------ @property def is_qdm_type(self) -> bool: """ Return if module is of type QDM. Returns ---------- bool: True if module is of type QDM. Raises ---------- KeyError Module is not available. """ return self.parent._is_qdm_type(self.slot_idx) # ------------------------------------------------------------------------ @property def is_eom_type(self) -> bool: """ Return if module is of type EOM. Returns ---------- bool: True if module is of type EOM. Raises ---------- KeyError Module is not available. """ return self.parent._is_eom_type(self.slot_idx) # ------------------------------------------------------------------------ @property def is_linq_type(self) -> bool: """ Return if module is of type LINQ. Returns ---------- bool: True if module is of type LINQ. Raises ---------- KeyError Module is not available. """ return self.parent._is_linq_type(self.slot_idx) # ------------------------------------------------------------------------ @property def is_qrc_type(self) -> bool: """ Return if module is of type QRC. Returns ---------- bool: True if module is of type QRC. Raises ---------- KeyError Module is not available. """ return self.parent._is_qrc_type(self.slot_idx) # ------------------------------------------------------------------------ @property def is_qsm_type(self) -> bool: """ Return if module is of type QSM. Returns ---------- bool: True if module is of type QSM. Raises ---------- KeyError Module is not available. """ return self.parent._is_qsm_type(self.slot_idx) # ------------------------------------------------------------------------ @property def is_rf_type(self) -> bool: """ Return if module is of type QCM-RF or QRM-RF. Returns ---------- bool: True if module is of type QCM-RF or QRM-RF. Raises ---------- KeyError Module is not available. """ return self.parent._is_rf_type(self.slot_idx) # ------------------------------------------------------------------------ @property def sequencers(self) -> list: """ Get list of sequencers submodules. Returns ---------- list List of sequencer submodules. """ sequencers_list = [ submodule for submodule in self.submodules.values() if "sequencer" in str(submodule) ] return sequencers_list # ------------------------------------------------------------------------ @property def io_channels(self) -> list: """ Get list of digital I/O channels. Returns ---------- list List of digital I/O channels. """ io_channels_list = [ submodule for submodule in self.submodules.values() if "io_channel" in str(submodule) ] return io_channels_list # ------------------------------------------------------------------------ @property def io_pulse_channels(self) -> list: """ Get list of digital I/O Pulse channels. Returns ---------- list List of digital I/O Pulse channels. """ io_pulse_channels_list = [ submodule for submodule in self.submodules.values() if "io_pulse_channel" in str(submodule) ] return io_pulse_channels_list # ------------------------------------------------------------------------ @property def quads(self) -> list: """ Get list of digital I/O quads. Returns ---------- list List of digital I/O quads. """ quads_list = [ submodule for submodule in self.submodules.values() if "quad" in str(submodule) ] return quads_list # ------------------------------------------------------------------------ @property def is_dummy(self) -> bool: """ Return True if the parent instrument is configured as dummy. Returns ------- bool Whether the parent is a dummy instrument. """ return self.parent.is_dummy # ------------------------------------------------------------------------
[docs] def toggle_all_lo(self, enable: bool) -> None: """ Turn ON or OFF all local oscillators present on this module. Parameters ---------- enable : bool Turn ON if True, OFF if False. Raises ---------- TypeError If called on a non-RF module. """ if not ((self.is_qcm_type or self.is_qrm_type) and self.is_rf_type): raise TypeError( "Toggling local oscillators is only supported for QCM-RF and QRM-RF modules." ) for param_name, param in self.parameters.items(): if RE_LO_EN_PARAMETER.fullmatch(param_name): param.set(enable)
# ------------------------------------------------------------------------
[docs] def set_qsm_outputs_to_zero(self) -> None: """ Resets the output for all channels to zero. Raises ------ NotImplementedError Functionality not available on this module. """ check_is_valid_type(self.is_qsm_type) self._set_qsm_outputs_to_zero(self.slot_idx)
# ------------------------------------------------------------------------
[docs] def set_safe_voltage_range(self, min_voltage: float, max_voltage: float) -> None: """ Set the safe voltage range for all the channels of the current module. Parameters ---------- min_voltage : float The desired minimum voltage in volts. max_voltage : float The desired maximum voltage in volts. """ check_is_valid_type(self.is_qsm_type) for io_channel_idx, _ in enumerate(self.io_channels): self._set_safe_voltage_range(io_channel_idx, min_voltage, max_voltage)
# ------------------------------------------------------------------------ @staticmethod def _get_required_parent_qrx_qcm_attr_names( num_lo: int, num_in_channels: int, num_out_channels: int, num_markers: int ) -> list: """ Return list of parent attributes names that are required for the QCoDeS parameters to function which is common in for QRM/QCM/QRC, so that they can be registered to this object using the _register method. Returns ---------- list List of parent attribute names to register. """ attr_names = [ # Module present attribute "_get_modules_present", "_get_modules_connected", # Channel map attributes "disconnect_outputs", "disconnect_inputs", "_iter_connections", ] # LO attributes for operation in ["set", "get"]: for idx in range(0, num_lo): attr_names += [ f"_{operation}_lo_freq_{idx}", f"_{operation}_lo_pwr_{idx}", f"_{operation}_lo_enable_{idx}", ] attr_names.append("_run_mixer_lo_calib") # Input attributes for operation in ["set", "get"]: for idx in range(0, num_in_channels): attr_names += [ f"_{operation}_in_amp_gain_{idx}", f"_{operation}_in_offset_{idx}", ] for idx in range(0, num_in_channels // 2): attr_names.append(f"_{operation}_in_att_{idx}") # Output attributes for operation in ["set", "get"]: for idx in range(0, num_out_channels): attr_names += [ f"_{operation}_out_amp_offset_{idx}", f"_{operation}_dac_offset_{idx}", ] for idx in range(0, num_out_channels // 2): attr_names += [ f"_{operation}_out_att_{idx}", f"_{operation}_max_out_att_{idx}", ] # Marker attributes for operation in ["set", "get"]: for idx in range(0, num_markers): attr_names.append(f"_{operation}_mrk_inv_en_{idx}") # Scope acquisition attributes for operation in ["set", "get"]: attr_names += [ f"_{operation}_acq_scope_config", f"_{operation}_acq_scope_config_val", f"_{operation}_pre_distortion_config_val", ] attr_names.append("_get_output_latency") # Sequencer program attributes attr_names += [ "get_assembler_status", "get_assembler_log", ] # Sequencer attributes attr_names += Sequencer._get_required_parent_attr_names() return attr_names @staticmethod def _get_required_parent_qrc_attr_names() -> list: attr_names = [ "_set_out_ch_comb_en", "_set_out_mix_dds", "_set_out_out_sel", "_get_out_mix_dds", "_get_out_out_sel", "_set_out_nyquist_filter", "_get_out_nyquist_filter", "_set_out_mixer_filter_bank", "_get_out_mixer_filter_bank", "_print_out_gpio_configuration", "_set_in_amp_iso_fw", "_set_in_ch_splt_en", "_set_in_dsa_1", "_set_in_dsa_2", "_set_in_mix_dds", "_set_in_mix_x2", "_set_in_nyq_sel", "_get_in_amp_iso_fw", "_get_in_ch_splt_en", "_get_in_dsa_1", "_get_in_dsa_2", "_get_in_mix_dds", "_get_in_mix_x2", "_get_in_nyq_sel", "_set_in_nyquist_filter", "_get_in_nyquist_filter", "_print_in_gpio_configuration", "_set_out_lo_frequency", "_set_out_lo_power", "_get_out_lo_power", "_init_out_lo", "_soft_sync_all_out_lo", "_power_down_out_lo_output", "_power_down_out_lo", "_set_out_att", "_get_out_att", "_set_in_att1", "_set_in_att2", "_get_in_att1", "_get_in_att2", "_get_max_out_att", "_set_out_freq", "_get_out_freq", "_set_output_mode", "_get_output_mode", "_set_input_mode", "_get_input_mode", "set_mixer_settings_freq_dac", "set_mixer_settings_coarse_delay_dac", "_get_mixer_settings_freq_adc", "reset_duc_phase_dac", "set_rfdc_nyquist_zone", "set_inv_sync_filter", "set_dac_current", "set_decoder_mode", "_set_out_mixer_mode", "_set_in_freq", "_get_in_freq", "_set_out_amp_ctrl", "_set_out_bpf_all_ctrl", "_set_out_bpf_switch_a_ctrl", "_set_out_bpf_switch_b_ctrl", "_set_out_hpf_ctrl", "_set_out_lpf_ctrl", "_set_out_pwr_ctrl", "_get_out_amp_ctrl", "_get_out_bpf_all_ctrl", "_get_out_bpf_switch_a_ctrl", "_get_out_bpf_switch_b_ctrl", "_get_out_hpf_ctrl", "_get_out_lpf_ctrl", "_get_out_pwr_ctrl", ] return attr_names # ------------------------------------------------------------------------ @staticmethod def _get_required_parent_qtm_attr_names() -> list: """ Return list of parent attributes names that are required for the QCoDeS parameters to function for a QTM, so that they can be registered to this object using the _register method. Returns ---------- list List of parent attribute names to register. """ attr_names = [ # Module present attribute "_get_modules_present", # Channel map attributes "_iter_connections", # Sequencer program attributes "get_assembler_status", "get_assembler_log", # Scope trigger logic "scope_trigger_arm", ] # Sequencer attributes attr_names += Sequencer._get_required_parent_attr_names() attr_names += IOChannelQTM._get_required_parent_attr_names() attr_names += IOPulseChannel._get_required_parent_attr_names() attr_names += Quad._get_required_parent_attr_names() return attr_names # ------------------------------------------------------------------------ @staticmethod def _get_required_parent_qsm_attr_names() -> list: """ Return list of parent attributes names that are required for the QCoDeS parameters to function for a QSM, so that they can be registered to this object using the _register method. Returns ---------- list List of parent attribute names to register. """ attr_names = [ # Module present attribute "_get_modules_present", "set_qsm_outputs_to_zero", "_set_safe_voltage_range", ] # Sequencer attributes attr_names += IOChannelQSM._get_required_parent_attr_names() return attr_names # ------------------------------------------------------------------------ def _register(self, attr_name: str) -> None: """ Register parent attribute to this sequencer using functools.partial to pre-select the slot index. If the attribute does not exist in the parent class, a method that raises a `NotImplementedError` exception is registered instead. The docstring of the parent attribute is also copied to the registered attribute. Parameters ---------- attr_name : str Attribute name of parent to register. """ if hasattr(self.parent, attr_name): parent_attr = getattr(self.parent, attr_name) partial_doc = ( "Note\n" + "----------\n" + "This method calls {1}.{0} using functools.partial to set the " + "slot index. The docstring above is of {1}.{0}:\n\n" ).format(attr_name, type(self.parent).__name__) partial_func = partial_with_numpy_doc(parent_attr, self.slot_idx, end_with=partial_doc) setattr(self, attr_name, partial_func) else: def raise_not_implemented_error(*args, **kwargs) -> None: raise NotImplementedError( f'{self.parent.name} does not have "{attr_name}" attribute.' ) setattr(self, attr_name, raise_not_implemented_error) # ------------------------------------------------------------------------ def _invalidate_qcodes_parameter_cache(self, sequencer: Optional[int] = None) -> None: """ Marks the cache of all QCoDeS parameters in the module, including in any sequencers the module might have, as invalid. Optionally, a sequencer can be specified. This will invalidate the cache of that sequencer only instead of all parameters. Parameters ---------- sequencer : Optional[int] Sequencer index of sequencer for which to invalidate the QCoDeS parameters. """ invalidate_qcodes_parameter_cache(self, sequencer) # ------------------------------------------------------------------------
[docs] def print_readable_snapshot(self, update: bool = False, max_chars: int = 80) -> None: """ Introduce additional spacing in the readable version of the snapshot. """ print() super().print_readable_snapshot(update=update, max_chars=max_chars)
# ------------------------------------------------------------------------ def __getitem__(self, key: str) -> Union[InstrumentChannel, Parameter, Callable[..., Any]]: """ Get sequencer or parameter using string based lookup. Parameters ---------- key : str Sequencer, parameter or function to retrieve. Returns ---------- Union[InstrumentChannel, Parameter, Callable[..., Any]] Sequencer, parameter or function. Raises ---------- KeyError Sequencer, parameter or function does not exist. """ return get_item(self, key)
# -- functions ---------------------------------------------------------------
[docs] def add_qcodes_params( parent: Union[Instrument, Module], num_seq: int, num_dio: int, num_in_channels: int, num_out_channels: int, num_markers: int, num_sm: int, ) -> None: """ Add all QCoDeS parameters for a single QCM/QRM module. Parameters ---------- parent : Union[Instrument, Module] Parent object to which the parameters need to be added. num_seq : int Number of sequencers to add as submodules. num_dio: int Number of DIO units. Applies to QTM. num_in_channels: Number of input channels. Does not apply to QTM since its channels are in/out. num_out_channels: int Number of output channels. Does not apply to QTM since its channels are in/out. num_markers: int Number of markers. num_sm: int Number of QSM channels. Applies to QSM. """ if parent.is_rf_type: num_out = num_out_channels // 2 num_in = num_in_channels // 2 else: num_out = num_out_channels num_in = num_in_channels # -- LO frequencies (RF-modules only) ------------------------------------ if parent.is_rf_type: if parent.is_qrm_type: parent.add_parameter( "out0_in0_lo_freq_cal_type_default", label="Default automatic mixer calibration setting", docstring="Sets/gets the Default automatic mixer" "calibration while setting local oscillator" "frequency for output and input 0.", unit="Hz", vals=vals.Enum("off", "lo only", "lo and sidebands"), set_cmd=None, get_cmd=None, initial_value="off", ) out0_in0_lo_freq = Parameter( "_out0_in0_lo_freq", label="Local oscillator frequency", docstring="Sets/gets the local oscillator frequency for output 0 and input 0.", unit="Hz", vals=vals.Numbers(2e9, 18e9), set_parser=int, get_parser=int, set_cmd=parent._set_lo_freq_1, get_cmd=parent._get_lo_freq_1, ) parent.add_parameter( "out0_in0_lo_freq", parameter_class=FrequencyParameter, source=out0_in0_lo_freq, calibration_function=partial(_calibrate_lo, parent, 1), ) parent.out0_in0_lo_cal = partial(parent._run_mixer_lo_calib, 1) elif parent.is_qcm_type: parent.add_parameter( "out0_lo_freq_cal_type_default", label="Default automatic mixer calibration setting", docstring="Sets/gets the Default automatic mixer" "calibration while setting local oscillator" "frequency for output 0.", unit="Hz", vals=vals.Enum("off", "lo only", "lo and sidebands"), set_cmd=None, get_cmd=None, initial_value="off", ) out0_lo_freq = Parameter( "_out0_lo_freq", label="Output 0 local oscillator frequency", docstring="Sets/gets the local oscillator frequency for output 0.", unit="Hz", vals=vals.Numbers(2e9, 18e9), set_parser=int, get_parser=int, set_cmd=parent._set_lo_freq_0, get_cmd=parent._get_lo_freq_0, ) parent.add_parameter( "out0_lo_freq", parameter_class=FrequencyParameter, source=out0_lo_freq, calibration_function=partial(_calibrate_lo, parent, 0), ) parent.out0_lo_cal = partial(parent._run_mixer_lo_calib, 0) parent.add_parameter( "out1_lo_freq_cal_type_default", label="Default automatic mixer calibration setting", docstring="Sets/gets the Default automatic mixer" "calibration while setting local oscillator" "frequency for output 1.", unit="Hz", vals=vals.Enum("off", "lo only", "lo and sidebands"), set_cmd=None, get_cmd=None, initial_value="off", ) out1_lo_freq = Parameter( "out1_lo_freq", label="Output 1 local oscillator frequency", docstring="Sets/gets the local oscillator frequency for output 1.", unit="Hz", vals=vals.Numbers(2e9, 18e9), set_parser=int, get_parser=int, set_cmd=parent._set_lo_freq_1, get_cmd=parent._get_lo_freq_1, ) parent.add_parameter( "out1_lo_freq", parameter_class=FrequencyParameter, source=out1_lo_freq, calibration_function=partial(_calibrate_lo, parent, 1), ) parent.out1_lo_cal = partial(parent._run_mixer_lo_calib, 1) elif parent.is_qrc_type: def _qrc_set_freq(freq: float, channel: int, only_out: bool) -> None: parent._set_out_freq(channel, round(freq * 1e-6)) if not only_out: parent._set_in_freq(channel, round(freq * 1e-6)) def _qrc_get_freq(channel: float, only_out: bool) -> float: out_freq = parent._get_out_freq(channel) * 10**6 if not only_out: in_freq = parent._get_in_freq(channel) * 10**6 if out_freq != in_freq: raise RuntimeError( f"Error while retrieving QRC frequency. " f"Module '{parent}' channel '{channel}' has " f"{out_freq} Hz output frequency and {in_freq} Hz " f"input frequency, which is not allowed." ) return out_freq # For QRC the output mixer for which frequency needs to be set is shared by # input 0 and output 0, and # input 1 and output 1; # the rest of the frequency settings only control output frequencies. for channel in range(0, 2): parent.add_parameter( f"out{channel}_in{channel}_freq", label=f"Output {channel} and input{channel} frequency", docstring=(f"Sets/gets output {channel} and input {channel} frequency."), unit="Hz", vals=MultiplesNumbers(10**6, min_value=0.5e9, max_value=9.6e9), set_cmd=partial(_qrc_set_freq, channel=channel, only_out=False), get_cmd=partial(_qrc_get_freq, channel=channel, only_out=False), ) for channel in range(2, num_out): parent.add_parameter( f"out{channel}_freq", label=f"Output {channel} frequency", docstring=(f"Sets/gets output {channel} frequency."), unit="Hz", vals=MultiplesNumbers(10**6, min_value=0.5e9, max_value=9.6e9), set_cmd=partial(_qrc_set_freq, channel=channel, only_out=True), get_cmd=partial(_qrc_get_freq, channel=channel, only_out=True), ) # -- LO enables (RF-modules only) ---------------------------------------- if parent.is_rf_type: if parent.is_qrm_type: parent.add_parameter( "out0_in0_lo_en", label="Local oscillator enable", docstring="Sets/gets the local oscillator enable for output 0 and input 0.", vals=vals.Bool(), set_parser=bool, get_parser=bool, set_cmd=parent._set_lo_enable_1, get_cmd=parent._get_lo_enable_1, ) elif parent.is_qcm_type: for i, set_lo_enable, get_lo_enable in zip( range(num_out), [f"_set_lo_enable_{n}" for n in range(num_out)], [f"_get_lo_enable_{n}" for n in range(num_out)], ): parent.add_parameter( f"out{i}_lo_en", label=f"Output {i} local oscillator enable", docstring="Sets/gets the local oscillator enable for output {i}.", vals=vals.Bool(), set_parser=bool, get_parser=bool, set_cmd=getattr(parent, set_lo_enable), get_cmd=getattr(parent, get_lo_enable), ) # -- Attenuation settings (RF-modules only) ------------------------------ if parent.is_rf_type: if parent.is_qrm_type: parent.add_parameter( "in0_att", label="Input 0 attenuation", docstring=( "Sets/gets input attenuation in a range of 0dB to 30dB with a resolution " "of 2dB per step." ), unit="dB", vals=vals.Multiples(2, min_value=0, max_value=30), set_parser=int, get_parser=int, set_cmd=parent._set_in_att_0, get_cmd=parent._get_in_att_0, ) if parent.is_qcm_type or parent.is_qrm_type: for x in range(0, num_out): max_att = getattr(parent, f"_get_max_out_att_{x}")() parent.add_parameter( f"out{x}_att", label=f"Output {x} attenuation", docstring="Sets/gets output attenuation in a range of 0 dB to " f"{max_att} dB with a resolution of 2dB per step.", unit="dB", vals=vals.Multiples( 2, min_value=0, max_value=max_att, ), set_parser=int, get_parser=int, set_cmd=getattr(parent, f"_set_out_att_{x}"), get_cmd=getattr(parent, f"_get_out_att_{x}"), ) elif parent.is_qrc_type: # Getters are currently not implemented for QRC attenuation. # Indexing of the channels start from 1 instead of 0 # for QRC output attenuation on the SCPI layer. for channel in range(0, num_out): parent.add_parameter( f"out{channel}_att", label=f"Output {channel} attenuation", docstring=( f"Sets/gets output attenuation in steps of 0.5 dB " f"from 0 dB to {parent._get_max_out_att(channel)}." ), unit="dB", vals=vals.Numbers( min_value=0.0, max_value=parent._get_max_out_att(channel), ), set_parser=float, get_parser=float, set_cmd=partial(parent._set_out_att, channel), get_cmd=partial(parent._get_out_att, channel), ) # -- Input gain (AWG baseband modules only) ------------------------------ if not parent.is_rf_type: for i, set_in_amp_gain, get_in_amp_gain in zip( range(num_in), [f"_set_in_amp_gain_{n}" for n in range(num_in)], [f"_get_in_amp_gain_{n}" for n in range(num_in)], ): parent.add_parameter( f"in{i}_gain", label=f"Input {i} gain", docstring=( f"Sets/gets input {i} gain in a range of -6dB to 26dB with a resolution " f"of 1dB per step." ), unit="dB", vals=vals.Numbers(-6, 26), set_parser=int, get_parser=int, set_cmd=getattr(parent, set_in_amp_gain), get_cmd=getattr(parent, get_in_amp_gain), ) # -- Input offset (AWG modules only) ------------------------------ if parent.is_qrm_type: for i, set_in_offset, get_in_offset in zip( range(num_in_channels), [f"_set_in_offset_{n}" for n in range(num_in_channels)], [f"_get_in_offset_{n}" for n in range(num_in_channels)], ): if parent.is_rf_type: parent.add_parameter( f"in{i // 2}_offset_path{i % 2}", label=f"Input 0 offset for path {i}", docstring="Sets/gets input 0 offset for path 0 in a range of -0.09V to 0.09V", unit="V", vals=vals.Numbers(-0.09, 0.09), set_parser=float, get_parser=float, set_cmd=getattr(parent, set_in_offset), get_cmd=getattr(parent, get_in_offset), ) else: parent.add_parameter( f"in{i}_offset", label=f"Input {i} offset", docstring=f"Sets/gets input {i} offset in a range of -0.09V to 0.09V", unit="V", vals=vals.Numbers(-0.09, 0.09), set_parser=float, get_parser=float, set_cmd=getattr(parent, set_in_offset), get_cmd=getattr(parent, get_in_offset), ) # -- Output offsets (All modules) ---------------------------------------- if parent.is_rf_type and not parent.is_qrc_type: for i, set_out_amp_offset, get_out_amp_offset in zip( range(num_out_channels), [f"_set_out_amp_offset_{n}" for n in range(num_out_channels)], [f"_get_out_amp_offset_{n}" for n in range(num_out_channels)], ): out = i // 2 path = i % 2 parent.add_parameter( f"out{out}_offset_path{path}", label=f"Output {out} offset for path {path}", docstring=f"Sets/gets output 0 offset for path {path}.", unit="mV", vals=vals.Numbers(-84.0, 73.0), set_parser=float, get_parser=float, set_cmd=getattr(parent, set_out_amp_offset), get_cmd=getattr(parent, get_out_amp_offset), ) elif parent.is_qrm_type or parent.is_qcm_type: for i, set_dac_offset, get_dac_offset in zip( range(num_out_channels), [f"_set_dac_offset_{n}" for n in range(num_out_channels)], [f"_get_dac_offset_{n}" for n in range(num_out_channels)], ): parent.add_parameter( f"out{i}_offset", label=f"Output {i} offset", docstring=f"Sets/gets output {i} offset", unit="V", vals=(vals.Numbers(-2.5, 2.5) if parent.is_qcm_type else vals.Numbers(-0.5, 0.5)), set_parser=float, get_parser=float, set_cmd=getattr(parent, set_dac_offset), get_cmd=getattr(parent, get_dac_offset), ) # -- Scope acquisition settings (QRM modules only) ----------------------- if parent.is_qrm_type or parent.is_qrc_type: for x in range(0, num_in_channels): parent.add_parameter( f"scope_acq_trigger_mode_path{x}", label=f"Scope acquisition trigger mode for input path {x}", docstring=( f"Sets/gets scope acquisition trigger mode for input path {x} " f"('sequencer' = triggered by sequencer, 'level' = triggered by input level)." ), unit="", vals=vals.Bool(), val_mapping={"level": True, "sequencer": False}, set_parser=bool, get_parser=bool, set_cmd=partial(parent._set_acq_scope_config_val, ["trig", "mode_path", x]), get_cmd=partial(parent._get_acq_scope_config_val, ["trig", "mode_path", x]), ) parent.add_parameter( f"scope_acq_trigger_level_path{x}", label=f"Scope acquisition trigger level for input path {x}", docstring=( f"Sets/gets scope acquisition trigger level when using input level " f"trigger mode for input path {x}." ), unit="", vals=vals.Numbers(-1.0, 1.0), set_parser=float, get_parser=float, set_cmd=partial(parent._set_acq_scope_config_val, ["trig", "lvl_path", x]), get_cmd=partial(parent._get_acq_scope_config_val, ["trig", "lvl_path", x]), ) parent.add_parameter( f"scope_acq_avg_mode_en_path{x}", label=f"Scope acquisition averaging mode enable for input path {x}", docstring=f"Sets/gets scope acquisition averaging mode enable for input path {x}.", unit="", vals=vals.Bool(), set_parser=bool, get_parser=bool, set_cmd=partial(parent._set_acq_scope_config_val, ["avg_en_path", x]), get_cmd=partial(parent._get_acq_scope_config_val, ["avg_en_path", x]), ) def get_scope_sequencer() -> None: sel_path = parent._get_acq_scope_config_val("sel_path") if any(sel_path[0] != v for v in sel_path): raise ValueError(f"Module '{parent}' has invalid sel_path '{sel_path}'.") return sel_path[0] def set_scope_sequencer(seq: int) -> None: for ch in range(num_in_channels): parent._set_acq_scope_config_val(["sel_path", ch], seq) parent.add_parameter( name="scope_acq_sequencer_select", label="Scope acquisition sequencer select", docstring="Sets/gets sequencer select that specifies which " "sequencer triggers the scope acquisition when using " "sequencer trigger mode.", unit="", vals=vals.Numbers(0, num_seq - 1), set_parser=int, get_parser=int, get_cmd=get_scope_sequencer, set_cmd=set_scope_sequencer, ) # -- Marker settings (All modules, only 2 markers for RF modules) -------- if parent.is_qcm_type or parent.is_qrm_type: num_markers_inv_en = 2 if parent.is_rf_type else num_markers for x in range(num_markers_inv_en): parent.add_parameter( f"marker{x}_inv_en", label=f"Output {x} marker invert enable", docstring=f"Sets/gets output {x} marker invert enable", unit="", vals=vals.Bool(), set_parser=bool, get_parser=bool, set_cmd=getattr(parent, f"_set_mrk_inv_en_{x}"), get_cmd=getattr(parent, f"_get_mrk_inv_en_{x}"), ) # -- Pre-distortion configuration settings # Only QCMs and QRMs have predistortions for now if parent.is_qcm_type or parent.is_qrm_type or parent.is_qrc_type: _add_rtp_qcodes_params(parent, num_out=num_out, num_markers=num_markers) # Add sequencers if parent.is_qcm_type or parent.is_qrm_type or parent.is_qtm_type or parent.is_qrc_type: for seq_idx in range(0, num_seq): seq = Sequencer(parent, f"sequencer{seq_idx}", seq_idx) parent.add_submodule(f"sequencer{seq_idx}", seq) if num_sm != 0 and num_dio != 0: # "io_channel" is used for both QTM io channels and QSM io channels. raise ValueError( f"Module '{parent}' has both QTM and QSM io channels, which is not allowed." ) # Add sm-related components for sm_idx in range(0, num_sm): io_channel = IOChannelQSM(parent, f"io_channel{sm_idx}", sm_idx) parent.add_submodule(f"io_channel{sm_idx}", io_channel) # Add dio-related components for dio_idx in range(0, num_dio): io_channel = IOChannelQTM(parent, f"io_channel{dio_idx}", dio_idx) parent.add_submodule(f"io_channel{dio_idx}", io_channel) for quad_idx in range(0, math.ceil(num_dio / 4)): quad = Quad(parent, f"quad{quad_idx}", quad_idx) parent.add_submodule(f"quad{quad_idx}", quad) # Add QTM-Pulse components if parent.is_eom_type: # For now QTM pulse only have 1 output io_pulse_channel = IOPulseChannel(parent, "io_pulse_channel0", 0) parent.add_submodule("io_pulse_channel", io_pulse_channel)
# ----------------------------------------------------------------------------
[docs] def invalidate_qcodes_parameter_cache( parent: Union[Instrument, Module], sequencer: Optional[int] = None, quad: Optional[int] = None, io_channel: Optional[int] = None, io_pulse_channel: Optional[int] = None, ) -> None: """ Marks the cache of all QCoDeS parameters in the module as invalid, including in any sequencer submodules the module might have. Optionally, a sequencer can be specified. This will invalidate the cache of that sequencer only instead of all parameters. Parameters ---------- parent : Union[Instrument, Module] The parent module object for which to invalidate the QCoDeS parameters. sequencer : Optional[int] The sequencer index for which to invalidate the QCoDeS parameters. quad : Optional[int] The quad index for which to invalidate the QCoDeS parameters. io_channel : Optional[int] The IO channel index for which to invalidate the QCoDeS parameters. io_pulse_channel : Optional[int] The IO pulse channel index for which to invalidate the QCoDeS parameters. """ # Invalidate module parameters if sequencer is None: for param in parent.parameters.values(): param.cache.invalidate() sequencer_list = parent.sequencers else: sequencer_list = [parent.sequencers[sequencer]] quad_list = parent.quads if quad is None else [parent.quads[quad]] io_channel_list = parent.io_channels if io_channel is None else [parent.io_channels[io_channel]] if io_pulse_channel is None: io_pulse_channel_list = parent.io_pulse_channels else: io_pulse_channel_list = [parent.io_pulse_channels[io_pulse_channel]] # Invalidate sequencer parameters for seq in sequencer_list: seq._invalidate_qcodes_parameter_cache() for q in quad_list: q._invalidate_qcodes_parameter_cache() for io_ch in io_channel_list: io_ch._invalidate_qcodes_parameter_cache() for io_pulse_ch in io_pulse_channel_list: io_pulse_ch._invalidate_qcodes_parameter_cache()
# ----------------------------------------------------------------------------
[docs] def get_item( parent: Union[Instrument, Module], key: str ) -> Union[InstrumentChannel, Parameter, Callable[[Any], Any]]: """ Get submodule or parameter using string based lookup. Parameters ---------- parent : Union[Instrument, Module] Parent module object to search. key : str submodule, parameter or function to retrieve. Returns ---------- Union[InstrumentChannel, Parameter, Callable[[Any], Any]] Submodule, parameter or function. Raises ---------- KeyError Submodule, parameter or function does not exist. """ # Check for submodule try: return parent.submodules[key] except KeyError: try: return parent.parameters[key] except KeyError: return parent.functions[key]
# ---------------------------------------------------------------------------- def _add_rtp_qcodes_params(parent: Union[Instrument, Module], num_out, num_markers) -> None: NUM_IIR = 4 if not parent.is_qcm_type and not parent.is_qrm_type and not parent.is_qrc_type: raise TypeError("RTP parameters can only be declared for QRC, QRM and QCM modules.") predistortion_val_mapping_filter = { "bypassed": "disabled", "delay_comp": "enabled", } predist_mapping_docstring = ( "If 'bypassed', the filter is disabled.\n" "If 'delay_comp', the filter is bypassed, but the output is delayed as if it were applied." ) def add_distortion_parameters(output) -> None: parent.add_parameter( f"out{output}_fir_coeffs", label=f"Coefficients for the FIR filter for output {output}", docstring=f"Sets/gets the coefficients for the FIR filter for output {output}", unit="", vals=vals.Sequence(elt_validator=vals.Numbers(-2, 1.99), length=32), set_cmd=partial( parent._set_pre_distortion_config_val, [f"out{output}", "FIR", "stage0"], ), get_cmd=partial( parent._get_pre_distortion_config_val, [f"out{output}", "FIR", "stage0"], ), ) for i in range(NUM_IIR): parent.add_parameter( f"out{output}_exp{i}_time_constant", label=f"Time constant of the exponential overshoot filter {i} for output {output}", docstring=( f"Sets/gets the time constant of the exponential overshoot filter {i} " f"for output {output}" ), unit="", vals=vals.Numbers(6, float("inf")), set_parser=float, get_parser=float, set_cmd=partial( parent._set_pre_distortion_config_val, [f"out{output}", "IIR", f"stage{i}", "tau"], ), get_cmd=partial( parent._get_pre_distortion_config_val, [f"out{output}", "IIR", f"stage{i}", "tau"], ), ) parent.add_parameter( f"out{output}_exp{i}_amplitude", label=f"Amplitude of the exponential overshoot filter {i} for output {output}", docstring=( f"Sets/gets the amplitude of the exponential overshoot filter {i} " f"for output {output}" ), unit="", vals=vals.Numbers(-1, 1), set_parser=float, get_parser=float, set_cmd=partial( parent._set_pre_distortion_config_val, [f"out{output}", "IIR", f"stage{i}", "amp"], ), get_cmd=partial( parent._get_pre_distortion_config_val, [f"out{output}", "IIR", f"stage{i}", "amp"], ), ) def add_output_parameters(output) -> None: parent.add_parameter( f"out{output}_latency", label=f"Gets the latency in output path {output}", docstring=( f"Gets the latency in output path {output}.\n" "The output path can change depending on the filter configuration of the output." ), unit="s", set_cmd=False, get_cmd=partial( parent._get_output_latency, 2 * output if parent.is_rf_type else output, ), ) if parent.is_rf_type: parent.add_parameter( f"out{output}_fir_config", label=f"Configuration of FIR filter for output {output}", docstring=( f"Sets/gets the configuration of FIR filter for output {output}." f"\n{predist_mapping_docstring}" ), unit="", val_mapping=predistortion_val_mapping_filter, set_cmd=partial( lambda output, val: parent.parent._set_pre_distortion_config( parent.slot_idx, { f"out{2 * output}": {"state": {"stage5": val}}, f"out{2 * output + 1}": {"state": {"stage5": val}}, }, ), output, ), get_cmd=partial( parent._get_pre_distortion_config_val, [f"out{output}", "state", "stage5"], ), ) for i in range(NUM_IIR): parent.add_parameter( f"out{output}_exp{i}_config", label=f"Configuration of exponential overshoot filter {i} for output {output}", docstring=( f"Sets/gets configuration of exponential overshoot filter {i} " f"for output {output}.\n{predist_mapping_docstring}" ), unit="", val_mapping=predistortion_val_mapping_filter, set_cmd=partial( lambda output, val, stage_idx=i + 1: parent.parent._set_pre_distortion_config( parent.slot_idx, { f"out{2 * output}": {"state": {f"stage{stage_idx}": val}}, f"out{2 * output + 1}": {"state": {f"stage{stage_idx}": val}}, }, ), output, ), get_cmd=partial( parent._get_pre_distortion_config_val, [f"out{2 * output}", "state", f"stage{i + 1}"], ), ) else: parent.add_parameter( f"out{output}_fir_config", label=f"Configuration of FIR filter for output {output}", docstring=( f"Sets/gets the configuration of FIR filter for output {output}.\n" f"{predist_mapping_docstring}" ), unit="", val_mapping=predistortion_val_mapping_filter, set_cmd=partial( parent._set_pre_distortion_config_val, [f"out{output}", "state", "stage5"], ), get_cmd=partial( parent._get_pre_distortion_config_val, [f"out{output}", "state", "stage5"], ), ) for i in range(NUM_IIR): parent.add_parameter( f"out{output}_exp{i}_config", label=f"Configuration of exponential overshoot filter {i} for output {output}", docstring=( f"Sets/gets configuration of exponential overshoot filter {i} " f"for output {output}.\n{predist_mapping_docstring}" ), unit="", val_mapping=predistortion_val_mapping_filter, set_cmd=partial( parent._set_pre_distortion_config_val, [f"out{output}", "state", f"stage{i + 1}"], ), get_cmd=partial( parent._get_pre_distortion_config_val, [f"out{output}", "state", f"stage{i + 1}"], ), ) def add_marker_parameters(x) -> None: parent.add_parameter( f"marker{x}_fir_config", label=f"Delay compensation config for the FIR filter on marker {x}", docstring=( f"Delay compensation config for the FIR filter on marker {x}. If 'bypassed', " f"the marker is not delayed. If 'enabled', the marker is delayed." ), unit="", val_mapping=predistortion_val_mapping_marker, set_cmd=partial( parent._set_pre_distortion_config_val, [f"out{x}", "markers", "state", "stage5"], ), get_cmd=partial( parent._get_pre_distortion_config_val, [f"out{x}", "markers", "state", "stage5"], ), ) for i in range(NUM_IIR): parent.add_parameter( f"marker{x}_exp{i}_config", label=( f"Delay compensation config for the exponential overshoot filter {i} " f"on marker {x}" ), docstring=( f"Delay compensation config for the exponential overshoot filter {i} " f"on marker {x}. If 'bypassed', the marker is not delayed. If 'enabled', " f"the marker is delayed." ), unit="", val_mapping=predistortion_val_mapping_marker, set_cmd=partial( parent._set_pre_distortion_config_val, [f"out{x}", "markers", "state", f"stage{i + 1}"], ), get_cmd=partial( parent._get_pre_distortion_config_val, [f"out{x}", "markers", "state", f"stage{i + 1}"], ), ) if not parent.is_rf_type: if parent.is_qcm_type: predist_mapping_docstring += "\nIf 'enabled', the filter is enabled." predistortion_val_mapping_filter["enabled"] = "enabled" predistortion_val_mapping_filter["delay_comp"] = "comp_delay" for output in range(num_out): add_output_parameters(output) if parent.is_qcm_type: add_distortion_parameters(output) else: for output in range(num_out): add_output_parameters(output) predistortion_val_mapping_marker = { "bypassed": "disabled", "delay_comp": "enabled", } for x in range(num_markers): add_marker_parameters(x) # ---------------------------------------------------------------------------- def _calibrate_lo( parent: Union[Instrument, Module], output: int, cal_type: Optional[str] = None, ) -> None: """ Calibrate the mixer according to the calibration type. Parameters ---------- parent : Union[Instrument, Module] Parent module object to search. output : str Output of the module. cal_type : Optional[str] Automatic mixer calibration to perform after setting the frequency. Can be one of 'off', 'lo only' or 'lo and sidebands'. Raises ---------- ValueError cal_type is not one of 'off', 'lo only' or 'lo and sidebands'. """ if cal_type is None: if parent.is_qrm_type: cal_type = parent.out0_in0_lo_freq_cal_type_default() else: cal_type = parent.parameters[f"out{output}_lo_freq_cal_type_default"]() if cal_type == "lo only": parent._run_mixer_lo_calib(output) return elif cal_type == "lo and sidebands": if parent.is_qrm_type: connected_sequencers = [ sequencer for sequencer in parent.sequencers if sequencer.parameters["connect_out0"]() == "IQ" ] else: connected_sequencers = [ sequencer for sequencer in parent.sequencers if ( sequencer.parameters[f"connect_out{output}"]() == "IQ" and sequencer.parameters[f"connect_out{(output + 1) % 2}"]() == "off" ) ] parent._run_mixer_lo_calib(output) for sequencer in connected_sequencers: sequencer.sideband_cal() return if cal_type != "off": raise ValueError("cal_type must be one of 'off', 'lo only' or 'lo and sidebands'.")