Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Sync rfboard with master #210

Merged
merged 10 commits into from
Dec 14, 2023
11 changes: 10 additions & 1 deletion docs/papers.rst
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
QICK papers
=================================================

This list of academic papers that used the QICK was last updated September 16, 2023.
This list of academic papers that used the QICK was last updated December 2, 2023.

Superconducting qubit
#############################
Expand All @@ -11,14 +11,23 @@ Superconducting qubit
* `Martinez, J. G. C., Chiu, C. S., Smitham, B. M. & Houck, A. A. Flat-band localization and interaction-induced delocalization of photons (2023) <https://arxiv.org/abs/2303.02170>`_.
* `Bryon, J. et al. Time-Dependent Magnetic Flux in Devices for Circuit Quantum Electrodynamics. Phys. Rev. Appl. 19, 034031 (2023) <https://link.aps.org/doi/10.1103/PhysRevApplied.19.034031>`_.

Spin defects
#############################
* `Riendeau, E. et al. Quantum Instrumentation Control Kit -- Defect Arbitrary Waveform Generator (QICK-DAWG): A Quantum Sensing Control Framework for Quantum Defects. (2023) <https://arxiv.org/abs/2311.18253>`_.

Single-photon detection
#############################
* `Xie, S. et al. Entangled Photon Pair Source Demonstrator using the Quantum Instrumentation Control Kit System (2023) <https://arxiv.org/abs/2304.01190>`_.

Dark matter detection
#############################
* `Knirck, S. et al. First Results from a Broadband Search for Dark Photon Dark Matter in the 44 to 52 ueV range with a coaxial dish antenna. (2023) <https://arxiv.org/abs/2310.13891>`_.

General-purpose software for quantum experiments
#############################
* `Efthymiou, S. et al. Qibolab: an open-source hybrid quantum operating system (2023) <https://arxiv.org/abs/2308.06313>`_.

QICK system
#############################
* `Ding, C. et al. Experimental advances with the QICK (Quantum Instrumentation Control Kit) for superconducting quantum hardware. (2023) <https://arxiv.org/abs/2311.17171>`_.
* `Stefanazzi, L. et al. The QICK (Quantum Instrumentation Control Kit): Readout and control for qubits and detectors. Rev. Sci. Instrum 93, 044709 (2022) <https://pubs.aip.org/aip/rsi/article/93/4/044709/2849124/The-QICK-Quantum-Instrumentation-Control-Kit>`_.
2 changes: 1 addition & 1 deletion qick_lib/qick/VERSION
Original file line number Diff line number Diff line change
@@ -1 +1 @@
0.2.207
0.2.209
172 changes: 90 additions & 82 deletions qick_lib/qick/asm_v1.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,14 +20,18 @@
class AbsRegisterManager(ABC):
"""Generic class for managing registers that will be written to a tProc-controlled block (signal generator or readout).
"""
PULSE_REGISTERS = ["freq", "phase", "addr", "gain", "mode", "t", "addr2", "gain2", "mode2", "mode3"]

def __init__(self, prog, tproc_ch, ch_name):
self.prog = prog
# the tProc output channel controlled by this manager
self.tproc_ch = tproc_ch
# the name of this block (for messages)
self.ch_name = ch_name
# the register page used by this manager
self.rp = prog._ch_page_tproc(tproc_ch)
# the register page and register map for this manager
# these are initialized by QickProgram._allocate_registers
self.rp = None
self.regmap = None
# default parameters
self.defaults = {}
# registers that are fully defined by the default parameters
Expand Down Expand Up @@ -58,9 +62,9 @@ def set_reg(self, name, val, comment=None, defaults=False):
elif name in self.default_regs:
# this reg was already written, so we skip it this time
return
r = self.prog._sreg_tproc(self.tproc_ch, name)
rp, r = self.regmap[(self.ch, name)]
if comment is None: comment = f'{name} = {val}'
self.prog.safe_regwi(self.rp, r, val, comment)
self.prog.safe_regwi(rp, r, val, comment)

def set_defaults(self, kwargs):
"""Set default values for parameters.
Expand Down Expand Up @@ -111,9 +115,10 @@ class ReadoutManager(AbsRegisterManager):
PARAMS_OPTIONAL = ['phrst', 'mode', 'outsel']

def __init__(self, prog, ro_ch):
self.rocfg = prog.soccfg['readouts'][ro_ch]
self.ch = ro_ch
self.rocfg = prog.soccfg['readouts'][self.ch]
tproc_ch = self.rocfg['tproc_ctrl']
super().__init__(prog, tproc_ch, "readout %d"%(ro_ch))
super().__init__(prog, tproc_ch, "readout %d"%(self.ch))

def check_params(self, params):
"""Check whether the parameters defined for a pulse are supported and sufficient for this generator and pulse type.
Expand Down Expand Up @@ -145,7 +150,7 @@ def write_regs(self, params, defaults):
phrst, mode, outsel = [params.get(x) for x in ['phrst', 'mode', 'outsel']]
mc = self.get_mode_code(phrst=phrst, mode=mode, outsel=outsel, length=params['length'])
self.set_reg('mode', mc, f'mode | outsel = 0b{mc//2**16:>05b} | length = {mc % 2**16} ')
self.next_pulse['regs'].append([self.prog._sreg_tproc(self.tproc_ch,x) for x in ['freq', '0', 'mode', '0', '0']])
self.next_pulse['regs'].append([self.regmap[(self.ch, x)][1] for x in ['freq', '0', 'mode', '0', '0']])

def get_mode_code(self, length, outsel=None, mode=None, phrst=None):
"""Creates mode code for the mode register in the set command, by setting flags and adding the pulse length.
Expand Down Expand Up @@ -198,10 +203,12 @@ class AbsGenManager(AbsRegisterManager):
PARAMS_OPTIONAL = {}

def __init__(self, prog, gen_ch):
self.gencfg = prog.soccfg['gens'][gen_ch]
self.ch = gen_ch
self.gencfg = prog.soccfg['gens'][self.ch]
tproc_ch = self.gencfg['tproc_ch']
super().__init__(prog, tproc_ch, "generator %d"%(gen_ch))
super().__init__(prog, tproc_ch, "generator %d"%(self.ch))
self.samps_per_clk = self.gencfg['samps_per_clk']
self.tmux_ch = self.gencfg.get('tmux_ch') # default to None if undefined

# dictionary of defined pulse envelopes
self.envelopes = prog.envelopes[gen_ch]
Expand Down Expand Up @@ -373,12 +380,12 @@ def write_regs(self, params, defaults):
if style=='const':
mc = self.get_mode_code(phrst=phrst, stdysel=stdysel, mode=mode, outsel="dds", length=params['length'])
self.set_reg('mode', mc, f'phrst| stdysel | mode | | outsel = 0b{mc//2**16:>05b} | length = {mc % 2**16} ')
self.next_pulse['regs'].append([self.prog._sreg_tproc(self.tproc_ch,x) for x in ['freq', 'phase', '0', 'gain', 'mode']])
self.next_pulse['regs'].append([self.regmap[(self.ch,x)][1] for x in ['freq', 'phase', '0', 'gain', 'mode']])
self.next_pulse['length'] = params['length']
elif style=='arb':
mc = self.get_mode_code(phrst=phrst, stdysel=stdysel, mode=mode, outsel=outsel, length=wfm_length)
self.set_reg('mode', mc, f'phrst| stdysel | mode | | outsel = 0b{mc//2**16:>05b} | length = {mc % 2**16} ')
self.next_pulse['regs'].append([self.prog._sreg_tproc(self.tproc_ch,x) for x in ['freq', 'phase', 'addr', 'gain', 'mode']])
self.next_pulse['regs'].append([self.regmap[(self.ch,x)][1] for x in ['freq', 'phase', 'addr', 'gain', 'mode']])
self.next_pulse['length'] = wfm_length
elif style=='flat_top':
# address for ramp-down
Expand All @@ -395,11 +402,17 @@ def write_regs(self, params, defaults):
mc = self.get_mode_code(phrst=False, stdysel=stdysel, mode='oneshot', outsel='product', length=wfm_length//2)
self.set_reg('mode3', mc, f'phrst| stdysel | mode | | outsel = 0b{mc//2**16:>05b} | length = {mc % 2**16} ')

self.next_pulse['regs'].append([self.prog._sreg_tproc(self.tproc_ch,x) for x in ['freq', 'phase', 'addr', 'gain', 'mode2']])
self.next_pulse['regs'].append([self.prog._sreg_tproc(self.tproc_ch,x) for x in ['freq', 'phase', '0', 'gain2', 'mode']])
self.next_pulse['regs'].append([self.prog._sreg_tproc(self.tproc_ch,x) for x in ['freq', 'phase', 'addr2', 'gain', 'mode3']])
self.next_pulse['regs'].append([self.regmap[(self.ch,x)][1] for x in ['freq', 'phase', 'addr', 'gain', 'mode2']])
self.next_pulse['regs'].append([self.regmap[(self.ch,x)][1] for x in ['freq', 'phase', '0', 'gain2', 'mode']])
self.next_pulse['regs'].append([self.regmap[(self.ch,x)][1] for x in ['freq', 'phase', 'addr2', 'gain', 'mode3']])
self.next_pulse['length'] = (wfm_length//2)*2 + params['length']

def get_mode_code(self, **kwargs):
mc = super().get_mode_code(**kwargs)
if self.tmux_ch is not None:
mc += (self.tmux_ch << 24)
return mc


class InterpolatedGenManager(AbsGenManager):
"""Manager for the interpolated signal generators.
Expand Down Expand Up @@ -458,15 +471,23 @@ def write_regs(self, params, defaults):
self.next_pulse = {}
self.next_pulse['rp'] = self.rp
self.next_pulse['regs'] = []

