A readout sequencer is a type of Q1 sequencer whose signal path generates two streams of output signals called path_0 and path_1 just like a control sequencer. Additionally, the signal path includes an acquisition path which acquires and processes the signal.
Note
A readout sequencer is not only an acquisition sequencer. It can both play and acquire signals, and is designed to acquire the same signal that is played from it.
The following describes the parts or blocks of the signal path of a readout sequencer along with examples to illustrate how the blocks work together. Some of the blocks perform mathematical operations to a signal. In these cases, a formula of the form \(y(t)=f(x(t))\) is presented, where \(t\) is time in discrete time steps of 1 ns corresponding to each time sample, \(y(t)\) is the output of the block, given that \(x(t)\) is the input to the block. The function \(f(x(t))\) can be manipulated using Q1ASM instructions or QCoDeS commands depending on the block.
A readout sequencer has a certain waveform memory from which signals can be played. The user can upload waveforms while uploading a sequence using the Sequencer.sequence() method. The method takes a dictionary as an argument, and one of the keys must be waveforms, which itself is a dictionary with a python list of floating point values in the range of 1.0 to -1.0 with a resolution of one nanosecond per sample. See below an example:
The data supplied to the waveform dictionary must be a python list. It cannot be a numpy array.
The waveform memory for each sequencer is 16384 samples (total of all waveforms stored in one sequencer), and the maximum number of waveforms that can be stored on a sequencer is 1024. The stored waveforms can be retrieved using Sequencer.get_waveforms().
The play RT instruction is used to start waveform playback from the waveform memory. The instruction takes two arguments before the real-time duration argument. The first two are the waveform indices for path 0 and path 1. Once the play instruction is executed by the RT core, the waveform is then completely played irrespective of further instructions, except when the RT core issues the playback of another waveform, in which case the waveform will be stopped and the new waveform will start.
Example: Play waveform
The example below assumes that the sequence dictionary in the Example:waveformupload above is uploaded to the sequencer.
play0,1,200# Play wfm1 on path_0 and gaussian on path_1. Then wait 200 ns.play1,1,100# Play gaussian on both path_0 and path_1. Then wait 100 ns.play1,1,50# Play gaussian on both path_0 and path_1. Then wait 50 ns.# Interrupt playback of gaussian from the previous play.play0,0,4# Play wfm1 on both path_0 and path_1. Then wait 4 ns.stop
Note
If the waveform is shorter than duration, the output is zeros after waveform playback.
A readout sequencer has a dedicated gain step for both paths 0 and 1, which can either be configured using the ControlSequencer.gain_awg_path0() QCoDeS parameter or within a sequence using the set_awg_gain Q1ASM latched instruction of the Q1 sequencer which enables pulse parametrization. The QCoDeS and Q1ASM gain readouts are multiplied together before the gain is applied to the signal.
The set_awg_gain Q1ASM latched instruction takes two integers from -32768 to 32767 as arguments for the gain of the two paths.
The following example plays 10 gaussian pulses with increasing amplitude.
The example assumes that the sequence dictionary in the Example:waveformupload above is uploaded to the sequencer.
move10,R0# Loop countermove1000,R1# Initial gain of 1000start:set_awg_gainR1,R1# Set gain to value in R1.play1,1,100# Play gaussian on both path_0 and path_1. Then wait 100 ns.addR1,1000,R1# R1 = R1 + 1000loopR0,@startstop
A readout sequencer has a dedicated offset step for both paths 0 and 1, which can either be configured using the ControlSequencer.offset_awg_path0() QCoDeS parameter or within a sequence using the set_awg_offs Q1ASM latched instruction of the Q1 sequencer which enables pulse parametrization. The QCoDeS and Q1ASM offset readouts are added together before the gain is applied to the signal.
The set_awg_offs Q1ASM latched instruction takes two integers from -32768 to 32767 as arguments for the offset of the two paths.
The following example plays 10 gaussian pulses with increasing offsets.
The example assumes that the sequence dictionary in the Example:waveformupload above is uploaded to the sequencer.
move10,R0# Loop countermove1000,R1# Initial offset of 1000start:set_awg_offsR1,R1# Set offset to value in R1.play1,1,100# Play gaussian on both path_0 and path_1. Then wait 100 ns.addR1,1000,R1# R1 = R1 + 1000loopR0,@startstop
A readout sequencer has a numerically controlled oscillator (NCO) whose frequency and phase parameters either be configured using the ControlSequencer.nco_freq() and ControlSequencer.nco_phase_offs() QCoDeS parameters or within a sequence using the set_freq, reset_ph, set_ph, and set_ph_delta Q1ASM latched instructions.
The NCO outputs a two signals (\(n_I\) and \(n_Q\)), which can be interpreted as a complex signal (\(\mathbf{n}(t)\)).
It is set to zero with the reset_ph instruction. Note that the reset_ph instruction also sets \(\phi=0\) and \(\Phi=0\).
The set_ph_delta Q1ASM instruction induces a phase kick \(t'\rightarrow t' + \delta\phi/\omega\) at the instant that the signal path is updated (when the next RT instruction is executed).
The ControlSequencer.nco_phase_offs() QCoDeS parameter and set_ph Q1ASM instruction are added together before it is applied to the NCO.
Warning
The ControlSequencer.nco_freq() QCoDeS parameter and set_freq Q1ASM instruction override each other. We highly recommend setting NCO frequencies only either using QCoDeS or Q1ASM and not both.
For further in depth details and a tutorial on how to use the Q1 sequencer NCO capabilities, please refer to the NCO tutorial.
The two output signals from the NCO (\(\mathbf{n}(t) = n_I(t) + \mathbf{i}n_Q(t)\)) and the signal from paths 0 and 1 (\(\mathbf{x}(t) = x_0(t) + \mathbf{i}x_1(t)\)) are multiplied as complex signals in this block to output a modulated signal (\(\mathbf{y}(t) = y_0(t) + \mathbf{i}y_1(t)\)).
The following example plays 10 square pulses with increasing frequency from 0 to 9 MHz.
move10,R0# Loop countermove0,R1# Initial frequency of 0 Hzstart:set_freqR1# Set the frequency to R1/4 Hz. The argument is in multiples of 0.25 Hz.set_awg_offs32767,32767# Set the path_0 and path_1 offset to maximum.upd_param1000# Apply the offset to the output and wait 1 us.set_awg_offs0,0# Set the path_0 and path_1 offset to 0.upd_param4# Apply the offset to the output and wait 4 ns.addR1,4000000,R1# R1 = R1 + 4000000. Corresponding to 1 MHz.loopR0,@startstop
If the two signals (path 0 and 1) generated by the readout sequencer are meant to be used as the I and Q signals to a physical mixer (such as in the RF modules), one may want to correct for mixer imperfections. The mixer correction block allows the user to set an amplitude ratio (\(\alpha\)) and a phase offset (\(\phi_m\)) between the I and Q signals, so that the output of the physical mixer is ideal. The output of the mixer correction block is calculated by the mixer correction block as follows:
Two input signals called input path 0 and input path 1 enter the acquisition signal path. These signals are configured to go through three paths: scope path, TTL path and the demodulated path.
Before acquiring signals through any of the paths, the acquisitions dictionary must be uploaded using the Sequencer.sequence() method. Below is an example of uploading acquisitions:
Example: acquisitions upload
sequencer0.sequence({"acquisitions":{# Reserve 1 bin for an acquisition called "single""single":{"num_bins":1,"index":0},# Reserve 1000 bins for an acquisition called "multiple""multiple":{"num_bins":1000,"index":1},},...})
Signals can be acquired and directly stored in memory, similar to an oscilloscope. In order to use the scope path, the QRM.scope_acq_trigger_mode_path0() method must be used to select how the scope will be started. This can be set to:
"sequencer": the scope will acquire a signal when the selected sequencer executes an acquire instruction. The sequencer is selected using QRM.scope_acq_sequencer_select().
Exception:
The scope acquisition is performed per module. Sequencers may merely be configured to start the scope acquisition using the acquire instruction. Every module only has one shared scope memory, unlike the binned memory for the other two acquisition paths.
Each time the scope is started, it will capture 16384 input samples on both paths and will store the raw input samples in a temporary buffer on the FPGA. If the scope is started again, this memory is overwritten. The samples may be moved from the temporary buffer to the RAM of the module using QRM.store_scope_acquisition(), which moves the samples into the specified acquisition in the acquisition list of the sequencer, located in the RAM of the instrument. Multiple acquisitions can be stored in this list before being retrieved using Sequencer.get_acquisitions() which returns a dictionary of acquisitions. Scope mode data is located under the “scope” key as lists of floating point values in the range of 1.0 to -1.0 with a resolution of one nanosecond per sample, as well as an indication if the ADC was out-of-range during the measurement.
Note
The bins reserved in the acquisition dictionary are not used by the scope path. The scope will always acquire 16384 samples.
It is possible to accumulate data acquired by repeated scope acquisitions by setting QRM.scope_acq_avg_mode_en_path0() to True. Data retrieved will then be element-wise accumulated over all the repetitions. i.e. sample N of the next acquisition is accumulated to sample N of acquisition memory. This happens while the acquisition is still in the temporary buffer, so after the desired number of averaging acquisitions is completed, QRM.store_scope_acquisition() may be used to store the accumulated result in the acquisition list. Once retrieved from the instrument, the accumulated samples will be divided by the number of acquisitions to get the element-wise average scope trace.
The TTL acquisition path is used to acquire and count trigger events. It can also be used to generate a histogram of trigger counts directly on the FPGA. The acquire_ttl Q1ASM instruction is used to start the TTL acquisition path.
In the TTL trigger path, one of the inputs is selected using the qblox_instruments ReadoutSequencer.ttl_acq_input_select() parameter. The acquire_ttl instruction enables the TTL trigger acquisition path by setting the enable argument to 1 and needs to actively disable the path again by using the same instruction with its enable argument set to 0.
Important
While the TTL trigger acquisition path is enabled, real-time integration and discretization is disabled.
Example: enabling and disabling the TTL trigger acquisition path
acquire_ttl0,0,1,1000# Acquire triggers and wait for 1000 ns.acquire_ttl0,0,0,4# Stop acquiring triggers and wait for 4 ns.stop# Stop sequencer.
The thresholding unit evaluates all data above the set threshold, returning a 1 if it is above and a 0 otherwise. This result can be shared across the cluster for feedback applications.
The threshold for this acquisition can be set for each sequencer individually, e.g. qrm.sequencer0.ttl_acq_threshold(0.5)
When a TTL trigger is detected at the threshold comparator, the resulting one-bit signal is pushed through an edge detection to transform the TTL trigger of undetermined duration into a single clock cycle trigger pulse.
The bin index is given as an argument of the instruction and can be automatically incremented by each detected trigger through the bin counter. ReadoutSequencer.ttl_acq_auto_bin_incr_en() selects if the bin index is automatically incremented when acquiring multiple triggers. Automatic bin increments allow for storing a histogram of trigger counts in the bins.
The resulting trigger pulse and the associated ADC values that caused the trigger detection can be stored through the binning and averaging module. When doing this, the retrigger rate is restricted to roughly 1MHz.
The count of triggers will be saved in ‘averages’ and can be retrieved by using sequencer.get_acquisitions(0)['0']['acquisition']['bins']['averages']
The demodulation path is used to demodulate a signal such that only the envelope is processed. The acquire Q1ASM instruction is used to start the TTL acquisition path.
The demodulation path allows for accumulating the demodulated signal in time (called “integration”), accumulating the demodulated signal across executions of the acquire instruction (called “averaging”) and also allows for thresholding the integrated signal to produce a 1-bit output.
Important
The NCO is shared between the AWG and ACQ paths. This allows a modulated signal that is played from the AWG to be demodulated back to the envelope by the ACQ since the frequency of modulation and demodulation is the same.
The two output signals from the NCO (\(\mathbf{n}(t) = n_I(t) - \mathbf{i}n_Q(t)\)) and the signal from paths 0 and 1 (\(\mathbf{x}(t) = x_0(t) + \mathbf{i}x_1(t)\)) are multiplied as complex signals in this block to output a demodulated signal (\(\mathbf{y}(t) = y_0(t) + \mathbf{i}y_1(t)\)).
Note
The NCO output \(n_I(t)\) and \(n_Q(t)\) are used as is for the AWG modulation block. For the ACQ demodulation block, the sign of \(n_Q(t)\) is flipped.
If you are using the sequencer to acquire the signal that is played, it is desirable to delay the NCO output by the time of flight. This ensures that the phase of the demodulated signal is constant even when the frequency is changed in a sweep for example.
The acquire instruction integrates the demodulated signal. The integrator block performs a square integration, or applies the provided integration weights to the incoming data. When using the square integration, the length of the integration is restricted to 16 ms.
Let us define the samples of the demodulated signal as \(\mathbf{x}[i] = x_0[i] + x_1[i]\), where i is the index of the sample (\(i=0\) is the sample corresponding to when the acquire instruction was executed).
There is also an option to integrate the demodulated signal using a different weight for each sample acquired before integration using the acquire_weighed instruction. For weighted integration, this length of the acquisition is limited to the length of the weight memory of 16 384 ns.
Weighted integration may be used to boost the signal to noise ratio of a measurement. For example, suppose that the amount of information in the acquired signal drops exponentially in time such that the end of the acquired signal has a lot more noise than the beginning. The integration weights could also be set to drop exponentially so that the noise at the end of the acquired signal is ignored.
\(L\) is the lengths of the uploaded weights selected by the acquire_weighed instruction.
\(w_0[i]\) and \(w_1[i]\) are the weights uploaded by the user to the weight memory.
Important
The weights \(w_0\) and \(w_1\) are not multiplied in a complex manner. See below for more details:
Complex weighted integration
In order to perform a true complex multiplication using the weights, you must acquire from two sequencers set up with the same frequency, phase, etc. parameters, and with weights of the two sequencers being \(w_0\) and \(-w_1\) for the first sequencer, and \(w_1\) and \(w_0\) for the second sequencer. This leads to:
The integration process continues irrespective of further sequence processor instructions, except when the sequence processor issues another acquisition using the acquire or acquire_weighed instructions, in which case the integration will be stopped, the result will be stored and a new integration will start.
The weight memory stores the integration weights set by the user in the provided sequence. The weights are expressed as a list of floating point values in the range of 1.0 to -1.0 with a resolution of one nanosecond per sample. The maximum number of weights that can be stored is 32, with a maximum of 16384 total weight samples. See below for an example of uploading the weights:
The demodulated integrated value and the thresholded bit result will be stored in memory. The acquire and acquire_weighed instructions that are used to start acquiring a signal using the demodulated path also define where in memory the results are stored by their arguments. If multiple acquisitions are acquired into the same bin the results are summed together, and divided when the results are transferred to the PC using Sequencer.get_acquisitions().
Below are two examples where:
consecutive acquisitions are acquired in a sequence of bins, and
Example: Acquire in different bins
The example assumes that the sequence dictionary in the Example:acquisitionsupload above is uploaded to the sequencer.
move100,R0# Loop countermove0,R1# Bin indexstart:# Append result to acquisition "multiple" (index 1) and bin R1.acquire1,R1,100# Start acquiring. Then wait 100 ns.addR1,1,R1# R1 = R1 + 1loop@start,R0# Loopstop
sequencer.get_acquisitions()['multiple']['acquisition']['bins'] is a list of 10 numbers each for path 0 and 1.
consecutive acquisitions are acquired in a single bin.
Example: Average using one bin
The example assumes that the sequence dictionary in the Example:acquisitionsupload above is uploaded to the sequencer.
move100,R0# Loop counterstart:# Append result to acquisition "single" (index 0) and bin 0.acquire0,0,100# Start acquiring. Then wait 100 ns. loop@start,R0# Loopstop
sequencer.get_acquisitions()['single']['acquisition']['bins'] is a the average of 10 acquisitions for path 0 and 1.
Any thresholded acquisition result, as discussed in the rotation and thresholding section, may be sent to the trigger network by following the next steps:
wait_sync4#Wait for sequencers to synchronizeplay0,0,4#Play readout pulsewait148#Wait for readout pulse to returnacquire0,0,100#Acquire readout pulsestop#Stop program
The program for sequencer 1:
wait_sync4#Wait for sequencers to synchronizeset_latch_en1,4#Latch any triggerlatch_rst1000#Reset the trigger network address counters, then wait on trigger address 12set_cond1,2048,0,4#Play waveform if trigger is seen (condition: OR T12)play0,0,20#""set_cond1,2048,1,4#Play waveform if trigger is NOT seen (condition: NOR T12)play1,1,20#""stop#Stop program
When acquiring using the TTL trigger acquisition path, any trigger detected during the acquisition period can be sent to the trigger network as well. Note that multiple triggers can be sent on the network during a single acquisition period. However, the trigger network can only be retriggered every 252 ns, so the rate of the TTL triggers on the input must be limited to the maximum trigger network rate.
During a TTL trigger acquisition, all detected triggers are treated like thresholded acquisition results. Mapping the TTL triggers on the trigger network is therefore identical to mapping thresholded acquisition results on the network and uses the same parameters.
Example: trigger counting
In this example, the trigger network address counters are used to count TTL triggers on the inputs. Sequencer 0 detects the triggers on the input of the device and then counts the resulting triggers on the trigger network. Finally, it plays a waveform if 5 or more triggers are detected.
wait_sync4#Wait for sequencers to synchronizeset_latch_en1,4#Latch any triggerlatch_rst4#Reset the trigger network address countersacquire_ttl0,0,1,5000#Acquire input triggersacquire_ttl0,0,0,4#Stop acquiring triggersset_cond1,2048,0,20#Play if 5 triggers have been seen (condition: OR T12)play0,0,20#""stop#Stop program