.. role:: raw-latex(raw) :format: latex .. _pulse-gates: Pulse-level descriptions of gates and measurement ================================================= To induce the quantum gates and measurements of a circuit, qubits are manipulated with classically-controlled stimulus fields. The details of these stimuli are typically unique per-qubit and may vary over time due to instabilities in the underlying systems. Furthermore, there is significant interest in applying optimal control methodologies to the construction of these controls in order to optimize gate and circuit performance. As a consequence, we desire to connect gate-level instructions to the underlying microcoded :cite:`wilkesBestWayDesign1989` stimulus programs emitted by the controllers to implement each operation. In OpenQASM we expose access to this level of control with pulse-level definitions of gates and measurement with a user-selectable pulse grammar. The entry point to such gate and measurement definitions is the ``defcal`` keyword analogous to the ``gate`` keyword, but where the ``defcal`` body specifies a pulse-level instruction sequence on *physical* qubits, e.g. .. code-block:: defcal rz(angle[20] theta) $0 { ... } defcal measure $0 -> bit { ... } defcal measure_iq q -> complex[float[32]] { ... } We distinguish gate and measurement definitions by the presence of a return value type in the latter case, analogous to the subroutine syntax defined earlier. Furthermore, the return value type does not need to return a classified value but can return some lower level data. The reference to *physical* rather than *virtual* qubits is critical because quantum registers are no longer interchangeable at the pulse level. Due to varying physical qubit properties a microcode definition of a gate on one qubit will not perform the equivalent operation on another qubit. To meaningfully describe gates as pulses we must bind operations to specific qubits. QASM achieves this by prefixing qubit references with ``$`` to indicate a specific qubit on the device, e.g. ``$2`` would refer to physical qubit 2. One can define a `defcal` using a regular identifier for a qubit, which causes that calibration definition to be valid for all physical qubits. This is most likely to be useful for gates that are implemented virtually. For instance, to define an equivalent `rz` calibration on qubits 0 and 1, we could write .. code-block:: defcal rz(angle[20] theta) q { ... } // we've defined ``rz`` on arbitrary physical qubits, so we can do: rz(3.14) $0; rz(3.14) $1; As a consequence of the need for specialization of operations on particular qubits, the same symbol may be defined multiple times, e.g. .. code-block:: defcal h $0 { ... } defcal h $1 { ... } and so forth. Some operations require further specialization on parameter values, so we also allow multiple declarations on the same physical qubits with different parameter values, e.g. .. code-block:: defcal rx(pi) $0 { ... } defcal rx(pi / 2) $0 { ... } Given multiple definitions of the same symbol, the compiler will match the most specific definition found for a given operation. Thus, given, #. ``defcal rx(angle[20] theta) q ...`` #. ``defcal rx(angle[20] theta) $0 ...`` #. ``defcal rx(pi / 2) $0 ...`` the operation ``rx(pi/2) $0`` would match to (3), ``rx(pi) $0`` would match (2), ``rx(pi/2) $1`` would match (1). Users specify the grammar used inside ``defcal`` blocks with a ``defcalgrammar "name"`` declaration. One such grammar is a `textual representation of OpenPulse `_ specified by: .. code-block:: defcalgrammar "openpulse"; Note that ``defcal`` and ``gate`` communicate orthogonal information to the compiler. ``gate``\s define unitary transformation rules to the compiler. The compiler may freely invoke such rules on operations while preserving the structure of a circuit as a collection of ``gate``\s and ``subroutine``\s. The ``defcal`` declarations instead define elements of a symbol lookup table. As soon as the compiler replaces a ``gate`` with a ``defcal`` definition, we have changed the fundamental structure of the circuit. Most of the time symbols in the ``defcal`` table will also have corresponding ``gate`` definitions. However, if a user provides a ``defcal`` for a symbol without a corresponding ``gate``, then we treat such operations like the ``opaque`` gates of prior versions of OpenQASM. Inline calibration blocks ~~~~~~~~~~~~~~~~~~~~~~~~~ As calibration grammars may require the ability to insert top-level configuration information, perform program setup, or make inline calls to calibration-level instructions, OpenQASM supports the ability to declare a ``cal`` block. Within the ``cal`` block the semantics of the selected ``defcalgrammar`` are valid. The ``cal`` block is of the same scope level as the enclosing block. The calibration grammar may choose to allow capturing values (with chosen syntax) from within the ``cal`` block that were declared within the containing parent scope. Values declared within the ``cal`` block are only referenceable from other ``cal`` blocks or ``defcal`` declarations that may observe that scope as defined by the calibration grammar. Values may not leak back to the block's enclosing scope. In practice, calibration grammars such as OpenPulse may apply a global scope to all identifiers in order to declare values shared across all ``defcal`` calls thereby linking them together. .. code-block:: OPENQASM 3; defcalgrammar "openpulse"; const float original_freq = 5.9e9; cal { // Defined within `cal`, so it may not leak back out to the enclosing blocks scope float new_freq = 5.2e9; // declare global port extern port d0; // reference `freq` variable from enclosing blocks scope frame d0f = newframe(d0, freq, 0.0); } defcal x $0 { waveform xp = gaussian(1.0, 160t, 40dt); // References frame and `new_freq` declared in top-level cal block play(d0f, xp); set_frequency(d0f, new_freq); play(d0f, xp); } Restrictions on defcal bodies ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ The contents of ``defcal`` bodies are subject to the restriction they must have a definite duration known at compile time, regardless of the parameters passed in or the state of the system when called. This allows the compiler to properly resolve ``durationof(...)`` calls and allows for optimizations. If there is to be control flow in the ``defcal``, each branch of the control flow must have definite and equivalent duration resolvable at compile time. Similarly, loops must be have a resolvable definite duration at compile time. For example, consider the case of a ``reset`` gate. The ``defcal`` for a ``reset`` gate can be composed of a single if statement, provided each branch of the if statement has definite and equivalent duration. .. code-block:: defcal reset $0 { bit res = // measure qubit $0 if (res == 1) { // flip the qubit } else { // delay for an equivalent amount of time } } Calibrations in practice ~~~~~~~~~~~~~~~~~~~~~~~~ By their very nature calibrations are transient and unique to a target system. They are typically generated by automatic calibration routines that are periodically run on the target system, that are in turn bootstrapped from previous calibrations. The majority of OpenQASM users will use the default calibrations, however, for those that want more control, but do not want to bootstrap calibrations for an entire system. It is expected that the target system provider will provide an include file to the user. This will contain the declaration of the ``defcalgrammar``, constants, ``defcal``\s and other grammar and system specific components such as ``port``\s, ``waveform``\s and ``frame``\s in the `OpenPulse defcalgrammar `. The user may then plugin to the existing calibrations by defining new calibrations, or overwriting existing ones by using the same ``port``\s and ``frame``\s. The example below demonstrates this in practice for a two-qubit, cross-resonance device using a ``backend.inc`` include file. The name ``backend.inc`` is arbitrary - it's just a file to be included using the existing ``include`` mechanism. .. code-block:: // backend.inc for openpulse two-qubit device defcalgrammar "openpulse"; const float q0_freq = 5.0e9; const float q1_freq = 5.1e9; cal { extern drag(complex[size] amp, duration l, duration sigma, float[size] beta) -> waveform; extern gaussian_square(complex[size] amp, duration l, duration square_width, duration sigma) -> waveform; extern port q0; extern port q1; frame q0_frame = newframe(q0, q0_freq, 0); frame q1_frame = newframe(q1, q1_freq, 0); } defcal rz(angle theta) $0 { shift_phase(q0_frame, theta); } defcal rz(angle theta) $1 { shift_phase(q1_frame, theta); } defcal sx $0 { waveform sx_wf = drag(0.2+0.1im, 160dt, 40dt, 0.05); play(q0_frame, sx_wf); } defcal sx $1 { waveform sx_wf = drag(0.1+0.05im, 160dt, 40dt, 0.1); play(q1_frame, sx_wf); } defcal cx $1, $0 { waveform CR90p = gaussian_square(0.2+0.05im, 560dt, 240dt, 40dt); waveform CR90m = gaussian_square(-0.2-0.05im, 560dt, 240dt, 40dt); rz(pi/2) $0; rz(-pi/2) $1; sx $0; sx $1; barrier $0, $1; play(q0_frame, CR90p); barrier $0, $1; sx $0; sx $0; barrier $0, $1; rz(-pi/2) $0; rz(pi/2) $1; sx $0; sx $1; play(q0_frame, CR90m); } The user would then include the ``backend.inc`` in their own program and use them as demonstrated below .. code-block:: OPENQASM 3.0; include "backend.inc" // Defcal using frames from backend.inc enabling the calibration // to "plugin" to the existing calibrations. defcal Y90p $0 { waveform y90p = drag(0.1-0.2im, 160dt, 40dt, 0.05); play(q0_frame, y90p); } // Teach the compiler what the unitary of a Y90p is gate Y90p q { rz(-pi/2) q; sx q; } // Use this defcal explicitly Y90p $0; cx $1, $0;