14 - XCOM Network Synchronization

Objective: Learn how to synchronize multiple QICK boards and exchange low-latency data using the XCOM network. This notebook covers:

  • XCOM architecture overview (full mesh network, deterministic latency)

  • Hardware setup (FMC transceiver board, external hub)

  • Initializing the XCOM network (AUTO_ID, master/slave configuration)

  • Sending and receiving data between boards (8-bit, 16-bit, 32-bit)

  • Loopback testing and latency measurement

  • tProc integration for real-time communication

  • Debugging XCOM (status registers, debug registers)

  • Synchronizing tProc absolute clocks across multiple boards

Prerequisites:

  • Two or more QICK boards with XCOM FMC transceiver boards

  • External XCOM hub connecting all boards

  • Stable external reference clock (e.g., rubidium clock)

  • Completed basic tutorials (00-05)

References:

  • XCOM Paper: arXiv:2603.18977v1

  • QICK XCOM driver: qick.ip.QICK_Xcom

1. XCOM Overview

XCOM (X - Communication) is a full mesh network that enables:

  • Synchronization of multiple QICK boards with <100 ps skew

  • Low-latency communication (186 ns per 32-bit word, can be reduced to 62 ns)

  • Deterministic message exchange without central control

  • All-to-all simultaneous communication on different channels

  • tProc integration for real-time data exchange

Hardware Requirements

┌─────────────────────────────────────────────────────────────────────────────┐
│                         XCOM Network Topology                               │
├─────────────────────────────────────────────────────────────────────────────┤
│                                                                             │
│   ┌──────────┐      ┌──────────┐      ┌──────────┐      ┌──────────┐        │
│   │ QICK #1  │      │ QICK #2  │      │ QICK #3  │      │ QICK #4  │        │
│   │ (Master) │      │ (Slave)  │      │ (Slave)  │      │ (Slave)  │        │
│   └────┬─────┘      └────┬─────┘      └────┬─────┘      └────┬─────┘        │
│        │                 │                 │                 │              │
│        ▼                 ▼                 ▼                 ▼              │
│   ┌─────────────────────────────────────────────────────────────────┐       │
│   │                      XCOM Hub (Fanout Buffers)                  │       │
│   └─────────────────────────────────────────────────────────────────┘       │
│                                                                             │
│   • Each board has a dedicated Tx channel (LVDS @ up to 312.9 MHz)          │
│   • All boards listen on all Rx channels (full mesh)                        │
│   • External reference clock (e.g., 100 MHz rubidium) for frequency sync    │
│                                                                             │
└─────────────────────────────────────────────────────────────────────────────┘

Supported boards: Up to 15 boards (prototype supports 5)

Key capabilities:

  • Sub-picosecond timing jitter with nested zero-delay PLL mode

  • Multi-tile sync (MTS) for DAC/ADC alignment across boards

  • 48-bit absolute clock counter synchronized across all boards

  • Deterministic communication latency (independent of network traffic)

2. Setup and Initialization

[ ]:
# Standard imports
%matplotlib inline
import matplotlib.pyplot as plt
import numpy as np
import time
import logging

from qick import *
from qick.asm_v2 import AveragerProgramV2
from qick.tprocv2_assembler import Assembler

# Setup logging
logging.basicConfig(level=logging.INFO, format='%(levelname)-8s [%(filename)s:%(lineno)d] %(message)s')

# Connect to the board (adjust path to your firmware with XCOM support)
# For multi-board setup, create separate Soc instances for each board
BITSTREAM_PATH = '/path/to/your/xcom_firmware.bit'  # <--- CHANGE THIS
soc = QickSoc(BITSTREAM_PATH, external_clk=True, force_init_clks=True)
soccfg = soc

print(f"Connected to QICK board")
print(f"Firmware version: {soccfg.get('fw_version', 'Unknown')}")
print(f"XCOM available: {hasattr(soc, 'xcom_0')}")

# For multi-board experiments, you would connect to each board separately:
# soc_master = QickSoc('master_firmware.bit', external_clk=True)
# soc_slave1 = QickSoc('slave1_firmware.bit', external_clk=True)
# soc_slave2 = QickSoc('slave2_firmware.bit', external_clk=True)

2.1 Initialize Clock and Tile Synchronization

Before using XCOM, the board clocks and DAC/ADC tiles must be synchronized.

