Skip to content

Channels

In the QuAM library, channels are a fundamental concept that represent the physical connections to the quantum hardware. They are defined in the quam.components.channels module.

We distinguish between the following channel types, where the terms "output" and "input" are always from the perspective of the OPX hardware:

1. Analog output channels

2. Analog output + input channels

3. Digital channels

Each analog Channel corresponds to an element in QUA, whereas the digital channel is part of an analog channel.

These channel combinations cover most use cases, although there are exceptions (input-only channels and single-output, IQ-input channels) which will be implemented in a subsequent QuAM release. If you need such channels, please create a Github issue.

Analog Output Channels

Analog output channels are the primary means of controlling the quantum hardware. They can be used to send various types of signals, such as microwave or RF signals, to control the quantum system. The two types of analog output channels are the SingleChannel and the IQChannel.

Analog Channel Ports

A SingleChannel is always attached to a single OPX output port, and similarly an IQChannel has an associated pair of IQ ports:

from quam.components import SingleChannel, IQChannel

single_channel = SingleChannel(
    opx_output=("con1", 1),
    ...
)
IQ_channel = IQChannel(
    opx_output_I=("con1", 2),
    opx_output_Q=("con1", 3),
    ...
)

DC Offset

Each analog channel can have a specified DC offset that remains for the duration of the QUA program. This can be set through SingleChannel.opx_output_offset for the SingleChannel, and through IQChannel.opx_output_offset_I and IQChannel.opx_output_offset_Q for the IQChannel.

Note that if multiple channels are attached to the same OPX output port(s), they may not have different output offsets. This raises a warning and chooses the DC offset of the last channel.

The DC offset can also be modified while a QUA program is running:

from qm.qua import program

with program() as prog:
    single_channel.set_dc_offset(offset=0.1)
    IQ_channel.set_dc_offset(offset=0.25, element_input="I")  # Set offset of port I
The offsets can also be QUA variables. Channel.set_dc_offset() is a light wrapper around qm.qua.set_dc_offset to attach it to the channel.

Frequency Converters

The IQChannel is usually connected to a mixer to upconvert the signal using a local oscillator. This frequency upconversion is represented in QuAM by a FrequencyConverter

from quam.components.hardware import FrequencyConverter, LocalOscillator, Mixer

IQ_channel = IQChannel(
    opx_output_I=("con1", 2),
    opx_output_Q=("con1", 3),
    intermediate_frequency=100e6,  # Hz
    frequency_converter=FrequencyConverter(
        local_oscillator=LocalOscillator(frequency=6e9, power=10),
        mixer=Mixer(),
    )
)

Integrated frequency conversion systems such as QM's Octave usually have additional features such as auto-calibration. For this reason they have a specialized frequency converter such as the OctaveUpConverter. See the QuAM Octave Documentation documentation for details.

Analog Pulses

QuAM has a range of standard Pulse components in quam.components.pulses. These pulses can be registered as part of the analog channel via Channel.operations such that the channel can output the associated pulse waveforms:

from quam.components import pulses

channel.operations["X180"] = pulses.SquarePulse(
    amplitude=0.1,  # V
    length=16,  # ns
)

Once a pulse has been registered in a channel, it can be played within a QUA program:

with program() as prog:
    channel.play("X180")
Channel.play() is a light wrapper around qm.qua.play() to attach it to the channel.

Details on pulses in QuAM can be found at the Pulses Documentation.

Analog Output + Input Channels

Aside from sending signals to the quantum hardware, data is usually also received back, and subsequently read out through the hardware's input ports. In QuAM, this is represented using the InOutSingleChannel and the InOutIQChannel. These channels don't only have associated output port(s) but also input port(s):

from quam.components import InOutSingleChannel, InOutIQChannel

single_io_channel = InOutSingleChannel(
    opx_output=("con1", 1),
    opx_input=("con1", 1)
    ...
)
IQ_io_channel = InOutIQChannel(
    opx_output_I=("con1", 2),
    opx_output_Q=("con1", 3),
    opx_input_I=("con1", 1),
    opx_input_Q=("con1", 2)
    ...
)

These are extensions of the SingleChannel and the IQChannel that add relevant features for readout.

