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
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.qcodes_drivers.io_channel import IOChannel
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


# -- class -------------------------------------------------------------------
[docs] class Module(InstrumentChannel): """ This class represents a QCM/QRM/QTM 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 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_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 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 elif self.is_qtm_type: NUM_SEQ = 8 NUM_DIO = 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: 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, ) # 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, )
# ------------------------------------------------------------------------ @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. Parameters ---------- 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_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. Parameters ---------- Returns ---------- list List of digital I/O Pulse channels. Raises ---------- """ 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 self.parent.is_dummy # ------------------------------------------------------------------------ @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 def _get_required_parent_qrc_attr_names() -> list: attr_names = [ "_get_bsp_status", "_set_nyquist_filter", "_enable_mixer_path", "_enable_mixer_doubler", "_select_mixer_filter_bank_all", "_select_mixer_filter_bank", "_set_output_configuration", "_enable_channel_combine", "_set_lo_power", "_set_lo_frequency", "_software_sync_all_pll", "_set_dsa_1", "_get_out_att", "_set_out_att", "_get_max_out_att", "set_mixer_settings_freq_dac", "set_mixer_settings_coarse_delay_dac", "reset_duc_phase_dac", "set_rfdc_nyquist_zone", "set_inv_sync_filter", "set_dac_current", "set_decoder_mode", "config_lmx", "_set_out_freq", "_print_gpio_configuration", ] 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 the 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 += IOChannel._get_required_parent_attr_names() attr_names += IOPulseChannel._get_required_parent_attr_names() attr_names += Quad._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 in stead of all parameters. Parameters ---------- sequencer : Optional[int] Sequencer index of sequencer for which to invalidate the QCoDeS parameters. """ invalidate_qcodes_parameter_cache(self, sequencer) # ------------------------------------------------------------------------ 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, ) -> 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. """ 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: # Indexing of the channels start from 1 instead of 0 # for QRC output frequency on the SCPI layer. def qrc_set_out_freq(channel: int, freq: float) -> None: parent._set_out_freq(channel + 1, round(freq * 10**-6)) # 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="Output 0 and input 0 frequency", docstring=("Sets/gets output 0 and input 0 frequency."), initial_cache_value=0.0, unit="Hz", vals=vals.Numbers(), set_cmd=partial(qrc_set_out_freq, channel), ) 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."), initial_cache_value=0.0, unit="Hz", vals=vals.Numbers(), set_cmd=partial(qrc_set_out_freq, channel), ) # -- 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 + 1)}." ), unit="dB", vals=vals.Numbers( min_value=0.0, max_value=parent._get_max_out_att(channel + 1), ), set_parser=float, get_parser=float, set_cmd=partial(parent._set_out_att, channel + 1), get_cmd=partial(parent._get_out_att, channel + 1), ) # -- 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 or parent.is_qrc_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]), ) parent.add_parameter( "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, set_cmd=partial(parent._set_acq_scope_config_val, "sel_acq"), get_cmd=partial(parent._get_acq_scope_config_val, "sel_acq"), ) # -- 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 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) # Add dio-related components for dio_idx in range(0, num_dio): io_channel = IOChannel(parent, f"io_channel{dio_idx}", dio_idx) parent.add_submodule(f"io_channel{dio_idx}", io_channel) for quad_idx in range(0, int(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 in stead 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. """ # 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'.")