[ ]:
# Initialize tile synchronization (required for multi-board alignment)
print("Initializing tile synchronization...")
soc.init_tile_sync()
soc.verify_clock_tree()
soc.sync_tiles()
print("Tile synchronization complete.")

# Verify external clock is locked
print(f"External clock locked: {soc.check_ext_clk()}")

3. XCOM Driver Overview

The QICK_Xcom class provides the Python interface to the XCOM hardware. It inherits from SocIP and uses AXI-lite for register access.

[ ]:
# Display XCOM registers and opcodes
print("=== XCOM Registers ===")
for reg_name, reg_addr in soc.xcom_0.REGISTERS.items():
    print(f"  {reg_name:15} : address {reg_addr}")

print("\n=== XCOM Opcodes ===")
for op_name, op_code in soc.xcom_0.opcodes.items():
    print(f"  {op_name:20} : {op_code}")

3.1 Key XCOM Operations

Method

Opcode

Description

auto_id()

XCOM_AUTO_ID

Auto-assign network IDs to all boards

set_local_id(chid)

XCOM_SET_ID

Manually set this board’s ID

send_byte(data, dst, reg)

XCOM_SEND_8BIT_1/2

Send 8 bits to board dst

send_half_word(data, dst, reg)

XCOM_SEND_16BIT_1/2

Send 16 bits to board dst

send_word(data, dst, reg)

XCOM_SEND_32BIT_1/2

Send 32 bits to board dst

set_flag(dst)

XCOM_SET_FLAG

Set flag on destination board

clear_flag(dst)

XCOM_CLEAR_FLAG

Clear flag on destination board

run_cmd(cmd, dt1, dt2)

(any)

Execute any opcode with data

Note: reg parameter (1 or 2) selects which destination register to write to.

4. Network Configuration

4.1 Reset and Auto-ID

The first step is to reset the XCOM network and assign IDs to all connected boards.

[ ]:
# Reset the XCOM network
print("Resetting XCOM network...")
soc.xcom_0.run_cmd(soc.xcom_0.opcodes['XCOM_RST'], 0, 0)
time.sleep(0.1)

# Run AUTO_ID to discover and assign IDs to all boards
print("Running AUTO_ID...")
soc.xcom_0.run_cmd(soc.xcom_0.opcodes['XCOM_AUTO_ID'], 0, 0)
time.sleep(0.5)

# Set local board ID (master board in this example)
BOARD_ID_MASTER = 2
soc.xcom_0.set_local_id(BOARD_ID_MASTER)
print(f"Local board ID set to: {BOARD_ID_MASTER}")

# Re-run AUTO_ID to confirm
soc.xcom_0.auto_id()
time.sleep(0.1)

# Configure XCOM (enable certain features)
# 0x400 typically enables the XCOM interface
soc.xcom_0.xcom_cfg = 0 + 0x400
print(f"XCOM config: 0x{soc.xcom_0.xcom_cfg:04X}")

4.2 Verify Configuration

Check the status and register values to ensure proper configuration.

[ ]:
# Print XCOM status
soc.xcom_0.print_status()

# Print all AXI registers
print("\n")
soc.xcom_0.print_axi_regs()

# Print debug information
print("\n")
soc.xcom_0.print_debug()

5. Sending Data Between Boards

5.1 Sending 8-bit Data

Use send_byte() to transmit 8-bit values to another board.

[ ]:
# Send 8-bit data to another board (e.g., board ID 5)
DEST_BOARD = 5
test_byte = 0xAA

print(f"Sending 8-bit data (0x{test_byte:02X}) to board {DEST_BOARD}...")
soc.xcom_0.send_byte(test_byte, DEST_BOARD, reg=2)
print("Data sent.")

# For multi-board setup, you would read the data on the destination board:
# received_data = soc_slave.xcom_0.mem  # Read from XCOM memory

5.2 Sending 16-bit Data

Use send_half_word() to transmit 16-bit values.

[ ]:
# Send 16-bit data
test_half_word = 0xABCD

print(f"Sending 16-bit data (0x{test_half_word:04X}) to board {DEST_BOARD}...")
soc.xcom_0.send_half_word(test_half_word, DEST_BOARD, reg=2)
print("Data sent.")

5.3 Sending 32-bit Data

Use send_word() to transmit 32-bit values.

[ ]:
# Send 32-bit data
test_word = 0xDEADBEEF

