diff --git a/lib/Conversion/HandshakeToHW/HandshakeToHW.cpp b/lib/Conversion/HandshakeToHW/HandshakeToHW.cpp index ed2f02f0a485..1f9bff4885e4 100644 --- a/lib/Conversion/HandshakeToHW/HandshakeToHW.cpp +++ b/lib/Conversion/HandshakeToHW/HandshakeToHW.cpp @@ -487,8 +487,7 @@ struct RTLBuilder { Value rst = Value()) : b(builder), loc(loc), clk(clk), rst(rst) {} - Value constant(unsigned width, int64_t value, Location *extLoc = nullptr) { - APInt apv = APInt(width, value); + Value constant(const APInt &apv, Location *extLoc = nullptr) { auto it = constants.find(apv); if (it != constants.end()) return it->second; @@ -497,6 +496,10 @@ struct RTLBuilder { constants[apv] = cval; return cval; } + + Value constant(unsigned width, int64_t value, Location *extLoc = nullptr) { + return constant(APInt(width, value), extLoc); + } std::pair wrap(Value data, Value valid, Location *extLoc = nullptr) { auto wrapOp = b.create(getLoc(extLoc), data, valid); @@ -526,6 +529,11 @@ struct RTLBuilder { StringAttr()); } + Value cmp(Value lhs, Value rhs, comb::ICmpPredicate predicate, + Location *extLoc = nullptr) { + return b.create(getLoc(extLoc), predicate, lhs, rhs); + } + // Bitwise 'and'. Value bAnd(ValueRange values, Location *extLoc = nullptr) { return b.create(getLoc(extLoc), values).getResult(); @@ -564,6 +572,19 @@ struct RTLBuilder { .getResults(); } + llvm::SmallVector toBits(Value v, Location *extLoc = nullptr) { + llvm::SmallVector bits; + for (unsigned i = 0, e = v.getType().getIntOrFloatBitWidth(); i != e; ++i) + bits.push_back( + b.create(getLoc(extLoc), v, i, /*bitWidth=*/1)); + return bits; + } + + // OR-reduction of the bits in 'v'. + Value rOr(Value v, Location *extLoc = nullptr) { + return bOr(toBits(v, extLoc), extLoc); + } + // Extract bits v[hi:lo] (inclusive). Value extract(Value v, unsigned lo, unsigned hi, Location *extLoc = nullptr) { unsigned width = hi - lo + 1; @@ -614,6 +635,33 @@ struct RTLBuilder { return arrayGet(arrayCreate(values, extLoc), index, extLoc); } + // Muxes a range of values. The select signal is expected to be a 1-hot + // encoded value. + Value ohMux(Value index, ValueRange inputs, Location *extLoc = nullptr) { + // Confirm the select input can be a one-hot encoding for the inputs. + unsigned numInputs = inputs.size(); + assert(numInputs == index.getType().getIntOrFloatBitWidth() && + "one-hot select can't mux inputs"); + + // Start the mux tree with zero value. + // Todo: clean up when i0 support is available. + Value muxValue; + auto dataType = inputs[0].getType(); + if (dataType.isa()) + muxValue = b.create(getLoc(extLoc)); + else + muxValue = constant(dataType.getIntOrFloatBitWidth(), 0, extLoc); + + // Iteratively chain together muxes from the high bit to the low bit. + for (size_t i = numInputs - 1; i == 0; --i) { + Value input = inputs[i]; + Value selectBit = bit(index, i, extLoc); + muxValue = mux(selectBit, {muxValue, input}, extLoc); + } + + return muxValue; + } + Location getLoc(Location *extLoc = nullptr) { return extLoc ? *extLoc : loc; } OpBuilder &b; Location loc; @@ -649,7 +697,8 @@ class HandshakeConversionPattern : public OpConversionPattern { ConversionPatternRewriter &rewriter) const override { // Check if a submodule has already been created for the op. If so, - // instantiate the submodule. Else, run the pattern-defined module builder. + // instantiate the submodule. Else, run the pattern-defined module + // builder. hw::HWModuleLike implModule = checkSubModuleOp(ls.parentModule, op); if (!implModule) { auto portInfo = ModulePortInfo(getPortInfoForOp(rewriter, op)); @@ -770,8 +819,8 @@ class HandshakeConversionPattern : public OpConversionPattern { // Extract the selection bit for this input. auto isSelected = s.bit(select1h, inIdx); - // '&' that with the result valid and ready, and assign to the input ready - // signal. + // '&' that with the result valid and ready, and assign to the input + // ready signal. auto activeAndResultValidAndReady = s.bAnd({isSelected, resValidAndReady}); in.ready->setValue(activeAndResultValidAndReady); @@ -801,8 +850,8 @@ class HandshakeConversionPattern : public OpConversionPattern { } // Builds a unit-rate actor around an inner operation. 'unitBuilder' is a - // function which takes the set of unwrapped data inputs, and returns a value - // which should be assigned to the output data value. + // function which takes the set of unwrapped data inputs, and returns a + // value which should be assigned to the output data value. void buildUnitRateJoinLogic( RTLBuilder &s, UnwrappedIO &unwrappedIO, llvm::function_ref unitBuilder) const { @@ -854,6 +903,149 @@ class HandshakeConversionPattern : public OpConversionPattern { }); } + /// Return the number of bits needed to index the given number of values. + static size_t getNumIndexBits(uint64_t numValues) { + return numValues > 1 ? llvm::Log2_64_Ceil(numValues) : 1; + } + + Value buildPriorityArbiter(RTLBuilder &s, ArrayRef inputs, + Value defaultValue, + DenseMap &indexMapping) const { + auto numInputs = inputs.size(); + auto priorityArb = defaultValue; + + for (size_t i = numInputs; i > 0; --i) { + size_t inputIndex = i - 1; + size_t oneHotIndex = 1 << inputIndex; + auto constIndex = s.constant(numInputs, oneHotIndex); + indexMapping[inputIndex] = constIndex; + priorityArb = s.mux(inputs[inputIndex], {constIndex, priorityArb}); + } + return priorityArb; + } + + // Builds merge-logic. If 'resIndex' is provided, resIndex is assigned the + // index of the input which was selected. + void buildMergeLogic(RTLBuilder &s, BackedgeBuilder &bb, + UnwrappedIO &unwrappedIO, OutputHandshake &resData, + OutputHandshake *resIndex = nullptr) const { + // Define some common types and values that will be used. + unsigned numInputs = unwrappedIO.inputs.size(); + auto indexType = s.b.getIntegerType(numInputs); + Value noWinner = s.constant(numInputs, 0); + Value c0I1 = s.constant(1, 0); + + // Declare register for storing arbitration winner. + auto won = bb.get(indexType); + Value wonReg = s.reg("won_reg", won, noWinner); + + // Declare wire for arbitration winner. + auto win = bb.get(indexType); + + // Declare wire for whether the circuit just fired and emitted both + // outputs. + auto fired = bb.get(s.b.getI1Type()); + + // Declare registers for storing if each output has been emitted. + auto resultEmitted = bb.get(s.b.getI1Type()); + Value resultEmittedReg = s.reg("result_emitted_reg", resultEmitted, c0I1); + std::unique_ptr indexEmitted; + Value indexEmittedReg; + if (resIndex) { + indexEmitted = std::make_unique(bb.get(s.b.getI1Type())); + indexEmittedReg = s.reg("index_emitted_reg", *indexEmitted, c0I1); + } + + // Declare wires for if each output is done. + auto resultDone = bb.get(s.b.getI1Type()); + std::unique_ptr indexDone; + if (resIndex) + indexDone = std::make_unique(bb.get(s.b.getI1Type())); + + // Create predicates to assert if the win wire or won register hold a + // valid index. + auto hasWinnerCondition = s.rOr({win}); + auto hadWinnerCondition = s.rOr({wonReg}); + + // Create an arbiter based on a simple priority-encoding scheme to assign + // an index to the win wire. If the won register is set, just use that. In + // the case that won is not set and no input is valid, set a sentinel + // value to indicate no winner was chosen. The constant values are + // remembered in a map so they can be re-used later to assign the arg + // ready outputs. + DenseMap argIndexValues; + Value priorityArb = buildPriorityArbiter(s, unwrappedIO.getInputValids(), + noWinner, argIndexValues); + priorityArb = s.mux(hadWinnerCondition, {priorityArb, wonReg}); + win.setValue(priorityArb); + + // Create the logic to assign the result and index outputs. The result + // valid output will always be assigned, and if isControl is not set, the + // result data output will also be assigned. The index valid and data + // outputs will always be assigned. The win wire from the arbiter is used + // to index into a tree of muxes to select the chosen input's signal(s), + // and is fed directly to the index output. Both the result and index + // valid outputs are gated on the win wire being set to something other + // than the sentinel value. + auto resultNotEmitted = s.bNot(resultEmittedReg); + auto resultValid = s.bAnd({hasWinnerCondition, resultNotEmitted}); + resData.valid->setValue(resultValid); + resData.data->setValue(s.ohMux(win, unwrappedIO.getInputDatas())); + + auto indexNotEmitted = s.bNot(indexEmittedReg); + auto indexValid = s.bAnd({hasWinnerCondition, indexNotEmitted}); + if (resIndex) { + resIndex->valid->setValue(indexValid); + + // Use the one-hot win wire to select the index to output in the index + // data. + SmallVector indexOutputs; + for (size_t i = 0; i < numInputs; ++i) + indexOutputs.push_back(s.constant(64, i)); + + auto indexOutput = s.ohMux(win, indexOutputs); + resIndex->data->setValue(indexOutput); + } + + // Create the logic to set the won register. If the fired wire is + // asserted, we have finished this round and can and reset the register to + // the sentinel value that indicates there is no winner. Otherwise, we + // need to hold the value of the win register until we can fire. + won.setValue(s.mux(fired, {win, noWinner})); + + // Create the logic to set the done wires for the result and index. For + // both outputs, the done wire is asserted when the output is valid and + // ready, or the emitted register for that output is set. + auto resultValidAndReady = s.bAnd({resultValid, resData.ready}); + resultDone.setValue(s.bOr({resultValidAndReady, resultEmittedReg})); + + if (resIndex) { + auto indexValidAndReady = s.bAnd({indexValid, resIndex->ready}); + indexDone->setValue(s.bOr({indexValidAndReady, indexEmittedReg})); + + // Create the logic to set the fired wire. It is asserted when both result + // and index are done. + fired.setValue(s.bAnd({resultDone, *indexDone})); + } + + // Create the logic to assign the emitted registers. If the fired wire is + // asserted, we have finished this round and can reset the registers to 0. + // Otherwise, we need to hold the values of the done registers until we + // can fire. + resultEmitted.setValue(s.mux(fired, {resultDone, c0I1})); + if (resIndex) + indexEmitted->setValue(s.mux(fired, {*indexDone, c0I1})); + + // Create the logic to assign the arg ready outputs. The logic is + // identical for each arg. If the fired wire is asserted, and the win wire + // holds an arg's index, that arg is ready. + auto winnerOrDefault = s.mux(fired, {noWinner, win}); + for (auto [i, ir] : llvm::enumerate(unwrappedIO.getInputReadys())) { + auto &indexValue = argIndexValues[i]; + ir->setValue(s.cmp(winnerOrDefault, indexValue, comb::ICmpPredicate::eq)); + } + } + private: OpBuilder &submoduleBuilder; HandshakeLoweringState &ls; @@ -953,8 +1145,8 @@ class ReturnConversionPattern LogicalResult matchAndRewrite(ReturnOp op, OpAdaptor adaptor, ConversionPatternRewriter &rewriter) const override { - // Locate existing output op, Append operands to output op, and move to the - // end of the block. + // Locate existing output op, Append operands to output op, and move to + // the end of the block. auto parent = cast(op->getParentOp()); auto outputOp = *parent.getBodyBlock()->getOps().begin(); outputOp->setOperands(adaptor.getOperands()); @@ -1003,6 +1195,38 @@ class UnpackConversionPattern : public HandshakeConversionPattern { }; }; +class ConditionalBranchConversionPattern + : public HandshakeConversionPattern { +public: + using HandshakeConversionPattern< + ConditionalBranchOp>::HandshakeConversionPattern; + void buildModule(ConditionalBranchOp op, BackedgeBuilder &bb, RTLBuilder &s, + hw::HWModulePortAccessor &ports) const override { + auto unwrappedIO = unwrapIO(s, bb, ports); + auto cond = unwrappedIO.inputs[0]; + auto arg = unwrappedIO.inputs[1]; + auto trueRes = unwrappedIO.outputs[0]; + auto falseRes = unwrappedIO.outputs[1]; + + auto condArgValid = s.bAnd({cond.valid, arg.valid}); + + // Connect valid signal of both results. + trueRes.valid->setValue(s.bAnd({cond.data, condArgValid})); + falseRes.valid->setValue(s.bAnd({s.bNot(cond.data), condArgValid})); + + // Connecte data signals of both results. + trueRes.data->setValue(arg.data); + falseRes.data->setValue(arg.data); + + // Connect ready signal of input and condition. + auto selectedResultReady = + s.mux(cond.data, {falseRes.ready, trueRes.ready}); + auto condArgReady = s.bAnd({selectedResultReady, condArgValid}); + arg.ready->setValue(condArgReady); + cond.ready->setValue(condArgReady); + }; +}; + template class ExtendConversionPattern : public HandshakeConversionPattern { public: @@ -1066,6 +1290,69 @@ class TruncateConversionPattern }; }; +class ControlMergeConversionPattern + : public HandshakeConversionPattern { +public: + using HandshakeConversionPattern::HandshakeConversionPattern; + void buildModule(ControlMergeOp op, BackedgeBuilder &bb, RTLBuilder &s, + hw::HWModulePortAccessor &ports) const override { + auto unwrappedIO = this->unwrapIO(s, bb, ports); + auto resData = unwrappedIO.outputs[0]; + auto resIndex = unwrappedIO.outputs[1]; + buildMergeLogic(s, bb, unwrappedIO, resData, &resIndex); + }; +}; + +class MergeConversionPattern : public HandshakeConversionPattern { +public: + using HandshakeConversionPattern::HandshakeConversionPattern; + void buildModule(MergeOp op, BackedgeBuilder &bb, RTLBuilder &s, + hw::HWModulePortAccessor &ports) const override { + auto unwrappedIO = this->unwrapIO(s, bb, ports); + auto resData = unwrappedIO.outputs[0]; + buildMergeLogic(s, bb, unwrappedIO, resData); + }; +}; + +class SinkConversionPattern : public HandshakeConversionPattern { +public: + using HandshakeConversionPattern::HandshakeConversionPattern; + void buildModule(SinkOp op, BackedgeBuilder &bb, RTLBuilder &s, + hw::HWModulePortAccessor &ports) const override { + auto unwrappedIO = this->unwrapIO(s, bb, ports); + // A sink is always ready to accept a new value. + unwrappedIO.inputs[0].ready->setValue(s.constant(1, 1)); + }; +}; + +class SourceConversionPattern : public HandshakeConversionPattern { +public: + using HandshakeConversionPattern::HandshakeConversionPattern; + void buildModule(SourceOp op, BackedgeBuilder &bb, RTLBuilder &s, + hw::HWModulePortAccessor &ports) const override { + auto unwrappedIO = this->unwrapIO(s, bb, ports); + // A source always provides a new (none-typed) value. + unwrappedIO.outputs[0].valid->setValue(s.constant(1, 1)); + unwrappedIO.outputs[0].data->setValue( + s.b.create(s.getLoc())); + }; +}; + +class ConstantConversionPattern + : public HandshakeConversionPattern { +public: + using HandshakeConversionPattern< + handshake::ConstantOp>::HandshakeConversionPattern; + void buildModule(handshake::ConstantOp op, BackedgeBuilder &bb, RTLBuilder &s, + hw::HWModulePortAccessor &ports) const override { + auto unwrappedIO = this->unwrapIO(s, bb, ports); + unwrappedIO.outputs[0].valid->setValue(unwrappedIO.inputs[0].valid); + unwrappedIO.inputs[0].ready->setValue(unwrappedIO.outputs[0].ready); + auto constantValue = op->getAttrOfType("value").getValue(); + unwrappedIO.outputs[0].data->setValue(s.constant(constantValue)); + }; +}; + class BufferConversionPattern : public HandshakeConversionPattern { public: using HandshakeConversionPattern::HandshakeConversionPattern; @@ -1122,7 +1409,8 @@ class BufferConversionPattern : public HandshakeConversionPattern { auto readyRegWire = bb.get(s.b.getI1Type()); auto readyReg = s.reg(getRegName("ready"), readyRegWire, c0I1); - // Create the logic to drive the current stage valid and potentially data. + // Create the logic to drive the current stage valid and potentially + // data. currentStage.valid = s.mux(readyReg, {validReg, readyReg}); // Create the logic to drive the current stage ready. @@ -1157,9 +1445,10 @@ class BufferConversionPattern : public HandshakeConversionPattern { auto emptyOrReady = s.bOr({notValidReg, readyBE}); preStage.ready->setValue(emptyOrReady); - // Create a mux that drives the register input. If the emptyOrReady signal - // is asserted, the mux selects the predValid signal. Otherwise, it - // selects the register output, keeping the output registered unchanged. + // Create a mux that drives the register input. If the emptyOrReady + // signal is asserted, the mux selects the predValid signal. Otherwise, + // it selects the register output, keeping the output registered + // unchanged. auto validRegMux = s.mux(emptyOrReady, {validReg, preStage.valid}); // Now we can drive the valid register. @@ -1322,11 +1611,8 @@ static LogicalResult convertFuncOp(ESITypeConverter &typeConverter, SyncConversionPattern>(typeConverter, op.getContext(), moduleBuilder, ls); - patterns.insert, - ExtModuleConversionPattern, - ExtModuleConversionPattern, - ExtModuleConversionPattern, - MuxConversionPattern, SelectConversionPattern, + patterns.insert, UnitRateConversionPattern, UnitRateConversionPattern, @@ -1342,6 +1628,9 @@ static LogicalResult convertFuncOp(ESITypeConverter &typeConverter, UnitRateConversionPattern, PackConversionPattern, UnpackConversionPattern, ComparisonConversionPattern, BufferConversionPattern, + SourceConversionPattern, SinkConversionPattern, + ConstantConversionPattern, MergeConversionPattern, + ControlMergeConversionPattern, ExtendConversionPattern, ExtendConversionPattern, TruncateConversionPattern, IndexCastConversionPattern>( diff --git a/test/Conversion/HandshakeToHW/test_cmerge.mlir b/test/Conversion/HandshakeToHW/test_cmerge.mlir new file mode 100644 index 000000000000..385216335ba2 --- /dev/null +++ b/test/Conversion/HandshakeToHW/test_cmerge.mlir @@ -0,0 +1,111 @@ +// RUN: circt-opt -lower-handshake-to-hw -split-input-file %s | FileCheck %s + +// Test a control merge that is control only. + +// CHECK-LABEL: hw.module @handshake_control_merge_out_ui64_2ins_2outs_ctrl( +// CHECK-SAME: %[[VAL_0:.*]]: !esi.channel, %[[VAL_1:.*]]: !esi.channel, %[[VAL_2:.*]]: i1, %[[VAL_3:.*]]: i1) -> (dataOut: !esi.channel, index: !esi.channel) { +// CHECK: %[[VAL_4:.*]], %[[VAL_5:.*]] = esi.unwrap.vr %[[VAL_0]], %[[VAL_6:.*]] : none +// CHECK: %[[VAL_7:.*]], %[[VAL_8:.*]] = esi.unwrap.vr %[[VAL_1]], %[[VAL_9:.*]] : none +// CHECK: %[[VAL_10:.*]], %[[VAL_11:.*]] = esi.wrap.vr %[[VAL_12:.*]], %[[VAL_13:.*]] : none +// CHECK: %[[VAL_14:.*]], %[[VAL_15:.*]] = esi.wrap.vr %[[VAL_16:.*]], %[[VAL_17:.*]] : i64 +// CHECK: %[[VAL_18:.*]] = hw.constant 0 : i2 +// CHECK: %[[VAL_19:.*]] = hw.constant false +// CHECK: %[[VAL_20:.*]] = seq.compreg %[[VAL_21:.*]], %[[VAL_2]], %[[VAL_3]], %[[VAL_18]] : i2 +// CHECK: %[[VAL_22:.*]] = seq.compreg %[[VAL_23:.*]], %[[VAL_2]], %[[VAL_3]], %[[VAL_19]] : i1 +// CHECK: %[[VAL_24:.*]] = seq.compreg %[[VAL_25:.*]], %[[VAL_2]], %[[VAL_3]], %[[VAL_19]] : i1 +// CHECK: %[[VAL_26:.*]] = comb.extract %[[VAL_27:.*]] from 0 : (i2) -> i1 +// CHECK: %[[VAL_28:.*]] = comb.extract %[[VAL_27]] from 1 : (i2) -> i1 +// CHECK: %[[VAL_29:.*]] = comb.or %[[VAL_26]], %[[VAL_28]] : i1 +// CHECK: %[[VAL_30:.*]] = comb.extract %[[VAL_20]] from 0 : (i2) -> i1 +// CHECK: %[[VAL_31:.*]] = comb.extract %[[VAL_20]] from 1 : (i2) -> i1 +// CHECK: %[[VAL_32:.*]] = comb.or %[[VAL_30]], %[[VAL_31]] : i1 +// CHECK: %[[VAL_33:.*]] = hw.constant -2 : i2 +// CHECK: %[[VAL_34:.*]] = comb.mux %[[VAL_8]], %[[VAL_18]], %[[VAL_33]] : i2 +// CHECK: %[[VAL_35:.*]] = hw.constant 1 : i2 +// CHECK: %[[VAL_36:.*]] = comb.mux %[[VAL_5]], %[[VAL_34]], %[[VAL_35]] : i2 +// CHECK: %[[VAL_27]] = comb.mux %[[VAL_32]], %[[VAL_20]], %[[VAL_36]] : i2 +// CHECK: %[[VAL_37:.*]] = hw.constant true +// CHECK: %[[VAL_38:.*]] = comb.xor %[[VAL_22]], %[[VAL_37]] : i1 +// CHECK: %[[VAL_13]] = comb.and %[[VAL_29]], %[[VAL_38]] : i1 +// CHECK: %[[VAL_12]] = esi.none : none +// CHECK: %[[VAL_39:.*]] = comb.xor %[[VAL_24]], %[[VAL_37]] : i1 +// CHECK: %[[VAL_17]] = comb.and %[[VAL_29]], %[[VAL_39]] : i1 +// CHECK: %[[VAL_16]] = hw.constant 0 : i64 +// CHECK: %[[VAL_40:.*]] = hw.constant 1 : i64 +// CHECK: %[[VAL_21]] = comb.mux %[[VAL_41:.*]], %[[VAL_18]], %[[VAL_27]] : i2 +// CHECK: %[[VAL_42:.*]] = comb.and %[[VAL_13]], %[[VAL_11]] : i1 +// CHECK: %[[VAL_43:.*]] = comb.or %[[VAL_42]], %[[VAL_22]] : i1 +// CHECK: %[[VAL_44:.*]] = comb.and %[[VAL_17]], %[[VAL_15]] : i1 +// CHECK: %[[VAL_45:.*]] = comb.or %[[VAL_44]], %[[VAL_24]] : i1 +// CHECK: %[[VAL_41]] = comb.and %[[VAL_43]], %[[VAL_45]] : i1 +// CHECK: %[[VAL_23]] = comb.mux %[[VAL_41]], %[[VAL_19]], %[[VAL_43]] : i1 +// CHECK: %[[VAL_25]] = comb.mux %[[VAL_41]], %[[VAL_19]], %[[VAL_45]] : i1 +// CHECK: %[[VAL_46:.*]] = comb.mux %[[VAL_41]], %[[VAL_27]], %[[VAL_18]] : i2 +// CHECK: %[[VAL_6]] = comb.icmp eq %[[VAL_46]], %[[VAL_35]] : i2 +// CHECK: %[[VAL_9]] = comb.icmp eq %[[VAL_46]], %[[VAL_33]] : i2 +// CHECK: hw.output %[[VAL_10]], %[[VAL_14]] : !esi.channel, !esi.channel +// CHECK: } + +handshake.func @test_cmerge(%arg0: none, %arg1: none, %arg2: none, ...) -> (none, index, none) { + %0:2 = control_merge %arg0, %arg1 : none + return %0#0, %0#1, %arg2 : none, index, none +} + +// ----- + +// Test a control merge that also outputs the selected input's data. + +// CHECK-LABEL: hw.module @handshake_control_merge_in_ui64_ui64_ui64_out_ui64_ui64( +// CHECK-SAME: %[[VAL_0:.*]]: !esi.channel, %[[VAL_1:.*]]: !esi.channel, %[[VAL_2:.*]]: !esi.channel, %[[VAL_3:.*]]: i1, %[[VAL_4:.*]]: i1) -> (dataOut: !esi.channel, index: !esi.channel) { +// CHECK: %[[VAL_5:.*]], %[[VAL_6:.*]] = esi.unwrap.vr %[[VAL_0]], %[[VAL_7:.*]] : i64 +// CHECK: %[[VAL_8:.*]], %[[VAL_9:.*]] = esi.unwrap.vr %[[VAL_1]], %[[VAL_10:.*]] : i64 +// CHECK: %[[VAL_11:.*]], %[[VAL_12:.*]] = esi.unwrap.vr %[[VAL_2]], %[[VAL_13:.*]] : i64 +// CHECK: %[[VAL_14:.*]], %[[VAL_15:.*]] = esi.wrap.vr %[[VAL_16:.*]], %[[VAL_17:.*]] : i64 +// CHECK: %[[VAL_18:.*]], %[[VAL_19:.*]] = esi.wrap.vr %[[VAL_16]], %[[VAL_20:.*]] : i64 +// CHECK: %[[VAL_21:.*]] = hw.constant 0 : i3 +// CHECK: %[[VAL_22:.*]] = hw.constant false +// CHECK: %[[VAL_23:.*]] = seq.compreg %[[VAL_24:.*]], %[[VAL_3]], %[[VAL_4]], %[[VAL_21]] : i3 +// CHECK: %[[VAL_25:.*]] = seq.compreg %[[VAL_26:.*]], %[[VAL_3]], %[[VAL_4]], %[[VAL_22]] : i1 +// CHECK: %[[VAL_27:.*]] = seq.compreg %[[VAL_28:.*]], %[[VAL_3]], %[[VAL_4]], %[[VAL_22]] : i1 +// CHECK: %[[VAL_29:.*]] = comb.extract %[[VAL_30:.*]] from 0 : (i3) -> i1 +// CHECK: %[[VAL_31:.*]] = comb.extract %[[VAL_30]] from 1 : (i3) -> i1 +// CHECK: %[[VAL_32:.*]] = comb.extract %[[VAL_30]] from 2 : (i3) -> i1 +// CHECK: %[[VAL_33:.*]] = comb.or %[[VAL_29]], %[[VAL_31]], %[[VAL_32]] : i1 +// CHECK: %[[VAL_34:.*]] = comb.extract %[[VAL_23]] from 0 : (i3) -> i1 +// CHECK: %[[VAL_35:.*]] = comb.extract %[[VAL_23]] from 1 : (i3) -> i1 +// CHECK: %[[VAL_36:.*]] = comb.extract %[[VAL_23]] from 2 : (i3) -> i1 +// CHECK: %[[VAL_37:.*]] = comb.or %[[VAL_34]], %[[VAL_35]], %[[VAL_36]] : i1 +// CHECK: %[[VAL_38:.*]] = hw.constant -4 : i3 +// CHECK: %[[VAL_39:.*]] = comb.mux %[[VAL_12]], %[[VAL_21]], %[[VAL_38]] : i3 +// CHECK: %[[VAL_40:.*]] = hw.constant 2 : i3 +// CHECK: %[[VAL_41:.*]] = comb.mux %[[VAL_9]], %[[VAL_39]], %[[VAL_40]] : i3 +// CHECK: %[[VAL_42:.*]] = hw.constant 1 : i3 +// CHECK: %[[VAL_43:.*]] = comb.mux %[[VAL_6]], %[[VAL_41]], %[[VAL_42]] : i3 +// CHECK: %[[VAL_30]] = comb.mux %[[VAL_37]], %[[VAL_23]], %[[VAL_43]] : i3 +// CHECK: %[[VAL_44:.*]] = hw.constant true +// CHECK: %[[VAL_45:.*]] = comb.xor %[[VAL_25]], %[[VAL_44]] : i1 +// CHECK: %[[VAL_17]] = comb.and %[[VAL_33]], %[[VAL_45]] : i1 +// CHECK: %[[VAL_16]] = hw.constant 0 : i64 +// CHECK: %[[VAL_46:.*]] = comb.xor %[[VAL_27]], %[[VAL_44]] : i1 +// CHECK: %[[VAL_20]] = comb.and %[[VAL_33]], %[[VAL_46]] : i1 +// CHECK: %[[VAL_47:.*]] = hw.constant 1 : i64 +// CHECK: %[[VAL_48:.*]] = hw.constant 2 : i64 +// CHECK: %[[VAL_24]] = comb.mux %[[VAL_49:.*]], %[[VAL_21]], %[[VAL_30]] : i3 +// CHECK: %[[VAL_50:.*]] = comb.and %[[VAL_17]], %[[VAL_15]] : i1 +// CHECK: %[[VAL_51:.*]] = comb.or %[[VAL_50]], %[[VAL_25]] : i1 +// CHECK: %[[VAL_52:.*]] = comb.and %[[VAL_20]], %[[VAL_19]] : i1 +// CHECK: %[[VAL_53:.*]] = comb.or %[[VAL_52]], %[[VAL_27]] : i1 +// CHECK: %[[VAL_49]] = comb.and %[[VAL_51]], %[[VAL_53]] : i1 +// CHECK: %[[VAL_26]] = comb.mux %[[VAL_49]], %[[VAL_22]], %[[VAL_51]] : i1 +// CHECK: %[[VAL_28]] = comb.mux %[[VAL_49]], %[[VAL_22]], %[[VAL_53]] : i1 +// CHECK: %[[VAL_54:.*]] = comb.mux %[[VAL_49]], %[[VAL_30]], %[[VAL_21]] : i3 +// CHECK: %[[VAL_7]] = comb.icmp eq %[[VAL_54]], %[[VAL_42]] : i3 +// CHECK: %[[VAL_10]] = comb.icmp eq %[[VAL_54]], %[[VAL_40]] : i3 +// CHECK: %[[VAL_13]] = comb.icmp eq %[[VAL_54]], %[[VAL_38]] : i3 +// CHECK: hw.output %[[VAL_14]], %[[VAL_18]] : !esi.channel, !esi.channel +// CHECK: } + +handshake.func @test_cmerge_data(%arg0: index, %arg1: index, %arg2: index) -> (index, index) { + %0:2 = control_merge %arg0, %arg1, %arg2 : index + return %0#0, %0#1 : index, index +} diff --git a/test/Conversion/HandshakeToHW/test_cond_br.mlir b/test/Conversion/HandshakeToHW/test_cond_br.mlir new file mode 100644 index 000000000000..55a7ff8ba098 --- /dev/null +++ b/test/Conversion/HandshakeToHW/test_cond_br.mlir @@ -0,0 +1,45 @@ +// RUN: circt-opt -split-input-file -lower-handshake-to-hw %s | FileCheck %s + +// CHECK-LABEL: hw.module @handshake_cond_br_in_ui1_ui64_out_ui64_ui64( +// CHECK-SAME: %[[VAL_0:.*]]: !esi.channel, %[[VAL_1:.*]]: !esi.channel) -> (outTrue: !esi.channel, outFalse: !esi.channel) { +// CHECK: %[[VAL_2:.*]], %[[VAL_3:.*]] = esi.unwrap.vr %[[VAL_0]], %[[VAL_4:.*]] : i1 +// CHECK: %[[VAL_5:.*]], %[[VAL_6:.*]] = esi.unwrap.vr %[[VAL_1]], %[[VAL_4]] : i64 +// CHECK: %[[VAL_7:.*]], %[[VAL_8:.*]] = esi.wrap.vr %[[VAL_5]], %[[VAL_9:.*]] : i64 +// CHECK: %[[VAL_10:.*]], %[[VAL_11:.*]] = esi.wrap.vr %[[VAL_5]], %[[VAL_12:.*]] : i64 +// CHECK: %[[VAL_13:.*]] = comb.and %[[VAL_3]], %[[VAL_6]] : i1 +// CHECK: %[[VAL_9]] = comb.and %[[VAL_2]], %[[VAL_13]] : i1 +// CHECK: %[[VAL_14:.*]] = hw.constant true +// CHECK: %[[VAL_15:.*]] = comb.xor %[[VAL_2]], %[[VAL_14]] : i1 +// CHECK: %[[VAL_12]] = comb.and %[[VAL_15]], %[[VAL_13]] : i1 +// CHECK: %[[VAL_16:.*]] = comb.mux %[[VAL_2]], %[[VAL_8]], %[[VAL_11]] : i1 +// CHECK: %[[VAL_4]] = comb.and %[[VAL_16]], %[[VAL_13]] : i1 +// CHECK: hw.output %[[VAL_7]], %[[VAL_10]] : !esi.channel, !esi.channel +// CHECK: } + +handshake.func @test_conditional_branch(%arg0: i1, %arg1: index) -> (index, index) { + %0:2 = cond_br %arg0, %arg1 : index + return %0#0, %0#1 : index, index +} + +// ----- + +// CHECK-LABEL: hw.module @handshake_cond_br_in_ui1_2ins_2outs_ctrl( +// CHECK-SAME: %[[VAL_0:.*]]: !esi.channel, %[[VAL_1:.*]]: !esi.channel) -> (outTrue: !esi.channel, outFalse: !esi.channel) { +// CHECK: %[[VAL_2:.*]], %[[VAL_3:.*]] = esi.unwrap.vr %[[VAL_0]], %[[VAL_4:.*]] : i1 +// CHECK: %[[VAL_5:.*]], %[[VAL_6:.*]] = esi.unwrap.vr %[[VAL_1]], %[[VAL_4]] : none +// CHECK: %[[VAL_7:.*]], %[[VAL_8:.*]] = esi.wrap.vr %[[VAL_5]], %[[VAL_9:.*]] : none +// CHECK: %[[VAL_10:.*]], %[[VAL_11:.*]] = esi.wrap.vr %[[VAL_5]], %[[VAL_12:.*]] : none +// CHECK: %[[VAL_13:.*]] = comb.and %[[VAL_3]], %[[VAL_6]] : i1 +// CHECK: %[[VAL_9]] = comb.and %[[VAL_2]], %[[VAL_13]] : i1 +// CHECK: %[[VAL_14:.*]] = hw.constant true +// CHECK: %[[VAL_15:.*]] = comb.xor %[[VAL_2]], %[[VAL_14]] : i1 +// CHECK: %[[VAL_12]] = comb.and %[[VAL_15]], %[[VAL_13]] : i1 +// CHECK: %[[VAL_16:.*]] = comb.mux %[[VAL_2]], %[[VAL_8]], %[[VAL_11]] : i1 +// CHECK: %[[VAL_4]] = comb.and %[[VAL_16]], %[[VAL_13]] : i1 +// CHECK: hw.output %[[VAL_7]], %[[VAL_10]] : !esi.channel, !esi.channel +// CHECK: } + +handshake.func @test_nonetyped_conditional_branch(%arg0: i1, %arg1: none) -> (none, none) { + %0:2 = cond_br %arg0, %arg1 : none + return %0#0, %0#1 : none, none +} diff --git a/test/Conversion/HandshakeToHW/test_constant.mlir b/test/Conversion/HandshakeToHW/test_constant.mlir new file mode 100644 index 000000000000..9d381e0deab1 --- /dev/null +++ b/test/Conversion/HandshakeToHW/test_constant.mlir @@ -0,0 +1,24 @@ +// RUN: circt-opt -lower-handshake-to-hw -split-input-file %s | FileCheck %s + +// CHECK-LABEL: hw.module @handshake_constant_c42_out_ui64( +// CHECK-SAME: %[[VAL_0:.*]]: !esi.channel) -> (out0: !esi.channel) { +// CHECK: %[[VAL_1:.*]], %[[VAL_2:.*]] = esi.unwrap.vr %[[VAL_0]], %[[VAL_3:.*]] : none +// CHECK: %[[VAL_4:.*]], %[[VAL_3]] = esi.wrap.vr %[[VAL_5:.*]], %[[VAL_2]] : i64 +// CHECK: %[[VAL_5]] = hw.constant 42 : i64 +// CHECK: hw.output %[[VAL_4]] : !esi.channel +// CHECK: } + +// CHECK-LABEL: hw.module @handshake_constant_c42_out_ui32( +// CHECK-SAME: %[[VAL_0:.*]]: !esi.channel) -> (out0: !esi.channel) { +// CHECK: %[[VAL_1:.*]], %[[VAL_2:.*]] = esi.unwrap.vr %[[VAL_0]], %[[VAL_3:.*]] : none +// CHECK: %[[VAL_4:.*]], %[[VAL_3]] = esi.wrap.vr %[[VAL_5:.*]], %[[VAL_2]] : i32 +// CHECK: %[[VAL_5]] = hw.constant 42 : i32 +// CHECK: hw.output %[[VAL_4]] : !esi.channel +// CHECK: } + +handshake.func @test_constant(%arg0: none, ...) -> (index, i32) { + %0:2 = fork [2] %arg0 : none + %1 = constant %0#0 {value = 42 : index} : index + %2 = constant %0#1 {value = 42 : i32} : i32 + return %1, %2 : index, i32 +} diff --git a/test/Conversion/HandshakeToHW/test_sink.mlir b/test/Conversion/HandshakeToHW/test_sink.mlir new file mode 100644 index 000000000000..44aa05c2f166 --- /dev/null +++ b/test/Conversion/HandshakeToHW/test_sink.mlir @@ -0,0 +1,13 @@ +// RUN: circt-opt -lower-handshake-to-hw %s | FileCheck %s + +// CHECK-LABEL: hw.module @handshake_sink_in_ui64( +// CHECK-SAME: %[[VAL_0:.*]]: !esi.channel) { +// CHECK: %[[VAL_1:.*]], %[[VAL_2:.*]] = esi.unwrap.vr %[[VAL_0]], %[[VAL_3:.*]] : i64 +// CHECK: %[[VAL_3]] = hw.constant true +// CHECK: hw.output +// CHECK: } + +handshake.func @test_sink(%arg0: index) -> () { + sink %arg0 : index + return +} diff --git a/test/Conversion/HandshakeToHW/test_source.mlir b/test/Conversion/HandshakeToHW/test_source.mlir new file mode 100644 index 000000000000..baf21bd6d2ca --- /dev/null +++ b/test/Conversion/HandshakeToHW/test_source.mlir @@ -0,0 +1,13 @@ +// RUN: circt-opt -lower-handshake-to-hw %s | FileCheck %s + +// CHECK-LABEL: hw.module @handshake_source_0ins_1outs_ctrl() -> (out0: !esi.channel) { +// CHECK: %[[VAL_0:.*]], %[[VAL_1:.*]] = esi.wrap.vr %[[VAL_2:.*]], %[[VAL_3:.*]] : none +// CHECK: %[[VAL_3]] = hw.constant true +// CHECK: %[[VAL_2]] = esi.none : none +// CHECK: hw.output %[[VAL_0]] : !esi.channel +// CHECK: } + +handshake.func @test_source() -> (none) { + %0 = source + return %0 : none +}