A Q1 Sequencer is a processing unit implemented in the FPGA of Qblox modules.
Each sequencer operates independently, similar to a core in a CPU.
Like a CPU that executes assembly instructions, a sequencer can be programmed using Q1ASM, Qblox’s dedicated assembly language.
These Q1ASM instructions are compiled to perform tasks such as signal generation, signal acquisition, and communication with other Q1 sequencers.
A single Qblox module contains multiple sequencers, and communication can occur both within a module and across modules or even clusters.
A Q1 sequencer is made up of a Q1 core and a real-time (RT) core. The Q1 core processes instructions similar to a CPU and pushes certain instructions to the RT core which performs actions on the signal path. The following diagram describes in detail the parts of all Q1 Sequencers along with examples to illustrate how the parts work together.
Diagram of a running Q1 Sequencer. Click on the heading of each block to learn more about each block. The arrows indicate information flow.
The Q1 core executes program logic (arithmetic, branching, and scheduling) while the RT core generates or modifies the signal in real time.
Each sequencer has an instruction memory where the Q1ASM instructions are uploaded to, using the sequencer.sequence() function. Each Q1ASM instruction is either classical or real-time (RT) and each type is explained in the following sections. Examples of classical instructions are add, jmp and set_awg_offs. RT instructions as these dictate the timing of operations. Example of RT instructions are play, acquire and wait. The full list of instructions and their use is shown here.
A control or timetag sequencer can store 16384 instructions, while a readout sequencer can store 12288 instructions. Note that these are instructions as counted by the stripped Q1ASM program as described in the “Example: My first sequence” in the Q1 core section below.
Warning
Every Sequence of instructions must end with the stop instruction.
Each Q1 processor contains 64 user programmable 32 bit registers (named R0 through R63) in each sequencer. Instructions in the Instruction Memory interact with registers. For example, addR0,R1,R2 adds the value in R0 to the value in R1 and stores the result in R2. Other instructions only read from registers. For example, waitR7 instructs the RT core to wait for R7 nanoseconds before proceeding to the next instruction in the queue.
Important
Each register may be updated/read no more than once every two instructions. A nop instruction may be used if the register value needs to be used immediately. As an example, the following Q1ASM program may lead to undefined behavior because R1 is used immediately after writing to it.
move2,R0move3,R1addR0,R1,R2stop
Adding a nop will pass a single cycle in the Q1 core which in this case ensures that register R1 is written successfully. Any other instruction that does not interact with the R1 register will also do the same.
The Q1 core is the part of the sequencer that processes the instructions, in the Instruction memory. Once the sequencer is started (Sequencer.start_sequencer()), the Q1 core executes every instruction one by one. The Q1 core fully executes classical instructions, and pushes the RT instructions to the RT queue. If the RT queue is full, the Q1 core pauses or stalls its execution until a spot is empty.
Example: My first Sequence
We now know enough to create our first sequence. This sequence will not produce any output or interact with the external environment, but it will help to illustrate how the sequencer works internally. The following is an example sequence that multiplies two numbers using repeated addition using the concepts described above. The first is the human readable program that the user uploads. The second is the stripped program (which is also valid Q1ASM) that the Q1 core processes.
move0,R0# Initialize answer register to zero.move100,R1# Initialize the multiplicand to 100.move21,R2# Initialize the multiplier to 21.loop_start:# Loop starting label used to jump back to.addR0,R1,R0# R0 = R0 + R1loopR2,@loop_start# R2 = R2 - 1. If R2 != 0, jump to loop_start.stop
In the stripped program on the right, the label loop_start has been removed, and the reference to it @loop_start has been replaced with the line number (4). All jump instructions (loop, jge, jlt) make the program continue from the line number that is specified.
After the sequence has been executed, the value in R2 will 100x21=2100.
Execution times for all instructions on the Q1 core are listed in the table of instructions.
Note
For RT instructions, the execution time indicates how long the Q1 core takes to fill the RT queue.
Some classical instructions like set_awg_offs and set_freq change special latched registers in the FPGA. These registers hold or latch the value until a RT instruction (such as upd_param or play) applies them to the parts of the sequencer that deal with signals.
For example, the set_awg_offs instruction latches a value for an output offset for a control or readout sequencer. The output offset does not change when the Q1 core executes this instruction. The output offset will only change when a RT instruction is executed. In the “Example: Square pulse” below, you can see how values latched by the Q1 core affect the behavior of the RT-core during execution.
Multiple Q1 instructions can be executed before a single RT instruction, allowing all parameters to take effect simultaneously. This enables gapless signal generation, as Q1 processing occurs in parallel with RT execution.
Special mentions
The set_cond latched instruction affects following RT instructions.
The wait RT instruction does not update latched parameters.
RT instructions pushed from the Q1 core arrive one by one to the RT queue, which holds 32 RT instructions. Every time a RT instruction is pushed to the RT queue by the Q1 core, a copy of the latched registers is also pushed to the queue. To see the list of RT instructions, see here. Each RT instruction contains an integer argument(duration) specified in units of ns. The minimum value for duration is 4 ns.
The RT core pops (or removes) the next RT instruction from the RT queue and starts the execution of the instruction. Starting execution of the RT instruction also involves updating the signal path with the latched parameters that are attached to the RT instruction. This updates the relevant part of the signal path. After starting the execution, the RT core then waits exactly duration ns of physical time before executing the next RT instruction.
Important
The next instruction in the RT queue will always be executed after duration ns. This may interrupt waveform playback or stop a running acquisition if the next instruction is a play or acquire respectively.
If there is no instruction in the RT queue to execute next, the sequencer will raise the SEQUENCE_PROCESSOR_RT_EXEC_COMMAND_UNDERFLOW error flag. A typical cause of underflow is a loop with a very short real-time run-time, since the jump of a loop takes some cycles to be executed by the Q1 core. See the column Execution time of Instructions for execution times in the Q1 core for each instruction.
Example: Square pulse
Here is a program that plays a 1 us square pulse with maximum amplitude on either a control or readout sequencer.
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. stop
Example: Sequence of square pulses
Below is a program that plays a sequence of square pulses with increasing amplitudes and time on either a control or readout sequencer.
move0,R0# Initialize amplitude register to 0.move100,R1# Initialize time register to 100.move25,R2# Initialize loop register to 25.start:set_awg_offsR0,R0# Set the path_0 and path_1 offset to R0.upd_paramR1# Apply the offset to the output and wait R1 register.set_awg_offs0,0# Set the path_0 and path_1 offset to R0.upd_paramR1# Apply the offset to the output and wait R1 register.addR0,100,R0# R0 = R0 + 100addR1,100,R1# R1 = R1 + 100loopR2,@start# loop 25 times.stop
The wait_sync instruction stops the RT core until all sequencers that have sequencer.sync_en(True) arrive at a wait_sync instruction and the SYNQ protocol is executed to synchronize all of the above sequencers. After this, the RT core waits for duration ns before executing the next RT instruction in the RT queue.
Warning
This means that the time required to execute a wait_sync instruction is not deterministic.
Warning
If multiple sequencers have the sequencer.sync_en(True), either all or none of the sequencers should have the wait_sync instruction. Otherwise, the sequencers that execute wait_sync will wait forever for the other sequencers to execute wait_sync.
The signal path is the part of the Q1 sequencer that either plays or acquires a signal. The parts of the sequencer above all work together to instruct or modify the signal path.
There are 3 types of Q1 sequencers each have different signal paths: