Overview

OpenPulse extends OpenQASM3 beyond the gate model by introducing constructs for ports, frames, and waveforms that describe the actual control signals sent to quantum hardware. This allows programmers to implement custom calibrated gates, adjust frequencies and phases dynamically, and align timing at the hardware level. With OpenPulse, quantum programs can bridge the gap between high-level circuits and the physical pulses that drive qubit operations.

PyQASM 🤝 OpenPulse

PyQASM now understands and validates OpenPulse constructs embedded in OpenQASM3 programs. This includes parsing cal blocks and defcal definitions and validating pulse-level statements. While most existing tools focus on gate-level OpenQASM, few provide strong support for pulse-level semantics and most of them lack comprehensive semantic analysis — such as validating scope rules, deterministic durations in defcal, and hardware-level constraints PyQASM aims to fill this gap by:
  • Providing a unified parser and validator for both OpenQASM3 and OpenPulse.
  • Enforcing semantic correctness of pulse operations, not just syntax.
  • Allowing users to configure backend-specific rules (duration tracking, frame limits, etc.).
  • Building towards a complete analysis and compilation toolkit for pulse-level programs, bridging high-level circuits with low-level control.
This makes PyQASM a foundation for researchers and hardware providers who need validation and compilation of OpenPulse programs.

Core Constructs

The OpenPulse grammar—activated by defcal grammar "openpulse"; — adds these core constructs to OpenQASM3.
  • Ports (port): Abstract I/O channels representing hardware control lines.
    • Real hardware often restricts the number of active frames per physical port. pyqasm introduces a frame_limit_per_port option that users can set when loading a program.
    import pyqasm
    
    qasm = """
    OPENQASM 3.0;
    defcalgrammar "openpulse";
    cal {
         port d0;
         frame fr1 = newframe(d0, 100.0, 5.0);
         frame fr2 = newframe(d0, 100.0, 5.0);
    }
    """
    module = pyqasm.loads(qasm, frame_limit_per_port = 1)
    module.unroll()
    print(pyqasm.dumps(module))
    
  • Frames (frame): Stateful carriers with port, timing, frequency, and phase.
    • Users can configure whether frames are allowed in defcal blocks via the frame_in_defcal option when loading a program.
    import pyqasm
    
    qasm = """
    OPENQASM 3.0;
    defcalgrammar "openpulse";
    cal {
         port d0;
         frame fr1 = newframe(d0, 100.0, 5.0);
    }
    defcal gate $0{
         frame fr2 = newframe(d0, 100.0, 5.0);
    }
    """
    
    module = pyqasm.loads(qasm, frame_in_defcal = False)
    module.unroll()
    print(pyqasm.dumps(module))
    
    • pyqasm also supports the full set of frame manipulators defined in the OpenPulse grammar
      • set_frequency() / shift_frequency() / shift_phase() / get_frequency() / get_phase().
  • Waveforms (waveform): Envelopes modulating signals on ports.
    • pyqasm supports all waveform types defined in the OpenPulse specification — gaussian, sech, gaussian_square, drag, constant, sine, mix, sum, phase_shift, scale.
    • validates all declarations conform to the OpenPulse grammar, including parameter correctness and waveform compatibility.
  • Capture (capture): Measurement/capture operations.
    • pyqasm also supports the capture operations defined in the OpenPulse grammar — capture_v1(), capture_v2(), capture_v3(), capture_v4().
  • Operations within cal / defcal:
    • play() — apply a waveform via a frame.
      • By default, play is intended for use inside defcal blocks. pyqasm introduces a configurable option, play_in_cal_block, which allows users to enable or restrict the use of play instructions inside cal blocks as well.
      import pyqasm
      
      qasm = """
      OPENQASM 3.0;
      defcalgrammar "openpulse";
      cal {
         port d0;
         frame fr1 = newframe(d0, 100.0, 5.0);
         waveform wf = constant(1.0+2.0im, 100.0ns);
         play(fr1, wf);
      }
      """
      module = pyqasm.loads(qasm, play_in_cal_block = False)
      module.unroll()
      print(pyqasm.dumps(module))
      
    • delay() — Advance the time-cursor of a frame by a given duration.
    • barrier()synchronize the time-cursor of multiple frames to the same point in time.

Capabilities

  • Duration Tracking
    • Each frame carries an internal time-cursor that advances as operations (play, delay, capture) are applied.
    • pyqasm performs semantic tracking of durations, ensuring that timing across frames remains consistent throughout the program.
    • This enables correct validation of defcal blocks, which must have deterministic total durations across all execution paths.
  • PyQASM Supports Frames-Collisions validation in defcal blocks.
  • PyQASM also unrolls cal blocks, which is useful for debugging and visualization. Since, defcal are acted as functions, we are not unrolling them.
  • Currently, pyqasm does not support extern declarations in OpenPulse blocks.
For more details, please refer to the OpenPulse specification.

Examples

import pyqasm
qasm_code = """
OPENQASM 3.0;
defcalgrammar "openpulse";

complex[float[32]] amp = 1.0 + 2.0im;
cal {
   port d0;
   frame driveframe = newframe(d0, 5.0e9, 0.0);
   waveform wf = gaussian(amp, 16ns, 4ns);
}

const float frequency_start = 4.5e9;
const float frequency_step = 1e6;
const int frequency_num_steps = 3;

defcal saturation_pulse $0 {
   play(driveframe, constant(amp, 100e-6s));
}

cal {
   set_frequency(driveframe, frequency_start);
}

for int i in [1:frequency_num_steps] {
   cal {
       shift_frequency(driveframe, frequency_step);
   }
   saturation_pulse $0;
}
"""
module = pyqasm.loads(qasm_code)
module.unroll()

print(pyqasm.dumps(module))