See also

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

Sharing Q1 Registers and Immediates#

This tutorial demonstrates the LINQ-based feedback communication path, which enables low-latency communication of registers and immediates between sequencers.

Every message is addressed with a non-zero 8-bit id:

  • 1–15: Self-cast (same sequencer)

  • 16–255: Intra-cast (within a module) or multi-cast (across multiple modules within a cluster), depending on routing configuration.

See the LINQ User Guide and Routing configuration for full details.

Instructions#

Sending data#

Data is sent with the fb_com_data, for more information see the Sharing Data section in the user guide.

Note: fb_com_data is a real-time command

Receiving data#

There are two ways to receive data. The first pops a message with a specific ID (given as an immediate), while the second pulls whatever is at the top of the FIFO and writes its ID into a register. The difference between these two approaches is explained in detail in the receiving data section.

The fb_pop_data command is used to pop a message with a specific ID. The fb_pull_data command pulls whatever is at the top of the feedback queue.

Note: fb_pop_data and fb_pull_data are Q1 commands

Hardware Requirements#

  • A Qblox Cluster with at least one module.

Setup#

First, we import the required packages and connect to the instrument.

[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: ERROR, Flags: FEEDBACK_NETWORK_CALIBRATION_FAILED, Slot flags: NONE

Get connected modules#

[4]:
# QTM modules
modules = cluster.get_connected_modules(lambda mod: mod.is_qtm_type)
# This uses the module of the correct type with the lowest slot index
module = list(modules.values())[0]

Intra-cast using fb_pop_data#

IDs 16–255 are reserved for intra-cast or multi-cast routing. With intra-cast, data is delivered to sequencers within the same module, depending on routing. Here we use intra-cast, therefore routing must be configured before the sequence is executed.

Here we use fb_pop_data to retrieve a message by a known ID.

[5]:
ID = 16
value = 420

cluster.clear_router()
module.set_local_route(ID)  # Send to all sequencers in this module

Below we use two sequencers, one sender and one receiver. Sequencer 0 (sender): Intra-casts the immediate value 420 with ID 16. Sequencer 1 (receiver): waits 150 ns for data with ID 16 to arrive, then pops it into R0 using fb_pop_data.

[6]:
prog_sender = f"""
wait_sync       4                           # Synchronize sequencers
fb_com_data     {ID}, {value}, 4            # Intra-cast value {value} with ID {ID}
stop
"""
[7]:
prog_receiver = f"""
wait_sync       4                          # Synchronize sequencers
wait            150                        # Block until data ID {ID} is in FIFO
fb_pop_data     {ID}, R0                   # Pop message with ID {ID} into R0
stop
"""
[8]:
module.sequencer0.sync_en(True)
module.sequencer1.sync_en(True)

module.sequencer0.sequence(
    {
        "waveforms": {},
        "program": prog_sender,
        "acquisitions": {},
        "weights": {},
    }
)
module.sequencer1.sequence(
    {
        "waveforms": {},
        "program": prog_receiver,
        "acquisitions": {},
        "weights": {},
    }
)
[9]:
module.arm_sequencer(0)
module.arm_sequencer(1)
module.start_sequencer()

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

Verify data#

[10]:
communicated_data = module.sequencer1.get_register("R0")

if communicated_data == value:
    print(f"✓ Verification passed: received {communicated_data} == sent {value}.")
else:
    print(f"✗ Verification failed: expected {value}, got {communicated_data}.")
✓ Verification passed: received 420 == sent 420.

Intra-cast using fb_pull_data#

This part demonstrates fb_pull_data, which pulls the next message at the top of the FIFO regardless of its ID. The ID is written into a register alongside the value, which is useful when the receiver does not know the ID in advance.

Sequencer 0 (sender): intra-casts value 420 with ID 16.

Sequencer 1 (receiver): pulls the message from the FIFO using fb_pull_data. The data will be stored in R0 and the ID in R1.

[11]:
prog_receiver_pull = """
wait_sync       4                           # Synchronize sequencers
wait            150                         # Block until message is in FIFO
fb_pull_data    R1, R0                      # Pull message: ID -> R1, value -> R0
stop
"""
[12]:
module.sequencer0.sequence(
    {
        "waveforms": {},
        "program": prog_sender,
        "acquisitions": {},
        "weights": {},
    }
)
module.sequencer1.sequence(
    {
        "waveforms": {},
        "program": prog_receiver_pull,
        "acquisitions": {},
        "weights": {},
    }
)
[13]:
module.arm_sequencer(0)
module.arm_sequencer(1)
module.start_sequencer()

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

Verify data#

[14]:
received_id = module.sequencer1.get_register("R1")  # R1 holds the id
received_value = module.sequencer1.get_register("R0")  # R0 holds the value

print(f"Received ID: {received_id} (expected {ID}), value: {received_value} (expected {value})")

if received_value == value and received_id == ID:
    print("✓ Verification passed.")
else:
    print("✗ Verification failed.")
Received ID: 16 (expected 16), value: 420 (expected 420)
✓ Verification passed.

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.

[15]:
# 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: ERROR, State: STOPPED, Exit Code: 0, Info Flags: FORCED_STOP, Warning Flags: NONE, Error Flags: SEQUENCE_PROCESSOR_RT_EXEC_COMMAND_UNDERFLOW, Log: []