See also

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

LINQ routing configuration#

Every piece of data sent via LINQ-based feedback is tagged with an 8-bit id that controls which sequencers receive it. There are three routing modes; choose the one that matches your target destination:

ID range

Routing mode

Destination

1–15

Self-cast

Originating sequencer only

16–255

Intra-cast

Sequencers within the same module

16–255

Multi-cast / broadcast

Sequencers across modules via the CMM

Each mode has a distinct latency; see the LINQ-based feedback documentation for a full overview.

IDs 16–255 must be configured before the sequencers are armed using one of the commands below. ID 0 is reserved to disable sharing of a specific data type.

Command

Description

cluster.clear_router()

Reset all routing to factory defaults.

module.set_local_route(id, sequencers=None)

Intra-cast: route id within the module, optionally to a subset of sequencers.

cluster.set_cmm_route(id, targets)

Multi-cast: route id to specific modules or sequencers via the CMM.

cluster.set_broadcast(id)

Broadcast: route id to every sequencer in the cluster via the CMM.

This tutorial demonstrates all three routing modes using Q1 register / immediate transfers, which are supported on every module variant.

Hardware requirements#

  • A Qblox Cluster with one module containing at least two sequencers.

Setup#

[1]:
from __future__ import annotations

from qcodes.instrument import find_or_create_instrument

from qblox_instruments import Cluster, ClusterType

Scan For Clusters#

We scan for the available devices connected via ethernet using the Plug & Play functionality of the Qblox Instruments package (see Plug & Play for more info).

!qblox-pnp list

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

Connect to Cluster#

We now make a connection with the Cluster.

[3]:
cluster: Cluster = find_or_create_instrument(
    Cluster,
    recreate=True,
    name=cluster_name,
    identifier=cluster_ip,
    dummy_cfg=(
        {
            2: ClusterType.CLUSTER_QCM,
            4: ClusterType.CLUSTER_QRM,
            6: ClusterType.CLUSTER_QCM_RF,
            8: ClusterType.CLUSTER_QRM_RF,
            10: ClusterType.CLUSTER_QTM,
            12: ClusterType.CLUSTER_QRC,
            16: ClusterType.CLUSTER_QSM,
        }
        if cluster_ip is None
        else None
    ),
)

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

Get connected modules#

[4]:
# QRM baseband modules
modules = cluster.get_connected_modules(lambda mod: mod.is_qrm_type and not mod.is_rf_type)
# This uses the module of the correct type with the lowest slot index
module = list(modules.values())[0]
[5]:
# Define sequencers to target: (slot_index, sequencer_index)
seq_targets = [(module.slot_idx, 0), (module.slot_idx, 1)]

Self-cast#

IDs 1–15 are reserved for self-cast, no routing configuration is needed. Data sent with these IDs is delivered only to the feedback queue of the originating sequencer.

Experiment parameters#

[6]:
SELF_CAST_ID = 5  # Any value 1–15
VALUE = 42

Define Q1ASM program#

[7]:
prog_self_cast = f"""
fb_com_data     {SELF_CAST_ID}, {VALUE}, 4     # Self-cast {VALUE} with ID {SELF_CAST_ID}
wait            60                             # Wait for self-cast latency
fb_pop_data     {SELF_CAST_ID}, R0             # Pop message into R0
stop
"""

Upload and run#

[8]:
module.sequencer0.sequence(
    {
        "waveforms": {},
        "program": prog_self_cast,
        "acquisitions": {},
        "weights": {},
    }
)

module.arm_sequencer(0)
module.start_sequencer()

print("Sequencer 0 status:", module.get_sequencer_status(0))
Sequencer 0 status: Status: OKAY, State: STOPPED, Exit Code: 0, Info Flags: NONE, Warning Flags: NONE, Error Flags: NONE, Log: []

Verify result#

[9]:
self_cast_result = module.sequencer0.get_register("R0")

print(f"Sent    : {VALUE}")
print(f"Received: {self_cast_result}")
print(
    "✓ PASS"
    if self_cast_result == VALUE
    else "✗ FAIL — check that the ID is in the self-cast range (1–15)"
)
Sent    : 42
Received: 42
✓ PASS

Intra-cast#

module.set_local_route(id) routes data with the given id to all sequencers within the module. To restrict delivery to a subset, pass an explicit list of sequencers:

module.set_local_route(id, [module.sequencer1, module.sequencer3])

Call cluster.clear_router() before each experiment to reset any routing left over from a previous run.

Experiment parameters#

[10]:
INTRA_ID = 16
VALUE = 99

Configure routing#

[11]:
cluster.clear_router()
module.set_local_route(INTRA_ID)  # All sequencers in module receive ID 16

Configure sequencers#

[12]:
module.sequencer0.sync_en(True)
module.sequencer1.sync_en(True)

Define Q1ASM programs#

[13]:
prog_intra_sender = f"""
wait_sync       4                              # Synchronize with receiver
fb_com_data     {INTRA_ID}, {VALUE}, 4         # Intra-cast {VALUE} with ID {INTRA_ID}
stop
"""

