09 - Appendix: Tips, Limits, and Troubleshooting

Objective: This reference notebook covers common pitfalls, hardware limits, debugging techniques, and advanced parameters for tProc v2 programs. Use this as a reference when things don’t work as expected.

1. Setup

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

from qick import *
from qick.asm_v2 import AveragerProgramV2, QickSweep1D, AsmV2

# Setup logging (set to DEBUG for detailed output)
logging.basicConfig(level=logging.INFO, format='%(levelname)-8s [%(filename)s:%(lineno)d] %(message)s')
logging.getLogger("qick_processor").setLevel(logging.WARNING)

# Connect to the board (adjust the path to your firmware)
BITSTREAM_PATH = 'qick_216.bit'#'/path/to/your/firmware.bit'  # <--- CHANGE THIS
soc = QickSoc(BITSTREAM_PATH)
soccfg = soc

# Define hardware channels
GEN_CH = 0
RO_CH = 0

2. tProc Hardware Limits

Understanding the tProc’s limitations will help you design programs that fit within hardware constraints.

[2]:
# Display tProc configuration from firmware
print("=== tProc Configuration ===")
if hasattr(soccfg, 'tproc'):
    print(f"Program Memory:  {soccfg.tproc['pmem_size']} words")
    print(f"Data Memory:     {soccfg.tproc['dmem_size']} words")
    print(f"Waveform Memory: {soccfg.tproc['wmem_size']} words")
    print(f"Core Clock:      {soccfg.tproc['f_core']} MHz")
    print(f"Timing Clock:    {soccfg.tproc['f_time']} MHz")
    print(f"tProc Version:   {soccfg.tproc['version']}")
    print(f"Revision:        {soccfg.tproc['revision']}")
    print(f"Has LFSR:        {bool(soccfg.tproc['has_lfsr'])}")
    print(f"Has Divider:     {bool(soccfg.tproc['has_divider'])}")
    print(f"Has Arith:       {bool(soccfg.tproc['has_arith'])}")
else:
    print("tProc info not available in soccfg")
=== tProc Configuration ===
Program Memory:  4096 words
Data Memory:     16384 words
Waveform Memory: 1024 words
Core Clock:      215.04 MHz
Timing Clock:    430.08 MHz
tProc Version:   2.0
Revision:        26
Has LFSR:        True
Has Divider:     True
Has Arith:       True

2.1 Program Memory Limit (4096 words)

Each tProc instruction occupies 72 bits (9 bytes). Complex programs with many loops and sweeps can approach this limit.

How to check program size:

[3]:
# Create a simple program to check its size
class SizeTestProgram(AveragerProgramV2):
    def _initialize(self, cfg):
        self.declare_gen(ch=cfg['gen_ch'], nqz=1)
        self.declare_readout(ch=cfg['ro_ch'], length=cfg['ro_len'])
        self.add_pulse(ch=cfg['gen_ch'], name="test", style="const",
                       freq=100, ro_ch=cfg['ro_ch'], length=0.1, phase=0, gain=1.0)
        self.add_readoutconfig(ch=cfg['ro_ch'], name="ro", freq=100, gen_ch=cfg['gen_ch'])
        self.send_readoutconfig(ch=cfg['ro_ch'], name="ro", t=0)
    def _body(self, cfg):
        self.pulse(ch=cfg['gen_ch'], name="test", t=0)
        self.trigger(ros=[cfg['ro_ch']], pins=[0], t=0.25)

config = {'gen_ch': GEN_CH, 'ro_ch': RO_CH, 'ro_len': 0.5}
prog = SizeTestProgram(soccfg, reps=1, final_delay=0.5, cfg=config)
prog.compile()

print(f"Program size: {len(prog.binprog['pmem'])} instructions")
print(f"Memory usage: {len(prog.binprog['pmem']) / soccfg.tproc['pmem_size'] * 100:.1f}%")
Program size: 20 instructions
Memory usage: 0.5%

2.2 Label Limit (2048 instruction offset)

