openqasm / qe-compiler

An MLIR based compiler dynamic circuit compiler for real-time control systems supporting OpenQASM 3
Other
38 stars 12 forks source link

fix: support ~ operation, close #278 #303

Closed Zhaoyilunnn closed 3 months ago

Zhaoyilunnn commented 3 months ago

Hi Team,

I've added rules to parse "~" symbol in qe-qasm: https://github.com/openqasm/qe-qasm/pull/29, at the side of qe-compiler, we need to generate a MLIR op for ASTOpTypeBitNot in lib/Frontend/OpenQASM3/QUIRGenQASM3Visitor.cpp.

After searching, I find that MLIR does not have arith operations directly related to "BitNot". In the context of OQ3, I believe "~cbit" and "!cbit" are equivalent in a "if" condition? So I just simply reused the processing logic of ASTOpTypeLogicalNot, which checks whether the target is a I1Type and also works for ASTOpTypeBitNot operation. It will also create an CmpIPredicate::ne operation.

Willing to update my pr if you have better solutions

Thanks, Yilun

Changes

  1. Update the commit id of qe-qasm project
  2. Reuse the processing logic of ASTOpTypeLogicalNot for ASTOpTypeBitNot.

Sample

Given the following source file

OPENQASM 3.0;

qubit $0;

gate x q {}

bit[4] qc0_c0;

if ((qc0_c0[0] & qc0_c0[1] | qc0_c0[0] & qc0_c0[2] | qc0_c0[1] & qc0_c0[2]) & ~(qc0_c0[0] & qc0_c0[1] & qc0_c0[2])) {
    x $0;
}
qc0_c0[3] = measure $0;

The compiler generates following MLIR

module {
  oq3.declare_variable @qc0_c0 : !quir.cbit<4>
  func.func @x(%arg0: !quir.qubit<1>) {
    return
  }
  func.func @main() -> i32 {
    qcs.init
    %c0 = arith.constant 0 : index
    %c1000 = arith.constant 1000 : index
    %c1 = arith.constant 1 : index
    scf.for %arg0 = %c0 to %c1000 step %c1 {
      %dur = quir.constant #quir.duration<1.000000e+00> : !quir.duration<ms>
      quir.delay %dur, () : !quir.duration<ms>, () -> ()
      qcs.shot_init {qcs.num_shots = 1000 : i32}
      %0 = quir.declare_qubit {id = 0 : i32} : !quir.qubit<1>
      %c0_i4 = arith.constant 0 : i4
      %1 = "oq3.cast"(%c0_i4) : (i4) -> !quir.cbit<4>
      oq3.variable_assign @qc0_c0 : !quir.cbit<4> = %1
      %2 = oq3.variable_load @qc0_c0 : !quir.cbit<4>
      %3 = oq3.cbit_extractbit(%2 : !quir.cbit<4>) [0] : i1
      %4 = oq3.variable_load @qc0_c0 : !quir.cbit<4>
      %5 = oq3.cbit_extractbit(%4 : !quir.cbit<4>) [1] : i1
      %6 = oq3.cbit_and %3, %5 : i1
      %7 = oq3.variable_load @qc0_c0 : !quir.cbit<4>
      %8 = oq3.cbit_extractbit(%7 : !quir.cbit<4>) [0] : i1
      %9 = oq3.variable_load @qc0_c0 : !quir.cbit<4>
      %10 = oq3.cbit_extractbit(%9 : !quir.cbit<4>) [2] : i1
      %11 = oq3.cbit_and %8, %10 : i1
      %12 = oq3.cbit_or %6, %11 : i1
      %13 = oq3.variable_load @qc0_c0 : !quir.cbit<4>
      %14 = oq3.cbit_extractbit(%13 : !quir.cbit<4>) [1] : i1
      %15 = oq3.variable_load @qc0_c0 : !quir.cbit<4>
      %16 = oq3.cbit_extractbit(%15 : !quir.cbit<4>) [2] : i1
      %17 = oq3.cbit_and %14, %16 : i1
      %18 = oq3.cbit_or %12, %17 : i1
      %19 = oq3.variable_load @qc0_c0 : !quir.cbit<4>
      %20 = oq3.cbit_extractbit(%19 : !quir.cbit<4>) [0] : i1
      %21 = oq3.variable_load @qc0_c0 : !quir.cbit<4>
      %22 = oq3.cbit_extractbit(%21 : !quir.cbit<4>) [1] : i1
      %23 = oq3.cbit_and %20, %22 : i1
      %24 = oq3.variable_load @qc0_c0 : !quir.cbit<4>
      %25 = oq3.cbit_extractbit(%24 : !quir.cbit<4>) [2] : i1
      %26 = oq3.cbit_and %23, %25 : i1
      %true = arith.constant true
      %27 = arith.cmpi ne, %26, %true : i1
      %28 = oq3.cbit_and %18, %27 : i1
      scf.if %28 {
        quir.call_gate @x(%0) : (!quir.qubit<1>) -> ()
        %false = arith.constant false
      }
      %29 = quir.measure(%0) : (!quir.qubit<1>) -> i1
      oq3.cbit_assign_bit @qc0_c0<4> [3] : i1 = %29
    } {qcs.shot_loop}
    qcs.finalize
    %c0_i32 = arith.constant 0 : i32
    return %c0_i32 : i32
  }
}