print(f"Sending 32-bit data (0x{test_word:08X}) to board {DEST_BOARD}...")
soc.xcom_0.send_word(test_word, DEST_BOARD, reg=2)
print("Data sent.")

5.4 Writing to XCOM Memory

XCOM has internal memory (16x32-bit) that can be written to and read from.

[ ]:
# Write to XCOM memory on destination board
dest_addr = 5
data_to_write = 350

print(f"Writing {data_to_write} to memory address {dest_addr} on board {DEST_BOARD}")
soc.xcom_0.run_cmd(soc.xcom_0.opcodes['XCOM_WRITE_MEM'], dest_addr, data_to_write)

# Read back the memory
print("\nReading XCOM memory (local board):")
print("addr\tvalue")
for i in range(16):
    soc.xcom_0.axi_addr = i
    print(f"{i:3d}\t{soc.xcom_0.mem}")

6. tProc Integration for XCOM

XCOM can be controlled directly from the tProc using assembly instructions. The PA (Peripheral Access) instruction is the primary mechanism for tProc-XCOM interaction.

[ ]:
# Example assembly program for XCOM communication from tProc
xcom_tproc_asm = """
// XCOM Communication Test Program (tProc side)
// Sends data to another board via XCOM

// Initialize registers
REG_WR r1 imm #1          // Configuration register 1
REG_WR r2 imm #2          // Configuration register 2
REG_WR r3 imm #3          // Configuration register 3
REG_WR r4 imm #4          // Configuration register 4
REG_WR r5 imm #5          // Destination board ID
REG_WR r6 imm #424        // Data to send

// Write to XCOM control/configuration
// ctrl_csf_qpa = clear flag in cfg/cntl
// cfg_src_qpa = configure source

// Trigger output pin for scope synchronization
TRIG p0 set

// Write to XCOM memory (WRITE_MEM operation)
REG_WR s_ctrl imm ctrl_csf_qpa   // Clear flag in cfg/cntl
WAIT qpa_rdy                       // Wait for peripheral ready
PA 7 r2 r6                         // Peripheral access: write r6 using config in r2
WAIT qpa_dt                        // Wait for data transfer complete

// Clear trigger
TRIG p0 clr

// Set flag on destination board
WAIT qpa_rdy
PA 1 r5                           // Set flag on board in r5
WAIT qpa_dt

.END
"""

# Compile the assembly
p_txt, p_bin = Assembler.str_asm2bin(xcom_tproc_asm)
print(f"Assembly compiled: {len(p_bin)} instructions")
print("\nAssembly code:")
print(p_txt)

# Load the program into tProc program memory
# Note: The method name may vary based on your firmware version
# soc.qick_processor_0.load_mem(mem_sel='pmem', buff_in=p_bin, check=False)
print("\nProgram ready to load to tProc.")
print("Uncomment the load_mem line and adjust the method name as needed for your firmware.")

6.1 tProc Peripheral Access (PA) Instruction

The PA instruction is used for peripheral access. The syntax is:

PA <cfg_reg> <addr_reg> <data_reg>

Where:

  • cfg_reg: Configuration register (controls the operation)

  • addr_reg: Address/target register (e.g., destination board ID)

  • data_reg: Data register (value to send)

Common PA operations:

PA cfg

Operation

Description

0

CLEAR_FLAG

Clear flag on destination board

1

SET_FLAG

Set flag on destination board

6

XCOM QCTRL

XCOM control operation

7

WRITE_MEM

Write to XCOM memory

7. Master-Slave Communication Example

In a multi-board setup, one board acts as the master and others as slaves. The master can:

  • Broadcast reset/start commands to synchronize absolute clocks

  • Send data to specific slaves

  • Receive data from slaves

  • Coordinate multi-board experiments

7.1 Master Board Configuration

[ ]:
# Master board setup (run on the board designated as master)
def setup_master_board(soc, master_id=2):
    """Configure a board as XCOM master."""
    print("Setting up master board...")

    # Reset XCOM
    soc.xcom_0.run_cmd(soc.xcom_0.opcodes['XCOM_RST'], 0, 0)
    time.sleep(0.1)

    # Auto-assign IDs
    soc.xcom_0.run_cmd(soc.xcom_0.opcodes['XCOM_AUTO_ID'], 0, 0)
    time.sleep(0.5)

    # Set local ID
    soc.xcom_0.set_local_id(master_id)

    # Configure XCOM
    soc.xcom_0.xcom_cfg = 0 + 0x400 + 0x100  # Additional config for master

    print(f"Master board configured with ID: {master_id}")
    return True

