[HandshakeToDC] Add conversion (#5214)

... progressive lowering of handshake!
This obviously requires a future addition of an `arith-to-comb/hw` pass to map arith stuff to comb/external hardware/pipelines, ...
This commit is contained in:
Morten Borup Petersen 2023-05-19 12:28:55 +02:00 committed by GitHub
parent 1cde37b489
commit fca113cf89
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 877 additions and 4 deletions

View File

@ -0,0 +1,38 @@
//===- HandshakeToDC.h ------------------------------------------*- C++ -*-===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//
//
// This file declares passes which together will lower the Handshake dialect to
// CIRCT RTL dialects.
//
//===----------------------------------------------------------------------===//
#ifndef CIRCT_CONVERSION_HANDSHAKETODC_H
#define CIRCT_CONVERSION_HANDSHAKETODC_H
#include "circt/Dialect/HW/HWOps.h"
#include "mlir/IR/Builders.h"
#include <memory>
namespace mlir {
class Pass;
} // namespace mlir
namespace circt {
std::unique_ptr<mlir::Pass> createHandshakeToDCPass();
namespace handshake {
// Converts 't' into a valid HW type. This is strictly used for converting
// 'index' types into a fixed-width type.
Type toValidType(Type t);
} // namespace handshake
} // namespace circt
#endif // CIRCT_CONVERSION_HANDSHAKETODC_H

View File

@ -27,6 +27,7 @@
#include "circt/Conversion/HWToLLHD.h"
#include "circt/Conversion/HWToLLVM.h"
#include "circt/Conversion/HWToSystemC.h"
#include "circt/Conversion/HandshakeToDC.h"
#include "circt/Conversion/HandshakeToHW.h"
#include "circt/Conversion/LLHDToLLVM.h"
#include "circt/Conversion/LoopScheduleToCalyx.h"

View File

@ -384,6 +384,25 @@ def LowerFIRRTLToHW : Pass<"lower-firrtl-to-hw", "mlir::ModuleOp"> {
];
}
//===----------------------------------------------------------------------===//
// HandshakeToDC
//===----------------------------------------------------------------------===//
def HandshakeToDC : Pass<"lower-handshake-to-dc", "mlir::ModuleOp"> {
let summary = "Lower Handshake to DC";
let description = [{
Lower Handshake to DC operations.
Currently, a `handshake.func` will be converted into a `hw.module`. This
is principally an incorrect jump of abstraction - DC does not imply any
RTL/hardware semantics. However, DC does not define a container operation,
and there does not exist an e.g. `func.graph_func` which would be a generic
function with graph region behaviour. Thus, for now, we just use `hw.module`
as a container operation.
}];
let constructor = "circt::createHandshakeToDCPass()";
let dependentDialects = ["dc::DCDialect", "mlir::func::FuncDialect", "hw::HWDialect"];
}
//===----------------------------------------------------------------------===//
// HandshakeToHW
//===----------------------------------------------------------------------===//

View File

@ -10,6 +10,7 @@ add_subdirectory(ExportVerilog)
add_subdirectory(FIRRTLToHW)
add_subdirectory(FSMToSV)
add_subdirectory(HandshakeToHW)
add_subdirectory(HandshakeToDC)
add_subdirectory(HWArithToHW)
add_subdirectory(HWToLLHD)
add_subdirectory(HWToLLVM)

View File

@ -0,0 +1,18 @@
add_circt_library(CIRCTHandshakeToDC
HandshakeToDC.cpp
ADDITIONAL_HEADER_DIRS
${MLIR_MAIN_INCLUDE_DIR}/mlir/Conversion/HandshakeToDC
LINK_LIBS PUBLIC
CIRCTHW
CIRCTHandshake
CIRCTHandshakeTransforms
CIRCTDC
MLIRIR
MLIRPass
MLIRArithDialect
MLIRFuncDialect
MLIRSupport
MLIRTransforms
)

View File