Jump instructions use 11 bits for address literals, limiting forward jumps to 2048 instructions. This affects loops and conditionals.

Workarounds:

  • Keep your program compact

  • Use reps_innermost=True for large sweeps (loops are nested differently)

  • Break large programs into multiple smaller ones

2.3 Time Limits (32-bit signed integers)

Timing parameters use 32-bit signed integers. The maximum time depends on the timing clock frequency.

[4]:
timing_clk_MHz = soccfg.tproc['f_time']  # e.g., 491.52 MHz
max_time_us = (2**31 - 1) / timing_clk_MHz
print(f"Maximum delay: {max_time_us:.0f} µs ({max_time_us/1e6:.1f} seconds)")
Maximum delay: 4993219 µs (5.0 seconds)

For most experiments, this is not a limiting factor.

3. Common Errors and Solutions

3.1 Pulse Collision Warnings

Warning: Pulse {name} overlaps with previous pulse

Cause: A pulse is scheduled before the previous pulse has finished.

Solution: Use delay_auto() to ensure proper spacing.

# Wrong:
self.pulse(ch=gen_ch, name="pulse1", t=0)
self.pulse(ch=gen_ch, name="pulse2", t=0.3)  # May overlap

# Correct:
self.pulse(ch=gen_ch, name="pulse1", t=0)
self.delay_auto(gap=0.1)  # Wait for pulse1 to finish + 0.1 µs
self.pulse(ch=gen_ch, name="pulse2", t=0)

3.2 Readout Latency Mismatch

Symptom: Feedback readout values don’t match buffered readout values.

Cause: read_wait is too short; the tProc reads stale data.

Solution: Increase read_wait until values match (see Notebook 04).

3.3 Sweep Value Quantization

Symptom: Swept values don’t match the requested range exactly.

Cause: Hardware parameters have limited precision (e.g., gain is 15-bit, phase is 32-bit).

Solution: Use get_pulse_param() to retrieve actual values for accurate analysis.

[5]:
# Example: Check quantization effects
requested_gains = np.linspace(0, 1, 10)

# In a real program, you would do:
# actual_gains = prog.get_pulse_param('pulse', 'gain', as_array=True)

# Simulate quantization (15-bit gain)
max_gain_int = 32766
actual_gains = np.round(requested_gains * max_gain_int) / max_gain_int

print("Requested vs Actual Gains:")
for req, act in zip(requested_gains[:5], actual_gains[:5]):
    print(f"  {req:.4f} -> {act:.4f}")
Requested vs Actual Gains:
  0.0000 -> 0.0000
  0.1111 -> 0.1111
  0.2222 -> 0.2222
  0.3333 -> 0.3333
  0.4444 -> 0.4445

3.4 Buffer Overflow

Symptom: Data appears truncated or corrupted.

Cause: More data was captured than the buffer can hold.

Solution: Increase buffer size or reduce acquisition length.

[6]:
# Estimate required buffer size
reps = 100
sweep_steps = 50
readout_samples = 256  # decimated samples
total_samples = reps * sweep_steps * readout_samples
required_transfers = np.ceil(total_samples / 256)
print(f"Required transfers: {required_transfers}")
Required transfers: 5000.0

4. Debugging Techniques

4.1 Enable Detailed Logging

Set logging level to DEBUG to see detailed assembler output.

[7]:
# Enable debug logging
logging.getLogger("qick.tprocv2_assembler").setLevel(logging.DEBUG)

# Run a simple program to see debug output
class DebugProgram(AveragerProgramV2):
    def _initialize(self, cfg):
        self.declare_gen(ch=cfg['gen_ch'], nqz=1)
        self.declare_readout(ch=cfg['ro_ch'], length=cfg['ro_len'])
        self.add_pulse(ch=cfg['gen_ch'], name="test", style="const",
                       freq=100, ro_ch=cfg['ro_ch'], length=0.1, phase=0, gain=1.0)
        self.add_readoutconfig(ch=cfg['ro_ch'], name="ro", freq=100, gen_ch=cfg['gen_ch'])
        self.send_readoutconfig(ch=cfg['ro_ch'], name="ro", t=0)
    def _body(self, cfg):
        self.pulse(ch=cfg['gen_ch'], name="test", t=0)
        self.trigger(ros=[cfg['ro_ch']], pins=[0], t=0.25)

