Timing in QUA¶
A QUA program defines not only the pulses that are played, but also specifies when they should be played.
The QUA syntax defines an implicit pulse dependency, which determines the order of pulses execution. The dependency can be summarized as follows:
- Each pulse is played immediately, unless specified otherwise or a calculation has to be done.
- We can delay the pulse play by using a
wait()command. Dependency-wise, this is equivalent to a play with zero amplitude.
- Pulses applied to the same element are dependent on each other according to the order in which they appear in the program.
- We can create a dependency between different elements via the
align()command. Whenever elements are 'aligned', the execution of operations that come after the alignment is dependent on the operations that come before.
Each element has its own thread, unless defined otherwise. If elements do share threads, they are dependent on each other as in case 3 above.
The Align Command¶
To further illustrate point number 4 above, consider the following pseudo-code examples:
In this example, the operations on
element_2 will only begin execution after the last operation on
element_1 took place (before the alignment).
Let's consider a slightly more complex example:
In the first part of the program above, before the alignment, the operations on the different elements will occur simultaneously,
according to the dependency rules above. In the second half, after the alignment, the operations on
element_2 will only begin after the operations from the first half, on both elements, has finished.
element_3 are independent of the other elements and will ignore the align command.
The Implicit Align¶
There is an implicit align whenever several elements participate in a flow control branch. That means that when elements appear together in a for loop, while loop, if / else / elif block or a switch case, they are aligned in the beginning of the flow control branch.
For example, the following example:
To the compiler, it is actually:
In the program, the while loop will only start after the pre-loop operations on
element_1 has finished.
In addition, every iteration will begin only after the operations on both element have ended.
Therefore, the loops duration will be determined by the longest of the two operation sequences.
Gaps in QUA¶
The QUA syntax of pulses and operations only defines their order of execution, it does not guarantee that they would be played without introducing gaps.
This means that two consecutive
play commands on the same element may have a short gap between them.
The compiler tries to minimize these gaps, but it is not always possible to completely eliminate them. For tips on how to reduce these gaps, see QUA Best Practice Guide.
It is also possible to indicate to the compiler sections in which it is critical to have no gaps, this can be done with the
Any command written inside a
strict_timing_() block will be required to be played without gaps.
In cases where this is not possible, an error will be raised indicating the gaps.
It is possible to add the flag
not-strict-timing to the execution to raise warnings instead of errors.
For more information on how to use flags, see compilation options.
In the following example, the two pulses will happen with no gaps in between:
In the next example, the
for_() loop is also inside the strict timing, which also requires that there will be no gaps between different iterations of the loop.
i.e., this means that the following code will produce a chain of 200 pulses alternating between
y90 with no gaps at all.
Deterministic Vs. non-Deterministic Align¶
Generally, an align command requires the passage of information from one thread to the other. When a thread reaches its last instruction before an alignment, it sends a signal to all the other threads that participate in the alignment, also known as a "hardware sync". This passage of information takes several clock cycles and introduces gaps to the pulse sequence. In cases where the duration of the operation on all the elements is known during compilation (Deterministic case), the compiler optimizes the sequence and replaces the hardware sync with precalculated wait commands for each element. This optimization ensures that there are no gaps formed in a deterministic case. However, if the run-time of one of the elements is not known during compilation (non-Deterministic case), gaps will be formed. See examples 3 and 4 below for more details.
Examples for Timing scenarios in QUA¶
We will present five examples in order to demonstrate the timing in qua:
- Two pulses from different elements.
- Two pulses from different elements with wait command.
- Two pulses from different elements with align command (deterministic case).
- Two pulses from different elements with align command (non-deterministic case).
- Two pulses from the same element.
For the executable examples look at timing tutorial.
Two pulses from different elements.
The two pulses start at the same time since they relate to different elements.
Two pulses from different elements with wait command.
The play on the resonator is delayed by a 400 ns (100 cycles).
Two pulses from different elements with align command (deterministic case)
The play on the resonator starts right after the qubit's play ends. This is due to the align command (in the deterministic case).
In the deterministic case above, the align command is translated to the wait command with the specific known wait time.
Two pulses from different elements with align command (non-deterministic case)
t is calculated in real-time.
If we zoom in to the area where one element ends and the other starts, we can see a gap between them.
When playing two elements with align command in the non-deterministic case, there is a few cycles delay between the pulses. Since in the non-deterministic case it takes few cycles to pass the information from one thread to the other.
Two pulses from the same element.
The two play commands are played one after the other since they are played from the same element.