# Slave board configuration (run on each slave board)
def setup_slave_board(soc, slave_id):
    """Configure a board as XCOM slave."""
    print(f"Setting up slave board with ID: {slave_id}...")

    # Reset XCOM
    soc.xcom_0.run_cmd(soc.xcom_0.opcodes['XCOM_RST'], 0, 0)
    time.sleep(0.1)

    # Auto-assign IDs
    soc.xcom_0.run_cmd(soc.xcom_0.opcodes['XCOM_AUTO_ID'], 0, 0)
    time.sleep(0.5)

    # Set local ID
    soc.xcom_0.set_local_id(slave_id)

    # Configure XCOM
    soc.xcom_0.xcom_cfg = 0 + 0x400

    print(f"Slave board configured with ID: {slave_id}")
    return True

print("Master/slave configuration functions defined.")
print("\nFor multi-board experiments:")
print("  master = setup_master_board(soc_master, master_id=2)")
print("  slave1 = setup_slave_board(soc_slave1, slave_id=5)")
print("  slave2 = setup_slave_board(soc_slave2, slave_id=7)")

7.2 Sending Data from Master to Slave

Once configured, the master can send data to any slave.

[ ]:
# Example: Master sending a ramp pattern to a slave
def master_send_ramp(soc_master, dest_slave_id, num_values=100):
    """Send a ramp of values from master to slave."""
    print(f"Master sending {num_values} values to slave {dest_slave_id}...")

    start_time = time.time()

    for i in range(num_values):
        # Send 16-bit value
        soc_master.xcom_0.send_half_word(i, dest_slave_id, reg=2)
        # Optional: wait for acknowledgment
        # while soc_master.xcom_0.mem == 0:
        #     pass

    elapsed = time.time() - start_time
    print(f"Sent {num_values} values in {elapsed:.3f}s ({num_values/elapsed:.0f} values/sec)")
    return elapsed

# On the slave side, you would read incoming data:
# def slave_receive_data(soc_slave, num_values=100):
#     data = []
#     for i in range(num_values):
#         data.append(soc_slave.xcom_0.mem)
#     return data

print("Master-to-slave communication functions defined.")

8. Loopback Testing

Loopback testing validates XCOM functionality by sending data to the same board (self-communication). This is useful for debugging without multiple boards.

[ ]:
# Loopback test: write to self
def xcom_loopback_test(soc, num_transfers=1000):
    """
    Perform XCOM loopback test by writing to self.
    Returns list of received data and measured latency.
    """
    dest = 2  # Send to self (using local board ID)

    # Configure XCOM for loopback
    soc.xcom_0.xcom_cfg = 2 + 0x100  # Loopback mode

    # Clear destination memory
    soc.xcom_0.run_cmd(soc.xcom_0.opcodes['XCOM_WRITE_MEM'], dest, 0)

    data_in = []
    start_time = time.time()

    for i in range(1, num_transfers):
        # Send 16-bit value
        soc.xcom_0.run_cmd(soc.xcom_0.opcodes['XCOM_SEND_16BIT_2'], dest, i)

        # Wait for data to be received
        while soc.xcom_0.mem == 0:
            pass

        data_in.append(soc.xcom_0.mem)

        # Verify data integrity
        if soc.xcom_0.mem != i:
            print(f"Data mismatch at i={i}: sent {i}, received {soc.xcom_0.mem}")

        # Clear for next transfer
        soc.xcom_0.run_cmd(soc.xcom_0.opcodes['XCOM_WRITE_MEM'], dest, 0)

    elapsed = time.time() - start_time
    return data_in, elapsed

# Run loopback test
print("Running XCOM loopback test...")
try:
    data_received, elapsed_time = xcom_loopback_test(soc, num_transfers=500)

    print(f"\nLoopback test complete.")
    print(f"Elapsed time: {elapsed_time:.3f}s")
    print(f"Transfers: {len(data_received)}")
    if len(data_received) > 0:
        print(f"Average latency per transfer: {elapsed_time/len(data_received)*1000:.3f} ms")

    # Plot received data to verify ramp pattern
    plt.figure(figsize=(12, 6))
    if len(data_received) > 100:
        plt.plot(data_received[0:100], '.-')
    else:
        plt.plot(data_received, '.-')
    plt.xlabel('Transfer Number')
    plt.ylabel('Received Value')
    plt.title('XCOM Loopback Test - Received Data')
    plt.grid(True)
    plt.show()