config = {'gen_ch': GEN_CH, 'ro_ch': RO_CH, 'ro_len': 0.5}
prog = DebugProgram(soccfg, reps=1, final_delay=0.5, cfg=config)

# Reset logging to INFO after debugging
logging.getLogger("qick.tprocv2_assembler").setLevel(logging.WARNING)
DEBUG    [tprocv2_assembler.py:1017] LIST2BIN: ##### LIST 2 BIN
DEBUG    [tprocv2_assembler.py:1029] list2bin: translating {'CMD': 'NOP', 'P_ADDR': 0}
DEBUG    [tprocv2_assembler.py:1029] list2bin: translating {'CMD': 'REG_WR', 'DST': 's12', 'SRC': 'imm', 'LIT': '#0', 'P_ADDR': 1, 'LINE': 1}
DEBUG    [tprocv2_assembler.py:1029] list2bin: translating {'CMD': 'WPORT_WR', 'DST': '4', 'SRC': 'wmem', 'ADDR': '&1', 'TIME': '@0', 'P_ADDR': 2, 'LINE': 2}
DEBUG    [tprocv2_assembler.py:1029] list2bin: translating {'CMD': 'TIME', 'C_OP': 'inc_ref', 'LIT': '#430', 'P_ADDR': 3, 'LINE': 3}
DEBUG    [tprocv2_assembler.py:1029] list2bin: translating {'CMD': 'REG_WR', 'DST': 'r0', 'SRC': 'imm', 'LIT': '#0', 'P_ADDR': 4, 'LINE': 4}
DEBUG    [tprocv2_assembler.py:1029] list2bin: translating {'CMD': 'WPORT_WR', 'DST': '0', 'SRC': 'wmem', 'ADDR': '&0', 'TIME': '@0', 'P_ADDR': 5, 'LINE': 6}
DEBUG    [tprocv2_assembler.py:1029] list2bin: translating {'CMD': 'TRIG', 'SRC': 'set', 'DST': '0', 'TIME': '@108', 'P_ADDR': 6, 'LINE': 7}
DEBUG    [tprocv2_assembler.py:1029] list2bin: translating {'CMD': 'TRIG', 'SRC': 'set', 'DST': '10', 'TIME': '@108', 'P_ADDR': 7, 'LINE': 8}
DEBUG    [tprocv2_assembler.py:1029] list2bin: translating {'CMD': 'TRIG', 'SRC': 'clr', 'DST': '0', 'TIME': '@118', 'P_ADDR': 8, 'LINE': 9}
DEBUG    [tprocv2_assembler.py:1029] list2bin: translating {'CMD': 'TRIG', 'SRC': 'clr', 'DST': '10', 'TIME': '@118', 'P_ADDR': 9, 'LINE': 10}
DEBUG    [tprocv2_assembler.py:1029] list2bin: translating {'CMD': 'REG_WR', 'DST': 's15', 'SRC': 'label', 'LABEL': 'SKIP', 'P_ADDR': 10, 'LINE': 11, 'ADDR': '&12'}
DEBUG    [tprocv2_assembler.py:1029] list2bin: translating {'CMD': 'WAIT', 'ADDR': 's15', 'C_OP': 'time', 'TIME': '@323', 'P_ADDR': 11, 'LINE': 12}
DEBUG    [tprocv2_assembler.py:1083] COMMAND_TRANSLATION: Command Wait add one more instruction 12
DEBUG    [tprocv2_assembler.py:1029] list2bin: translating {'CMD': 'TIME', 'C_OP': 'inc_ref', 'LIT': '#538', 'P_ADDR': 13, 'LINE': 13}
DEBUG    [tprocv2_assembler.py:1029] list2bin: translating {'CMD': 'REG_WR', 'DST': 's12', 'SRC': 'op', 'OP': 's12 + #1', 'P_ADDR': 14, 'LINE': 14}
DEBUG    [tprocv2_assembler.py:1029] list2bin: translating {'CMD': 'REG_WR', 'DST': 's15', 'SRC': 'label', 'LABEL': 'reps', 'P_ADDR': 15, 'LINE': 15, 'ADDR': '&5'}
DEBUG    [tprocv2_assembler.py:1029] list2bin: translating {'CMD': 'TEST', 'OP': 'r0 - #0', 'P_ADDR': 16, 'LINE': 16}
DEBUG    [tprocv2_assembler.py:1029] list2bin: translating {'CMD': 'JUMP', 'ADDR': 's15', 'IF': 'NZ', 'WR': 'r0 op', 'OP': 'r0 + #1', 'P_ADDR': 17, 'LINE': 17}
DEBUG    [tprocv2_assembler.py:1029] list2bin: translating {'CMD': 'REG_WR', 'DST': 's15', 'SRC': 'label', 'LABEL': 'NEXT', 'P_ADDR': 18, 'LINE': 18, 'ADDR': '&19'}
DEBUG    [tprocv2_assembler.py:1029] list2bin: translating {'CMD': 'JUMP', 'ADDR': 's15', 'P_ADDR': 19, 'LINE': 19}