# if we use the tproc mux, the mux address needs to be written to its own register
if self.tmux_ch is None:
tmux_reg = '0'
else:
self.set_reg('mode3', self.tmux_ch << 24)
tmux_reg = 'mode3'

if style=='const':
mc = self.get_mode_code(phrst=phrst, stdysel=stdysel, mode=mode, outsel="dds", length=params['length'])
self.set_reg('mode', mc, f'stdysel | mode | outsel = 0b{mc//2**16:>05b} | length = {mc % 2**16} ')
self.next_pulse['regs'].append([self.prog._sreg_tproc(self.tproc_ch,x) for x in ['freq', 'addr', 'mode', '0', '0']])
self.next_pulse['regs'].append([self.regmap[(self.ch,x)][1] for x in ['freq', 'addr', 'mode', '0', tmux_reg]])
self.next_pulse['length'] = params['length']
elif style=='arb':
mc = self.get_mode_code(phrst=phrst, stdysel=stdysel, mode=mode, outsel=outsel, length=wfm_length)
self.set_reg('mode', mc, f'stdysel | mode | outsel = 0b{mc//2**16:>05b} | length = {mc % 2**16} ')
self.next_pulse['regs'].append([self.prog._sreg_tproc(self.tproc_ch,x) for x in ['freq', 'addr', 'mode', '0', '0']])
self.next_pulse['regs'].append([self.regmap[(self.ch,x)][1] for x in ['freq', 'addr', 'mode', '0', tmux_reg]])
self.next_pulse['length'] = wfm_length
elif style=='flat_top':
maxv_scale = self.gencfg['maxv_scale']
Expand All @@ -485,11 +506,11 @@ def write_regs(self, params, defaults):
# gain+addr for ramp-down
self.set_reg('addr2', (gain << 16) | addr+(wfm_length+1)//2, f'gain = {gain} | addr = {addr}')

self.next_pulse['regs'].append([self.prog._sreg_tproc(self.tproc_ch,x) for x in ['freq', 'addr', 'mode2', '0', '0']])
self.next_pulse['regs'].append([self.prog._sreg_tproc(self.tproc_ch,x) for x in ['freq', 'gain', 'mode', '0', '0']])
self.next_pulse['regs'].append([self.prog._sreg_tproc(self.tproc_ch,x) for x in ['freq', 'addr2', 'mode2', '0', '0']])
self.next_pulse['regs'].append([self.regmap[(self.ch,x)][1] for x in ['freq', 'addr', 'mode2', '0', tmux_reg]])
self.next_pulse['regs'].append([self.regmap[(self.ch,x)][1] for x in ['freq', 'gain', 'mode', '0', tmux_reg]])
self.next_pulse['regs'].append([self.regmap[(self.ch,x)][1] for x in ['freq', 'addr2', 'mode2', '0', tmux_reg]])
# workaround for FIR bug: we play a zero-gain DDS pulse (length equal to the flat segment) after the ramp-down, which brings the FIR to zero
self.next_pulse['regs'].append([self.prog._sreg_tproc(self.tproc_ch,x) for x in ['0', '0', 'mode', '0', '0']])
self.next_pulse['regs'].append([self.regmap[(self.ch,x)][1] for x in ['0', '0', 'mode', '0', tmux_reg]])
# set the pulse duration (including the extra duration for the FIR workaround)
self.next_pulse['length'] = (wfm_length//2)*2 + 2*params['length']

Expand Down Expand Up @@ -531,7 +552,7 @@ def write_regs(self, params, defaults):
self.next_pulse = {}
self.next_pulse['rp'] = self.rp
self.next_pulse['regs'] = []
self.next_pulse['regs'].append([self.prog._sreg_tproc(self.tproc_ch,x) for x in ['freq', 'phase', '0', '0', '0']])
self.next_pulse['regs'].append([self.regmap[(self.ch,x)][1] for x in ['freq', 'phase', '0', '0', '0']])
self.next_pulse['length'] = params['length']

class QickProgram(AbsQickProgram):
Expand Down Expand Up @@ -579,7 +600,6 @@ class QickProgram(AbsQickProgram):
# 13, 14 and 15 for loop counters, 31 for the trigger time.
# Pairs of channels share a register page.
# The flat_top pulse uses some extra registers.
pulse_registers = ["freq", "phase", "addr", "gain", "mode", "t", "addr2", "gain2", "mode2", "mode3"]

# Attributes to dump when saving the program to JSON.
dump_keys = ['prog_list', 'envelopes', 'ro_chs', 'gen_chs', 'counter_addr', 'reps', 'expts', 'rounds', 'shot_angle', 'shot_threshold']
Expand Down Expand Up @@ -614,12 +634,50 @@ def __init__(self, soccfg):
self._gen_mgrs = [self.gentypes[ch['type']](self, iCh) for iCh, ch in enumerate(soccfg['gens'])]
self._ro_mgrs = [ReadoutManager(self, iCh) if 'tproc_ctrl' in ch else None for iCh, ch in enumerate(soccfg['readouts'])]

# Mapping from gen/RO channels and parameters to register pages and numbers
self._gen_pagemap = {}
self._gen_regmap = {}
self._ro_pagemap = {}
self._ro_regmap = {}
self._allocate_registers()

# Number of times the whole program is to be run.
self.rounds = 1
# Rotation angle and thresholds for single-shot readout.
self.shot_angle = None
self.shot_threshold = None

def _allocate_registers(self):
# assign tProc-controlled generator/readout channels to pages
# we pack the first channel in page 0
# subsequent channels are packed in pairs (which allows for 15 channels)
# if pairs won't fit, we pack in triplets
mgrs = [x for x in self._gen_mgrs + self._ro_mgrs if x is not None]
if (len(mgrs)>15):
mgrs_per_page = 3
else:
mgrs_per_page = 2
groups = [[]]
for iMgr, mgr in enumerate(mgrs):
if iMgr % mgrs_per_page == 1: groups.append([])
groups[-1].append(mgr)

for page, mgrs in enumerate(groups):
nRegs = sum([len(mgr.PULSE_REGISTERS) for mgr in mgrs])
regnum = 32 - nRegs
for iMgr, mgr in enumerate(mgrs):
mgr.rp = page
if isinstance(mgr, ReadoutManager):
self._ro_pagemap[mgr.ch] = page
mgr.regmap = self._ro_regmap
else:
self._gen_pagemap[mgr.ch] = page
mgr.regmap = self._gen_regmap
# for convenience, map the zero register
mgr.regmap[(mgr.ch, '0')] = (page, 0)
for iReg, regname in enumerate(mgr.PULSE_REGISTERS):
mgr.regmap[(mgr.ch, regname)] = (page, regnum)
regnum += 1

def dump_prog(self):
"""
Expand All @@ -646,51 +704,6 @@ def config_all(self, soc, load_pulses=True, reset=False, debug=False):
# load this program into the soc's tproc
soc.load_bin_program(self.compile(debug=debug), reset=reset)

def _ch_page_tproc(self, ch):
"""Gets tProc register page associated with channel.
Page 0 gets one tProc output because it also has some other registers.
Other pages get two outputs each.

This method is for internal use only.
User code should use ch_page() (for generators) or ch_page_ro() (for readouts).

Parameters
----------
ch : int
tProc output channel

Returns
-------
int
tProc page number

"""
return (ch+1)//2

def _sreg_tproc(self, ch, name):
"""Gets tProc register number associated with a channel and register name.

This method is for internal use only.
User code should use sreg() (for generators) or sreg_ro() (for readouts).

Parameters
----------
ch : int
tProc output channel
name : str
Name of special register ("gain", "freq")

Returns
-------
int
tProc special register number

"""
# special case for when we want to use the zero register
if name=='0': return 0
n_regs = len(self.pulse_registers)
return 31 - (n_regs * 2) + n_regs*((ch+1)%2) + self.pulse_registers.index(name)

def ch_page(self, gen_ch):
"""Gets tProc register page associated with generator channel.

Expand All @@ -705,8 +718,7 @@ def ch_page(self, gen_ch):
tProc page number

"""
tproc_ch = self.soccfg['gens'][gen_ch]['tproc_ch']
return self._ch_page_tproc(tproc_ch)
return self._gen_pagemap[gen_ch]

def sreg(self, gen_ch, name):
"""Gets tProc special register number associated with a generator channel and register name.
Expand All @@ -724,8 +736,7 @@ def sreg(self, gen_ch, name):
tProc special register number

"""
tproc_ch = self.soccfg['gens'][gen_ch]['tproc_ch']
return self._sreg_tproc(tproc_ch, name)
return self._gen_regmap[(gen_ch, name)][1]

def ch_page_ro(self, ro_ch):
"""Gets tProc register page associated with tProc-controlled readout channel.
Expand All @@ -741,8 +752,7 @@ def ch_page_ro(self, ro_ch):
tProc page number

"""
tproc_ch = self.soccfg['readouts'][ro_ch]['tproc_ctrl']
return self._ch_page_tproc(tproc_ch)
return self._ro_pagemap[ro_ch]

def sreg_ro(self, ro_ch, name):
"""Gets tProc special register number associated with a readout channel and register name.
Expand All @@ -760,8 +770,7 @@ def sreg_ro(self, ro_ch, name):
tProc special register number

"""
tproc_ch = self.soccfg['readouts'][ro_ch]['tproc_ctrl']
return self._sreg_tproc(tproc_ch, name)
return self._ro_regmap[(ro_ch, name)][1]

def add_pulse(self, ch, name, idata=None, qdata=None):
"""Adds a waveform to the waveform library within the program.
Expand Down Expand Up @@ -886,12 +895,11 @@ def readout(self, ch, t):
ch_list = ch2list(ch)
for ch in ch_list:
tproc_ch = self.soccfg['readouts'][ch]['tproc_ctrl']
rp = self._ch_page_tproc(tproc_ch)
rp, r_t = self._ro_regmap[(ch, 't')]
next_pulse = self._ro_mgrs[ch].next_pulse
if next_pulse is None:
raise RuntimeError("no pulse has been set up for channel %d"%(ch))

r_t = self._sreg_tproc(tproc_ch, 't')
self.safe_regwi(rp, r_t, t, f't = {t}')

for regs in next_pulse['regs']:
Expand Down Expand Up @@ -957,13 +965,11 @@ def pulse(self, ch, t='auto'):
ch_list = ch2list(ch)
for ch in ch_list:
tproc_ch = self.soccfg['gens'][ch]['tproc_ch']
rp = self._ch_page_tproc(tproc_ch)
rp, r_t = self._gen_regmap[(ch, 't')]
next_pulse = self._gen_mgrs[ch].next_pulse
if next_pulse is None:
raise RuntimeError("no pulse has been set up for channel %d"%(ch))

r_t = self._sreg_tproc(tproc_ch, 't')

if t is not None:
ts = self.get_timestamp(gen_ch=ch)
if t == 'auto':
Expand Down Expand Up @@ -1197,8 +1203,10 @@ def reset_phase(self, gen_ch: Union[int, List[int]] = None, ro_ch: Union[int, Li
ch_mgr.set_registers(phrst_params)

# write phase reset time register
rp = self._ch_page_tproc(tproc_ch)
r_t = self._sreg_tproc(tproc_ch, 't')
if ch_type=="generator":
rp, r_t = self._gen_regmap[(ch, 't')]
else:
rp, r_t = self._ro_regmap[(ch, 't')]
self.safe_regwi(rp, r_t, t, f't = {t}')
# schedule phrst at $r_t
for regs in ch_mgr.next_pulse["regs"]:
Expand Down
Loading
Loading