except Exception as e:
    print(f"Loopback test failed: {e}")

9. Latency Measurement

XCOM provides deterministic, low-latency communication. Precise latency can be measured using the tProc’s absolute time counter.

[ ]:
# Assembly program to measure XCOM round-trip latency
latency_measure_asm = """
// XCOM Latency Measurement Program
// Measures round-trip latency by timing a send-receive cycle

// Initialize registers
REG_WR r1 imm #1
REG_WR r2 imm #2          // Destination board ID
REG_WR r3 imm #3
REG_WR r4 imm #4
REG_WR r5 imm #5
REG_WR r10 imm #0         // DMEM pointer for data storage

// Write some zeros to DMEM
DMEM_WR [r10] imm #0
DMEM_WR [r10 + &1] imm #0
DMEM_WR [r10 + &2] imm #0
DMEM_WR [r10 + &3] imm #0
DMEM_WR [r10 + &4] imm #0
DMEM_WR [r10 + &5] imm #0

// Clear XCOM flag
REG_WR s_ctrl imm ctrl_csf_qpa

LOOP:
    // Record start time
    REG_WR r12 op -op(s_usr_time)

    // Send data via XCOM
    WAIT qpa_rdy
    PA 6 r2 r6               // XCOM QCTRL operation
    WAIT qpa_dt

    // Wait for response (flag from destination)
    WAIT qpa_rdy
    PA 0 r2                  // Clear flag
    WAIT qpa_dt

    // Record end time
    REG_WR r13 op -op(s_usr_time)

    // Store delta time in DMEM
    DMEM_WR [r10] op -op(r13 - r12)

    // Increment pointer and data
    REG_WR r6 op -op(r6 + #1)
    REG_WR r10 op -op(r10 + #1)

    // Loop counter
    REG_WR r11 op -op(r11 - #1) -uf
    JUMP LOOP -if(NZ)

.END
"""

print("Latency measurement program ready.")
print("Run this on the master board to measure XCOM round-trip latency.")

9.1 Analyzing Latency Data

After running the latency measurement program, analyze the collected timing data.

[ ]:
# Example: Reading latency data from DMEM after tProc execution
def read_latency_data(soc, num_samples=100):
    """Read latency measurement data from tProc DMEM."""
    delta_times = []

    for i in range(num_samples):
        # Read from DMEM (addresses 0, 1, 2, ...)
        # Method name may vary based on firmware
        # delta = soc.qick_processor_0.single_read(2, i)
        delta_times.append(0)  # Placeholder

    return delta_times

print("Latency data analysis functions defined.")
print("\nAfter running the tProc program, you would analyze data with:")
print("  delta_times = read_latency_data(soc, num_samples=100)")
print("  plt.hist(delta_times, bins=50)")
print("  print(f'Average latency: {np.mean(delta_times)} cycles')")

10. Using Flags for Synchronization

XCOM flags provide a fast, single-bit signaling mechanism between boards. They are useful for:

  • Triggering actions on slave boards

  • Synchronizing state machines across boards

  • Implementing token ring or handshake protocols

[ ]:
# Set flag on another board
def set_remote_flag(soc, dest_board_id):
    """Set the flag on a remote board."""
    print(f"Setting flag on board {dest_board_id}...")
    soc.xcom_0.set_flag(dest_board_id)
    print("Flag set.")

# Clear flag on another board
def clear_remote_flag(soc, dest_board_id):
    """Clear the flag on a remote board."""
    print(f"Clearing flag on board {dest_board_id}...")
    soc.xcom_0.clear_flag(dest_board_id)
    print("Flag cleared.")

# Check local flag status
def check_local_flag(soc):
    """Check the status of the local flag."""
    flag_status = soc.xcom_0.flag
    print(f"Local flag: {flag_status}")
    return flag_status

# From tProc, you can wait for a flag using WAIT instruction
# WAIT qpa_rdy
# PA 0 r2    # Check/clear flag
# WAIT qpa_dt

print("Flag operations defined.")
print("\nExample usage:")
print("  set_remote_flag(soc_master, slave_id)")
print("  check_local_flag(soc_slave)")

11. Absolute Clock Synchronization

One of XCOM’s most powerful features is absolute clock synchronization across all boards. The master can broadcast reset and start commands to align all tProc absolute time counters.