4.2 Inspect Program Structure

Use print(prog) to see the compiled program structure.

[8]:
print(prog)
macros:
        WriteReg(dst='s_core_w1', src=0)
        ConfigReadout(ch=0, name='ro', t=0, tag=None, t_params={'t': <qick.asm_v2.QickParam object at 0xffff66c569e0>}, t_regs={'t': 0})
        Delay(t=1.0, auto=True, gens=True, ros=True, tag=None, t_params={'t': <qick.asm_v2.QickParam object at 0xffff66c56320>}, t_regs={'t': 430})
        OpenLoop(n=1, name='reps')
        Pulse(ch=0, name='test', t=0, tag=None, t_params={'t': <qick.asm_v2.QickParam object at 0xffff66c54910>}, t_regs={'t': 0})
        Trigger(ros=[0], tts=[], pins=[0], t=0.25, width=0.023251488095238096, ddr4=False, mr=False, tag=None, t_params={'t': <qick.asm_v2.QickParam object at 0xffff66c563b0>, 'width': <qick.asm_v2.QickParam object at 0xffff66c55e70>}, t_regs={'t': 108, 'width': 10}, outdict=defaultdict(<class 'int'>, {}), trigset={0, 10})
        Wait(t=0, auto=True, gens=False, ros=True, tag=None, no_warn=True, t_params={'t': <qick.asm_v2.QickParam object at 0xffff66c562f0>}, t_regs={'t': 323})
        Delay(t=0.5, auto=True, gens=True, ros=True, tag=None, t_params={'t': <qick.asm_v2.QickParam object at 0xffff66c55120>}, t_regs={'t': 538})
        IncReg(dst='s_core_w1', src=1)
        CloseLoop()
        End()
registers:
        reps: QickRegisterV2(addr=0, init=None)
pulses:
        test: QickPulse(waveforms=[Waveform(name='test_w0', freq=QickRawParam(par='freq', start=44810940, spans={}, quantize=10, steps={}), phase=QickRawParam(par='phase', start=0, spans={}, quantize=1, steps={}), env=0, gain=QickRawParam(par='gain', start=32766, spans={}, quantize=1, steps={}), length=QickRawParam(par='length', start=60, spans={}, quantize=1, steps={}), conf=9)])
        ro: QickPulse(waveforms=[Waveform(name='ro_w0', freq=QickRawParam(par='freq', start=-174762666, spans={}, quantize=-39, steps={}), phase=0, env=0, gain=0, length=3, conf=264)])