Both the InOutSingleChannel and the InOutIQChannel combine output + input as in most cases a signal is also sent to probe the quantum hardware. Support for input-only analog channels is planned for a future release.

Readout Pulses

Channels that have input ports can also have readout pulses:

from quam.components import pulses
io_channel.operations["readout"] = pulses.SquareReadoutPulse(
    length=16,  # ns
    amplitude=0.1,  # V
    integration_weights_angle=0.0,  # rad, optional rotation of readout signal
)
As can be seen, the readout pulse (in this case SquareReadoutPulse) is similar to the regular pulses, but with additional parameters for readout. Specifically, it contains the attributes integration_weights_angle and integration_weights to specify how the readout signal should be integrated.

Digital Channels

QuAM supports digital output channels (output from the OPX perspective) through the component DigitalOutputChannel. These can be added to any analog channel through the attribute Channel.digital_outputs. As an example:

from quam.components import SingleChannel, DigitalOutputChannel

analog_channel = SingleChannel(
    opx_output=("con1", 1),
    digital_outputs={
        "dig_out1": DigitalOutputChannel(opx_output=("con1", 1))
    }
)
The docstring of DigitalOutputChannel describes all the available properties.

Multiple digital outputs can be attached to the same analog channel:

analog_channel.digital_outputs = {
    "dig_out1": DigitalOutputChannel(opx_output=("con1", 1)),
    "dig_out2": DigitalOutputChannel(opx_output=("con1", 2)),
}
In this case, any digital pulses will be played to all digital channels.

Digital-only Channel

It is also possible to create a digital-only channel, i.e. using digital ports without any analog ports.

from quam.components import Channel, DigitalOutputChannel
channel = Channel(
    id="channel",
    digital_outputs={"1": DigitalOutputChannel(opx_output=("con1", 1))},
)

Digital Pulses

Once a DigitalOutputChannel is added to a Channel, digital waveforms can be played on it. This is done by attaching a digital waveform to a Pulse through the attribute Pulse.digital_marker:

from quam.components import pulses

pulse = pulses.SquarePulse(
    length=80,
    amplitude=0.2,
    digital_marker=[(1, 20), (0, 20), (1, 40)]
)
In the example above, the square pulse will also output digital waveform: "high" for 20 ns ⇨ "low" for 20 ns ⇨ "high" for 40 ns. This digital waveform will be played on all digital channels that are attached to the analog channel.

Digital-only Pulses

A digital pulse can also be played without a corresponding analog pulse. This can be done by directly using the base pulses.Pulse class:

channel.operations["digital"] = pulses.Pulse(length=100, digital_marker=[(1, 20, 0, 10)])

Sticky channels

A channel can be set to be sticky, meaning that the voltage after a pulse will remain at the last value of the pulse. Details can be found in the Sticky channel QUA documentation. Any channel can be made sticky by adding the channels.StickyChannelAddon to it:

from quam.components.channels import StickyChannelAddon

channel.sticky = StickyChannelAddon(duration=...)

Time Tagging

Time tagging is a feature that allows for the measurement of the time of arrival of a signal. It is implemented as the TimeTaggingAddon to the InSingleChannel.

To use the time tagging feature, the TimeTaggingAddon must be added to the InSingleChannel:

from quam.components.channels import InSingleChannel, TimeTaggingAddon

channel = InSingleChannel(
    id="channel",
    opx_input=("con1", 1),
    time_tagging=TimeTaggingAddon(
        signal_threshold=0.195,  # in units of V
        signal_polarity="below",
        derivative_threshold=0.073,  # in units of V/ns
        derivative_polarity="below",
    )
)
All parameters are optional, and are by default set to the values shown above.

Once the time tagging addon is added, the InSingleChannel.measure_time_tagging() method can be used within a QUA program to measure the time of arrival of the signal:

times, counts = channel.measure_time_tagging(size=1000, max_duration=3000)

  • The size parameter specifies the maximum number of samples to collect.
  • The max_duration parameter specifies the maximum duration to collect samples for.

Two QUA variables are returned:

  • times is a QUA array containing the times of arrival of the signal. It will contain at most size entries, though it may contain fewer if the maximum duration is reached first.
  • counts is a QUA integer containing the number of measured events, being at most equal to size.

Additional information on time tagging can be found in the Time Tagging QUA documentation.