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 tagged with a non-zero 8-bit id that determines routing:
ID range |
Routing mode |
Configuration required |
|---|---|---|
1–15 |
Self-cast (originating sequencer only) |
No |
16–255 |
Intra-cast (within a module) or Multi-cast (across modules) |
Yes — before arming |
The three Q1ASM commands used in this tutorial are:
Command |
Core |
Description |
|---|---|---|
|
Real-time |
Send |
|
Q1 |
Pop the next message matching |
|
Q1 |
Pop the top FIFO entry; store its ID in |
See the LINQ User Guide for full details on routing, latencies, and data types.
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]:
module.disconnect_outputs()
module.disconnect_inputs()
module.sequencer0.nco_prop_delay_comp_en(True)
module.sequencer0.mod_en_awg(True)
module.sequencer0.demod_en_acq(True)
# module.sequencer0.connect_sequencer("io0_1")
module.sequencer0.connect_out0("I")
module.sequencer0.connect_out1("Q")
module.sequencer0.connect_acq_I("in0")
module.sequencer0.connect_acq_Q("in1")
[6]:
# Define sequencers to target: (slot_index, sequencer_index)
seq_targets = [(module.slot_idx, 0), (module.slot_idx, 1)]
Intra-cast Using fb_pop_data#
IDs 16–255 must be routed explicitly before the sequencers are armed. With intra-cast, data is delivered to sequencers within the same module.
Use fb_pop_data when the receiver knows the expected message ID in advance, it pops the first message in the FIFO that matches the given ID.
Experiment parameters#
[7]:
INTRA_ID = 16
VALUE = 420
Configure routing#
[8]:
cluster.clear_router()
module.set_local_route(INTRA_ID) # Deliver ID 16 to all sequencers in this module
Configure sequencers#
[9]:
module.sequencer0.sync_en(True)
module.sequencer1.sync_en(True)
Define Q1ASM programs#
Sequencer 0 (sender): intra-casts the value 420 with ID 16.
Sequencer 1 (receiver): waits for the intra-cast latency (150 ns), then pops the message with ID 16 into R0.
[10]:
prog_sender = f"""
wait_sync 4 # Synchronize sequencers
fb_com_data {INTRA_ID}, {VALUE}, 4 # Intra-cast {VALUE} with ID {INTRA_ID}
stop
"""
prog_receiver_pop = f"""
wait_sync 4 # Synchronize sequencers
wait 160 # Wait for intra-cast latency (160 ns)
fb_pop_data {INTRA_ID}, R0 # Pop message with ID {INTRA_ID} into R0
stop
"""
Upload and run#
[11]:
# Upload.
cluster.update_sequences(
sequences={
seq_targets[0]: {
"waveforms": {},
"program": prog_sender,
"acquisitions": {},
"weights": {},
},
seq_targets[1]: {
"waveforms": {},
"program": prog_receiver_pop,
"acquisitions": {},
"weights": {},
},
}
)
[12]:
# Arm and start.
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#
[13]:
received_value = module.sequencer1.get_register("R0")
print(f"Sent : {VALUE}")
print(f"Received: {received_value}")
print("✓ PASS" if received_value == VALUE else f"✗ FAIL — expected {VALUE}, got {received_value}")
Sent : 420
Received: 420
✓ PASS
Intra-cast using fb_pull_data#
fb_pull_data pops the top entry from the FIFO regardless of ID, and writes both the ID and the value into separate registers. Use it when the receiver cannot predict which message will arrive first.
This section reuses the routing configuration and sender program defined above. Sequencer 0 again intra-casts 420 with ID 16; sequencer 1 receives it via fb_pull_data.
Define Q1ASM program#
[14]:
prog_receiver_pull = """
wait_sync 4 # Synchronize sequencers
wait 160 # Wait for intra-cast latency (160 ns)
fb_pull_data R1, R0 # Pull message: ID -> R1, value -> R0
stop
"""
Upload and run#
[15]:
# Upload.
cluster.update_sequences(
sequences={
seq_targets[0]: {
"waveforms": {},
"program": prog_sender,
"acquisitions": {},
"weights": {},
},
seq_targets[1]: {
"waveforms": {},
"program": prog_receiver_pull,
"acquisitions": {},
"weights": {},
},
}
)
[16]:
# Arm and start.
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#
[17]:
received_id = module.sequencer1.get_register("R1")
received_value = module.sequencer1.get_register("R0")
print(f"ID — sent: {INTRA_ID}, received: {received_id}")
print(f"Value — sent: {VALUE}, received: {received_value}")
print(
"✓ PASS"
if received_value == VALUE and received_id == INTRA_ID
else "✗ FAIL — check routing configuration and FIFO state"
)
ID — sent: 16, received: 16
Value — sent: 420, received: 420
✓ 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.
[18]:
# 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: []