diff --git a/CHANGELOG.md b/CHANGELOG.md index 9bf4690fa..94ce08b8f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,9 @@ This project adheres to [Semantic Versioning](http://semver.org/). - integer values must be non-negative - support for resource constrained scheduler - creates .map file reporting measurement statements present in input, to allow retrieving measurements downstream + - support for Error Injection (conditional gates based on thresholded Pseudo Random Number Generator result) +- pass opt.ConstProp: constant propagator, replaces constant expressions by their result +- pass opt.DeadCodeElim: dead code eliminator, removes dead code, currently only unreachable if-branches ### Changed - pass dec.Instructions: duration=0 in new-style decomposition rules now disables checking whether expansion fits, allowing automatic calculation of duration (and requiring scheduling after decomposition of such rules) diff --git a/include/ql/ir/ops.h b/include/ql/ir/ops.h index 1bbbb3016..f0d0d0e94 100644 --- a/include/ql/ir/ops.h +++ b/include/ql/ir/ops.h @@ -59,6 +59,13 @@ DataTypeLink add_type(const Ref &ir, Args... args) { * Returns the data type with the given name, or returns an empty link if the * type does not exist. */ +DataTypeLink find_type(const ir::Platform &platform, const utils::Str &name); + +/** + * Returns the data type with the given name, or returns an empty link if the + * type does not exist. + */ +// FIXME: this function only really needs ir->platform, similarly for several functions below DataTypeLink find_type(const Ref &ir, const utils::Str &name); /** diff --git a/src/ql/arch/cc/pass/gen/vq1asm/detail/backend.cc b/src/ql/arch/cc/pass/gen/vq1asm/detail/backend.cc index edfee6896..329edac6e 100644 --- a/src/ql/arch/cc/pass/gen/vq1asm/detail/backend.cc +++ b/src/ql/arch/cc/pass/gen/vq1asm/detail/backend.cc @@ -95,6 +95,8 @@ void Backend::codegen_block(const ir::BlockBaseRef &block, const Str &name, Int codegen.block_start(name, depth); // Loop over the statements and handle them individually. + // + // Functions that can throw an exception are wrapped in a try/catch to add context to the error message for (const auto &stmt : block->statements) { if (auto insn = stmt->as_instruction()) { diff --git a/src/ql/arch/cc/pass/gen/vq1asm/detail/bundle_info.h b/src/ql/arch/cc/pass/gen/vq1asm/detail/bundle_info.h index fb5a34bfb..e5ca62468 100644 --- a/src/ql/arch/cc/pass/gen/vq1asm/detail/bundle_info.h +++ b/src/ql/arch/cc/pass/gen/vq1asm/detail/bundle_info.h @@ -27,7 +27,9 @@ enum class ConditionType { // 1 operand: UNARY, NOT, // 2 operands - AND, NAND, OR, NOR, XOR, NXOR + AND, NAND, OR, NOR, XOR, NXOR, + // rnd_bit() + RND_BIT }; @@ -35,7 +37,7 @@ struct tInstructionCondition { public: ConditionType cond_type; utils::Vec cond_operands; - Str describe; // for annotation purposes + Str describe; // the original condition, for logging purposes }; @@ -62,7 +64,7 @@ class BundleInfo { // real-time measurement results: flag and operands Bool isMeasRsltRealTime = false; - UInt breg_operand = 0; // the breg where the result is to be stored + UInt bregTargetMeasRsltRealTime = 0; // the breg where the result is to be stored // original instruction, for logging purposes Str describe; diff --git a/src/ql/arch/cc/pass/gen/vq1asm/detail/codegen.cc b/src/ql/arch/cc/pass/gen/vq1asm/detail/codegen.cc index e46ed64ef..77b16b95e 100644 --- a/src/ql/arch/cc/pass/gen/vq1asm/detail/codegen.cc +++ b/src/ql/arch/cc/pass/gen/vq1asm/detail/codegen.cc @@ -36,7 +36,8 @@ static Str as_label(const Str &label) { return label + ":"; } * Decode the expression for a conditional instruction into the old format as used for the API. Eventually this will have * to be changed, but as long as the CC can handle expressions with 2 variables only this covers all we need. */ -// FIXME: move to datapath +// FIXME: move to datapath? +// FIXME: redesign like handle_expression() static tInstructionCondition decode_condition(const OperandContext &operandContext, const ir::ExpressionRef &condition) { ConditionType cond_type; utils::Vec cond_operands; @@ -92,14 +93,13 @@ static tInstructionCondition decode_condition(const OperandContext &operandConte QL_ICE("unsupported gate condition"); } #if OPT_CC_USER_FUNCTIONS - // FIXME: note that is only here to allow playing around with function calls as condition. Real support - // requires a redesign } else if ( - fn->function_type->name == "rnd" || - fn->function_type->name == "rnd_seed" + fn->function_type->name == "rnd_bit" ) { - cond_type = ConditionType::ALWAYS; + cond_type = ConditionType::RND_BIT; + cond_operands.push_back(fn->operands[0]->as_int_literal()->value); // FIXME: check. QL_WOUT("FIXME: instruction condition function not yet handled: " + fn->function_type->name); + // FIXME: check profile #endif } else { CHECK_COMPAT(fn->operands.size() == 2, "expected 2 operands"); @@ -274,7 +274,7 @@ Codegen::Codegen(const ir::Ref &ir, const OptionsRef &options) , options(options) , operandContext(ir) , cs(operandContext) - , fncs(operandContext, dp, cs) + , fncs(*ir->platform, operandContext, dp, cs) { // NB: a new Backend is instantiated per call to compile, and // as a result also a Codegen, so we don't need to cleanup @@ -491,7 +491,7 @@ Codegen::CodeGenMap Codegen::collectCodeGenInfo( }; #endif // allocate SM bit for classic operand - UInt smBit = dp.allocateSmBit(bi.breg_operand, instrIdx); + UInt smBit = dp.allocateSmBit(bi.bregTargetMeasRsltRealTime, instrIdx); // remind mapping of bit -> smBit for setting MUX codeGenInfo.measResultRealTimeMap.emplace(group, MeasResultRealTimeInfo{smBit, resultBit, bi.describe}); @@ -516,8 +516,13 @@ void Codegen::bundle_finish( CodeGenMap codeGenMap = collectCodeGenInfo(startCycle, durationInCycles); // compute stuff requiring overview over all instruments: - // FIXME: add: - // - DSM used, for seq_inv_sm + UInt rnd_adv_all = 0; // mask of all PRNGs used (which need to advance to the next value) + for (UInt instrIdx = 0; instrIdx < settings.getInstrumentsSize(); instrIdx++) { + CodeGenInfo codeGenInfo = codeGenMap.at(instrIdx); + rnd_adv_all |= dp.getRndAdv(codeGenInfo.condGateMap); + + // FIXME: add DSM used, for seq_inv_sm + } // determine whether bundle has any real-time measurement results Bool bundleHasMeasRsltRealTime = false; @@ -542,6 +547,7 @@ void Codegen::bundle_finish( if (codeGenInfo.instrHasOutput) { emitOutput( codeGenInfo.condGateMap, + rnd_adv_all, codeGenInfo.digOut, codeGenInfo.instrMaxDurationInCycles, instrIdx, @@ -675,10 +681,10 @@ void Codegen::custom_instruction(const ir::CustomInstruction &custom) { throw; } } - if (ops.has_integer) { + if (ops.integers.size() > 0) { QL_INPUT_ERROR("CC backend cannot handle integer operands yet"); } - if (ops.has_angle) { + if (ops.angles.size() > 0) { QL_INPUT_ERROR("CC backend cannot handle real (angle) operands yet"); } @@ -765,7 +771,7 @@ void Codegen::custom_instruction(const ir::CustomInstruction &custom) { * note that Creg's are managed through a class, whereas bregs are just numbers * - breg result (new) * - * In the new IR (or, better said, in the new way "prototype"s for instruction operands van be defined + * In the new IR (or, better said, in the new way "prototype"s for instruction operands can be defined * using access modes as described in * https://openql.readthedocs.io/en/latest/gen/reference_configuration.html#instructions-section * it is not well possible to specify a measurement that returns its result in a different bit than @@ -793,11 +799,11 @@ void Codegen::custom_instruction(const ir::CustomInstruction &custom) { // handle classic operand if (ops.bregs.empty()) { // FIXME: currently always - bi.breg_operand = ops.qubits[0]; // implicit classic bit for qubit - QL_IOUT("using implicit bit " << bi.breg_operand << " for qubit " << ops.qubits[0]); + bi.bregTargetMeasRsltRealTime = ops.qubits[0]; // implicit classic bit for qubit + QL_IOUT("using implicit bit " << bi.bregTargetMeasRsltRealTime << " for qubit " << ops.qubits[0]); } else { // FIXME: currently impossible - bi.breg_operand = ops.bregs[0]; - QL_IOUT("using explicit bit " << bi.breg_operand << " for qubit " << ops.qubits[0]); + bi.bregTargetMeasRsltRealTime = ops.bregs[0]; + QL_IOUT("using explicit bit " << bi.bregTargetMeasRsltRealTime << " for qubit " << ops.qubits[0]); } } @@ -1084,7 +1090,7 @@ void Codegen::handle_expression(const ir::ExpressionRef &expression, const Str & QL_ICE("expected reference to breg, but got: " << ir::describe(expression)); } } else if (auto fn = expression->as_function_call()) { - // FIXME: handle (bit) cast? + // Note that the platform doesn't define a bit cast function // handle the function fncs.dispatch(fn, label_if_false, describe); @@ -1114,6 +1120,17 @@ void Codegen::emitProgramStart(const Str &progName) { cs.emit(".CODE"); // start .CODE section +#if OPT_CC_USER_FUNCTIONS + cs.emit("# random processor constants"); + cs.emit(".DEF RND_REG_SEED_3 0 # Random number generator register: seed[127:96]"); + cs.emit(".DEF RND_REG_SEED_2 1 # Random number generator register: seed[95:64]"); + cs.emit(".DEF RND_REG_SEED_1 2 # Random number generator register: seed[63:32]"); + cs.emit(".DEF RND_REG_SEED_0 3 # Random number generator register: seed[31:0]"); + cs.emit(".DEF RND_REG_THRESHOLD 4 # Random number generator register: threshold"); + cs.emit(".DEF RND_REG_RANGE 5 # Random number generator register: range"); + cs.emit(".DEF RND_REG_VALUE 6 # Random number generator register: value"); +#endif + // NB: new seq_bar semantics (firmware from 20191219 onwards) comment("# synchronous start and latency compensation"); cs.emit("", "seq_bar", "", "# synchronization, delay set externally through SET_SEQ_BAR_CNT"); @@ -1193,6 +1210,7 @@ void Codegen::emitMeasRsltRealTime( void Codegen::emitOutput( const CondGateMap &condGateMap, + UInt rnd_adv_all, tDigital digOut, UInt instrMaxDurationInCycles, UInt instrIdx, @@ -1224,12 +1242,17 @@ void Codegen::emitOutput( UInt smAddr = dp.emitPl(pl, condGateMap, instrIdx, slot); // emit code for conditional gate + Str arg = QL_SS2S("S" << smAddr << "," << pl << "," << instrMaxDurationInCycles); + if (rnd_adv_all != 0) { + arg += QL_SS2S(",0x" << std::hex << rnd_adv_all); + } cs.emit( slot, "seq_out_sm", - QL_SS2S("S" << smAddr << "," << pl << "," << instrMaxDurationInCycles), - QL_SS2S("# cycle " << startCycle << "-" << startCycle + instrMaxDurationInCycles << ": conditional code word/mask on '" << instrumentName << "'") + arg, + QL_SS2S("# cycle " << startCycle << "-" << startCycle + instrMaxDurationInCycles << ": conditional code word/mask on '" << instrumentName << "'") // FIXME: upfate comment for rnd_adv ); + // FIXME: also make this happen on instruments not involved now } // update lastEndCycle diff --git a/src/ql/arch/cc/pass/gen/vq1asm/detail/codegen.h b/src/ql/arch/cc/pass/gen/vq1asm/detail/codegen.h index 4e25b0709..3bdfe10d4 100644 --- a/src/ql/arch/cc/pass/gen/vq1asm/detail/codegen.h +++ b/src/ql/arch/cc/pass/gen/vq1asm/detail/codegen.h @@ -128,11 +128,6 @@ class Codegen { void handle_expression(const ir::ExpressionRef &expression, const Str &label_if_false, const Str &descr); // FIXME: private? -protected: - // FIXME: split off emitting into separate class -// friend class Functions; // needs access to emit*() - - private: // types /** * Code generation info for single instrument. @@ -195,7 +190,7 @@ class Codegen { void emitProgramStart(const Str &progName); void emitProgramFinish(); void emitMeasRsltRealTime(const MeasResultRealTimeMap &measResultRealTimeMap, UInt instrIdx, UInt startCycle, Int slot, const Str &instrumentName); - void emitOutput(const CondGateMap &condGateMap, tDigital digOut, UInt instrMaxDurationInCycles, UInt instrIdx, UInt startCycle, Int slot, const Str &instrumentName); + void emitOutput(const CondGateMap &condGateMap, UInt rnd_adv_all, tDigital digOut, UInt instrMaxDurationInCycles, UInt instrIdx, UInt startCycle, Int slot, const Str &instrumentName); void emitPadToCycle(UInt instrIdx, UInt startCycle, Int slot, const Str &instrumentName); // generic helpers diff --git a/src/ql/arch/cc/pass/gen/vq1asm/detail/codesection.h b/src/ql/arch/cc/pass/gen/vq1asm/detail/codesection.h index d81a9b2fa..470f992ae 100644 --- a/src/ql/arch/cc/pass/gen/vq1asm/detail/codesection.h +++ b/src/ql/arch/cc/pass/gen/vq1asm/detail/codesection.h @@ -12,6 +12,7 @@ #define NUM_RSRVD_CREGS 2 // must match number of REG_TMP* #define NUM_CREGS (64-NUM_RSRVD_CREGS) // starting from R0 #define NUM_BREGS 1024 // bregs require mapping to DSM, which introduces holes, so we probably fail before we reach this limit +#define NUM_RND 8 // must match HDL namespace ql { namespace arch { diff --git a/src/ql/arch/cc/pass/gen/vq1asm/detail/datapath.cc b/src/ql/arch/cc/pass/gen/vq1asm/detail/datapath.cc index afd3c454b..9de5ec605 100644 --- a/src/ql/arch/cc/pass/gen/vq1asm/detail/datapath.cc +++ b/src/ql/arch/cc/pass/gen/vq1asm/detail/datapath.cc @@ -44,7 +44,7 @@ UInt Datapath::allocateSmBit(UInt breg_operand, UInt instrIdx) { // - DSM size is 1024 bits (128 bytes) // Other notes: // - we don't attempt to be smart about DSM transfer size allocation - // - new allocations to the same breg_operand overwrite the old mapping + // - new allocations to the same breg overwrite the old mapping // - we don't reuse SM bits (thus wasting space) UInt smBit = 0; @@ -61,11 +61,11 @@ UInt Datapath::allocateSmBit(UInt breg_operand, UInt instrIdx) { auto it = mapBregToSmBit.find(breg_operand); if (it != mapBregToSmBit.end()) { - QL_IOUT("overwriting mapping of breg_operand " << it->second); + QL_IOUT("overwriting mapping of breg " << it->second); } } - QL_IOUT("mapping breg_operand " << breg_operand << " to smBit " << smBit); + QL_IOUT("mapping breg " << breg_operand << " to smBit " << smBit); mapBregToSmBit.set(breg_operand) = smBit; // created on demand smBitLastInstrIdx = instrIdx; @@ -74,16 +74,15 @@ UInt Datapath::allocateSmBit(UInt breg_operand, UInt instrIdx) { return smBit; } -// NB: bit_operand can be breg_operand or cond_operand, depending on context of caller. FIXME: cond_operand no longer exists,update identifiers and strings -UInt Datapath::getSmBit(UInt bit_operand) const { +UInt Datapath::getSmBit(UInt breg) const { UInt smBit; - auto it = mapBregToSmBit.find(bit_operand); + auto it = mapBregToSmBit.find(breg); if (it != mapBregToSmBit.end()) { smBit = it->second; - QL_DOUT("found mapping: bit_operand " << bit_operand << " to smBit " << smBit); + QL_DOUT("found mapping: breg " << breg << " to smBit " << smBit); } else { - QL_INPUT_ERROR("Request for DSM bit of bit_operand " << bit_operand << " that was never assigned by measurement"); // NB: message refers to user perspective (and thus calling semantics) + QL_INPUT_ERROR("Request for DSM bit of breg " << breg << " that was never assigned by measurement"); // NB: message refers to user perspective (and thus calling semantics) } return smBit; } @@ -172,11 +171,30 @@ UInt Datapath::getMuxSmAddr(const MeasResultRealTimeMap &measResultRealTimeMap) } +UInt Datapath::getRndAdv(const CondGateMap &condGateMap) { + UInt rnd_adv = 0; // mask of PRNGs used + + for (auto &cg : condGateMap) { + Int group = cg.first; + CondGateInfo cgi = cg.second; + + if (cgi.instructionCondition.cond_type == ConditionType::RND_BIT) { + UInt prng = cgi.instructionCondition.cond_operands[0]; + rnd_adv |= 1UL<=NUM_RND) QL_INPUT_ERROR("Illegal RND index"); + Int seed = a.ops.integers[1]; + if(seed<0 || seed>0xFFFFFFFF) QL_INPUT_ERROR("Illegal seed"); // FIXME: 128 bit + + // FIXME: 128 different bits + cs.emit("", "rnd_set", QL_SS2S(idx << ",$RND_REG_SEED_3," << seed)); + cs.emit("", "rnd_set", QL_SS2S(idx << ",$RND_REG_SEED_2," << seed)); + cs.emit("", "rnd_set", QL_SS2S(idx << ",$RND_REG_SEED_1," << seed)); + cs.emit("", "rnd_set", QL_SS2S(idx << ",$RND_REG_SEED_0," << seed)); + +/* // FIXME + rnd_get 0,$RND_REG_VALUE,R0 + rnd_get 7,$RND_REG_SEED_3,R1 + + rnd_set + rnd_set 0,$RND_REG_SEED_2,0xA5A5A5A5 + rnd_set 0,$RND_REG_SEED_1,0x5A5A5A5A + rnd_set 0,$RND_REG_SEED_0,0xFFFFFFFF # FIXME: order for real applications? + + rnd_set 6,,0x0000FFFF + + rnd_set 5,$RND_REG_THRESHOLD,R0 +*/ } -void Functions::rnd_seed_i(const FncArgs &a) { - // FIXME +void Functions::rnd_threshold_ir(const FncArgs &a) { + Int idx = a.ops.integers[0]; + if(idx<0 || idx>=NUM_RND) QL_INPUT_ERROR("Illegal RND index"); + Real threshold = a.ops.angles[0]; // NB: floating point parameters are collected in 'angles'. The naming is a historic artefact + if(threshold<0 || threshold>1.0) QL_INPUT_ERROR("Illegal threshold"); + UInt thresholdVal = threshold * 0xFFFFFFFF; + + cs.emit("", "rnd_set", QL_SS2S(idx << ",$RND_REG_THRESHOLD," << thresholdVal), QL_SS2S("# threshold = " << threshold)); } -void Functions::rnd_C(const FncArgs &a) { - // FIXME +void Functions::rnd_range_ii(const FncArgs &a) { + Int idx = a.ops.integers[0]; + if(idx<0 || idx>=NUM_RND) QL_INPUT_ERROR("Illegal RND index"); + Int range = a.ops.integers[1]; + if(range<0 || range>0xFFFFFFFF) QL_INPUT_ERROR("Illegal range"); + + cs.emit("", "rnd_set", QL_SS2S(idx << ",$RND_REG_RANGE," << range)); +} + +void Functions::rnd_bit_i(const FncArgs &a) { + QL_INPUT_ERROR("rnd_bit_i can only be used in 'cond' context"); } void Functions::rnd_i(const FncArgs &a) { - // FIXME + Int idx = a.ops.integers[0]; + if(idx<0 || idx>=NUM_RND) QL_INPUT_ERROR("Illegal RND index"); + + cs.emit("", "rnd_get", QL_SS2S(idx << ",$RND_REG_VALUE," << as_reg(a.dest_reg))); } #endif @@ -296,8 +340,7 @@ void Functions::rnd_i(const FncArgs &a) { * * The set of functions available here should match that in the platform as set by * 'convert_old_to_new(const compat::PlatformRef &old)'. - * Unfortunately, consistency must currently be maintained manually. - * FIXME: we might check against ir->platform->functions + * Unfortunately, consistency must currently be maintained manually; we perform a runtime check in register_functions() * * We maintain separate tables for functions returning an * - int (in the context of a SetInstruction); the result is passed to the LHS register @@ -330,9 +373,10 @@ X("operator^", iC, op_grp_int_2op_Ci_iC, "xor") \ #define CC_FUNCTION_LIST_INT_USER \ /* user functions */ \ -X("rnd_seed", C, rnd_seed_C, "") \ -X("rnd_seed", i, rnd_seed_i, "") \ -X("rnd", C, rnd_C, "") \ +X("rnd_seed", ii, rnd_seed_ii, "") /* FIXME: 128 bit */ \ +X("rnd_threshold", ir, rnd_threshold_ir, "") \ +X("rnd_range", ii, rnd_range_ii, "") \ +X("rnd_bit", i, rnd_bit_i, "") \ X("rnd", i, rnd_i, "") #define CC_FUNCTION_LIST_BIT \ @@ -340,10 +384,12 @@ X("rnd", i, rnd_i, "") /* bit arithmetic, 1 operand: "!" */ \ X("operator!", B, op_linv_B, "") \ \ -/* bit arithmetic, 2 operands: "&&", "||", "^^" */ \ +/* bit arithmetic, 2 operands: "&&", "||", "^^", "==", "!=" */ \ X("operator&&", BB, op_grp_bit_2op_BB, "") \ X("operator||", BB, op_grp_bit_2op_BB, "") \ X("operator^^", BB, op_grp_bit_2op_BB, "") \ +X("operator==", BB, op_grp_bit_2op_BB, "") \ +X("operator!=", BB, op_grp_bit_2op_BB, "") \ \ /* relop, group 1: "==", "!=" */ \ X("operator==", CC, op_grp_rel1_CC, "jge") \ @@ -389,6 +435,83 @@ void Functions::register_functions() { CC_FUNCTION_LIST_BIT }; #undef X + +#if 1 // FIXME: WIP + // check consistency of our function set against platform + + // get the types we expect + // NB: these are created in convert_old_to_new(const compat::PlatformRef &old) + auto bit_type = platform.default_bit_type; + auto int_type = platform.default_int_type; + auto real_type = find_type(platform, "real"); + + for (const auto &fnc : platform.functions) { + + // determine operand data_types + // NB: the data_types encoding is similar to func_gen::Function::generate_impl_footer, and also Operands::profile, + // but note that all have different semantics + Str data_types; + for (const auto &ot : fnc->operand_types) { + if(ot->data_type == bit_type) { + data_types += "b"; + } else if (ot->data_type == int_type) { + data_types += "i"; + } else if (ot->data_type == real_type) { + data_types += "r"; + } else { + QL_WOUT("Platform function '" << fnc->name << "' has operand of unknown type"); + } + + // FIXME: also check .mode? + } + + // determine function map, based on return type + FuncMap *fm; + if (fnc->return_type == bit_type) { + fm = &func_map_bit; + } else if (fnc->return_type == int_type) { + fm = &func_map_int; + } else { + QL_WOUT("Platform function '" << fnc->name << "' has unknown return type"); + } + + // determine expected profiles for data_types + // Note that we do not expect profiles with only constant arguments, since these are handled by pass 'opt.ConstProp' + Vec expected_profiles; + if (data_types == "b") { + expected_profiles = {"B"}; + } else if (data_types == "bb") { + expected_profiles = {"BB"}; + } else if (data_types == "i") { + if (fnc->name == "rnd" || fnc->name == "rnd_bit") { // FIXME: handle exceptions is a more scalable way + expected_profiles = {"i"}; + } else { + expected_profiles = {"C"}; + } + } else if (data_types == "ii") { + if (fnc->name == "rnd_seed") { // FIXME: handle exceptions is a more scalable way + expected_profiles = {"ii"}; + } else { + expected_profiles = {"CC", "Ci", "iC"}; + } + } else if (data_types == "ir") { + expected_profiles = {"ir"}; + } else { + QL_IOUT("Platform function '" << fnc->name << "' with data_types '" << data_types << "' not supported by CC backend"); + } + + // check whether we implement the expected function variants + for (const auto &profile : expected_profiles) { + // make an exception for 'int cast' function that we handle elsewhere (in Codegen::handle_set_instruction) + if (fnc->name == "int" && profile == "B") continue; + + if (fm->find(fnc->name + "_" + profile) == fm->end()) { + QL_IOUT("Platform function '" << fnc->name << "' with profile '" << profile << "' not supported by CC backend"); + } + } + + } +#endif } diff --git a/src/ql/arch/cc/pass/gen/vq1asm/detail/functions.h b/src/ql/arch/cc/pass/gen/vq1asm/detail/functions.h index de39e3394..dfcbfcb10 100644 --- a/src/ql/arch/cc/pass/gen/vq1asm/detail/functions.h +++ b/src/ql/arch/cc/pass/gen/vq1asm/detail/functions.h @@ -20,7 +20,7 @@ class Codegen; // to prevent recursive include loop class Functions { public: - explicit Functions(const OperandContext &operandContext, const Datapath &dp, CodeSection &cs); + explicit Functions(const ir::Platform &platform, const OperandContext &operandContext, const Datapath &dp, CodeSection &cs); ~Functions() = default; /* @@ -69,10 +69,12 @@ class Functions { private: // vars // references to object instances needed + const ir::Platform &platform; // platform info, specifically the set of functions available const OperandContext &operandContext; // context for Operand processing const Datapath &dp; // handling of CC datapath CodeSection &cs; // handling of code section + // maps to access functions FuncMap func_map_int; // map name to function info, see register_functions() FuncMap func_map_bit; // idem, for functions returning a bit @@ -126,9 +128,10 @@ class Functions { * other functions returning an int, for code generation in expressions */ #if OPT_CC_USER_FUNCTIONS - void rnd_seed_C(const FncArgs &a); - void rnd_seed_i(const FncArgs &a); - void rnd_C(const FncArgs &a); + void rnd_seed_ii(const FncArgs &a); + void rnd_threshold_ir(const FncArgs &a); + void rnd_range_ii(const FncArgs &a); + void rnd_bit_i(const FncArgs &a); void rnd_i(const FncArgs &a); #endif diff --git a/src/ql/arch/cc/pass/gen/vq1asm/detail/operands.cc b/src/ql/arch/cc/pass/gen/vq1asm/detail/operands.cc index e67bfd019..0acea9d75 100644 --- a/src/ql/arch/cc/pass/gen/vq1asm/detail/operands.cc +++ b/src/ql/arch/cc/pass/gen/vq1asm/detail/operands.cc @@ -147,13 +147,10 @@ void Operands::append(const OperandContext &operandContext, const ir::Expression Str operand_type = "?"; // default unless overwritten. Currently, only used for function parameters if (auto real_lit = expr->as_real_literal()) { - CHECK_COMPAT(!has_angle, "encountered gate with multiple angle (real) operands"); - has_angle = true; - angle = real_lit->value; + angles.push_back(real_lit->value); + operand_type = "r"; } else if (auto int_lit = expr->as_int_literal()) { - CHECK_COMPAT(!has_integer, "encountered gate with multiple integer operands"); - has_integer = true; - integer = int_lit->value; + integers.push_back(int_lit->value); operand_type = "i"; } else if (expr->as_bit_literal()) { // FIXME: do something @@ -188,8 +185,7 @@ void Operands::append(const OperandContext &operandContext, const ir::Expression ); } } else if (expr->as_function_call()) { - QL_ICE("encountered unsupported function call in operand list: " << describe(expr)); -// QL_INPUT_ERROR("cannot currently handle function call within function call '" << ir::describe(op) << "'"); + QL_INPUT_ERROR("encountered unsupported function call in operand list: " << describe(expr)); } else { QL_ICE("unsupported expression: " << describe(expr)); } diff --git a/src/ql/arch/cc/pass/gen/vq1asm/detail/operands.h b/src/ql/arch/cc/pass/gen/vq1asm/detail/operands.h index cf07ff2be..d7dae2afb 100644 --- a/src/ql/arch/cc/pass/gen/vq1asm/detail/operands.h +++ b/src/ql/arch/cc/pass/gen/vq1asm/detail/operands.h @@ -100,32 +100,23 @@ class Operands { */ utils::Vec bregs; - /** - * Angle operand existence. - */ - utils::Bool has_angle = false; - /** * Angle operand value. */ - utils::Real angle = 0.0; - - /** - * Integer operand existence. - */ - utils::Bool has_integer = false; + utils::Vec angles; /** * Integer operand value. */ - utils::Int integer = 0; + utils::Vec integers; /** * The profile for the operands provided. Encoding: * - 'b': bit literal - * - 'i': int literal * - 'B': breg reference + * - 'i': int literal * - 'C': creg reference + * - 'r': real literal (NB: we don't have registers supporting reals) * - '?': anything else * * Inspired by func_gen::Function::generate_impl_footer and cqasm::types::from_spec, but notice that we add 'C' and @@ -134,7 +125,7 @@ class Operands { Str profile; /** - * Appends an operand. + * Appends an operand (for an IR custom_instruction or function_call) */ void append(const OperandContext &operandContext, const ir::ExpressionRef &expr); diff --git a/src/ql/ir/consistency.cc b/src/ql/ir/consistency.cc index c0d34bf09..abd8826c7 100644 --- a/src/ql/ir/consistency.cc +++ b/src/ql/ir/consistency.cc @@ -950,6 +950,7 @@ void check_consistency(const Ref &ir) { ir->visit(consistency_checker); } catch (utils::Exception &e) { + // FIXME: we can also get a '::tree::base::NotWellFormed' // If the check fails, dump the tree. QL_EOUT( diff --git a/src/ql/ir/cqasm/read.cc b/src/ql/ir/cqasm/read.cc index e8bf8bc8f..457b641b9 100644 --- a/src/ql/ir/cqasm/read.cc +++ b/src/ql/ir/cqasm/read.cc @@ -2,6 +2,8 @@ * cQASM 1.2 reader logic as human-readable complement of the IR. */ +#define OPT_ANNOTATE_SOURCE_LOCATION 0 // FIXME: WIP on annotation + #include "ql/ir/cqasm/read.h" #include "ql/utils/filesystem.h" @@ -10,7 +12,12 @@ #include "ql/ir/consistency.h" #include "ql/ir/old_to_new.h" #include "ql/com/ddg/build.h" + #include "cqasm.hpp" +#if OPT_ANNOTATE_SOURCE_LOCATION +#include "cqasm-annotations.hpp" // for SourceLocation +#include "ql/utils/tree.h" // for Annotatable +#endif namespace ql { namespace ir { @@ -734,7 +741,6 @@ static void convert_block( ql_operands.add(convert_expression(ir, cq_operand, sgmq_size, sgmq_index)); } } - ql_insns.push_back(make_instruction(ir, cq_insn->name, ql_operands, ql_condition)); } else if ( !options.measure_all_target.empty() && @@ -789,7 +795,15 @@ static void convert_block( ql_operands[1] = x; } +#if OPT_ANNOTATE_SOURCE_LOCATION + auto ql_insn = make_instruction(ir, cq_insn->name, ql_operands, ql_condition); + const auto source_location = cq_insn->get_annotation<::cqasm::annotations::SourceLocation>(); + QL_IOUT("set source location for '" << cq_insn->name << "' to '" << source_location << "'"); + ql_insn->set_annotation(source_location); + ql_insns.push_back(ql_insn); +#else ql_insns.push_back(make_instruction(ir, cq_insn->name, ql_operands, ql_condition)); +#endif } } diff --git a/src/ql/ir/ir.tree b/src/ql/ir/ir.tree index c79937052..2f9f12e97 100644 --- a/src/ql/ir/ir.tree +++ b/src/ql/ir/ir.tree @@ -20,9 +20,9 @@ include "ql/ir/prim.h" initialize_function prim::initialize serdes_functions prim::serialize prim::deserialize -// Include SourceLocation annotation object for the debug dump generator. -//src_include "cqasm-parse-helper.hpp" -//location cqasm::parser::SourceLocation +// Include SourceLocation annotation object for the debug dump generator (i.e. class Dumper). +// src_include "cqasm-v1-parse-helper.hpp" +// location cqasm::v1::parser::SourceLocation # Namespace for the IR tree node classes. namespace ql diff --git a/src/ql/ir/old_to_new.cc b/src/ql/ir/old_to_new.cc index 4bb093eca..acc9d1414 100644 --- a/src/ql/ir/old_to_new.cc +++ b/src/ql/ir/old_to_new.cc @@ -293,6 +293,12 @@ Ref convert_old_to_new(const compat::PlatformRef &old) { // Add type for angle operands. auto real_type = add_type(ir, "real"); +#if OPT_CC_USER_FUNCTIONS + // Add extra types. + add_type(ir, "json"); + add_type(ir, "string"); +#endif + // Add the instruction set. We load this from the JSON data rather than // trying to use instruction_map, because the latter has some pretty ****ed // up stuff going on in it to make the legacy decompositions work. @@ -939,12 +945,27 @@ Ref convert_old_to_new(const compat::PlatformRef &old) { if(architecture == "cc") { QL_IOUT("adding hardcoded CC functions"); fn = add_function_type(ir, utils::make("rnd_seed")); - fn->operand_types.emplace(prim::OperandMode::READ, int_type); // seed - fn->return_type = int_type; + fn->operand_types.emplace(prim::OperandMode::READ, int_type); // RNG_index, literal + fn->operand_types.emplace(prim::OperandMode::READ, int_type); // seed, literal + fn->return_type = int_type; // FIXME: void + + fn = add_function_type(ir, utils::make("rnd_threshold")); + fn->operand_types.emplace(prim::OperandMode::READ, int_type); // RNG_index, literal + fn->operand_types.emplace(prim::OperandMode::READ, real_type); // threshold, literal + fn->return_type = int_type; // FIXME: void + + fn = add_function_type(ir, utils::make("rnd_range")); + fn->operand_types.emplace(prim::OperandMode::READ, int_type); // RNG_index, literal + fn->operand_types.emplace(prim::OperandMode::READ, int_type); // range, literal + fn->return_type = int_type; // FIXME: void + + fn = add_function_type(ir, utils::make("rnd_bit")); + fn->operand_types.emplace(prim::OperandMode::READ, int_type); // RNG_index, literal + fn->return_type = bit_type; fn = add_function_type(ir, utils::make("rnd")); - fn->operand_types.emplace(prim::OperandMode::READ, real_type); // threshold - fn->return_type = bit_type; + fn->operand_types.emplace(prim::OperandMode::READ, int_type); // RNG_index, literal + fn->return_type = int_type; } #endif diff --git a/src/ql/ir/ops.cc b/src/ql/ir/ops.cc index ae7fb835a..2e522b445 100644 --- a/src/ql/ir/ops.cc +++ b/src/ql/ir/ops.cc @@ -14,9 +14,9 @@ namespace ir { * Returns the data type with the given name, or returns an empty link if the * type does not exist. */ -DataTypeLink find_type(const Ref &ir, const utils::Str &name) { - auto begin = ir->platform->data_types.get_vec().begin(); - auto end = ir->platform->data_types.get_vec().end(); +DataTypeLink find_type(const ir::Platform &platform, const utils::Str &name) { + auto begin = platform.data_types.get_vec().begin(); + auto end = platform.data_types.get_vec().end(); auto pos = std::lower_bound( begin, end, utils::make(name), @@ -29,6 +29,14 @@ DataTypeLink find_type(const Ref &ir, const utils::Str &name) { } } +/** + * Returns the data type with the given name, or returns an empty link if the + * type does not exist. + */ +DataTypeLink find_type(const Ref &ir, const utils::Str &name) { + return find_type(*ir->platform, name); +} + /** * Returns the data type of/returned by an expression. */ diff --git a/tests/cc/test_cc_cqasm.py b/tests/cc/test_cc_cqasm.py index df4a1a501..db762953d 100644 --- a/tests/cc/test_cc_cqasm.py +++ b/tests/cc/test_cc_cqasm.py @@ -6,6 +6,7 @@ import unittest import pathlib import inspect +from sys import platform #from utils import file_compare import openql as ql @@ -31,6 +32,120 @@ def run_test_case(self, name): # ql.set_option('log_level', 'LOG_DEBUG') # ql.set_option('log_level', 'LOG_WARNING') + if 0: # extracted from PycQED. FIXME: use this instead of code below (after fixing failing tests) + pl = ql.Platform("cc", "config_cc_s17_direct_iq_openql_0_10.json") + c = pl.get_compiler() + + # remove default pass list (this also removes support for most *global* options as defined in + # https://openql.readthedocs.io/en/latest/gen/reference_options.html, except for 'log_level') + # NB: this defeats automatic backend selection by OpenQL based on key "eqasm_compiler" + c.clear_passes() + + # cQASM reader as very first step + c.append_pass( + 'io.cqasm.Read', + 'reader', + { + 'cqasm_file': in_fn + } + ) + + # perform legacy decompositions (those defined in the "gate_decomposition" section), see: + # - https://openql.readthedocs.io/en/latest/gen/reference_passes.html#instruction-decomposer + # - https://openql.readthedocs.io/en/latest/gen/reference_passes.html#predicate-key + c.append_pass( + 'dec.Instructions', + 'dec_legacy', + { + 'predicate_key': 'name', + 'predicate_value': 'legacy' + } + ) + + # perform new-style decompositions, pre-scheduling + c.append_pass( + 'dec.Instructions', + 'dec_pre_sched', + { + 'predicate_key': 'when', + 'predicate_value': 'pre-sched' + } + ) + + # report the initial qasm + c.append_pass( + 'io.cqasm.Report', + 'initial', + { + 'output_suffix': '.cq', + 'with_timing': 'no' + } + ) + + # add constant propagation pass + c.append_pass( + 'opt.ConstProp', + 'const_prop', + { + 'output_prefix': 'test_output/%N.%P', + 'debug': 'yes' + } + ) + + # add dead code elimination pass + c.append_pass( + 'opt.DeadCodeElim', + 'dead_code_elim', + { + 'output_prefix': 'test_output/%N.%P', + 'debug': 'yes' + } + ) + + # schedule + c.append_pass( + 'sch.ListSchedule', + 'scheduler', + { + 'resource_constraints': 'yes' + } + ) + + # perform new-style decompositions, post-scheduling + c.append_pass( + 'dec.Instructions', + 'dec_post_sched', + { + 'predicate_key': 'when', + 'predicate_value': 'post-sched' + } + ) + + # report scheduled qasm + c.append_pass( + 'io.cqasm.Report', + 'scheduled', + { + 'output_suffix': '.cq', + } + ) + + # generate code using CC backend + # NB: OpenQL >= 0.10 no longer has a CC-light backend + c.append_pass( + 'arch.cc.gen.VQ1Asm', + 'cc_backend' + ) + + # set compiler pass options + c.set_option('*.output_prefix', 'test_output/%N.%P') + c.set_option('cc_backend.output_prefix', 'test_output/%N') + c.set_option('scheduler.scheduler_target', 'alap') + c.set_option('cc_backend.run_once', 'yes') # if you want to loop, write a cqasm loop + + # compile + c.compile_with_frontend(pl) + if 1: # use pass manager pl = ql.Platform("cc", "config_cc_s17_direct_iq_openql_0_10.json") @@ -76,7 +191,7 @@ def run_test_case(self, name): 'reader', { 'cqasm_file': in_fn, - 'output_prefix': 'test_output/%N.%P', + 'output_prefix': 'test_output/%N.%P', # FIXME: %N.%P does not work correctly for reader 'debug': 'yes' } ) @@ -110,6 +225,10 @@ def test_looping(self): def test_cond_gate(self): self.run_test_case('cond_gate') + @unittest.skipIf(platform == "linux" or platform == "darwin", "Fails on Ubuntu and Macos, see 'test_rnd_proc.cq'") # FIXME: actually solve underlying issue + def test_rnd_proc(self): + self.run_test_case('rnd_proc') + def test_const_prop(self): self.run_test_case('const_prop') diff --git a/tests/cc/test_cond_gate.cq b/tests/cc/test_cond_gate.cq index 760f2ce2c..7bc3589f7 100644 --- a/tests/cc/test_cond_gate.cq +++ b/tests/cc/test_cond_gate.cq @@ -23,10 +23,6 @@ cond (b[0] && b[1]) rx180 q[0] cond (b[0] || b[1]) rx180 q[0] cond (b[0] ^^ b[1]) rx180 q[0] -# FIXME: seems to break CI, see https://github.com/QuTech-Delft/OpenQL/runs/5588352276?check_suite_focus=true -# cond (rnd(0.5)) rx180 q[0] - - .condGateWithInstrDecomposition #cond (false) x q[0] cond (true) x q[0] @@ -38,9 +34,6 @@ cond (b[0] && b[1]) x q[0] cond (b[0] || b[1]) x q[0] cond (b[0] ^^ b[1]) x q[0] -# FIXME: seems to break CI, see https://github.com/QuTech-Delft/OpenQL/runs/5588352276?check_suite_focus=true -# cond (rnd(0.5)) x q[0] - # from: deps/libqasm/src/cqasm/tests/v1-parsing/semantic/insn-condition-new-ok/input.cq # note; differing lengths between condition and parameters are allowed # cond (b[0:3]) x q[4:5] # Error: Unknown error: in pass reader, phase main: Duplicate node of type N2ql2ir10ExpressionEat address 0x7f9e8b5d9978 found in tree diff --git a/tests/cc/test_looping.cq b/tests/cc/test_looping.cq index ef5698a68..7bd953273 100644 --- a/tests/cc/test_looping.cq +++ b/tests/cc/test_looping.cq @@ -140,10 +140,18 @@ if (i==0) { while (i<10000) { } +.condLe +while (i<=10000) { +} + .condGt while (i>10000) { } +.condGe +while (i>=10000) { +} + .condEq while (i==10000) { } diff --git a/tests/cc/test_rnd_proc.cq b/tests/cc/test_rnd_proc.cq new file mode 100644 index 000000000..567e9ef72 --- /dev/null +++ b/tests/cc/test_rnd_proc.cq @@ -0,0 +1,24 @@ +# File: test_rnd_proc.cq +# Purpose: test random processor with CC backend +# Notes: + +version 1.2 +qubits 17 + +pragma @ql.name("test_rnd_proc") +# pragma @ql.platform("config_cc_s17_direct_iq_openql_0_10.json") + + +# initial support for error injection +# all the lines containing "rnd_*" make CI fail on Ubuntu if "debug" is enabled in any pass, because generating debug +# output results in "RuntimeError: Container error: in pass reader, phase debugging.after: dereferencing past-the-end iterator", see: +# - https://github.com/QuTech-Delft/OpenQL/pull/457/commits/d8a1953864a8dc9fdddcb84aa77c282fe77b8b24 +# - https://github.com/QuTech-Delft/OpenQL/actions/runs/3090616085/jobs/4999645433 (if still available) +map foo = creg(0) +set foo = rnd_seed(0, 12345678) +set foo = rnd_threshold(0, 0.5) +cond (rnd_bit(0)) rx180 q[0] + +# Impossible: +# set foo = rnd_seed(0, 0x12345678) # FIXME: test_cond_gate.cq:29:21: syntax error, unexpected BAD_NUMBER +# rnd_seed(0, 12345678) # FIXME: Error: test_cond_gate.cq:31:11: syntax error, unexpected ',' diff --git a/tests/test_cqasm.py b/tests/test_cqasm.py index 39a340455..4605d825a 100644 --- a/tests/test_cqasm.py +++ b/tests/test_cqasm.py @@ -2,7 +2,7 @@ import filecmp import unittest from utils import file_compare -from openql import openql as ql +import openql as ql curdir = os.path.dirname(os.path.realpath(__file__)) platf = ql.Platform("starmon", "none")