Readout System - QICK Firmware
Overview
The QICK readout system is built around two main IP blocks:
Readout Block - Digital down-conversion (DDC), filtering, and decimation
Average + Buffer Block - Data accumulation and storage
The readout process is triggered by a dedicated single-bit trig_N_o pin
from the tProcessor (one per averaging buffer instance – see
tProc Waveform Ports vs. Trigger Outputs in Firmware for the full pin model) and can be
configured for raw sample capture or coherent averaging.
Readout Block
The readout block performs:
Digital Down-Conversion (DDC) - Mixes the ADC input with a DDS tone
FIR Filtering - Anti-aliasing filter
Decimation by 8 - Reduces sample rate for easier processing
Key parameters:
Parameter |
Description |
|---|---|
DDS frequency |
Configured via register (not intended for real-time hopping) |
FIR taps |
Fixed coefficients (compiled into firmware) |
Decimation factor |
Fixed at 8 |
Input selection |
Can route raw input, DDS wave, or frequency-shifted signal |
The user can select which signal is sent to the FIR stage using an output selection register.
Average + Buffer Block
This block receives the decimated data stream and can:
Store raw samples - Capture individual samples for analysis
Perform coherent averaging - Accumulate multiple acquisitions
Capabilities:
Parameter |
Value |
|---|---|
Raw buffer length |
Board-dependent, |
Accumulated buffer length |
Board-dependent, |
Trigger source |
One dedicated |
The buffer IP that stores/accumulates this data (registers, DMA transfer, edge-counter mode) is documented separately in Averager + Buffer (axis_avg_buffer) - QICK Firmware.
Python Interface
Normal QICK programs do not poke the readout/buffer drivers directly.
The workflow, inside a QickProgram (e.g. an
AveragerProgramV2 subclass), is:
Declare each readout channel you’ll use, once, with
QickProgram.declare_readout()– this sets the downconversion frequency/phase/output-select and the readout window length.Fire the ADC capture at the right point in your sequence with
QickProgramV2.trigger()(or the tProc v1 equivalent).After running the program, pull the data back with
AcquireMixin.acquire()(accumulated) orAcquireMixin.acquire_decimated()(raw waveform) – see Accumulated vs. decimated readout for which one to use.
from qick import *
soc = QickSoc()
class LoopbackProgram(AveragerProgramV2):
def _initialize(self, cfg):
# readout channel 0: 100 MHz downconversion, 3 us window
self.declare_readout(ch=0, length=3.0, freq=100.0, gen_ch=0)
self.declare_gen(ch=0, nqz=1)
self.add_pulse(ch=0, name="probe", style="const",
freq=100.0, phase=0, gain=0.5, length=3.0)
def _body(self, cfg):
self.trigger(ros=[0], t=0) # arm readout channel 0
self.pulse(ch=0, name="probe", t=0)
self.delay_auto(t=1.0) # wait for pulse+readout to finish before looping
prog = LoopbackProgram(soccfg, reps=1000, final_delay=10.0, cfg={})
# Accumulated: one (I, Q) point per rep, averaged
iq_avg = prog.acquire(soc)
# Decimated: the raw readout waveform (for debugging the window/pulse)
prog2 = LoopbackProgram(soccfg, reps=1, final_delay=10.0, cfg={})
iq_dec = prog2.acquire_decimated(soc)
Low-level driver access:
The readout and buffer are still reachable directly, as soc.readouts[i]
and soc.avg_bufs[i], for debugging or hand-rolled acquisition outside
the acquire()/acquire_decimated() flow – see Averager + Buffer (axis_avg_buffer) - QICK Firmware’s
Python Interface section for the buffer-level config_avg/
transfer_avg/config_buf/transfer_buf calls this normally sits on
top of. AbsReadout.set_all()/set_all_int on the readout driver
itself are explicitly for debugging only – the recommended way to configure
a readout’s frequency/phase/output-select is declare_readout().
Hardware Considerations
Clock frequencies:
ADC sampling rate:
soc.fs_adc(typically 3072 MHz)After decimation by 8: effective rate =
soc.fs_adc / 8
Buffer sizes:
Raw mode: up to
soc.cfg['readouts'][i]['buf_maxlen']I/Q pairs (1024 on the reference board, i.e. 2k total samples)Average mode: up to
soc.cfg['readouts'][i]['avg_maxlen']I/Q pairs (16384 on the reference board, i.e. 32k total) – read these from your own board’s config rather than assuming a number, see Averager + Buffer (axis_avg_buffer) - QICK Firmware.
Example: Calculating acquisition time
n_samples = 1024
decimation = 8
adc_rate = soc.fs_adc # Hz
sample_period = 1 / (adc_rate / decimation) # seconds per decimated sample
acquisition_time = n_samples * sample_period
print(f"Acquisition takes {acquisition_time * 1e6:.2f} us")