prog_intra_receiver = f"""
wait_sync       4                              # Synchronize with sender
wait            152                            # Wait for intra-cast latency
fb_pop_data     {INTRA_ID}, R0                 # Pop message with ID {INTRA_ID} into R0
stop
"""

Upload and run#

[14]:
# Upload and run.
cluster.update_sequences(
    sequences={
        seq_targets[0]: {
            "waveforms": {},
            "program": prog_intra_sender,
            "acquisitions": {},
            "weights": {},
        },
        seq_targets[1]: {
            "waveforms": {},
            "program": prog_intra_receiver,
            "acquisitions": {},
            "weights": {},
        },
    }
)

cluster.arm_sequencers(sequencers=seq_targets)
cluster.start_sequencers(sequencers=seq_targets)

print(cluster.get_sequencer_statuses(sequencers=seq_targets))
[SequencerStatus(status=<SequencerStatuses.OKAY>, state=<SequencerStates.STOPPED>, exit_code=0, info_flags=[], warn_flags=[], err_flags=[], log=[]), SequencerStatus(status=<SequencerStatuses.OKAY>, state=<SequencerStates.STOPPED>, exit_code=0, info_flags=[], warn_flags=[], err_flags=[], log=[])]

Verify result#

[15]:
intra_result = module.sequencer1.get_register("R0")

print(f"Sent    : {VALUE}")
print(f"Received: {intra_result}")
print(
    "✓ PASS"
    if intra_result == VALUE
    else "✗ FAIL — check that set_local_route was called before arming"
)
Sent    : 99
Received: 99
✓ PASS

Multi-cast and broadcast#

cluster.set_cmm_route(id, targets) routes data via the CMM to specific modules or sequencers. cluster.set_broadcast(id) delivers to every sequencer in the cluster, including the sender.

Here we configure two IDs simultaneously: ID 32 is targeted to sequencer 1 only (multi-cast), while ID 64 is broadcast to all sequencers. Sequencer 0 sends both in one program, and sequencer 1 pops both.

Experiment parameters#

[16]:
MULTI_ID = 32
BROADCAST_ID = 64
VALUE_MULTI = 2025
VALUE_BCAST = 12345

Configure routing#

[17]:
cluster.clear_router()
cluster.set_cmm_route(MULTI_ID, [module.sequencer1])  # ID 32 → sequencer 1 only
cluster.set_broadcast(BROADCAST_ID)  # ID 64 → all sequencers

Configure sequencers#

[18]:
module.sequencer0.sync_en(True)
module.sequencer1.sync_en(True)

Define Q1ASM programs#

[19]:
prog_multi_sender = f"""
wait_sync       4
fb_com_data     {MULTI_ID},     {VALUE_MULTI},  4      # Multi-cast to sequencer 1
fb_com_data     {BROADCAST_ID}, {VALUE_BCAST},  4      # Broadcast to all sequencers
stop
"""

prog_multi_receiver = f"""
wait_sync       4
wait            500                                    # Wait conservatively for multi-cast latency
fb_pop_data     {MULTI_ID},     R0                     # Pop multi-cast value into R0
fb_pop_data     {BROADCAST_ID}, R1                     # Pop broadcast value into R1
stop
"""

Upload and run#

[20]:
# Upload and run.
cluster.update_sequences(
    sequences={
        seq_targets[0]: {
            "waveforms": {},
            "program": prog_multi_sender,
            "acquisitions": {},
            "weights": {},
        },
        seq_targets[1]: {
            "waveforms": {},
            "program": prog_multi_receiver,
            "acquisitions": {},
            "weights": {},
        },
    }
)

cluster.arm_sequencers(sequencers=seq_targets)
cluster.start_sequencers(sequencers=seq_targets)

print(cluster.get_sequencer_statuses(sequencers=seq_targets))
[SequencerStatus(status=<SequencerStatuses.OKAY>, state=<SequencerStates.STOPPED>, exit_code=0, info_flags=[], warn_flags=[], err_flags=[], log=[]), SequencerStatus(status=<SequencerStatuses.OKAY>, state=<SequencerStates.STOPPED>, exit_code=0, info_flags=[], warn_flags=[], err_flags=[], log=[])]

Verify result#

[21]:
multi_result = module.sequencer1.get_register("R0")
bcast_result = module.sequencer1.get_register("R1")

print(f"Multi-cast  — sent: {VALUE_MULTI},  received: {multi_result}")
print(f"Broadcast   — sent: {VALUE_BCAST}, received: {bcast_result}")
print(
    "✓ PASS"
    if multi_result == VALUE_MULTI and bcast_result == VALUE_BCAST
    else "✗ FAIL — check CMM route and broadcast configuration"
)
Multi-cast  — sent: 2025,  received: 2025
Broadcast   — sent: 12345, received: 12345
✓ PASS

Stop#

Finally, let’s stop the sequencers if they haven’t already and close the instrument connection. One can also display a detailed snapshot containing the instrument parameters before closing the connection by uncommenting the corresponding lines.

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

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