OpenQASM 2 as Input¶
MQSSCI’s passes operate on MLIR, but circuits are often written as OpenQASM 2 text. The QASM2 frontend translates an OpenQASM 2 program into the CUDA-Q Quake dialect, so it can then go through the same passes and pipelines as any other Quake circuit. It is available in three forms:
the standalone
mqss-translatetool, built next tomqss-opt;the
MQSSCompilerC++ interface, whosecompile/compileSourceaccept OpenQASM 2 directly;the
translateQASM2ToQuakeC++ function, for callers who only want the Quake module.
The translation is a translation, not a pass: a QASM2 string is not an MLIR module, so there is
nothing for a pass to run on. It is the inverse direction of the quake-to-qasm2 pass.
Supported OpenQASM 2 Subset¶
The translator is deliberately small. It currently handles circuits such as Bell and GHZ states:
Construct |
Support |
|---|---|
|
Accepted; other versions are rejected. See the caveats below. |
|
Accepted and ignored; the supported gates are built in. |
|
Supported. All |
|
Supported, but only checked as the target of a |
|
Supported, applied to an indexed qubit, e.g. |
|
Supported, as a controlled |
|
Supported. Emits one |
|
Supported for equal-size registers; see the caveats below. |
|
Supported. |
Everything else is rejected with an error (see Caveats and Limitations).
Using mqss-translate¶
mqss-translate is built together with mqss-opt (see build) and installed into the
same INSTALL_DIR, so it is on your $PATH after eval "$(make set-target-paths)". It is built on
MLIR’s mlir-translate driver, so it accepts the usual -o, --help and - (stdin) arguments.
Take the Bell state bell.qasm:
OPENQASM 2.0;
include "qelib1.inc";
qreg q[2];
creg c[2];
h q[0];
cx q[0],q[1];
measure q[0] -> c[0];
measure q[1] -> c[1];
Translate it with:
mqss-translate --import-qasm2 bell.qasm
The result is a Quake module along these lines (SSA value names may differ):
module {
func.func @__nvqpp__mlirgen__qasm2_kernel() attributes {"cudaq-entrypoint", "cudaq-kernel"} {
%0 = quake.alloca !quake.veq<2>
%1 = quake.extract_ref %0[0] : (!quake.veq<2>) -> !quake.ref
%2 = quake.extract_ref %0[1] : (!quake.veq<2>) -> !quake.ref
quake.h %1 : (!quake.ref) -> ()
quake.x [%1] %2 : (!quake.ref, !quake.ref) -> ()
%measOut = quake.mz %1 : (!quake.ref) -> !quake.measure
%measOut_0 = quake.mz %2 : (!quake.ref) -> !quake.measure
return
}
}
Each measure q[i] -> c[j]; statement becomes one quake.mz on that qubit (%1 is q[0], %2 is
q[1]). A register-wide measure q -> c; is instead a single measurement of the whole register, as
in CUDA-Q’s own output:
%measOut = quake.mz %0 : (!quake.veq<3>) -> !cc.stdvec<!quake.measure>
This applies when q is the whole allocation, i.e. the program’s only qreg. If the program has
several qregs, measure q -> c; on one of them is emitted as one quake.mz per qubit instead.
The classical register itself is not represented in the Quake output.
The program becomes a single entry-point kernel named __nvqpp__mlirgen__qasm2_kernel, with the
cudaq-entrypoint and cudaq-kernel attributes the rest of MQSSCI expects.
Because the output is ordinary Quake text, it can be piped straight into mqss-opt:
mqss-translate --import-qasm2 bell.qasm | mqss-opt --BasisConversionPass=gates=prx,cz
See Running test circuits for what mqss-opt can do with it.
Using the MQSSCompiler Interface¶
MQSSCompiler::compile and MQSSCompiler::compileSource (and all their overloads) accept OpenQASM
2 in place of a Quake file or string. No extra option is needed:
#include "MQSSCIInterfaces/MQSSCompiler.h"
mqss::mqssci::MQSSCompiler compiler;
mqss::mqssci::CompilerOptions opts;
opts.optimization_level = mqss::mqssci::OptLevel::O1;
opts.result_format = mqss::mqssci::ResultFormat::OPENQASM2;
const std::string qasm2 = "OPENQASM 2.0;\n"
"include \"qelib1.inc\";\n"
"qreg q[2];\n"
"creg c[2];\n"
"h q[0];\n"
"cx q[0],q[1];\n"
"measure q -> c;\n";
std::optional<std::string> compiled =
compiler.compileSource(qasm2, {"rx", "cz", "rz"}, opts);
// Or, from a file: compiler.compile("path/to/bell.qasm", {"rx", "cz", "rz"}, opts);
The input format is detected from the content. If the first token of the input, after whitespace and
// comments, is OPENQASM, the text is translated to Quake first. Anything else is parsed as
MLIR, exactly as before. The file extension is not consulted, so compile and compileSource
behave the same way. From there the circuit goes through the normal pipeline: optimization, qubit
mapping, native-gate decomposition and lowering to result_format. The backend_name, native-gate
set and qubit_connectivity arguments work as described in Using MQSSCI as a Library.
An input that starts with OPENQASM but uses a version other than 2.x, or that falls outside the
supported subset above, makes compile/compileSource return std::nullopt after reporting a
diagnostic, like any other failure.
Getting the Quake module only¶
To get the translated Quake without compiling it, call translateQASM2ToQuake. It returns the
module’s textual MLIR form, or std::nullopt if the translation failed:
std::optional<std::string> quake = compiler.translateQASM2ToQuake(qasm2);
mqss::mqssci::translation::translateQASM2ToQuake(llvm::StringRef, mlir::MLIRContext &), declared
in Translation/QASM2ToQuake.h, is the lower-level entry point. It returns an mlir::ModuleOp in a
context you provide, and is what mqss-translate and MQSSCompiler are both built on.
MQSSCompiler::getSupportedInputFormats() lists OpenQASM 2 alongside the Quake and Catalyst
formats; see Discovering Supported Input Formats.
Caveats and Limitations¶
The translator covers a small, fixed subset of OpenQASM 2. Keep the following in mind.
Language coverage¶
Only a fixed gate set. The accepted gates are exactly
h x y z s sdg t tdg cx cz. Any other gate — including onesqelib1.incdefines, such asrx,u1,swaporccx— is rejected as unsupported.No parameterized gates or expressions. Gates with parameters,
U/u1/u2/u3, and arithmetic expressions such aspi/2are not handled.No user-defined or opaque gates.
gateandopaquedeclarations are rejected.No
barrier,resetorif. These are errors, not silently dropped, so no classical control flow is available.Gates act on indexed qubits only.
h q;(broadcast over a register) is rejected; writeh q[0];.measureis the one statement that accepts a whole register.includeis not read. Anyincludestatement is accepted and ignored. The file is never opened, so the gates listed above are the only ones available, whatever it names.Only
//comments. Block comments (/* ... */) are not recognized and produce an error.Keywords are case-sensitive, and the version header must be spelled
OPENQASM.
Registers and measurements¶
qregorder. Everyqregmust be declared before the first gate or measurement. A register name, quantum or classical, can only be declared once.Registers are flattened. All
qregs share a singlequake.alloca, laid out in declaration order. Register names and boundaries are not kept in the Quake output.Classical registers are not represented. A
cregis only checked to exist and to be large enough. The Quake output carries no classical-register name and no classical bit index, someasure q[0] -> c[1];andmeasure q[0] -> c[0];produce identical Quake. Measurements appear in statement order, and their results are not consumed by any other operation.Register-wide
measuredepends on the program.measure q -> c;becomes a singleveqmeasurement only whenqis the program’s entire allocation, i.e. its onlyqreg. With severalqregs it is emitted as onequake.mzper qubit.Mismatched registers are errors.
measureneeds both operands indexed or both whole, and whole registers must be the same size.
Output shape¶
One kernel. The program becomes a single function,
__nvqpp__mlirgen__qasm2_kernel, markedcudaq-entrypointandcudaq-kernel. Module-level attributes that CUDA-Q’s own frontend adds (data layout, target triple,quake.mangled_name_map) are not emitted.Round trips do not reproduce the input text. Translating to Quake and lowering back with
quake-to-qasm2yields a normalized program: registers are renamed, e.g.var0, and gates may be rewritten by the pipeline. Compare circuits by behavior, not by text.
Detection and diagnostics¶
MQSSCompilerrecognizes QASM2 by its header. Input is treated as OpenQASM only if its first token, after whitespace and//comments, isOPENQASM. A QASM file without the header is parsed as MLIR and fails with an MLIR parse error.mqss-translate --import-qasm2does not require the header, since you have already said the input is QASM.Only the version prefix is checked.
OPENQASM 2.0;and2.xheaders pass; anything else, such asOPENQASM 3.0;, is rejected. OpenQASM 3 syntax is not supported.The first error stops translation. The translator reports one diagnostic and gives up, so fixing it may reveal the next one. Through
MQSSCompilerit is followed by a generic “failed to parse input circuit” message.Locations are line and column within the text. Diagnostics point to
<qasm2>:line:col, not to the file path, even when the text came from a file passed tocompile.InputFormat::QASM2is for reporting. It makesgetSupportedInputFormats()list OpenQASM 2, but it is not an option you set: the format is always detected from the content.
Tests¶
The OpenQASM 2 path is covered by:
unit tests in
tests/unittests/MQSSCIInterfaces/test_Interface.cpp, which compile the Bell and GHZ fixturestests/unittests/input/two_qubit_bell.qasmandghz.qasmthroughcompileSourceand check the rejected inputs. Run them withmake test-interfaces;a lit test for the tool,
tests/dialects/quake/QASM2ToQuakeBell.test, run as part ofcheck-mqss.