11.1 Synchronization Protocol

  1. Frequency synchronization: All boards share the same external reference clock (e.g., 100 MHz rubidium)

  2. Phase synchronization: PLLs configured in nested zero-delay mode

  3. DAC/ADC alignment: Multi-tile sync (MTS) calibration

  4. Absolute time alignment: Master broadcasts RESET and START commands

[ ]:
# Master broadcasts reset to all boards
def broadcast_reset(soc_master):
    """Broadcast absolute clock reset to all boards."""
    print("Broadcasting RESET command to all boards...")

    # Use XCOM_QRST_SYNC opcode to broadcast reset
    soc_master.xcom_0.run_cmd(soc_master.xcom_0.opcodes['XCOM_QRST_SYNC'], 0, 0)
    time.sleep(0.1)
    print("RESET broadcast complete.")

# Master broadcasts start to all boards
def broadcast_start(soc_master):
    """Broadcast absolute clock start to all boards."""
    print("Broadcasting START command to all boards...")

    # Use XCOM_QCTRL with appropriate parameters to start
    soc_master.xcom_0.run_cmd(soc_master.xcom_0.opcodes['XCOM_QCTRL'], 4, 6)  # proc_start
    time.sleep(0.1)
    print("START broadcast complete.")

# Verify synchronization
def verify_sync(soc_board, expected_time=None):
    """Read absolute time from tProc to verify sync."""
    # Read absolute time from tProc (address depends on firmware)
    # This is a placeholder - actual register address may vary
    # abs_time = soc_board.qick_processor_0.single_read(2, 0)
    abs_time = 0  # Placeholder
    print(f"Board absolute time: {abs_time}")
    return abs_time

print("Synchronization functions defined.")
print("\nTypical synchronization sequence:")
print("  1. broadcast_reset(soc_master)   # Reset all absolute clocks")
print("  2. time.sleep(0.1)")
print("  3. broadcast_start(soc_master)   # Start all absolute clocks")
print("  4. verify_sync(soc_master)")
print("  5. verify_sync(soc_slave1)    # Should show same value")

12. Debugging XCOM

XCOM provides several debugging registers to help diagnose issues.

[ ]:
def diagnose_xcom(soc):
    """Run comprehensive XCOM diagnostics."""
    print("=" * 60)
    print("XCOM DIAGNOSTICS")
    print("=" * 60)

    # Check XCOM presence
    if not hasattr(soc, 'xcom_0'):
        print("❌ XCOM not found! Check firmware bitstream.")
        return False
    print("✅ XCOM found")

    # Print status register
    print("\n--- Status Register ---")
    soc.xcom_0.print_status()

    # Print debug register
    print("\n--- Debug Register ---")
    soc.xcom_0.print_debug()

    # Print all registers
    print("\n--- All Registers ---")
    soc.xcom_0.print_axi_regs()

    # Test basic read/write
    print("\n--- Basic Read/Write Test ---")
    test_addr = 5
    test_value = 0x12345678

    try:
        soc.xcom_0.axi_addr = test_addr
        original = soc.xcom_0.mem
        soc.xcom_0.mem = test_value
        readback = soc.xcom_0.mem
        soc.xcom_0.mem = original  # Restore

        if readback == test_value:
            print(f"✅ Memory write/read: wrote 0x{test_value:08X}, read 0x{readback:08X}")
        else:
            print(f"❌ Memory mismatch: wrote 0x{test_value:08X}, read 0x{readback:08X}")
    except Exception as e:
        print(f"❌ Memory test failed: {e}")

    # Test loopback (if configured)
    print("\n--- Loopback Test ---")
    try:
        data, elapsed = xcom_loopback_test(soc, num_transfers=10)
        if len(data) == 9 and all(data[i] == i+1 for i in range(9)):
            print(f"✅ Loopback test passed ({len(data)} transfers)")
        else:
            print(f"⚠️ Loopback test had {len(data)} transfers, expected 9")
    except Exception as e:
        print(f"❌ Loopback test failed: {e}")

    print("\n" + "=" * 60)
    return True

# Run diagnostics
diagnose_xcom(soc)

13. Common Issues and Troubleshooting