expanded ASM:
             NOP
             REG_WR s12 imm #0
             WPORT_WR p4 wmem [&1] @0
             TIME #430 inc_ref
             REG_WR r0 imm #0
        reps:
             WPORT_WR p0 wmem [&0] @0
             TRIG p0 set @108
             TRIG p10 set @108
             TRIG p0 clr @118
             TRIG p10 clr @118
             REG_WR s15 label SKIP
             WAIT [s15] @323 time
             TIME #538 inc_ref
             REG_WR s12 op -op(s12 + #1)
             REG_WR s15 label reps
             TEST -op(r0 - #0)
             JUMP [s15] -if(NZ) -wr(r0 op) -op(r0 + #1)
             REG_WR s15 label NEXT
             JUMP [s15]

4.3 Check Specific Macros

Use get_macro(tag) to inspect individual instructions.

[9]:
# Create a program with tags to inspect macros
class TaggedProgram(AveragerProgramV2):
    def _initialize(self, cfg):
        self.declare_gen(ch=cfg['gen_ch'], nqz=1)
        self.declare_readout(ch=cfg['ro_ch'], length=cfg['ro_len'])
        self.add_pulse(ch=cfg['gen_ch'], name="test", style="const",
                       freq=100, ro_ch=cfg['ro_ch'], length=0.1, phase=0, gain=1.0)
        self.add_readoutconfig(ch=cfg['ro_ch'], name="ro", freq=100, gen_ch=cfg['gen_ch'])
        self.send_readoutconfig(ch=cfg['ro_ch'], name="ro", t=0)

    def _body(self, cfg):
        self.pulse(ch=cfg['gen_ch'], name="test", t=0)
        self.delay_auto(0.1)   # sin keyword 'gap', sin 'tag'
        self.trigger(ros=[cfg['ro_ch']], pins=[0], t=0.25)

config = {'gen_ch': GEN_CH, 'ro_ch': RO_CH, 'ro_len': 0.5}
prog_tagged = TaggedProgram(soccfg, reps=1, final_delay=0.5, cfg=config)
print("Program created with", len(prog_tagged.macro_list), "macros")
print(prog_tagged)  # inspect the full structure
Program created with 12 macros
macros:
        WriteReg(dst='s_core_w1', src=0)
        ConfigReadout(ch=0, name='ro', t=0, tag=None, t_params={'t': <qick.asm_v2.QickParam object at 0xffff66c9a980>}, t_regs={'t': 0})
        Delay(t=1.0, auto=True, gens=True, ros=True, tag=None, t_params={'t': <qick.asm_v2.QickParam object at 0xffff66c9aa10>}, t_regs={'t': 430})
        OpenLoop(n=1, name='reps')
        Pulse(ch=0, name='test', t=0, tag=None, t_params={'t': <qick.asm_v2.QickParam object at 0xffff66c9ab30>}, t_regs={'t': 0})
        Delay(t=0.1, auto=True, gens=True, ros=True, tag=None, t_params={'t': <qick.asm_v2.QickParam object at 0xffff66c9aad0>}, t_regs={'t': 86})
        Trigger(ros=[0], tts=[], pins=[0], t=0.25, width=0.023251488095238096, ddr4=False, mr=False, tag=None, t_params={'t': <qick.asm_v2.QickParam object at 0xffff66c9a9b0>, 'width': <qick.asm_v2.QickParam object at 0xffff66c9aa40>}, t_regs={'t': 108, 'width': 10}, outdict=defaultdict(<class 'int'>, {}), trigset={0, 10})
        Wait(t=0, auto=True, gens=False, ros=True, tag=None, no_warn=True, t_params={'t': <qick.asm_v2.QickParam object at 0xffff66c9ad10>}, t_regs={'t': 323})
        Delay(t=0.5, auto=True, gens=True, ros=True, tag=None, t_params={'t': <qick.asm_v2.QickParam object at 0xffff66c9ab60>}, t_regs={'t': 538})
        IncReg(dst='s_core_w1', src=1)
        CloseLoop()
        End()
registers:
        reps: QickRegisterV2(addr=0, init=None)
pulses:
        test: QickPulse(waveforms=[Waveform(name='test_w0', freq=QickRawParam(par='freq', start=44810940, spans={}, quantize=10, steps={}), phase=QickRawParam(par='phase', start=0, spans={}, quantize=1, steps={}), env=0, gain=QickRawParam(par='gain', start=32766, spans={}, quantize=1, steps={}), length=QickRawParam(par='length', start=60, spans={}, quantize=1, steps={}), conf=9)])
        ro: QickPulse(waveforms=[Waveform(name='ro_w0', freq=QickRawParam(par='freq', start=-174762666, spans={}, quantize=-39, steps={}), phase=0, env=0, gain=0, length=3, conf=264)])
expanded ASM:
             NOP
             REG_WR s12 imm #0
             WPORT_WR p4 wmem [&1] @0
             TIME #430 inc_ref
             REG_WR r0 imm #0
        reps:
             WPORT_WR p0 wmem [&0] @0
             TIME #86 inc_ref
             TRIG p0 set @108
             TRIG p10 set @108
             TRIG p0 clr @118
             TRIG p10 clr @118
             REG_WR s15 label SKIP
             WAIT [s15] @323 time
             TIME #538 inc_ref
             REG_WR s12 op -op(s12 + #1)
             REG_WR s15 label reps
             TEST -op(r0 - #0)
             JUMP [s15] -if(NZ) -wr(r0 op) -op(r0 + #1)
             REG_WR s15 label NEXT
             JUMP [s15]
[10]:
# Inspect acros from program
print("All macros:")
for i, macro in enumerate(prog_tagged.macro_list):
    print(f"  [{i}] {type(macro).__name__}: {macro}")

# Buscar por tipo en lugar de por tag
def get_macros_by_type(prog, macro_type_name):
    return [(i, m) for i, m in enumerate(prog.macro_list)
            if type(m).__name__ == macro_type_name]

print("\nPulse macros:",  get_macros_by_type(prog_tagged, 'Pulse'))
print("Delay macros:",   get_macros_by_type(prog_tagged, 'Delay'))
print("Trigger macros:", get_macros_by_type(prog_tagged, 'Trigger'))
All macros:
  [0] WriteReg: WriteReg(dst='s_core_w1', src=0)
  [1] ConfigReadout: ConfigReadout(ch=0, name='ro', t=0, tag=None, t_params={'t': <qick.asm_v2.QickParam object at 0xffff66c9a980>}, t_regs={'t': 0})
  [2] Delay: Delay(t=1.0, auto=True, gens=True, ros=True, tag=None, t_params={'t': <qick.asm_v2.QickParam object at 0xffff66c9aa10>}, t_regs={'t': 430})
  [3] OpenLoop: OpenLoop(n=1, name='reps')
  [4] Pulse: Pulse(ch=0, name='test', t=0, tag=None, t_params={'t': <qick.asm_v2.QickParam object at 0xffff66c9ab30>}, t_regs={'t': 0})
  [5] Delay: Delay(t=0.1, auto=True, gens=True, ros=True, tag=None, t_params={'t': <qick.asm_v2.QickParam object at 0xffff66c9aad0>}, t_regs={'t': 86})
  [6] Trigger: Trigger(ros=[0], tts=[], pins=[0], t=0.25, width=0.023251488095238096, ddr4=False, mr=False, tag=None, t_params={'t': <qick.asm_v2.QickParam object at 0xffff66c9a9b0>, 'width': <qick.asm_v2.QickParam object at 0xffff66c9aa40>}, t_regs={'t': 108, 'width': 10}, outdict=defaultdict(<class 'int'>, {}), trigset={0, 10})
  [7] Wait: Wait(t=0, auto=True, gens=False, ros=True, tag=None, no_warn=True, t_params={'t': <qick.asm_v2.QickParam object at 0xffff66c9ad10>}, t_regs={'t': 323})
  [8] Delay: Delay(t=0.5, auto=True, gens=True, ros=True, tag=None, t_params={'t': <qick.asm_v2.QickParam object at 0xffff66c9ab60>}, t_regs={'t': 538})
  [9] IncReg: IncReg(dst='s_core_w1', src=1)
  [10] CloseLoop: CloseLoop()
  [11] End: End()

Pulse macros: [(4, Pulse(ch=0, name='test', t=0, tag=None, t_params={'t': <qick.asm_v2.QickParam object at 0xffff66c9ab30>}, t_regs={'t': 0}))]
Delay macros: [(2, Delay(t=1.0, auto=True, gens=True, ros=True, tag=None, t_params={'t': <qick.asm_v2.QickParam object at 0xffff66c9aa10>}, t_regs={'t': 430})), (5, Delay(t=0.1, auto=True, gens=True, ros=True, tag=None, t_params={'t': <qick.asm_v2.QickParam object at 0xffff66c9aad0>}, t_regs={'t': 86})), (8, Delay(t=0.5, auto=True, gens=True, ros=True, tag=None, t_params={'t': <qick.asm_v2.QickParam object at 0xffff66c9ab60>}, t_regs={'t': 538}))]
Trigger macros: [(6, Trigger(ros=[0], tts=[], pins=[0], t=0.25, width=0.023251488095238096, ddr4=False, mr=False, tag=None, t_params={'t': <qick.asm_v2.QickParam object at 0xffff66c9a9b0>, 'width': <qick.asm_v2.QickParam object at 0xffff66c9aa40>}, t_regs={'t': 108, 'width': 10}, outdict=defaultdict(<class 'int'>, {}), trigset={0, 10}))]

4.4 Verify Pulse Parameters

Use get_pulse_param() to check actual parameters after quantization.

[11]:
# In a sweep program, you can check all swept values
class SweepCheckProgram(AveragerProgramV2):
    def _initialize(self, cfg):
        self.declare_gen(ch=cfg['gen_ch'], nqz=1)
        self.declare_readout(ch=cfg['ro_ch'], length=cfg['ro_len'])
        self.add_loop("sweep", cfg['steps'])
        self.add_pulse(ch=cfg['gen_ch'], name="test", style="const",
                       freq=cfg['freq'], ro_ch=cfg['ro_ch'], length=0.1,
                       phase=QickSweep1D("sweep", -180, 180),
                       gain=QickSweep1D("sweep", 0, 1))
        self.add_readoutconfig(ch=cfg['ro_ch'], name="ro", freq=cfg['freq'], gen_ch=cfg['gen_ch'])
        self.send_readoutconfig(ch=cfg['ro_ch'], name="ro", t=0)
    def _body(self, cfg):
        self.pulse(ch=cfg['gen_ch'], name="test", t=0)
        self.trigger(ros=[cfg['ro_ch']], pins=[0], t=0.25)

config_check = {'gen_ch': GEN_CH, 'ro_ch': RO_CH, 'freq': 100, 'steps': 5, 'ro_len': 0.5}
prog_check = SweepCheckProgram(soccfg, reps=1, final_delay=0.5, cfg=config_check)

phases = prog_check.get_pulse_param('test', 'phase', as_array=True)
gains = prog_check.get_pulse_param('test', 'gain', as_array=True)

print(f"Phase values: {phases}")
print(f"Gain values: {gains}")
Phase values: [-180.  -90.    0.   90.  180.]
Gain values: [0.         0.24998474 0.49996948 0.74995422 0.99993896]

5. Advanced Parameters

5.1 The reps_innermost Parameter

By default, the reps loop (experiment repetitions) is the outermost loop. This reduces sensitivity to slow drifts.

[12]:
class LoopOrderProgram(AveragerProgramV2):
    def _initialize(self, cfg):
        self.declare_gen(ch=cfg['gen_ch'], nqz=1)
        self.declare_readout(ch=cfg['ro_ch'], length=cfg['ro_len'])
        self.add_loop("sweep", 3)
        self.add_pulse(ch=cfg['gen_ch'], name="test", style="const",
                       freq=100, ro_ch=cfg['ro_ch'], length=0.1, phase=0,
                       gain=QickSweep1D("sweep", 0, 1))
        self.add_readoutconfig(ch=cfg['ro_ch'], name="ro", freq=100, gen_ch=cfg['gen_ch'])
        self.send_readoutconfig(ch=cfg['ro_ch'], name="ro", t=0)
    def _body(self, cfg):
        self.pulse(ch=cfg['gen_ch'], name="test", t=0)
        self.trigger(ros=[cfg['ro_ch']], pins=[0], t=0.25)

config_loop = {'gen_ch': GEN_CH, 'ro_ch': RO_CH, 'ro_len': 0.5}

# Default: reps outermost
prog_default = LoopOrderProgram(soccfg, reps=4, final_delay=0.5, cfg=config_loop, reps_innermost=False)

# Alternative: reps innermost
prog_innermost = LoopOrderProgram(soccfg, reps=4, final_delay=0.5, cfg=config_loop, reps_innermost=True)

print("Default (reps outermost): Sweep completes all steps before next repetition")
print("Alternative (reps innermost): All repetitions at each sweep step before moving to next step")
Default (reps outermost): Sweep completes all steps before next repetition
Alternative (reps innermost): All repetitions at each sweep step before moving to next step

5.2 The final_delay and initial_delay Parameters

These parameters add automatic delays at the beginning and end of each shot.

[13]:
# initial_delay: added after _initialize() completes
# final_delay: added at the end of each shot before the next repetition

prog_with_delays = LoopOrderProgram(soccfg, reps=2, final_delay=10.0,
                                   cfg=config_loop, initial_delay=5.0)
print("Program with 5 µs initial delay and 10 µs final delay per shot")
Program with 5 µs initial delay and 10 µs final delay per shot

6. Quick Reference: Parameter Quantization

Parameter

Units

Resolution

Range

Frequency

MHz

Depends on DDS bit-width

±DDS_range

Phase

degrees

360/2^32 ≈ 8.4e-8°

0-360° (wraps)

Gain

linear

1/32766 ≈ 3e-5

0 to 1

Time/Duration

µs

1/timing_clock (≈2 ns)

±2^31 ticks

Length (const pulse)

µs

Same as time

>0

Length (flat-top flat)

µs

Same as time

>0

7. Troubleshooting Checklist

When an experiment doesn’t work as expected, check these items in order:

  1. Hardware Connection - Is the board connected? Does soccfg show correct configuration?

  2. Firmware Version - Is the firmware compatible with your QICK version?

  3. Channel Configuration - Are the generator and readout channels correct?

  4. Pulse Parameters - Are frequency, gain, and length within valid ranges?

  5. Timeline - Are pulses colliding? Use delay_auto() for spacing.

  6. Readout Latency - Is read_wait properly tuned for feedback?

  7. Buffer Arm - Are buffers armed before running? Do they have enough capacity?

  8. Program Size - Is the program exceeding tProc memory limits?

  9. Logging - Set logging to DEBUG and look for warnings or errors.

  10. Data Retrieval - Are you using the correct time axis for your buffer type?

8. Summary

This reference notebook covers:

  • Hardware limits of the tProc (program memory, labels, time ranges)

  • Common errors and their solutions

  • Debugging techniques (logging, program inspection, parameter verification)

  • Advanced parameters (reps_innermost, final_delay, initial_delay)

  • Parameter quantization reference table

  • Troubleshooting checklist

Next Steps:

  • Proceed to `10_Multi_Board_Synchronization.ipynb <./10_Multi_Board_Synchronization.ipynb>`__ to learn about how to connect and work with multiple boards inter-connected.

  • Review the other notebooks in this series for detailed examples

  • Refer to the QICK Documentation for complete API reference

  • Join the QICK community for support