@ -0,0 +1,605 @@
//===- HandshakeToDC.cpp - Translate Handshake into DC --------------------===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//===----------------------------------------------------------------------===//
//
// This is the main Handshake to DC Conversion Pass Implementation.
//
//===----------------------------------------------------------------------===//
#include "circt/Conversion/HandshakeToDC.h"
#include "../PassDetail.h"
#include "circt/Dialect/Comb/CombOps.h"
#include "circt/Dialect/DC/DCDialect.h"
#include "circt/Dialect/DC/DCOps.h"
#include "circt/Dialect/DC/DCTypes.h"
#include "circt/Dialect/HW/HWOps.h"
#include "circt/Dialect/HW/HWTypes.h"
#include "circt/Dialect/Handshake/HandshakeOps.h"
#include "circt/Dialect/Handshake/HandshakePasses.h"
#include "circt/Dialect/Handshake/Visitor.h"
#include "mlir/Dialect/Arith/IR/Arith.h"
#include "mlir/Pass/PassManager.h"
#include "mlir/Transforms/DialectConversion.h"
#include "llvm/Support/MathExtras.h"
#include <optional>
using namespace mlir;
using namespace circt;
using namespace handshake;
using namespace dc;
using namespace hw;
namespace {
using ConvertedOps = DenseSet<Operation *>;
struct DCTuple {
DCTuple() = default;
DCTuple(Value token, ValueRange data) : token(token), data(data) {}
DCTuple(dc::UnpackOp unpack)
: token(unpack.getToken()), data(unpack.getOutputs()) {}
Value token;
ValueRange data;
};
// Unpack a !dc.value<...> into a DCTuple.
static DCTuple unpack(OpBuilder &b, Value v) {
if (v.getType().isa<dc::ValueType>())
return DCTuple(b.create<dc::UnpackOp>(v.getLoc(), v));
assert(v.getType().isa<dc::TokenType>() && "Expected a dc::TokenType");
return DCTuple(v, ValueRange{});
}
static Value pack(OpBuilder &b, Value token, ValueRange data) {
if (data.empty())
return token;
return b.create<dc::PackOp>(token.getLoc(), token, data);
}
class DCTypeConverter : public TypeConverter {
public:
DCTypeConverter() {
addConversion([](Type type) -> Type {
if (type.isa<NoneType>())
return dc::TokenType::get(type.getContext());
return dc::ValueType::get(type.getContext(), type);
});
addConversion([](ValueType type) { return type; });
addConversion([](TokenType type) { return type; });
addTargetMaterialization(
[](mlir::OpBuilder &builder, mlir::Type resultType,
mlir::ValueRange inputs,
mlir::Location loc) -> std::optional<mlir::Value> {
if (inputs.size() != 1)
return std::nullopt;
// Materialize !dc.value<> -> !dc.token
if (resultType.isa<dc::TokenType>() &&
inputs.front().getType().isa<dc::ValueType>())
return unpack(builder, inputs.front()).token;
// Materialize !dc.token -> !dc.value<>
auto vt = resultType.dyn_cast<dc::ValueType>();
if (vt && vt.getInnerTypes().empty())
return pack(builder, inputs.front(), ValueRange{});
return inputs[0];
});
addSourceMaterialization(
[](mlir::OpBuilder &builder, mlir::Type resultType,
mlir::ValueRange inputs,
mlir::Location loc) -> std::optional<mlir::Value> {
if (inputs.size() != 1)
return std::nullopt;
// Materialize !dc.value<> -> !dc.token
if (resultType.isa<dc::TokenType>() &&
inputs.front().getType().isa<dc::ValueType>())
return unpack(builder, inputs.front()).token;
// Materialize !dc.token -> !dc.value<>
auto vt = resultType.dyn_cast<dc::ValueType>();
if (vt && vt.getInnerTypes().empty())
return pack(builder, inputs.front(), ValueRange{});
return inputs[0];
});
}
};
template <typename OpTy>
class DCOpConversionPattern : public OpConversionPattern<OpTy> {
public:
using OpConversionPattern<OpTy>::OpConversionPattern;
using OpAdaptor = typename OpTy::Adaptor;
DCOpConversionPattern(MLIRContext *context, TypeConverter &typeConverter,
ConvertedOps *convertedOps)
: OpConversionPattern<OpTy>(typeConverter, context),
convertedOps(convertedOps) {}
mutable ConvertedOps *convertedOps;
};
class CondBranchConversionPattern
: public DCOpConversionPattern<handshake::ConditionalBranchOp> {
public:
using DCOpConversionPattern<
handshake::ConditionalBranchOp>::DCOpConversionPattern;
using OpAdaptor = typename handshake::ConditionalBranchOp::Adaptor;
LogicalResult
matchAndRewrite(handshake::ConditionalBranchOp op, OpAdaptor adaptor,
ConversionPatternRewriter &rewriter) const override {
auto condition = unpack(rewriter, adaptor.getConditionOperand());
auto data = unpack(rewriter, adaptor.getDataOperand());
// Join the token of the condition and the input.
auto join = rewriter.create<dc::JoinOp>(
op.getLoc(), ValueRange{condition.token, data.token});
// Pack that together with the condition data.
auto packedCondition = pack(rewriter, join, ValueRange{condition.data});
// Branch on the input data and the joined control input.
auto branch = rewriter.create<dc::BranchOp>(op.getLoc(), packedCondition);
// Pack the branch output tokens with the input data, and replace the uses.
llvm::SmallVector<Value, 4> packed;
packed.push_back(pack(rewriter, branch.getTrueToken(), data.data));
packed.push_back(pack(rewriter, branch.getFalseToken(), data.data));
rewriter.replaceOp(op, packed);
return success();
}
};
class ForkOpConversionPattern
: public DCOpConversionPattern<handshake::ForkOp> {
public:
using DCOpConversionPattern<handshake::ForkOp>::DCOpConversionPattern;
using OpAdaptor = typename handshake::ForkOp::Adaptor;
LogicalResult
matchAndRewrite(handshake::ForkOp op, OpAdaptor adaptor,
ConversionPatternRewriter &rewriter) const override {
auto input = unpack(rewriter, adaptor.getOperand());
auto forkOut = rewriter.create<dc::ForkOp>(op.getLoc(), input.token,
op.getNumResults());
// Pack the fork result tokens with the input data, and replace the uses.
llvm::SmallVector<Value, 4> packed;
for (auto res : forkOut.getResults())
packed.push_back(pack(rewriter, res, ValueRange{input.data}));
rewriter.replaceOp(op, packed);
return success();
}
};
class JoinOpConversion : public DCOpConversionPattern<handshake::JoinOp> {
public:
using DCOpConversionPattern<handshake::JoinOp>::DCOpConversionPattern;
using OpAdaptor = typename handshake::JoinOp::Adaptor;
LogicalResult
matchAndRewrite(handshake::JoinOp op, OpAdaptor adaptor,
ConversionPatternRewriter &rewriter) const override {
llvm::SmallVector<Value, 4> inputTokens;
for (auto input : adaptor.getData())
inputTokens.push_back(unpack(rewriter, input).token);
rewriter.replaceOpWithNewOp<dc::JoinOp>(op, inputTokens);
return success();
}
};
class ControlMergeOpConversion
: public DCOpConversionPattern<handshake::ControlMergeOp> {
public:
using DCOpConversionPattern<handshake::ControlMergeOp>::DCOpConversionPattern;
using OpAdaptor = typename handshake::ControlMergeOp::Adaptor;
LogicalResult
matchAndRewrite(handshake::ControlMergeOp op, OpAdaptor adaptor,
ConversionPatternRewriter &rewriter) const override {
if (op.getDataOperands().size() != 2)
return op.emitOpError("expected two data operands");
llvm::SmallVector<Value> tokens, data;
for (auto input : adaptor.getDataOperands()) {
auto up = unpack(rewriter, input);
tokens.push_back(up.token);
if (!up.data.empty())
data.push_back(up.data.front());
}
// control-side
Value selectedIndex = rewriter.create<dc::MergeOp>(op.getLoc(), tokens);
auto mergeOpUnpacked = unpack(rewriter, selectedIndex);
auto selValue = mergeOpUnpacked.data.front();
Value dataSide = selectedIndex;
if (!data.empty()) {
// Data side mux using the selected input.
auto dataMux = rewriter.create<arith::SelectOp>(op.getLoc(), selValue,
data[0], data[1]);
convertedOps->insert(dataMux);
// Pack the data mux with the control token.
auto packed = pack(rewriter, mergeOpUnpacked.token, ValueRange{dataMux});
dataSide = packed;
}
// if the original op used `index` as the select operand type, we need to
// index-cast the unpacked select operand
if (op.getIndex().getType().isa<IndexType>()) {
selValue = rewriter.create<arith::IndexCastOp>(
op.getLoc(), rewriter.getIndexType(), selValue);
convertedOps->insert(selValue.getDefiningOp());
selectedIndex =
pack(rewriter, mergeOpUnpacked.token, ValueRange{selValue});
}
rewriter.replaceOp(op, {dataSide, selectedIndex});
return success();
}
};
class SyncOpConversion : public DCOpConversionPattern<handshake::SyncOp> {
public:
using DCOpConversionPattern<handshake::SyncOp>::DCOpConversionPattern;
using OpAdaptor = typename handshake::SyncOp::Adaptor;
LogicalResult
matchAndRewrite(handshake::SyncOp op, OpAdaptor adaptor,
ConversionPatternRewriter &rewriter) const override {
llvm::SmallVector<Value, 4> inputTokens;
for (auto input : adaptor.getOperands())
inputTokens.push_back(unpack(rewriter, input).token);
auto syncToken = rewriter.create<dc::JoinOp>(op.getLoc(), inputTokens);
// Wrap all outputs with the synchronization token
llvm::SmallVector<Value, 4> wrappedInputs;
for (auto input : adaptor.getOperands())
wrappedInputs.push_back(pack(rewriter, syncToken, ValueRange{input}));
rewriter.replaceOp(op, wrappedInputs);
return success();
}
};
class ConstantOpConversion
: public DCOpConversionPattern<handshake::ConstantOp> {
public:
using DCOpConversionPattern<handshake::ConstantOp>::DCOpConversionPattern;
using OpAdaptor = typename handshake::ConstantOp::Adaptor;
LogicalResult
matchAndRewrite(handshake::ConstantOp op, OpAdaptor adaptor,
ConversionPatternRewriter &rewriter) const override {
// Wrap the constant with a token.
auto token = rewriter.create<dc::SourceOp>(op.getLoc());
auto cst =
rewriter.create<arith::ConstantOp>(op.getLoc(), adaptor.getValue());
convertedOps->insert(cst);
rewriter.replaceOp(op,
pack(rewriter, token, llvm::SmallVector<Value>{cst}));
return success();
}
};
struct UnitRateConversionPattern : public ConversionPattern {
public:
UnitRateConversionPattern(MLIRContext *context, TypeConverter &converter,
ConvertedOps *joinedOps)
: ConversionPattern(converter, MatchAnyOpTypeTag(), 1, context),
joinedOps(joinedOps) {}
using ConversionPattern::ConversionPattern;
// Generic pattern which replaces an operation by one of the same type, but
// with the in- and outputs synchronized through join semantics.
LogicalResult
matchAndRewrite(Operation *op, ArrayRef<Value> operands,
ConversionPatternRewriter &rewriter) const override {
if (op->getNumResults() != 1)
return op->emitOpError("expected single result for pattern to apply");
llvm::SmallVector<Value, 4> inputData;
llvm::SmallVector<Value, 4> inputTokens;
for (auto input : operands) {
auto dct = unpack(rewriter, input);
inputData.append(dct.data.begin(), dct.data.end());
inputTokens.push_back(dct.token);
}
// Join the tokens of the inputs.
auto join = rewriter.create<dc::JoinOp>(op->getLoc(), inputTokens);
// Patchwork to fix bad IR design in Handshake.
auto opName = op->getName();
if (opName.getStringRef() == "handshake.select") {
opName = OperationName("arith.select", getContext());
} else if (opName.getStringRef() == "handshake.constant") {
opName = OperationName("arith.constant", getContext());
}
// Re-create the operation using the unpacked input data.
OperationState state(op->getLoc(), opName, inputData, op->getResultTypes(),
op->getAttrs(), op->getSuccessors());
Operation *newOp = rewriter.create(state);
joinedOps->insert(newOp);
// Pack the result token with the output data, and replace the use.
rewriter.replaceOp(
op, ValueRange{pack(rewriter, join.getResult(), newOp->getResults())});
return success();
}
mutable ConvertedOps *joinedOps;
};
class SinkOpConversionPattern
: public DCOpConversionPattern<handshake::SinkOp> {
public:
using DCOpConversionPattern<handshake::SinkOp>::DCOpConversionPattern;
using OpAdaptor = typename handshake::SinkOp::Adaptor;
LogicalResult
matchAndRewrite(handshake::SinkOp op, OpAdaptor adaptor,
ConversionPatternRewriter &rewriter) const override {
auto input = unpack(rewriter, adaptor.getOperand());
rewriter.replaceOpWithNewOp<dc::SinkOp>(op, input.token);
return success();
}
};
class SourceOpConversionPattern
: public DCOpConversionPattern<handshake::SourceOp> {
public:
using DCOpConversionPattern<handshake::SourceOp>::DCOpConversionPattern;
using OpAdaptor = typename handshake::SourceOp::Adaptor;
LogicalResult
matchAndRewrite(handshake::SourceOp op, OpAdaptor adaptor,
ConversionPatternRewriter &rewriter) const override {
rewriter.replaceOpWithNewOp<dc::SourceOp>(op);
return success();
}
};
class BufferOpConversion : public DCOpConversionPattern<handshake::BufferOp> {
public:
using DCOpConversionPattern<handshake::BufferOp>::DCOpConversionPattern;
using OpAdaptor = typename handshake::BufferOp::Adaptor;
LogicalResult
matchAndRewrite(handshake::BufferOp op, OpAdaptor adaptor,
ConversionPatternRewriter &rewriter) const override {
rewriter.getI32IntegerAttr(1);
rewriter.replaceOpWithNewOp<dc::BufferOp>(
op, adaptor.getOperand(), static_cast<size_t>(op.getNumSlots()));
return success();
}
};
class ReturnOpConversion : public DCOpConversionPattern<handshake::ReturnOp> {
public:
using DCOpConversionPattern<handshake::ReturnOp>::DCOpConversionPattern;
using OpAdaptor = typename handshake::ReturnOp::Adaptor;
LogicalResult
matchAndRewrite(handshake::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.
auto hwModule = op->getParentOfType<hw::HWModuleOp>();
auto outputOp = *hwModule.getBodyBlock()->getOps<hw::OutputOp>().begin();
outputOp->setOperands(adaptor.getOperands());
outputOp->moveAfter(&hwModule.getBodyBlock()->back());
rewriter.eraseOp(op);
return success();
}
};
class MuxOpConversionPattern : public DCOpConversionPattern<handshake::MuxOp> {
public:
using DCOpConversionPattern<handshake::MuxOp>::DCOpConversionPattern;
using OpAdaptor = typename handshake::MuxOp::Adaptor;
LogicalResult
matchAndRewrite(handshake::MuxOp op, OpAdaptor adaptor,
ConversionPatternRewriter &rewriter) const override {
auto select = unpack(rewriter, adaptor.getSelectOperand());
auto selectData = select.data.front();
auto selectToken = select.token;
bool isIndexType = selectData.getType().isa<IndexType>();
bool withData = !op.getResult().getType().isa<NoneType>();
llvm::SmallVector<DCTuple> inputs;
for (auto input : adaptor.getDataOperands())
inputs.push_back(unpack(rewriter, input));
Value dataMux;
Value controlMux = inputs.front().token;
// Convert the data-side mux to a sequence of arith.select operations.
// The data and control muxes are assumed one-hot and the base-case is set
// as the first input.
if (withData)
dataMux = inputs[0].data.front();
llvm::SmallVector<Value> controlMuxInputs = {inputs.front().token};
for (auto [i, input] :
llvm::enumerate(llvm::make_range(inputs.begin() + 1, inputs.end()))) {
if (!withData)
continue;
Value cmpIndex;
Value inputData = input.data.front();
Value inputControl = input.token;
if (isIndexType) {
cmpIndex = rewriter.create<arith::ConstantIndexOp>(op.getLoc(), i);
} else {
size_t width = selectData.getType().cast<IntegerType>().getWidth();
cmpIndex = rewriter.create<arith::ConstantIntOp>(op.getLoc(), i, width);
}
auto inputSelected = rewriter.create<arith::CmpIOp>(
op.getLoc(), arith::CmpIPredicate::eq, selectData, cmpIndex);
dataMux = rewriter.create<arith::SelectOp>(op.getLoc(), inputSelected,
inputData, dataMux);
// Legalize the newly created operations.
convertedOps->insert(cmpIndex.getDefiningOp());
convertedOps->insert(dataMux.getDefiningOp());
convertedOps->insert(inputSelected);
// And similarly for the control mux, by muxing the input token with a
// select value that has it's control from the original select token +
// the inputSelected value.
auto inputSelectedControl =
pack(rewriter, selectToken, ValueRange{inputSelected});
controlMux = rewriter.create<dc::SelectOp>(
op.getLoc(), inputSelectedControl, inputControl, controlMux);
convertedOps->insert(controlMux.getDefiningOp());
}
// finally, pack the control and data side muxes into the output value.
rewriter.replaceOp(op, pack(rewriter, controlMux,
withData ? ValueRange{dataMux} : ValueRange{}));
return success();
}
};
static hw::ModulePortInfo getModulePortInfo(TypeConverter &tc,
handshake::FuncOp funcOp) {
hw::ModulePortInfo ports({}, {});
auto *ctx = funcOp->getContext();
auto ft = funcOp.getFunctionType();
// Add all inputs of funcOp.
for (auto [index, type] : llvm::enumerate(ft.getInputs())) {
ports.inputs.push_back({StringAttr::get(ctx, "in" + std::to_string(index)),
hw::PortDirection::INPUT, tc.convertType(type),
index, hw::InnerSymAttr{}});
}
// Add all outputs of funcOp.
for (auto [index, type] : llvm::enumerate(ft.getResults())) {
ports.outputs.push_back(
{StringAttr::get(ctx, "out" + std::to_string(index)),
hw::PortDirection::OUTPUT, tc.convertType(type), index,
hw::InnerSymAttr{}});
}
return ports;
}
class FuncOpConversion : public DCOpConversionPattern<handshake::FuncOp> {
public:
using DCOpConversionPattern<handshake::FuncOp>::DCOpConversionPattern;
using OpAdaptor = typename handshake::FuncOp::Adaptor;
// Replaces a handshake.func with a hw.module, converting the argument and
// result types using the provided type converter.
// @mortbopet: Not a fan of converting to hw here seeing as we don't
// necessarily have hardware semantics here. But, DC doesn't define a function
// operation, and there is no "func.graph_func" or any other generic function
// operation which is a graph region...
LogicalResult
matchAndRewrite(handshake::FuncOp op, OpAdaptor adaptor,
ConversionPatternRewriter &rewriter) const override {
ModulePortInfo ports = getModulePortInfo(*getTypeConverter(), op);
if (op.isExternal()) {
rewriter.create<hw::HWModuleExternOp>(
op.getLoc(), rewriter.getStringAttr(op.getName()), ports);
} else {
auto hwModule = rewriter.create<hw::HWModuleOp>(
op.getLoc(), rewriter.getStringAttr(op.getName()), ports);
auto &region = op->getRegions().front();
Region &moduleRegion = hwModule->getRegions().front();
rewriter.mergeBlocks(&region.getBlocks().front(), hwModule.getBodyBlock(),
hwModule.getBodyBlock()->getArguments());
TypeConverter::SignatureConversion result(moduleRegion.getNumArguments());
(void)getTypeConverter()->convertSignatureArgs(
TypeRange(moduleRegion.getArgumentTypes()), result);
rewriter.applySignatureConversion(&moduleRegion, result);
}
rewriter.eraseOp(op);
return success();
}
};
class HandshakeToDCPass : public HandshakeToDCBase<HandshakeToDCPass> {
public:
void runOnOperation() override {
mlir::ModuleOp mod = getOperation();
// Maintain the set of operations which has been converted either through
// unit rate conversion, or as part of other conversions.
// Rationale:
// This is needed for all of the arith ops that get created as part of the
// handshake ops (e.g. arith.select for handshake.mux). There's a bit of a
// dilemma here seeing as all operations need to be converted/touched in a
// handshake.func - which is done so by UnitRateConversionPattern (when no
// other pattern applies). However, we obviously don't want to run said
// pattern on these newly created ops since they do not have handshake
// semantics.
ConvertedOps convertedOps;
ConversionTarget target(getContext());
target.addIllegalDialect<handshake::HandshakeDialect>();
target.addLegalDialect<dc::DCDialect, func::FuncDialect, hw::HWDialect>();
target.addLegalOp<mlir::ModuleOp>();
// The various patterns will insert new operations into the module to
// facilitate the conversion - however, these operations must be
// distinguishable from already converted operations (which may be of the
// same type as the newly inserted operations). To do this, we mark all
// operations which have been converted as legal, and all other operations
// as illegal.
target.markUnknownOpDynamicallyLegal(
[&](Operation *op) { return convertedOps.contains(op); });
DCTypeConverter typeConverter;
RewritePatternSet patterns(&getContext());
// Add handshake conversion patterns.
// Note: merge/control merge are not supported - these are non-deterministic
// operators and we do not care for them.
patterns
.add<FuncOpConversion, BufferOpConversion, CondBranchConversionPattern,
SinkOpConversionPattern, SourceOpConversionPattern,
MuxOpConversionPattern, ReturnOpConversion,
ForkOpConversionPattern, JoinOpConversion,
ControlMergeOpConversion, ConstantOpConversion, SyncOpConversion>(
&getContext(), typeConverter, &convertedOps);
// ALL other single-result operations are converted via the
// UnitRateConversionPattern.
patterns.add<UnitRateConversionPattern>(&getContext(), typeConverter,
&convertedOps);
if (failed(applyPartialConversion(mod, target, std::move(patterns))))
signalPassFailure();
}
};
} // namespace
std::unique_ptr<mlir::Pass> circt::createHandshakeToDCPass() {
return std::make_unique<HandshakeToDCPass>();
}