[ ]:
print("""
╔══════════════════════════════════════════════════════════════════════════════════════════════════════════════╗
║                                    XCOM TROUBLESHOOTING GUIDE                                                ║
╠══════════════════════════════════════════════════════════════════════════════════════════════════════════════╣
║                                                                                                              ║
║  Issue                         │ Likely Cause                    │ Solution                                  ║
║  ──────────────────────────────┼─────────────────────────────────┼───────────────────────────────────────────║
║  XCOM not detected             │ Wrong firmware                  │ Use bitstream with XCOM IP                ║
║                                │ Missing FMC board               │ Install XCOM transceiver board            ║
║  ──────────────────────────────┼─────────────────────────────────┼───────────────────────────────────────────║
║  AUTO_ID fails                 │ No external hub connected       │ Connect all boards to XCOM hub            ║
║                                │ Cable mismatch                  │ Use matched-length LVDS cables            ║
║  ──────────────────────────────┼─────────────────────────────────┼───────────────────────────────────────────║
║  High latency (>200 ns)        │ XCOM clock too low              │ Increase LVDS clock (up to 312.9 MHz)     │
║                                │ Software overhead               │ Use tProc instead of Python for timing    │
║  ──────────────────────────────┼─────────────────────────────────┼───────────────────────────────────────────║
║  Data corruption               │ LVDS signal integrity           │ Check cable quality and length            │
║                                │ Clock skew                      │ Run MTS calibration                       │
║  ──────────────────────────────┼─────────────────────────────────┼───────────────────────────────────────────║
║  Flag not received             │ Wrong destination ID            │ Verify board IDs with auto_id()           │
║                                │ Flag not cleared                │ Always clear flag after receipt           │
║  ──────────────────────────────┼─────────────────────────────────┼───────────────────────────────────────────║
║  tProc XCOM commands fail      │ Peripheral not ready            │ Add WAIT qpa_rdy before PA                │
║                                │ Incorrect PA configuration      │ Verify cfg register values                │
║  ──────────────────────────────┼─────────────────────────────────┼───────────────────────────────────────────║
║  Synchronization drift         │ External clock not locked       │ Verify PLL lock status                    │
║                                │ Missing MTS calibration         │ Run init_tile_sync() and sync_tiles()     │
║                                                                                                              ║
╠══════════════════════════════════════════════════════════════════════════════════════════════════════════════╣
║                                         QUICK DIAGNOSTIC COMMANDS                                            ║
╠══════════════════════════════════════════════════════════════════════════════════════════════════════════════╣
║                                                                                                              ║
║  # Check if XCOM is present                                                                                  ║
║  hasattr(soc, 'xcom_0')                                                                                      ║
║                                                                                                              ║
║  # Print all XCOM registers                                                                                  ║
║  soc.xcom_0.print_axi_regs()                                                                                 ║
║                                                                                                              ║
║  # Check XCOM status                                                                                         ║
║  soc.xcom_0.print_status()                                                                                   ║
║                                                                                                              ║
║  # Run full diagnostics                                                                                      ║
║  diagnose_xcom(soc)                                                                                          ║
║                                                                                                              ║
╚══════════════════════════════════════════════════════════════════════════════════════════════════════════════╝
""")

14. Summary

You have learned:

  1. XCOM Architecture: Full mesh network for multi-board synchronization and communication

  2. Hardware Requirements: FMC transceiver boards and external hub for 5-15 boards

  3. Network Configuration: AUTO_ID, set_local_id, master/slave roles

  4. Data Transfer: Sending 8/16/32-bit data between boards with deterministic latency

  5. tProc Integration: Using PA instruction for real-time XCOM access

  6. Flags: Fast single-bit signaling for synchronization

  7. Absolute Clock Sync: Broadcasting RESET/START to align all tProcs

  8. Debugging: Status registers, diagnostics, troubleshooting

Key Takeaways

  • XCOM enables <100 ps skew and 186 ns deterministic latency between boards

  • Each board has a dedicated Tx channel; all boards listen on all Rx channels

  • The master board can synchronize absolute clocks across the entire network

  • tProc integration allows real-time XCOM communication without software overhead

  • Always run MTS calibration and verify external clock before using XCOM

Next Steps

  • For simpler multi-board synchronization (external clock + external start), see `10_Multi_Board_Synchronization.ipynb <./10_Multi_Board_Synchronization.ipynb>`__

  • Proceed to explore other advanced topics

  • For more information, refer to the XCOM paper (arXiv:2603.18977v1)

  • Check the QICK firmware documentation for XCOM IP details

References