View File

@ -24,10 +24,6 @@ namespace cf {
class ControlFlowDialect;
} // namespace cf
namespace dc {
class DCDialect;
} // namespace dc
namespace memref {
class MemRefDialect;
} // namespace memref
@ -56,6 +52,10 @@ namespace arc {
class ArcDialect;
} // namespace arc
namespace dc {
class DCDialect;
} // namespace dc
namespace fsm {
class FSMDialect;
} // namespace fsm

View File

@ -0,0 +1,190 @@
// RUN: circt-opt %s --lower-handshake-to-dc | FileCheck %s
// CHECK-LABEL: hw.module @test_fork(
// CHECK-SAME: %[[VAL_0:.*]]: !dc.token) -> (out0: !dc.token, out1: !dc.token) {
// CHECK: %[[VAL_1:.*]]:2 = dc.fork [2] %[[VAL_0]]
// CHECK: hw.output %[[VAL_1]]#0, %[[VAL_1]]#1 : !dc.token, !dc.token
// CHECK: }
handshake.func @test_fork(%arg0: none) -> (none, none) {
%0:2 = fork [2] %arg0 : none
return %0#0, %0#1 : none, none
}
// CHECK-LABEL: hw.module @test_fork_data(
// CHECK-SAME: %[[VAL_0:.*]]: !dc.value<i32>) -> (out0: !dc.value<i32>) {
// CHECK: %[[VAL_1:.*]], %[[VAL_2:.*]] = dc.unpack %[[VAL_0]] : !dc.value<i32>
// CHECK: %[[VAL_3:.*]]:2 = dc.fork [2] %[[VAL_1]]
// CHECK: %[[VAL_4:.*]] = dc.pack %[[VAL_3]]#0{{\[}}%[[VAL_2]]] : i32
// CHECK: %[[VAL_5:.*]] = dc.pack %[[VAL_3]]#1{{\[}}%[[VAL_2]]] : i32
// CHECK: %[[VAL_6:.*]], %[[VAL_7:.*]] = dc.unpack %[[VAL_4]] : !dc.value<i32>
// CHECK: %[[VAL_8:.*]], %[[VAL_9:.*]] = dc.unpack %[[VAL_5]] : !dc.value<i32>
// CHECK: %[[VAL_10:.*]] = dc.join %[[VAL_6]], %[[VAL_8]]
// CHECK: %[[VAL_11:.*]] = arith.addi %[[VAL_7]], %[[VAL_9]] : i32
// CHECK: %[[VAL_12:.*]] = dc.pack %[[VAL_10]]{{\[}}%[[VAL_11]]] : i32
// CHECK: hw.output %[[VAL_12]] : !dc.value<i32>
// CHECK: }
handshake.func @test_fork_data(%arg0: i32) -> (i32) {
%0:2 = fork [2] %arg0 : i32
%1 = arith.addi %0#0, %0#1 : i32
return %1 : i32
}
// CHECK-LABEL: hw.module @top(
// CHECK-SAME: %[[VAL_0:.*]]: !dc.value<i64>, %[[VAL_1:.*]]: !dc.value<i64>, %[[VAL_2:.*]]: !dc.token) -> (out0: !dc.value<i64>, out1: !dc.token) {
// CHECK: %[[VAL_3:.*]], %[[VAL_4:.*]] = dc.unpack %[[VAL_0]] : !dc.value<i64>
// CHECK: %[[VAL_5:.*]], %[[VAL_6:.*]] = dc.unpack %[[VAL_1]] : !dc.value<i64>
// CHECK: %[[VAL_7:.*]] = dc.join %[[VAL_3]], %[[VAL_5]]
// CHECK: %[[VAL_8:.*]] = arith.cmpi slt, %[[VAL_4]], %[[VAL_6]] : i64
// CHECK: %[[VAL_9:.*]] = dc.pack %[[VAL_7]]{{\[}}%[[VAL_8]]] : i1
// CHECK: %[[VAL_10:.*]], %[[VAL_11:.*]] = dc.unpack %[[VAL_9]] : !dc.value<i1>
// CHECK: %[[VAL_12:.*]], %[[VAL_13:.*]] = dc.unpack %[[VAL_1]] : !dc.value<i64>
// CHECK: %[[VAL_14:.*]], %[[VAL_15:.*]] = dc.unpack %[[VAL_0]] : !dc.value<i64>
// CHECK: %[[VAL_16:.*]] = dc.join %[[VAL_10]], %[[VAL_12]], %[[VAL_14]]
// CHECK: %[[VAL_17:.*]] = arith.select %[[VAL_11]], %[[VAL_13]], %[[VAL_15]] : i64
// CHECK: %[[VAL_18:.*]] = dc.pack %[[VAL_16]]{{\[}}%[[VAL_17]]] : i64
// CHECK: hw.output %[[VAL_18]], %[[VAL_2]] : !dc.value<i64>, !dc.token
// CHECK: }
handshake.func @top(%arg0: i64, %arg1: i64, %arg8: none, ...) -> (i64, none) {
%0 = arith.cmpi slt, %arg0, %arg1 : i64
%1 = arith.select %0, %arg1, %arg0 : i64
return %1, %arg8 : i64, none
}
// CHECK-LABEL: hw.module @mux(
// CHECK-SAME: %[[VAL_0:.*]]: !dc.value<i1>, %[[VAL_1:.*]]: !dc.value<i64>, %[[VAL_2:.*]]: !dc.value<i64>) -> (out0: !dc.value<i64>) {
// CHECK: %[[VAL_3:.*]], %[[VAL_4:.*]] = dc.unpack %[[VAL_0]] : !dc.value<i1>
// CHECK: %[[VAL_5:.*]], %[[VAL_6:.*]] = dc.unpack %[[VAL_1]] : !dc.value<i64>
// CHECK: %[[VAL_7:.*]], %[[VAL_8:.*]] = dc.unpack %[[VAL_2]] : !dc.value<i64>
// CHECK: %[[VAL_9:.*]] = arith.constant false
// CHECK: %[[VAL_10:.*]] = arith.cmpi eq, %[[VAL_4]], %[[VAL_9]] : i1
// CHECK: %[[VAL_11:.*]] = arith.select %[[VAL_10]], %[[VAL_8]], %[[VAL_6]] : i64
// CHECK: %[[VAL_12:.*]] = dc.pack %[[VAL_3]]{{\[}}%[[VAL_10]]] : i1
// CHECK: %[[VAL_13:.*]] = dc.select %[[VAL_12]], %[[VAL_7]], %[[VAL_5]]
// CHECK: %[[VAL_14:.*]] = dc.pack %[[VAL_13]]{{\[}}%[[VAL_11]]] : i64
// CHECK: hw.output %[[VAL_14]] : !dc.value<i64>
// CHECK: }
handshake.func @mux(%select : i1, %a : i64, %b : i64) -> i64{
%0 = handshake.mux %select [%a, %b] : i1, i64
return %0 : i64
}
// CHECK-LABEL: hw.module @mux4(
// CHECK-SAME: %[[VAL_0:.*]]: !dc.value<i2>, %[[VAL_1:.*]]: !dc.value<i64>, %[[VAL_2:.*]]: !dc.value<i64>, %[[VAL_3:.*]]: !dc.value<i64>, %[[VAL_4:.*]]: !dc.value<i64>) -> (out0: !dc.value<i64>) {
// CHECK: %[[VAL_5:.*]], %[[VAL_6:.*]] = dc.unpack %[[VAL_0]] : !dc.value<i2>
// CHECK: %[[VAL_7:.*]], %[[VAL_8:.*]] = dc.unpack %[[VAL_1]] : !dc.value<i64>
// CHECK: %[[VAL_9:.*]], %[[VAL_10:.*]] = dc.unpack %[[VAL_2]] : !dc.value<i64>
// CHECK: %[[VAL_11:.*]], %[[VAL_12:.*]] = dc.unpack %[[VAL_3]] : !dc.value<i64>
// CHECK: %[[VAL_13:.*]], %[[VAL_14:.*]] = dc.unpack %[[VAL_4]] : !dc.value<i64>
// CHECK: %[[VAL_15:.*]] = arith.constant 0 : i2
// CHECK: %[[VAL_16:.*]] = arith.cmpi eq, %[[VAL_6]], %[[VAL_15]] : i2
// CHECK: %[[VAL_17:.*]] = arith.select %[[VAL_16]], %[[VAL_10]], %[[VAL_8]] : i64
// CHECK: %[[VAL_18:.*]] = dc.pack %[[VAL_5]]{{\[}}%[[VAL_16]]] : i1
// CHECK: %[[VAL_19:.*]] = dc.select %[[VAL_18]], %[[VAL_9]], %[[VAL_7]]
// CHECK: %[[VAL_20:.*]] = arith.constant 1 : i2
// CHECK: %[[VAL_21:.*]] = arith.cmpi eq, %[[VAL_6]], %[[VAL_20]] : i2
// CHECK: %[[VAL_22:.*]] = arith.select %[[VAL_21]], %[[VAL_12]], %[[VAL_17]] : i64
// CHECK: %[[VAL_23:.*]] = dc.pack %[[VAL_5]]{{\[}}%[[VAL_21]]] : i1
// CHECK: %[[VAL_24:.*]] = dc.select %[[VAL_23]], %[[VAL_11]], %[[VAL_19]]
// CHECK: %[[VAL_25:.*]] = arith.constant -2 : i2
// CHECK: %[[VAL_26:.*]] = arith.cmpi eq, %[[VAL_6]], %[[VAL_25]] : i2
// CHECK: %[[VAL_27:.*]] = arith.select %[[VAL_26]], %[[VAL_14]], %[[VAL_22]] : i64
// CHECK: %[[VAL_28:.*]] = dc.pack %[[VAL_5]]{{\[}}%[[VAL_26]]] : i1
// CHECK: %[[VAL_29:.*]] = dc.select %[[VAL_28]], %[[VAL_13]], %[[VAL_24]]
// CHECK: %[[VAL_30:.*]] = dc.pack %[[VAL_29]]{{\[}}%[[VAL_27]]] : i64
// CHECK: hw.output %[[VAL_30]] : !dc.value<i64>
// CHECK: }
handshake.func @mux4(%select : i2, %a : i64, %b : i64, %c : i64, %d : i64) -> i64{
%0 = handshake.mux %select [%a, %b, %c, %d] : i2, i64
return %0 : i64
}
// CHECK-LABEL: hw.module @test_conditional_branch(
// CHECK-SAME: %[[VAL_0:.*]]: !dc.value<i1>, %[[VAL_1:.*]]: !dc.value<index>, %[[VAL_2:.*]]: !dc.token) -> (out0: !dc.value<index>, out1: !dc.value<index>, out2: !dc.token) {
// CHECK: %[[VAL_3:.*]], %[[VAL_4:.*]] = dc.unpack %[[VAL_0]] : !dc.value<i1>
// CHECK: %[[VAL_5:.*]], %[[VAL_6:.*]] = dc.unpack %[[VAL_1]] : !dc.value<index>
// CHECK: %[[VAL_7:.*]] = dc.join %[[VAL_3]], %[[VAL_5]]
// CHECK: %[[VAL_8:.*]] = dc.pack %[[VAL_7]]{{\[}}%[[VAL_4]]] : i1
// CHECK: %[[VAL_9:.*]], %[[VAL_10:.*]] = dc.branch %[[VAL_8]]
// CHECK: %[[VAL_11:.*]] = dc.pack %[[VAL_9]]{{\[}}%[[VAL_6]]] : index
// CHECK: %[[VAL_12:.*]] = dc.pack %[[VAL_10]]{{\[}}%[[VAL_6]]] : index
// CHECK: hw.output %[[VAL_11]], %[[VAL_12]], %[[VAL_2]] : !dc.value<index>, !dc.value<index>, !dc.token
// CHECK: }
handshake.func @test_conditional_branch(%arg0: i1, %arg1: index, %arg2: none, ...) -> (index, index, none) {
%0:2 = cond_br %arg0, %arg1 : index
return %0#0, %0#1, %arg2 : index, index, none
}
// CHECK-LABEL: hw.module @test_conditional_branch_none(
// CHECK-SAME: %[[VAL_0:.*]]: !dc.value<i1>, %[[VAL_1:.*]]: !dc.token) -> (out0: !dc.token, out1: !dc.token) {
// CHECK: %[[VAL_2:.*]], %[[VAL_3:.*]] = dc.unpack %[[VAL_0]] : !dc.value<i1>
// CHECK: %[[VAL_4:.*]] = dc.join %[[VAL_2]], %[[VAL_1]]
// CHECK: %[[VAL_5:.*]] = dc.pack %[[VAL_4]]{{\[}}%[[VAL_3]]] : i1
// CHECK: %[[VAL_6:.*]], %[[VAL_7:.*]] = dc.branch %[[VAL_5]]
// CHECK: hw.output %[[VAL_6]], %[[VAL_7]] : !dc.token, !dc.token
// CHECK: }
handshake.func @test_conditional_branch_none(%arg0: i1, %arg1: none) -> (none, none) {
%0:2 = cond_br %arg0, %arg1 : none
return %0#0, %0#1 : none, none
}
// CHECK-LABEL: hw.module @test_constant(
// CHECK-SAME: %[[VAL_0:.*]]: !dc.token) -> (out0: !dc.value<i32>) {
// CHECK: %[[VAL_1:.*]] = dc.source
// CHECK: %[[VAL_2:.*]] = arith.constant 42 : i32
// CHECK: %[[VAL_3:.*]] = dc.pack %[[VAL_1]]{{\[}}%[[VAL_2]]] : i32
// CHECK: hw.output %[[VAL_3]] : !dc.value<i32>
// CHECK: }
handshake.func @test_constant(%arg0: none) -> (i32) {
%1 = constant %arg0 {value = 42 : i32} : i32
return %1: i32
}
// CHECK-LABEL: hw.module @test_control_merge(
// CHECK-SAME: %[[VAL_0:.*]]: !dc.token, %[[VAL_1:.*]]: !dc.token) -> (out0: !dc.token, out1: !dc.value<index>) {
// CHECK: %[[VAL_2:.*]] = dc.merge %[[VAL_0]], %[[VAL_1]]
// CHECK: %[[VAL_3:.*]], %[[VAL_4:.*]] = dc.unpack %[[VAL_2]] : !dc.value<i1>
// CHECK: %[[VAL_5:.*]], %[[VAL_6:.*]] = dc.unpack %[[VAL_2]] : !dc.value<i1>
// CHECK: %[[VAL_7:.*]] = arith.index_cast %[[VAL_6]] : i1 to index
// CHECK: %[[VAL_8:.*]] = dc.pack %[[VAL_5]]{{\[}}%[[VAL_7]]] : index
// CHECK: hw.output %[[VAL_3]], %[[VAL_8]] : !dc.token, !dc.value<index>
// CHECK: }
handshake.func @test_control_merge(%arg0 : none, %arg1 : none) -> (none, index) {
%out, %idx = control_merge %arg0, %arg1 : none, index
return %out, %idx : none, index
}
// CHECK-LABEL: hw.module @test_control_merge_data(
// CHECK-SAME: %[[VAL_0:.*]]: !dc.value<i2>, %[[VAL_1:.*]]: !dc.value<i2>) -> (out0: !dc.value<i2>, out1: !dc.value<index>) {
// CHECK: %[[VAL_2:.*]], %[[VAL_3:.*]] = dc.unpack %[[VAL_0]] : !dc.value<i2>
// CHECK: %[[VAL_4:.*]], %[[VAL_5:.*]] = dc.unpack %[[VAL_1]] : !dc.value<i2>
// CHECK: %[[VAL_6:.*]] = dc.merge %[[VAL_2]], %[[VAL_4]]
// CHECK: %[[VAL_7:.*]], %[[VAL_8:.*]] = dc.unpack %[[VAL_6]] : !dc.value<i1>
// CHECK: %[[VAL_9:.*]] = arith.select %[[VAL_8]], %[[VAL_3]], %[[VAL_5]] : i2
// CHECK: %[[VAL_10:.*]] = dc.pack %[[VAL_7]]{{\[}}%[[VAL_9]]] : i2
// CHECK: %[[VAL_11:.*]] = arith.index_cast %[[VAL_8]] : i1 to index
// CHECK: %[[VAL_12:.*]] = dc.pack %[[VAL_7]]{{\[}}%[[VAL_11]]] : index
// CHECK: hw.output %[[VAL_10]], %[[VAL_12]] : !dc.value<i2>, !dc.value<index>
// CHECK: }
handshake.func @test_control_merge_data(%arg0 : i2, %arg1 : i2) -> (i2, index) {
%out, %idx = control_merge %arg0, %arg1 : i2, index
return %out, %idx : i2, index
}
// CHECK-LABEL: hw.module @branch_and_merge(
// CHECK-SAME: %[[VAL_0:.*]]: !dc.value<i1>, %[[VAL_1:.*]]: !dc.token) -> (out0: !dc.token, out1: !dc.value<index>) {
// CHECK: %[[VAL_2:.*]] = dc.merge %[[VAL_3:.*]], %[[VAL_4:.*]]
// CHECK: %[[VAL_5:.*]], %[[VAL_6:.*]] = dc.unpack %[[VAL_2]] : !dc.value<i1>
// CHECK: %[[VAL_7:.*]], %[[VAL_8:.*]] = dc.unpack %[[VAL_2]] : !dc.value<i1>
// CHECK: %[[VAL_9:.*]] = arith.index_cast %[[VAL_8]] : i1 to index
// CHECK: %[[VAL_10:.*]] = dc.pack %[[VAL_7]]{{\[}}%[[VAL_9]]] : index
// CHECK: %[[VAL_11:.*]], %[[VAL_12:.*]] = dc.unpack %[[VAL_0]] : !dc.value<i1>
// CHECK: %[[VAL_13:.*]] = dc.join %[[VAL_11]], %[[VAL_1]]
// CHECK: %[[VAL_14:.*]] = dc.pack %[[VAL_13]]{{\[}}%[[VAL_12]]] : i1
// CHECK: %[[VAL_3]], %[[VAL_4]] = dc.branch %[[VAL_14]]
// CHECK: hw.output %[[VAL_5]], %[[VAL_10]] : !dc.token, !dc.value<index>
// CHECK: }
handshake.func @branch_and_merge(%0 : i1, %1 : none) -> (none, index) {
%out, %idx = control_merge %true, %false : none, index
%true, %false = cond_br %0, %1 : none
return %out, %idx : none, index
}

View File

@ -30,6 +30,7 @@ target_link_libraries(circt-opt
CIRCTFSMTransforms
CIRCTFSMToSV
CIRCTHandshake
CIRCTHandshakeToDC
CIRCTHandshakeToHW
CIRCTHandshakeTransforms
CIRCTLLHD