From ab2acedb6512a74f56d91c8ee8068a348bec5258 Mon Sep 17 00:00:00 2001 From: Luke Jacobs Date: Fri, 29 Oct 2021 00:14:35 -0500 Subject: [PATCH 1/4] Initial ip_library_exporter feature commit. Testing how fstream can be used to write to the ip library. --- lib/Translation/EmitHLSCpp.cpp | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/lib/Translation/EmitHLSCpp.cpp b/lib/Translation/EmitHLSCpp.cpp index 53bb95d0..e8455f14 100644 --- a/lib/Translation/EmitHLSCpp.cpp +++ b/lib/Translation/EmitHLSCpp.cpp @@ -15,6 +15,9 @@ #include "scalehls/Support/Utils.h" #include "llvm/Support/raw_ostream.h" +#include +#include + using namespace mlir; using namespace scalehls; @@ -111,6 +114,7 @@ class ScaleHLSEmitterBase { // The stream to emit to. raw_ostream &os; + // std::fstream &os; // Instead of writing to stdout by default, write to file stream /// Value name management methods. SmallString<8> addName(Value val, bool isPtr = false); @@ -1615,12 +1619,20 @@ using namespace std; )XXX"; + // Right now, decide to emit compiled HLS C++ to the ip library store without asking the user + std::fstream fio; + std::string line = "This is a test that the emitModule function can theoretically write compiled HLS code into the ip library file."; + fio.open("ip_library.txt", std::ios::trunc | std::ios::out | std::ios::in); + fio << line << std::endl; + for (auto &op : *module.getBody()) { if (auto func = dyn_cast(op)) emitFunction(func); else emitError(&op, "is unsupported operation."); } + + fio.close(); } //===----------------------------------------------------------------------===// From b67cc0fc9a9dae9320824b92ea1af7b65b1d2c27 Mon Sep 17 00:00:00 2001 From: Luke Jacobs Date: Wed, 17 Nov 2021 00:30:58 -0600 Subject: [PATCH 2/4] Add experimental JSON IP library emission support --- .../scalehls/Translation/EmissionMethods.h | 388 ++++ include/scalehls/Translation/EmitHLSCpp.h | 12 + include/scalehls/Translation/EmitIPLibrary.h | 15 + lib/Translation/EmissionMethods.cpp | 1273 +++++++++++++ lib/Translation/EmitHLSCpp.cpp | 1621 +---------------- lib/Translation/EmitIPLibrary.cpp | 57 + .../scalehls-translate/scalehls-translate.cpp | 2 + 7 files changed, 1750 insertions(+), 1618 deletions(-) create mode 100644 include/scalehls/Translation/EmissionMethods.h create mode 100644 include/scalehls/Translation/EmitIPLibrary.h create mode 100644 lib/Translation/EmissionMethods.cpp create mode 100644 lib/Translation/EmitIPLibrary.cpp diff --git a/include/scalehls/Translation/EmissionMethods.h b/include/scalehls/Translation/EmissionMethods.h new file mode 100644 index 00000000..2aa0fbd6 --- /dev/null +++ b/include/scalehls/Translation/EmissionMethods.h @@ -0,0 +1,388 @@ +#include "scalehls/Translation/EmitHLSCpp.h" +#include "mlir/Dialect/Affine/IR/AffineValueMap.h" +#include "mlir/IR/AffineExprVisitor.h" +#include "mlir/IR/IntegerSet.h" +#include "mlir/Translation.h" +#include "scalehls/Dialect/HLSCpp/Visitor.h" +#include "scalehls/Dialect/HLSKernel/Visitor.h" +#include "scalehls/InitAllDialects.h" +#include "scalehls/Support/Utils.h" +#include "llvm/Support/raw_ostream.h" + +#include +#include + +using namespace mlir; +using namespace scalehls; + +namespace mlir { +namespace scalehls { + +//===----------------------------------------------------------------------===// +// Some Base Classes +//===----------------------------------------------------------------------===// + +/// This class maintains the mutable state that cross-cuts and is shared by the +/// various emitters. +class ScaleHLSEmitterState { +public: + explicit ScaleHLSEmitterState(raw_ostream &os) : os(os) {} + + // The stream to emit to. + raw_ostream &os; + + bool encounteredError = false; + unsigned currentIndent = 0; + + // This table contains all declared values. + DenseMap> nameTable; + +private: + ScaleHLSEmitterState(const ScaleHLSEmitterState &) = delete; + void operator=(const ScaleHLSEmitterState &) = delete; +}; + +/// This is the base class for all of the HLSCpp Emitter components. Simple methods are implemented here. +class ScaleHLSEmitterBase { +public: + explicit ScaleHLSEmitterBase(ScaleHLSEmitterState &state) + : state(state), os(state.os) {} + + InFlightDiagnostic emitError(Operation *op, const Twine &message) { + state.encounteredError = true; + return op->emitError(message); + } + + raw_ostream &indent() { return os.indent(state.currentIndent); } + + void addIndent() { state.currentIndent += 2; } + void reduceIndent() { state.currentIndent -= 2; } + + // All of the mutable state we are maintaining. + ScaleHLSEmitterState &state; + + // The stream to emit to. + raw_ostream &os; + // std::fstream &os; // Instead of writing to stdout by default, write to file stream + + /// Value name management methods. + SmallString<8> addName(Value val, bool isPtr = false); + + SmallString<8> addAlias(Value val, Value alias); + + SmallString<8> getName(Value val); + + bool isDeclared(Value val) { + if (getName(val).empty()) { + return false; + } else + return true; + } + +private: + ScaleHLSEmitterBase(const ScaleHLSEmitterBase &) = delete; + void operator=(const ScaleHLSEmitterBase &) = delete; + +}; + +class ModuleEmitter : public ScaleHLSEmitterBase { +public: + using operand_range = Operation::operand_range; + explicit ModuleEmitter(ScaleHLSEmitterState &state) + : ScaleHLSEmitterBase(state) {} + + /// SCF statement emitters. + void emitScfFor(scf::ForOp op); + void emitScfIf(scf::IfOp op); + void emitScfYield(scf::YieldOp op); + + /// Affine statement emitters. + void emitAffineFor(AffineForOp op); + void emitAffineIf(AffineIfOp op); + void emitAffineParallel(AffineParallelOp op); + void emitAffineApply(AffineApplyOp op); + template + void emitAffineMaxMin(OpType op, const char *syntax); + void emitAffineLoad(AffineLoadOp op); + void emitAffineStore(AffineStoreOp op); + void emitAffineYield(AffineYieldOp op); + + /// Memref-related statement emitters. + template void emitAlloc(OpType op); + void emitLoad(memref::LoadOp op); + void emitStore(memref::StoreOp op); + + /// Tensor-related statement emitters. + void emitTensorLoad(memref::TensorLoadOp op); + void emitTensorStore(memref::TensorStoreOp op); + void emitTensorToMemref(memref::BufferCastOp op); + void emitDim(memref::DimOp op); + void emitRank(RankOp op); + + /// Standard expression emitters. + void emitBinary(Operation *op, const char *syntax); + void emitUnary(Operation *op, const char *syntax); + + /// IP operation emitter. + void emitIP(IPOp op); + + /// Special operation emitters. + void emitSelect(SelectOp op); + void emitConstant(ConstantOp op); + template void emitCast(CastOpType op); + void emitCall(CallOp op); + + /// Structure operations emitters. + void emitAssign(AssignOp op); + + /// Top-level MLIR module emitter. + void emitModule(ModuleOp module, bool emitHeader = true); + +private: + /// C++ component emitters. + void emitValue(Value val, unsigned rank = 0, bool isPtr = false); + void emitArrayDecl(Value array); + unsigned emitNestedLoopHead(Value val); + void emitNestedLoopTail(unsigned rank); + void emitInfoAndNewLine(Operation *op); + + /// MLIR component and HLS C++ pragma emitters. + void emitBlock(Block &block); + void emitLoopDirectives(Operation *op); + void emitArrayDirectives(Value memref); + void emitFunctionDirectives(FuncOp func, ArrayRef portList); + void emitFunction(FuncOp func); +}; + +//===----------------------------------------------------------------------===// +// AffineEmitter Class +//===----------------------------------------------------------------------===// + +class AffineExprEmitter : public ScaleHLSEmitterBase, + public AffineExprVisitor { +public: + using operand_range = Operation::operand_range; + explicit AffineExprEmitter(ScaleHLSEmitterState &state, unsigned numDim, + operand_range operands) + : ScaleHLSEmitterBase(state), numDim(numDim), operands(operands) {} + + void visitAddExpr(AffineBinaryOpExpr expr) { emitAffineBinary(expr, "+"); } + void visitMulExpr(AffineBinaryOpExpr expr) { emitAffineBinary(expr, "*"); } + void visitModExpr(AffineBinaryOpExpr expr) { emitAffineBinary(expr, "%"); } + void visitFloorDivExpr(AffineBinaryOpExpr expr) { + emitAffineBinary(expr, "/"); + } + void visitCeilDivExpr(AffineBinaryOpExpr expr) { + // This is super inefficient. + os << "("; + visit(expr.getLHS()); + os << " + "; + visit(expr.getRHS()); + os << " - 1) / "; + visit(expr.getRHS()); + os << ")"; + } + + void visitConstantExpr(AffineConstantExpr expr) { os << expr.getValue(); } + + void visitDimExpr(AffineDimExpr expr) { + os << getName(operands[expr.getPosition()]); + } + void visitSymbolExpr(AffineSymbolExpr expr) { + os << getName(operands[numDim + expr.getPosition()]); + } + + /// Affine expression emitters. + void emitAffineBinary(AffineBinaryOpExpr expr, const char *syntax) { + os << "("; + if (auto constRHS = expr.getRHS().dyn_cast()) { + if ((unsigned)*syntax == (unsigned)*"*" && constRHS.getValue() == -1) { + os << "-"; + visit(expr.getLHS()); + os << ")"; + return; + } + if ((unsigned)*syntax == (unsigned)*"+" && constRHS.getValue() < 0) { + visit(expr.getLHS()); + os << " - "; + os << -constRHS.getValue(); + os << ")"; + return; + } + } + if (auto binaryRHS = expr.getRHS().dyn_cast()) { + if (auto constRHS = binaryRHS.getRHS().dyn_cast()) { + if ((unsigned)*syntax == (unsigned)*"+" && constRHS.getValue() == -1 && + binaryRHS.getKind() == AffineExprKind::Mul) { + visit(expr.getLHS()); + os << " - "; + visit(binaryRHS.getLHS()); + os << ")"; + return; + } + } + } + visit(expr.getLHS()); + os << " " << syntax << " "; + visit(expr.getRHS()); + os << ")"; + } + + void emitAffineExpr(AffineExpr expr) { visit(expr); } + +private: + unsigned numDim; + operand_range operands; +}; + +//===----------------------------------------------------------------------===// +// Definition of StmtVisitor, ExprVisitor, and PragmaVisitor Classes +//===----------------------------------------------------------------------===// + +class StmtVisitor : public HLSCppVisitorBase { +public: + StmtVisitor(ModuleEmitter &emitter) : emitter(emitter) {} + + using HLSCppVisitorBase::visitOp; + /// SCF statements. + bool visitOp(scf::ForOp op) { return emitter.emitScfFor(op), true; }; + bool visitOp(scf::IfOp op) { return emitter.emitScfIf(op), true; }; + bool visitOp(scf::ParallelOp op) { return true; }; + bool visitOp(scf::ReduceOp op) { return true; }; + bool visitOp(scf::ReduceReturnOp op) { return true; }; + bool visitOp(scf::YieldOp op) { return emitter.emitScfYield(op), true; }; + + /// Affine statements. + bool visitOp(AffineForOp op) { return emitter.emitAffineFor(op), true; } + bool visitOp(AffineIfOp op) { return emitter.emitAffineIf(op), true; } + bool visitOp(AffineParallelOp op) { + return emitter.emitAffineParallel(op), true; + } + bool visitOp(AffineApplyOp op) { return emitter.emitAffineApply(op), true; } + bool visitOp(AffineMaxOp op) { + return emitter.emitAffineMaxMin(op, "max"), true; + } + bool visitOp(AffineMinOp op) { + return emitter.emitAffineMaxMin(op, "min"), true; + } + bool visitOp(AffineLoadOp op) { return emitter.emitAffineLoad(op), true; } + bool visitOp(AffineStoreOp op) { return emitter.emitAffineStore(op), true; } + bool visitOp(AffineYieldOp op) { return emitter.emitAffineYield(op), true; } + + /// Memref-related statements. + bool visitOp(memref::AllocOp op) { + return emitter.emitAlloc(op), true; + } + bool visitOp(memref::AllocaOp op) { + return emitter.emitAlloc(op), true; + } + bool visitOp(memref::LoadOp op) { return emitter.emitLoad(op), true; } + bool visitOp(memref::StoreOp op) { return emitter.emitStore(op), true; } + bool visitOp(memref::DeallocOp op) { return true; } + + /// Tensor-related statements. + bool visitOp(memref::TensorLoadOp op) { + return emitter.emitTensorLoad(op), true; + } + bool visitOp(memref::TensorStoreOp op) { + return emitter.emitTensorStore(op), true; + } + bool visitOp(memref::BufferCastOp op) { + return emitter.emitTensorToMemref(op), true; + } + bool visitOp(memref::DimOp op) { return emitter.emitDim(op), true; } + bool visitOp(RankOp op) { return emitter.emitRank(op), true; } + + /// Structure operations. + bool visitOp(AssignOp op) { return emitter.emitAssign(op), true; } + bool visitOp(CastOp op) { return emitter.emitCast(op), true; } + bool visitOp(MulOp op) { return emitter.emitBinary(op, "*"), true; } + bool visitOp(AddOp op) { return emitter.emitBinary(op, "+"), true; } + +private: + ModuleEmitter &emitter; +}; + +class ExprVisitor : public HLSCppVisitorBase { +public: + ExprVisitor(ModuleEmitter &emitter) : emitter(emitter) {} + + using HLSCppVisitorBase::visitOp; + /// Float binary expressions. + bool visitOp(CmpFOp op); + bool visitOp(AddFOp op) { return emitter.emitBinary(op, "+"), true; } + bool visitOp(SubFOp op) { return emitter.emitBinary(op, "-"), true; } + bool visitOp(MulFOp op) { return emitter.emitBinary(op, "*"), true; } + bool visitOp(DivFOp op) { return emitter.emitBinary(op, "/"), true; } + bool visitOp(RemFOp op) { return emitter.emitBinary(op, "%"), true; } + + /// Integer binary expressions. + bool visitOp(CmpIOp op); + bool visitOp(AddIOp op) { return emitter.emitBinary(op, "+"), true; } + bool visitOp(SubIOp op) { return emitter.emitBinary(op, "-"), true; } + bool visitOp(MulIOp op) { return emitter.emitBinary(op, "*"), true; } + bool visitOp(SignedDivIOp op) { return emitter.emitBinary(op, "/"), true; } + bool visitOp(SignedRemIOp op) { return emitter.emitBinary(op, "%"), true; } + bool visitOp(UnsignedDivIOp op) { return emitter.emitBinary(op, "/"), true; } + bool visitOp(UnsignedRemIOp op) { return emitter.emitBinary(op, "%"), true; } + bool visitOp(XOrOp op) { return emitter.emitBinary(op, "^"), true; } + bool visitOp(AndOp op) { return emitter.emitBinary(op, "&"), true; } + bool visitOp(OrOp op) { return emitter.emitBinary(op, "|"), true; } + bool visitOp(ShiftLeftOp op) { return emitter.emitBinary(op, "<<"), true; } + bool visitOp(SignedShiftRightOp op) { + return emitter.emitBinary(op, ">>"), true; + } + bool visitOp(UnsignedShiftRightOp op) { + return emitter.emitBinary(op, ">>"), true; + } + + /// Unary expressions. + bool visitOp(AbsFOp op) { return emitter.emitUnary(op, "abs"), true; } + bool visitOp(CeilFOp op) { return emitter.emitUnary(op, "ceil"), true; } + bool visitOp(NegFOp op) { return emitter.emitUnary(op, "-"), true; } + bool visitOp(math::CosOp op) { return emitter.emitUnary(op, "cos"), true; } + bool visitOp(math::SinOp op) { return emitter.emitUnary(op, "sin"), true; } + bool visitOp(math::TanhOp op) { return emitter.emitUnary(op, "tanh"), true; } + bool visitOp(math::SqrtOp op) { return emitter.emitUnary(op, "sqrt"), true; } + bool visitOp(math::RsqrtOp op) { + return emitter.emitUnary(op, "1.0 / sqrt"), true; + } + bool visitOp(math::ExpOp op) { return emitter.emitUnary(op, "exp"), true; } + bool visitOp(math::Exp2Op op) { return emitter.emitUnary(op, "exp2"), true; } + bool visitOp(math::LogOp op) { return emitter.emitUnary(op, "log"), true; } + bool visitOp(math::Log2Op op) { return emitter.emitUnary(op, "log2"), true; } + bool visitOp(math::Log10Op op) { + return emitter.emitUnary(op, "log10"), true; + } + + /// Special operations. + bool visitOp(SelectOp op) { return emitter.emitSelect(op), true; } + bool visitOp(ConstantOp op) { return emitter.emitConstant(op), true; } + bool visitOp(IndexCastOp op) { + return emitter.emitCast(op), true; + } + bool visitOp(UIToFPOp op) { return emitter.emitCast(op), true; } + bool visitOp(SIToFPOp op) { return emitter.emitCast(op), true; } + bool visitOp(FPToUIOp op) { return emitter.emitCast(op), true; } + bool visitOp(FPToSIOp op) { return emitter.emitCast(op), true; } + bool visitOp(CallOp op) { return emitter.emitCall(op), true; } + bool visitOp(ReturnOp op) { return true; } + +private: + ModuleEmitter &emitter; +}; + +class KernelVisitor : public HLSKernelVisitorBase { +public: + KernelVisitor(ModuleEmitter &emitter) : emitter(emitter) {} + + using HLSKernelVisitorBase::visitOp; + /// IP operation. + bool visitOp(IPOp op) { return emitter.emitIP(op), true; } + +private: + ModuleEmitter &emitter; +}; + +} +} \ No newline at end of file diff --git a/include/scalehls/Translation/EmitHLSCpp.h b/include/scalehls/Translation/EmitHLSCpp.h index f8ab61a9..f3a2dfa2 100644 --- a/include/scalehls/Translation/EmitHLSCpp.h +++ b/include/scalehls/Translation/EmitHLSCpp.h @@ -7,8 +7,20 @@ #ifndef SCALEHLS_TRANSLATION_EMITHLSCPP_H #define SCALEHLS_TRANSLATION_EMITHLSCPP_H +#include "scalehls/Translation/EmitHLSCpp.h" +#include "mlir/Dialect/Affine/IR/AffineValueMap.h" +#include "mlir/IR/AffineExprVisitor.h" +#include "mlir/IR/IntegerSet.h" +#include "mlir/Translation.h" +#include "scalehls/Dialect/HLSCpp/Visitor.h" +#include "scalehls/Dialect/HLSKernel/Visitor.h" +#include "scalehls/InitAllDialects.h" +#include "scalehls/Support/Utils.h" +#include "llvm/Support/raw_ostream.h" + #include "mlir/IR/BuiltinOps.h" + namespace mlir { namespace scalehls { diff --git a/include/scalehls/Translation/EmitIPLibrary.h b/include/scalehls/Translation/EmitIPLibrary.h new file mode 100644 index 00000000..0fc5e6a7 --- /dev/null +++ b/include/scalehls/Translation/EmitIPLibrary.h @@ -0,0 +1,15 @@ +// #ifndef SCALEHLS_TRANSLATION_EMITHLSCPP_H +// #define SCALEHLS_TRANSLATION_EMITHLSCPP_H + +#include "mlir/IR/BuiltinOps.h" + +namespace mlir { +namespace scalehls { + +LogicalResult emitIPLibrary(ModuleOp module, llvm::raw_ostream &os); +void registerEmitIPLibraryTranslation(); + +} // namespace scalehls +} // namespace mlir + +// #endif // SCALEHLS_TRANSLATION_EMITHLSCPP_H diff --git a/lib/Translation/EmissionMethods.cpp b/lib/Translation/EmissionMethods.cpp new file mode 100644 index 00000000..f091f430 --- /dev/null +++ b/lib/Translation/EmissionMethods.cpp @@ -0,0 +1,1273 @@ +//===----------------------------------------------------------------------===// +// +// Copyright 2020-2021 The ScaleHLS Authors. +// +//===----------------------------------------------------------------------===// + +#include "mlir/Dialect/Affine/IR/AffineValueMap.h" +#include "mlir/IR/AffineExprVisitor.h" +#include "mlir/IR/IntegerSet.h" +#include "mlir/Translation.h" +#include "scalehls/Dialect/HLSCpp/Visitor.h" +#include "scalehls/Dialect/HLSKernel/Visitor.h" +#include "scalehls/InitAllDialects.h" +#include "scalehls/Support/Utils.h" +#include "llvm/Support/raw_ostream.h" + +#include "scalehls/Translation/EmitHLSCpp.h" +#include "scalehls/Translation/EmissionMethods.h" + +#include +#include + +using namespace mlir; +using namespace scalehls; + +// Adding these namespace bookends right here so that these functions can be used by EmitIPLibrary and any other passes that +// will be added in the future. +namespace mlir { +namespace scalehls { + +//===----------------------------------------------------------------------===// +// Utils +//===----------------------------------------------------------------------===// + +static SmallString<16> getTypeName(Value val) { + // Handle memref, tensor, and vector types. + auto valType = val.getType(); + if (auto arrayType = val.getType().dyn_cast()) + valType = arrayType.getElementType(); + + // Handle float types. + if (valType.isa()) + return SmallString<16>("float"); + else if (valType.isa()) + return SmallString<16>("double"); + + // Handle integer types. + else if (valType.isa()) + return SmallString<16>("int"); + else if (auto intType = valType.dyn_cast()) { + if (intType.getWidth() == 1) + return SmallString<16>("bool"); + else { + std::string signedness = ""; + if (intType.getSignedness() == IntegerType::SignednessSemantics::Unsigned) + signedness = "u"; + + switch (intType.getWidth()) { + case 8: + case 16: + case 32: + case 64: + return SmallString<16>(signedness + "int" + + std::to_string(intType.getWidth()) + "_t"); + default: + return SmallString<16>("ap_" + signedness + "int<" + + std::to_string(intType.getWidth()) + ">"); + } + } + } else + val.getDefiningOp()->emitError("has unsupported type."); + + return SmallString<16>(); +} + +// TODO: update naming rule. +SmallString<8> ScaleHLSEmitterBase::addName(Value val, bool isPtr) { + assert(!isDeclared(val) && "has been declared before."); + + SmallString<8> valName; + if (isPtr) + valName += "*"; + + valName += StringRef("v" + std::to_string(state.nameTable.size())); + state.nameTable[val] = valName; + + return valName; +} + +SmallString<8> ScaleHLSEmitterBase::addAlias(Value val, Value alias) { + assert(!isDeclared(alias) && "has been declared before."); + assert(isDeclared(val) && "hasn't been declared before."); + + auto valName = getName(val); + state.nameTable[alias] = valName; + + return valName; +} + +SmallString<8> ScaleHLSEmitterBase::getName(Value val) { + // For constant scalar operations, the constant number will be returned rather + // than the value name. + if (auto defOp = val.getDefiningOp()) { + if (auto constOp = dyn_cast(defOp)) { + auto constAttr = constOp.getValue(); + + if (auto floatAttr = constAttr.dyn_cast()) { + auto value = floatAttr.getValueAsDouble(); + if (std::isfinite(value)) + return SmallString<8>(std::to_string(value)); + else if (value > 0) + return SmallString<8>("INFINITY"); + else + return SmallString<8>("-INFINITY"); + + } else if (auto intAttr = constAttr.dyn_cast()) { + auto value = intAttr.getInt(); + return SmallString<8>(std::to_string(value)); + + } else if (auto boolAttr = constAttr.dyn_cast()) + return SmallString<8>(std::to_string(boolAttr.getValue())); + } + } + return state.nameTable.lookup(val); +} + +//===----------------------------------------------------------------------===// +// ExprVisitor Class Definition +//===----------------------------------------------------------------------===// + +bool ExprVisitor::visitOp(CmpFOp op) { + switch (op.getPredicate()) { + case CmpFPredicate::OEQ: + case CmpFPredicate::UEQ: + return emitter.emitBinary(op, "=="), true; + case CmpFPredicate::ONE: + case CmpFPredicate::UNE: + return emitter.emitBinary(op, "!="), true; + case CmpFPredicate::OLT: + case CmpFPredicate::ULT: + return emitter.emitBinary(op, "<"), true; + case CmpFPredicate::OLE: + case CmpFPredicate::ULE: + return emitter.emitBinary(op, "<="), true; + case CmpFPredicate::OGT: + case CmpFPredicate::UGT: + return emitter.emitBinary(op, ">"), true; + case CmpFPredicate::OGE: + case CmpFPredicate::UGE: + return emitter.emitBinary(op, ">="), true; + default: + op.emitError("has unsupported compare type."); + return false; + } +} + +bool ExprVisitor::visitOp(CmpIOp op) { + switch (op.getPredicate()) { + case CmpIPredicate::eq: + return emitter.emitBinary(op, "=="), true; + case CmpIPredicate::ne: + return emitter.emitBinary(op, "!="), true; + case CmpIPredicate::slt: + case CmpIPredicate::ult: + return emitter.emitBinary(op, "<"), true; + case CmpIPredicate::sle: + case CmpIPredicate::ule: + return emitter.emitBinary(op, "<="), true; + case CmpIPredicate::sgt: + case CmpIPredicate::ugt: + return emitter.emitBinary(op, ">"), true; + case CmpIPredicate::sge: + case CmpIPredicate::uge: + return emitter.emitBinary(op, ">="), true; + } +} + +//===----------------------------------------------------------------------===// +// ModuleEmitter Class Definition +//===----------------------------------------------------------------------===// + +/// SCF statement emitters. +void ModuleEmitter::emitScfFor(scf::ForOp op) { + indent(); + os << "for ("; + auto iterVar = op.getInductionVar(); + + // Emit lower bound. + emitValue(iterVar); + os << " = "; + emitValue(op.lowerBound()); + os << "; "; + + // Emit upper bound. + emitValue(iterVar); + os << " < "; + emitValue(op.upperBound()); + os << "; "; + + // Emit increase step. + emitValue(iterVar); + os << " += "; + emitValue(op.step()); + os << ") {"; + emitInfoAndNewLine(op); + + addIndent(); + + emitLoopDirectives(op); + emitBlock(*op.getBody()); + reduceIndent(); + + indent(); + os << "}\n"; +} + +void ModuleEmitter::emitScfIf(scf::IfOp op) { + // Declare all values returned by scf::YieldOp. They will be further handled + // by the scf::YieldOp emitter. + for (auto result : op.getResults()) { + if (!isDeclared(result)) { + indent(); + if (result.getType().isa()) + emitArrayDecl(result); + else + emitValue(result); + os << ";\n"; + } + } + + indent(); + os << "if ("; + emitValue(op.condition()); + os << ") {"; + emitInfoAndNewLine(op); + + addIndent(); + emitBlock(op.thenRegion().front()); + reduceIndent(); + + if (!op.elseRegion().empty()) { + indent(); + os << "} else {\n"; + addIndent(); + emitBlock(op.elseRegion().front()); + reduceIndent(); + } + + indent(); + os << "}\n"; +} + +void ModuleEmitter::emitScfYield(scf::YieldOp op) { + if (op.getNumOperands() == 0) + return; + + // For now, only and scf::If operations will use scf::Yield to return + // generated values. + if (auto parentOp = dyn_cast(op->getParentOp())) { + unsigned resultIdx = 0; + for (auto result : parentOp.getResults()) { + unsigned rank = emitNestedLoopHead(result); + indent(); + emitValue(result, rank); + os << " = "; + emitValue(op.getOperand(resultIdx++), rank); + os << ";"; + emitInfoAndNewLine(op); + emitNestedLoopTail(rank); + } + } +} + +/// Affine statement emitters. +void ModuleEmitter::emitAffineFor(AffineForOp op) { + indent(); + os << "for ("; + auto iterVar = op.getInductionVar(); + + // Emit lower bound. + emitValue(iterVar); + os << " = "; + auto lowerMap = op.getLowerBoundMap(); + AffineExprEmitter lowerEmitter(state, lowerMap.getNumDims(), + op.getLowerBoundOperands()); + if (lowerMap.getNumResults() == 1) + lowerEmitter.emitAffineExpr(lowerMap.getResult(0)); + else { + for (unsigned i = 0, e = lowerMap.getNumResults() - 1; i < e; ++i) + os << "max("; + lowerEmitter.emitAffineExpr(lowerMap.getResult(0)); + for (auto &expr : llvm::drop_begin(lowerMap.getResults(), 1)) { + os << ", "; + lowerEmitter.emitAffineExpr(expr); + os << ")"; + } + } + os << "; "; + + // Emit upper bound. + emitValue(iterVar); + os << " < "; + auto upperMap = op.getUpperBoundMap(); + AffineExprEmitter upperEmitter(state, upperMap.getNumDims(), + op.getUpperBoundOperands()); + if (upperMap.getNumResults() == 1) + upperEmitter.emitAffineExpr(upperMap.getResult(0)); + else { + for (unsigned i = 0, e = upperMap.getNumResults() - 1; i < e; ++i) + os << "min("; + upperEmitter.emitAffineExpr(upperMap.getResult(0)); + for (auto &expr : llvm::drop_begin(upperMap.getResults(), 1)) { + os << ", "; + upperEmitter.emitAffineExpr(expr); + os << ")"; + } + } + os << "; "; + + // Emit increase step. + emitValue(iterVar); + os << " += " << op.getStep() << ") {"; + emitInfoAndNewLine(op); + + addIndent(); + + emitLoopDirectives(op); + emitBlock(*op.getBody()); + reduceIndent(); + + indent(); + os << "}\n"; +} + +void ModuleEmitter::emitAffineIf(AffineIfOp op) { + // Declare all values returned by AffineYieldOp. They will be further + // handled by the AffineYieldOp emitter. + for (auto result : op.getResults()) { + if (!isDeclared(result)) { + indent(); + if (result.getType().isa()) + emitArrayDecl(result); + else + emitValue(result); + os << ";\n"; + } + } + + indent(); + os << "if ("; + auto constrSet = op.getIntegerSet(); + AffineExprEmitter constrEmitter(state, constrSet.getNumDims(), + op.getOperands()); + + // Emit all constraints. + unsigned constrIdx = 0; + for (auto &expr : constrSet.getConstraints()) { + constrEmitter.emitAffineExpr(expr); + if (constrSet.isEq(constrIdx)) + os << " == 0"; + else + os << " >= 0"; + + if (constrIdx++ != constrSet.getNumConstraints() - 1) + os << " && "; + } + os << ") {"; + emitInfoAndNewLine(op); + + addIndent(); + emitBlock(*op.getThenBlock()); + reduceIndent(); + + if (op.hasElse()) { + indent(); + os << "} else {\n"; + addIndent(); + emitBlock(*op.getElseBlock()); + reduceIndent(); + } + + indent(); + os << "}\n"; +} + +void ModuleEmitter::emitAffineParallel(AffineParallelOp op) { + // Declare all values returned by AffineParallelOp. They will be further + // handled by the AffineYieldOp emitter. + for (auto result : op.getResults()) { + if (!isDeclared(result)) { + indent(); + if (result.getType().isa()) + emitArrayDecl(result); + else + emitValue(result); + os << ";\n"; + } + } + + auto steps = getIntArrayAttrValue(op, op.getStepsAttrName()); + for (unsigned i = 0, e = op.getNumDims(); i < e; ++i) { + indent(); + os << "for ("; + auto iterVar = op.getBody()->getArgument(i); + + // Emit lower bound. + emitValue(iterVar); + os << " = "; + auto lowerMap = op.getLowerBoundsValueMap().getAffineMap(); + AffineExprEmitter lowerEmitter(state, lowerMap.getNumDims(), + op.getLowerBoundsOperands()); + lowerEmitter.emitAffineExpr(lowerMap.getResult(i)); + os << "; "; + + // Emit upper bound. + emitValue(iterVar); + os << " < "; + auto upperMap = op.getUpperBoundsValueMap().getAffineMap(); + AffineExprEmitter upperEmitter(state, upperMap.getNumDims(), + op.getUpperBoundsOperands()); + upperEmitter.emitAffineExpr(upperMap.getResult(i)); + os << "; "; + + // Emit increase step. + emitValue(iterVar); + os << " += " << steps[i] << ") {"; + emitInfoAndNewLine(op); + + addIndent(); + } + + emitBlock(*op.getBody()); + + for (unsigned i = 0, e = op.getNumDims(); i < e; ++i) { + reduceIndent(); + + indent(); + os << "}\n"; + } +} + +void ModuleEmitter::emitAffineApply(AffineApplyOp op) { + indent(); + emitValue(op.getResult()); + os << " = "; + auto affineMap = op.getAffineMap(); + AffineExprEmitter(state, affineMap.getNumDims(), op.getOperands()) + .emitAffineExpr(affineMap.getResult(0)); + os << ";"; + emitInfoAndNewLine(op); +} + +template +void ModuleEmitter::emitAffineMaxMin(OpType op, const char *syntax) { + indent(); + emitValue(op.getResult()); + os << " = "; + auto affineMap = op.getAffineMap(); + AffineExprEmitter affineEmitter(state, affineMap.getNumDims(), + op.getOperands()); + for (unsigned i = 0, e = affineMap.getNumResults() - 1; i < e; ++i) + os << syntax << "("; + affineEmitter.emitAffineExpr(affineMap.getResult(0)); + for (auto &expr : llvm::drop_begin(affineMap.getResults(), 1)) { + os << ", "; + affineEmitter.emitAffineExpr(expr); + os << ")"; + } + os << ";"; + emitInfoAndNewLine(op); +} + +void ModuleEmitter::emitAffineLoad(AffineLoadOp op) { + indent(); + emitValue(op.getResult()); + os << " = "; + emitValue(op.getMemRef()); + auto affineMap = op.getAffineMap(); + AffineExprEmitter affineEmitter(state, affineMap.getNumDims(), + op.getMapOperands()); + for (auto index : affineMap.getResults()) { + os << "["; + affineEmitter.emitAffineExpr(index); + os << "]"; + } + os << ";"; + emitInfoAndNewLine(op); +} + +void ModuleEmitter::emitAffineStore(AffineStoreOp op) { + indent(); + emitValue(op.getMemRef()); + auto affineMap = op.getAffineMap(); + AffineExprEmitter affineEmitter(state, affineMap.getNumDims(), + op.getMapOperands()); + for (auto index : affineMap.getResults()) { + os << "["; + affineEmitter.emitAffineExpr(index); + os << "]"; + } + os << " = "; + emitValue(op.getValueToStore()); + os << ";"; + emitInfoAndNewLine(op); +} + +// TODO: For now, all values created in the AffineIf region will be declared +// in the generated C++. However, values which will be returned by affine +// yield operation should not be declared again. How to "bind" the pair of +// values inside/outside of AffineIf region needs to be considered. +void ModuleEmitter::emitAffineYield(AffineYieldOp op) { + if (op.getNumOperands() == 0) + return; + + // For now, only AffineParallel and AffineIf operations will use + // AffineYield to return generated values. + if (auto parentOp = dyn_cast(op->getParentOp())) { + unsigned resultIdx = 0; + for (auto result : parentOp.getResults()) { + unsigned rank = emitNestedLoopHead(result); + indent(); + emitValue(result, rank); + os << " = "; + emitValue(op.getOperand(resultIdx++), rank); + os << ";"; + emitInfoAndNewLine(op); + emitNestedLoopTail(rank); + } + } else if (auto parentOp = dyn_cast(op->getParentOp())) { + indent(); + os << "if ("; + unsigned ivIdx = 0; + for (auto iv : parentOp.getBody()->getArguments()) { + emitValue(iv); + os << " == 0"; + if (ivIdx++ != parentOp.getBody()->getNumArguments() - 1) + os << " && "; + } + os << ") {\n"; + + // When all induction values are 0, generated values will be directly + // assigned to the current results, correspondingly. + addIndent(); + unsigned resultIdx = 0; + for (auto result : parentOp.getResults()) { + unsigned rank = emitNestedLoopHead(result); + indent(); + emitValue(result, rank); + os << " = "; + emitValue(op.getOperand(resultIdx++), rank); + os << ";"; + emitInfoAndNewLine(op); + emitNestedLoopTail(rank); + } + reduceIndent(); + + indent(); + os << "} else {\n"; + + // Otherwise, generated values will be accumulated/reduced to the + // current results with corresponding AtomicRMWKind operations. + addIndent(); + auto RMWAttrs = + getIntArrayAttrValue(parentOp, parentOp.getReductionsAttrName()); + resultIdx = 0; + for (auto result : parentOp.getResults()) { + unsigned rank = emitNestedLoopHead(result); + indent(); + emitValue(result, rank); + switch ((AtomicRMWKind)RMWAttrs[resultIdx]) { + case (AtomicRMWKind::addf): + case (AtomicRMWKind::addi): + os << " += "; + emitValue(op.getOperand(resultIdx++), rank); + break; + case (AtomicRMWKind::assign): + os << " = "; + emitValue(op.getOperand(resultIdx++), rank); + break; + case (AtomicRMWKind::maxf): + case (AtomicRMWKind::maxs): + case (AtomicRMWKind::maxu): + os << " = max("; + emitValue(result, rank); + os << ", "; + emitValue(op.getOperand(resultIdx++), rank); + os << ")"; + break; + case (AtomicRMWKind::minf): + case (AtomicRMWKind::mins): + case (AtomicRMWKind::minu): + os << " = min("; + emitValue(result, rank); + os << ", "; + emitValue(op.getOperand(resultIdx++), rank); + os << ")"; + break; + case (AtomicRMWKind::mulf): + case (AtomicRMWKind::muli): + os << " *= "; + emitValue(op.getOperand(resultIdx++), rank); + break; + } + os << ";"; + emitInfoAndNewLine(op); + emitNestedLoopTail(rank); + } + reduceIndent(); + + indent(); + os << "}\n"; + } +} + +/// Memref-related statement emitters. +template +void ModuleEmitter::emitAlloc(OpType op) { + // A declared result indicates that the memref is output of the function, and + // has been declared in the function signature. + if (isDeclared(op.getResult())) + return; + + // Vivado HLS only supports static shape on-chip memory. + if (!op.getType().hasStaticShape()) + emitError(op, "is unranked or has dynamic shape."); + + indent(); + emitArrayDecl(op.getResult()); + os << ";"; + emitInfoAndNewLine(op); + emitArrayDirectives(op.getResult()); +} + +void ModuleEmitter::emitLoad(memref::LoadOp op) { + indent(); + emitValue(op.getResult()); + os << " = "; + emitValue(op.getMemRef()); + for (auto index : op.getIndices()) { + os << "["; + emitValue(index); + os << "]"; + } + os << ";"; + emitInfoAndNewLine(op); +} + +void ModuleEmitter::emitStore(memref::StoreOp op) { + indent(); + emitValue(op.getMemRef()); + for (auto index : op.getIndices()) { + os << "["; + emitValue(index); + os << "]"; + } + os << " = "; + emitValue(op.getValueToStore()); + os << ";"; + emitInfoAndNewLine(op); +} + +/// Tensor-related statement emitters. +void ModuleEmitter::emitTensorLoad(memref::TensorLoadOp op) { + auto rank = emitNestedLoopHead(op.getResult()); + indent(); + emitValue(op.getResult(), rank); + os << " = "; + emitValue(op.getOperand(), rank); + os << ";"; + emitInfoAndNewLine(op); + emitNestedLoopTail(rank); +} + +void ModuleEmitter::emitTensorStore(memref::TensorStoreOp op) { + auto rank = emitNestedLoopHead(op.getOperand(0)); + indent(); + emitValue(op.getOperand(1), rank); + os << " = "; + emitValue(op.getOperand(0), rank); + os << ";"; + emitInfoAndNewLine(op); + emitNestedLoopTail(rank); +} + +void ModuleEmitter::emitTensorToMemref(memref::BufferCastOp op) { + // A declared result indicates that the memref is output of the function, and + // has been declared in the function signature. + if (isDeclared(op.getResult())) { + auto rank = emitNestedLoopHead(op.getResult()); + indent(); + emitValue(op.getResult(), rank); + os << " = "; + emitValue(op.getOperand(), rank); + os << ";"; + emitInfoAndNewLine(op); + emitNestedLoopTail(rank); + } else { + addAlias(op.getOperand(), op.getResult()); + emitArrayDirectives(op.getResult()); + } +} + +void ModuleEmitter::emitDim(memref::DimOp op) { + if (auto constOp = dyn_cast(op.getOperand(1).getDefiningOp())) { + auto constVal = constOp.getValue().cast().getInt(); + auto type = op.getOperand(0).getType().cast(); + + if (type.hasStaticShape()) { + if (constVal >= 0 && constVal < (int64_t)type.getShape().size()) { + indent(); + emitValue(op.getResult()); + os << " = "; + os << type.getShape()[constVal] << ";"; + emitInfoAndNewLine(op); + } else + emitError(op, "index is out of range."); + } else + emitError(op, "is unranked or has dynamic shape."); + } else + emitError(op, "index is not a constant."); +} + +void ModuleEmitter::emitRank(RankOp op) { + auto type = op.getOperand().getType().cast(); + if (type.hasRank()) { + indent(); + emitValue(op.getResult()); + os << " = "; + os << type.getRank() << ";"; + emitInfoAndNewLine(op); + } else + emitError(op, "is unranked."); +} + +/// Standard expression emitters. +void ModuleEmitter::emitBinary(Operation *op, const char *syntax) { + auto rank = emitNestedLoopHead(op->getResult(0)); + indent(); + emitValue(op->getResult(0), rank); + os << " = "; + emitValue(op->getOperand(0), rank); + os << " " << syntax << " "; + emitValue(op->getOperand(1), rank); + os << ";"; + emitInfoAndNewLine(op); + emitNestedLoopTail(rank); +} + +void ModuleEmitter::emitUnary(Operation *op, const char *syntax) { + auto rank = emitNestedLoopHead(op->getResult(0)); + indent(); + emitValue(op->getResult(0), rank); + os << " = " << syntax << "("; + emitValue(op->getOperand(0), rank); + os << ");"; + emitInfoAndNewLine(op); + emitNestedLoopTail(rank); +} + +/// IP operation emitter. +void ModuleEmitter::emitIP(IPOp op) { + auto name = op.name(); + os << " __IP__" << name << "("; + + unsigned argIdx = 0; + for (auto arg : op.getOperands()) { + emitValue(arg); + if (argIdx++ != op.getOperands().size() - 1) { + os << ", "; + } + } + os << ");\n"; +} + +/// Special operation emitters. +void ModuleEmitter::emitSelect(SelectOp op) { + unsigned rank = emitNestedLoopHead(op.getResult()); + unsigned conditionRank = rank; + if (!op.getCondition().getType().isa()) + conditionRank = 0; + + indent(); + emitValue(op.getResult(), rank); + os << " = "; + emitValue(op.getCondition(), conditionRank); + os << " ? "; + os << "(" << getTypeName(op.getTrueValue()) << ")"; + emitValue(op.getTrueValue(), rank); + os << " : "; + os << "(" << getTypeName(op.getFalseValue()) << ")"; + emitValue(op.getFalseValue(), rank); + os << ";"; + emitInfoAndNewLine(op); + emitNestedLoopTail(rank); +} + +void ModuleEmitter::emitConstant(ConstantOp op) { + // This indicates the constant type is scalar (float, integer, or bool). + if (isDeclared(op.getResult())) + return; + + if (auto denseAttr = op.getValue().dyn_cast()) { + indent(); + emitArrayDecl(op.getResult()); + os << " = {"; + auto type = op.getResult().getType().cast().getElementType(); + + unsigned elementIdx = 0; + for (auto element : denseAttr.getAttributeValues()) { + if (type.isF32()) { + auto value = element.cast().getValue().convertToFloat(); + if (std::isfinite(value)) + os << value; + else if (value > 0) + os << "INFINITY"; + else + os << "-INFINITY"; + + } else if (type.isF64()) { + auto value = element.cast().getValue().convertToDouble(); + if (std::isfinite(value)) + os << value; + else if (value > 0) + os << "INFINITY"; + else + os << "-INFINITY"; + + } else if (type.isInteger(1)) + os << element.cast().getValue(); + else if (type.isIntOrIndex()) + os << element.cast().getValue(); + else + emitError(op, "array has unsupported element type."); + + if (elementIdx++ != denseAttr.getNumElements() - 1) + os << ", "; + } + os << "};"; + emitInfoAndNewLine(op); + } else + emitError(op, "has unsupported constant type."); +} + +template +void ModuleEmitter::emitCast(CastOpType op) { + indent(); + emitValue(op.getResult()); + os << " = "; + emitValue(op.getOperand()); + os << ";"; + emitInfoAndNewLine(op); +} + +void ModuleEmitter::emitCall(CallOp op) { + // Handle returned value by the callee. + for (auto result : op.getResults()) { + if (!isDeclared(result)) { + indent(); + if (result.getType().isa()) + emitArrayDecl(result); + else + emitValue(result); + os << ";\n"; + } + } + + // Emit the function call. + indent(); + os << op.getCallee() << "("; + + // Handle input arguments. + unsigned argIdx = 0; + for (auto arg : op.getOperands()) { + emitValue(arg); + + if (argIdx++ != op.getNumOperands() - 1) + os << ", "; + } + + // Handle output arguments. + for (auto result : op.getResults()) { + // The address should be passed in for scalar result arguments. + if (result.getType().isa()) + os << ", "; + else + os << ", &"; + + emitValue(result); + } + + os << ");"; + emitInfoAndNewLine(op); +} + +/// Structure operation emitters. +void ModuleEmitter::emitAssign(AssignOp op) { + unsigned rank = emitNestedLoopHead(op.getResult()); + indent(); + emitValue(op.getResult(), rank); + os << " = "; + emitValue(op.getOperand(), rank); + os << ";"; + emitInfoAndNewLine(op); + emitNestedLoopTail(rank); +} + +/// C++ component emitters. +void ModuleEmitter::emitValue(Value val, unsigned rank, bool isPtr) { + assert(!(rank && isPtr) && "should be either an array or a pointer."); + + // Value has been declared before or is a constant number. + if (isDeclared(val)) { + os << getName(val); + for (unsigned i = 0; i < rank; ++i) + os << "[iv" << i << "]"; + return; + } + + os << getTypeName(val) << " "; + + // Add the new value to nameTable and emit its name. + os << addName(val, isPtr); + for (unsigned i = 0; i < rank; ++i) + os << "[iv" << i << "]"; +} + +void ModuleEmitter::emitArrayDecl(Value array) { + assert(!isDeclared(array) && "has been declared before."); + + auto arrayType = array.getType().cast(); + if (arrayType.hasStaticShape()) { + emitValue(array); + for (auto &shape : arrayType.getShape()) + os << "[" << shape << "]"; + } else + emitValue(array, /*rank=*/0, /*isPtr=*/true); +} + +unsigned ModuleEmitter::emitNestedLoopHead(Value val) { + unsigned rank = 0; + + if (auto type = val.getType().dyn_cast()) { + if (!type.hasStaticShape()) { + emitError(val.getDefiningOp(), "is unranked or has dynamic shape."); + return 0; + } + + // Declare a new array. + if (!isDeclared(val)) { + indent(); + emitArrayDecl(val); + os << ";\n"; + } + + // Create nested loop. + unsigned dimIdx = 0; + for (auto &shape : type.getShape()) { + indent(); + os << "for (int iv" << dimIdx << " = 0; "; + os << "iv" << dimIdx << " < " << shape << "; "; + os << "++iv" << dimIdx++ << ") {\n"; + + addIndent(); + } + rank = type.getRank(); + } + + return rank; +} + +void ModuleEmitter::emitNestedLoopTail(unsigned rank) { + for (unsigned i = 0; i < rank; ++i) { + reduceIndent(); + + indent(); + os << "}\n"; + } +} + +void ModuleEmitter::emitInfoAndNewLine(Operation *op) { + os << "\t//"; + // Print line number. + if (auto loc = op->getLoc().dyn_cast()) + os << " L" << loc.getLine(); + + // Print schedule information. + if (auto timing = getTiming(op)) + os << ", [" << timing.getBegin() << "," << timing.getEnd() << ")"; + + // Print loop information. + if (auto loopInfo = getLoopInfo(op)) + os << ", iterCycle=" << loopInfo.getIterLatency() + << ", II=" << loopInfo.getMinII(); + + os << "\n"; +} + +/// MLIR component and HLS C++ pragma emitters. +void ModuleEmitter::emitBlock(Block &block) { + for (auto &op : block) { + if (ExprVisitor(*this).dispatchVisitor(&op)) + continue; + + if (StmtVisitor(*this).dispatchVisitor(&op)) + continue; + + if (KernelVisitor(*this).dispatchVisitor(&op)) + continue; + + emitError(&op, "can't be correctly emitted."); + } +} + +void ModuleEmitter::emitLoopDirectives(Operation *op) { + auto loopDirect = getLoopDirective(op); + if (!loopDirect) + return; + + if (loopDirect.getPipeline()) { + indent(); + os << "#pragma HLS pipeline II=" << loopDirect.getTargetII() << "\n"; + + } else if (loopDirect.getDataflow()) { + indent(); + os << "#pragma HLS dataflow\n"; + } +} + +void ModuleEmitter::emitArrayDirectives(Value memref) { + bool emitPragmaFlag = false; + auto type = memref.getType().cast(); + + if (auto layoutMap = getLayoutMap(type)) { + // Emit array_partition pragma(s). + SmallVector factors; + getPartitionFactors(type, &factors); + + for (int64_t dim = 0; dim < type.getRank(); ++dim) { + if (factors[dim] != 1) { + emitPragmaFlag = true; + + indent(); + os << "#pragma HLS array_partition"; + os << " variable="; + emitValue(memref); + + // Emit partition type. + if (layoutMap.getResult(dim).getKind() == AffineExprKind::FloorDiv) + os << " block"; + else + os << " cyclic"; + + os << " factor=" << factors[dim]; + os << " dim=" << dim + 1 << "\n"; + } + } + } + + // Emit resource pragma when the array is not DRAM kind and is not fully + // partitioned. + auto kind = MemoryKind(type.getMemorySpaceAsInt()); + if (kind != MemoryKind::DRAM && !isFullyPartitioned(type)) { + emitPragmaFlag = true; + + indent(); + os << "#pragma HLS resource"; + os << " variable="; + emitValue(memref); + + os << " core="; + if (kind == MemoryKind::BRAM_1P) + os << "ram_1p_bram"; + else if (kind == MemoryKind::BRAM_S2P) + os << "ram_s2p_bram"; + else if (kind == MemoryKind::BRAM_T2P) + os << "ram_t2p_bram"; + else + os << "ram_s2p_bram"; + os << "\n"; + } + + // Emit an empty line. + if (emitPragmaFlag) + os << "\n"; +} + +void ModuleEmitter::emitFunctionDirectives(FuncOp func, + ArrayRef portList) { + auto funcDirect = getFuncDirective(func); + if (!funcDirect) + return; + + if (funcDirect.getPipeline()) { + indent(); + os << "#pragma HLS pipeline II=" << funcDirect.getTargetInterval() << "\n"; + + // An empty line. + os << "\n"; + } else if (funcDirect.getDataflow()) { + indent(); + os << "#pragma HLS dataflow\n"; + + // An empty line. + os << "\n"; + } + + // Only top function should emit interface pragmas. + if (funcDirect.getTopFunc()) { + indent(); + os << "#pragma HLS interface s_axilite port=return bundle=ctrl\n"; + + for (auto &port : portList) { + // Array ports and scalar ports are handled separately. Here, we only + // handle MemRef types since we assume the IR has be fully bufferized. + if (auto memrefType = port.getType().dyn_cast()) { + // Only emit interface pragma when the array is not fully partitioned. + if (!isFullyPartitioned(memrefType)) { + indent(); + os << "#pragma HLS interface"; + // For now, we set the offset of all m_axi interfaces as slave. + if (MemoryKind(memrefType.getMemorySpaceAsInt()) == MemoryKind::DRAM) + os << " m_axi offset=slave"; + else + os << " bram"; + + os << " port="; + emitValue(port); + os << "\n"; + } + } else { + indent(); + os << "#pragma HLS interface s_axilite"; + os << " port="; + + // TODO: This is a temporary solution. + auto name = getName(port); + if (name.front() == "*"[0]) + name.erase(name.begin()); + os << name; + os << " bundle=ctrl\n"; + } + } + + // An empty line. + os << "\n"; + + // Emit other pragmas for function ports. + for (auto &port : portList) + if (port.getType().isa()) + emitArrayDirectives(port); + } +} + +void ModuleEmitter::emitFunction(FuncOp func) { + if (func.getBlocks().size() != 1) + emitError(func, "has zero or more than one basic blocks."); + + if (auto funcDirect = getFuncDirective(func)) + if (funcDirect.getTopFunc()) + os << "/// This is top function.\n"; + + if (auto timing = getTiming(func)) { + os << "/// Latency=" << timing.getLatency(); + os << ", interval=" << timing.getInterval(); + os << "\n"; + } + + if (auto resource = getResource(func)) { + os << "/// DSP=" << resource.getDsp(); + // os << ", BRAM=" << resource.getBram(); + // os << ", LUT=" << resource.getLut(); + os << "\n"; + } + + // Emit function signature. + os << "void " << func.getName() << "(\n"; + addIndent(); + + // This vector is to record all ports of the function. + SmallVector portList; + + // Emit input arguments. + unsigned argIdx = 0; + for (auto &arg : func.getArguments()) { + indent(); + if (arg.getType().isa()) + emitArrayDecl(arg); + else + emitValue(arg); + + portList.push_back(arg); + if (argIdx++ != func.getNumArguments() - 1) + os << ",\n"; + } + + // Emit results. + if (auto funcReturn = dyn_cast(func.front().getTerminator())) { + for (auto result : funcReturn.getOperands()) { + os << ",\n"; + indent(); + // TODO: a known bug, cannot return a value twice, e.g. return %0, %0 : + // index, index. However, typically this should not happen. + if (result.getType().isa()) + emitArrayDecl(result); + else + // In Vivado HLS, pointer indicates the value is an output. + emitValue(result, /*rank=*/0, /*isPtr=*/true); + + portList.push_back(result); + } + } else + emitError(func, "doesn't have a return operation as terminator."); + + reduceIndent(); + os << "\n) {"; + emitInfoAndNewLine(func); + + // Emit function body. + addIndent(); + + emitFunctionDirectives(func, portList); + emitBlock(func.front()); + reduceIndent(); + os << "}\n"; + + // An empty line. + os << "\n"; +} + +/// Top-level MLIR module emitter. +void ModuleEmitter::emitModule(ModuleOp module, bool emitHeader) { + + // Don't emit the header if emitting to an IP library file + if (emitHeader) { + os << R"XXX( + //===------------------------------------------------------------*- C++ -*-===// + // + // Automatically generated file for High-level Synthesis (HLS). + // + //===----------------------------------------------------------------------===// + + #include + #include + #include + #include + #include + #include + #include + #include + + using namespace std; + + )XXX"; + } + + // Right now, decide to emit compiled HLS C++ to the ip library store without asking the user + std::fstream fio; + std::string line = "This is a test that the emitModule function that will, in the future, write compiled HLS code into the ip library file."; + fio.open("ip_library.txt", std::ios::trunc | std::ios::out | std::ios::in); + fio << line << std::endl; + + for (auto &op : *module.getBody()) { + if (auto func = dyn_cast(op)) + emitFunction(func); + else + emitError(&op, "is unsupported operation."); + } + + fio.close(); +} + +} +} diff --git a/lib/Translation/EmitHLSCpp.cpp b/lib/Translation/EmitHLSCpp.cpp index e8455f14..f1bc091d 100644 --- a/lib/Translation/EmitHLSCpp.cpp +++ b/lib/Translation/EmitHLSCpp.cpp @@ -15,1630 +15,15 @@ #include "scalehls/Support/Utils.h" #include "llvm/Support/raw_ostream.h" +// Bring in the emission methods defined in EmissionMethods.cpp +#include "scalehls/Translation/EmissionMethods.h" + #include #include using namespace mlir; using namespace scalehls; -//===----------------------------------------------------------------------===// -// Utils -//===----------------------------------------------------------------------===// - -static SmallString<16> getTypeName(Value val) { - // Handle memref, tensor, and vector types. - auto valType = val.getType(); - if (auto arrayType = val.getType().dyn_cast()) - valType = arrayType.getElementType(); - - // Handle float types. - if (valType.isa()) - return SmallString<16>("float"); - else if (valType.isa()) - return SmallString<16>("double"); - - // Handle integer types. - else if (valType.isa()) - return SmallString<16>("int"); - else if (auto intType = valType.dyn_cast()) { - if (intType.getWidth() == 1) - return SmallString<16>("bool"); - else { - std::string signedness = ""; - if (intType.getSignedness() == IntegerType::SignednessSemantics::Unsigned) - signedness = "u"; - - switch (intType.getWidth()) { - case 8: - case 16: - case 32: - case 64: - return SmallString<16>(signedness + "int" + - std::to_string(intType.getWidth()) + "_t"); - default: - return SmallString<16>("ap_" + signedness + "int<" + - std::to_string(intType.getWidth()) + ">"); - } - } - } else - val.getDefiningOp()->emitError("has unsupported type."); - - return SmallString<16>(); -} - -//===----------------------------------------------------------------------===// -// Some Base Classes -//===----------------------------------------------------------------------===// - -namespace { -/// This class maintains the mutable state that cross-cuts and is shared by the -/// various emitters. -class ScaleHLSEmitterState { -public: - explicit ScaleHLSEmitterState(raw_ostream &os) : os(os) {} - - // The stream to emit to. - raw_ostream &os; - - bool encounteredError = false; - unsigned currentIndent = 0; - - // This table contains all declared values. - DenseMap> nameTable; - -private: - ScaleHLSEmitterState(const ScaleHLSEmitterState &) = delete; - void operator=(const ScaleHLSEmitterState &) = delete; -}; -} // namespace - -namespace { -/// This is the base class for all of the HLSCpp Emitter components. -class ScaleHLSEmitterBase { -public: - explicit ScaleHLSEmitterBase(ScaleHLSEmitterState &state) - : state(state), os(state.os) {} - - InFlightDiagnostic emitError(Operation *op, const Twine &message) { - state.encounteredError = true; - return op->emitError(message); - } - - raw_ostream &indent() { return os.indent(state.currentIndent); } - - void addIndent() { state.currentIndent += 2; } - void reduceIndent() { state.currentIndent -= 2; } - - // All of the mutable state we are maintaining. - ScaleHLSEmitterState &state; - - // The stream to emit to. - raw_ostream &os; - // std::fstream &os; // Instead of writing to stdout by default, write to file stream - - /// Value name management methods. - SmallString<8> addName(Value val, bool isPtr = false); - - SmallString<8> addAlias(Value val, Value alias); - - SmallString<8> getName(Value val); - - bool isDeclared(Value val) { - if (getName(val).empty()) { - return false; - } else - return true; - } - -private: - ScaleHLSEmitterBase(const ScaleHLSEmitterBase &) = delete; - void operator=(const ScaleHLSEmitterBase &) = delete; -}; -} // namespace - -// TODO: update naming rule. -SmallString<8> ScaleHLSEmitterBase::addName(Value val, bool isPtr) { - assert(!isDeclared(val) && "has been declared before."); - - SmallString<8> valName; - if (isPtr) - valName += "*"; - - valName += StringRef("v" + std::to_string(state.nameTable.size())); - state.nameTable[val] = valName; - - return valName; -} - -SmallString<8> ScaleHLSEmitterBase::addAlias(Value val, Value alias) { - assert(!isDeclared(alias) && "has been declared before."); - assert(isDeclared(val) && "hasn't been declared before."); - - auto valName = getName(val); - state.nameTable[alias] = valName; - - return valName; -} - -SmallString<8> ScaleHLSEmitterBase::getName(Value val) { - // For constant scalar operations, the constant number will be returned rather - // than the value name. - if (auto defOp = val.getDefiningOp()) { - if (auto constOp = dyn_cast(defOp)) { - auto constAttr = constOp.getValue(); - - if (auto floatAttr = constAttr.dyn_cast()) { - auto value = floatAttr.getValueAsDouble(); - if (std::isfinite(value)) - return SmallString<8>(std::to_string(value)); - else if (value > 0) - return SmallString<8>("INFINITY"); - else - return SmallString<8>("-INFINITY"); - - } else if (auto intAttr = constAttr.dyn_cast()) { - auto value = intAttr.getInt(); - return SmallString<8>(std::to_string(value)); - - } else if (auto boolAttr = constAttr.dyn_cast()) - return SmallString<8>(std::to_string(boolAttr.getValue())); - } - } - return state.nameTable.lookup(val); -} - -//===----------------------------------------------------------------------===// -// ModuleEmitter Class Declaration -//===----------------------------------------------------------------------===// - -namespace { -class ModuleEmitter : public ScaleHLSEmitterBase { -public: - using operand_range = Operation::operand_range; - explicit ModuleEmitter(ScaleHLSEmitterState &state) - : ScaleHLSEmitterBase(state) {} - - /// SCF statement emitters. - void emitScfFor(scf::ForOp op); - void emitScfIf(scf::IfOp op); - void emitScfYield(scf::YieldOp op); - - /// Affine statement emitters. - void emitAffineFor(AffineForOp op); - void emitAffineIf(AffineIfOp op); - void emitAffineParallel(AffineParallelOp op); - void emitAffineApply(AffineApplyOp op); - template - void emitAffineMaxMin(OpType op, const char *syntax); - void emitAffineLoad(AffineLoadOp op); - void emitAffineStore(AffineStoreOp op); - void emitAffineYield(AffineYieldOp op); - - /// Memref-related statement emitters. - template void emitAlloc(OpType op); - void emitLoad(memref::LoadOp op); - void emitStore(memref::StoreOp op); - - /// Tensor-related statement emitters. - void emitTensorLoad(memref::TensorLoadOp op); - void emitTensorStore(memref::TensorStoreOp op); - void emitTensorToMemref(memref::BufferCastOp op); - void emitDim(memref::DimOp op); - void emitRank(RankOp op); - - /// Standard expression emitters. - void emitBinary(Operation *op, const char *syntax); - void emitUnary(Operation *op, const char *syntax); - - /// IP operation emitter. - void emitIP(IPOp op); - - /// Special operation emitters. - void emitSelect(SelectOp op); - void emitConstant(ConstantOp op); - template void emitCast(CastOpType op); - void emitCall(CallOp op); - - /// Structure operations emitters. - void emitAssign(AssignOp op); - - /// Top-level MLIR module emitter. - void emitModule(ModuleOp module); - -private: - /// C++ component emitters. - void emitValue(Value val, unsigned rank = 0, bool isPtr = false); - void emitArrayDecl(Value array); - unsigned emitNestedLoopHead(Value val); - void emitNestedLoopTail(unsigned rank); - void emitInfoAndNewLine(Operation *op); - - /// MLIR component and HLS C++ pragma emitters. - void emitBlock(Block &block); - void emitLoopDirectives(Operation *op); - void emitArrayDirectives(Value memref); - void emitFunctionDirectives(FuncOp func, ArrayRef portList); - void emitFunction(FuncOp func); -}; -} // namespace - -//===----------------------------------------------------------------------===// -// AffineEmitter Class -//===----------------------------------------------------------------------===// - -namespace { -class AffineExprEmitter : public ScaleHLSEmitterBase, - public AffineExprVisitor { -public: - using operand_range = Operation::operand_range; - explicit AffineExprEmitter(ScaleHLSEmitterState &state, unsigned numDim, - operand_range operands) - : ScaleHLSEmitterBase(state), numDim(numDim), operands(operands) {} - - void visitAddExpr(AffineBinaryOpExpr expr) { emitAffineBinary(expr, "+"); } - void visitMulExpr(AffineBinaryOpExpr expr) { emitAffineBinary(expr, "*"); } - void visitModExpr(AffineBinaryOpExpr expr) { emitAffineBinary(expr, "%"); } - void visitFloorDivExpr(AffineBinaryOpExpr expr) { - emitAffineBinary(expr, "/"); - } - void visitCeilDivExpr(AffineBinaryOpExpr expr) { - // This is super inefficient. - os << "("; - visit(expr.getLHS()); - os << " + "; - visit(expr.getRHS()); - os << " - 1) / "; - visit(expr.getRHS()); - os << ")"; - } - - void visitConstantExpr(AffineConstantExpr expr) { os << expr.getValue(); } - - void visitDimExpr(AffineDimExpr expr) { - os << getName(operands[expr.getPosition()]); - } - void visitSymbolExpr(AffineSymbolExpr expr) { - os << getName(operands[numDim + expr.getPosition()]); - } - - /// Affine expression emitters. - void emitAffineBinary(AffineBinaryOpExpr expr, const char *syntax) { - os << "("; - if (auto constRHS = expr.getRHS().dyn_cast()) { - if ((unsigned)*syntax == (unsigned)*"*" && constRHS.getValue() == -1) { - os << "-"; - visit(expr.getLHS()); - os << ")"; - return; - } - if ((unsigned)*syntax == (unsigned)*"+" && constRHS.getValue() < 0) { - visit(expr.getLHS()); - os << " - "; - os << -constRHS.getValue(); - os << ")"; - return; - } - } - if (auto binaryRHS = expr.getRHS().dyn_cast()) { - if (auto constRHS = binaryRHS.getRHS().dyn_cast()) { - if ((unsigned)*syntax == (unsigned)*"+" && constRHS.getValue() == -1 && - binaryRHS.getKind() == AffineExprKind::Mul) { - visit(expr.getLHS()); - os << " - "; - visit(binaryRHS.getLHS()); - os << ")"; - return; - } - } - } - visit(expr.getLHS()); - os << " " << syntax << " "; - visit(expr.getRHS()); - os << ")"; - } - - void emitAffineExpr(AffineExpr expr) { visit(expr); } - -private: - unsigned numDim; - operand_range operands; -}; -} // namespace - -//===----------------------------------------------------------------------===// -// StmtVisitor, ExprVisitor, and PragmaVisitor Classes -//===----------------------------------------------------------------------===// - -namespace { -class StmtVisitor : public HLSCppVisitorBase { -public: - StmtVisitor(ModuleEmitter &emitter) : emitter(emitter) {} - - using HLSCppVisitorBase::visitOp; - /// SCF statements. - bool visitOp(scf::ForOp op) { return emitter.emitScfFor(op), true; }; - bool visitOp(scf::IfOp op) { return emitter.emitScfIf(op), true; }; - bool visitOp(scf::ParallelOp op) { return true; }; - bool visitOp(scf::ReduceOp op) { return true; }; - bool visitOp(scf::ReduceReturnOp op) { return true; }; - bool visitOp(scf::YieldOp op) { return emitter.emitScfYield(op), true; }; - - /// Affine statements. - bool visitOp(AffineForOp op) { return emitter.emitAffineFor(op), true; } - bool visitOp(AffineIfOp op) { return emitter.emitAffineIf(op), true; } - bool visitOp(AffineParallelOp op) { - return emitter.emitAffineParallel(op), true; - } - bool visitOp(AffineApplyOp op) { return emitter.emitAffineApply(op), true; } - bool visitOp(AffineMaxOp op) { - return emitter.emitAffineMaxMin(op, "max"), true; - } - bool visitOp(AffineMinOp op) { - return emitter.emitAffineMaxMin(op, "min"), true; - } - bool visitOp(AffineLoadOp op) { return emitter.emitAffineLoad(op), true; } - bool visitOp(AffineStoreOp op) { return emitter.emitAffineStore(op), true; } - bool visitOp(AffineYieldOp op) { return emitter.emitAffineYield(op), true; } - - /// Memref-related statements. - bool visitOp(memref::AllocOp op) { - return emitter.emitAlloc(op), true; - } - bool visitOp(memref::AllocaOp op) { - return emitter.emitAlloc(op), true; - } - bool visitOp(memref::LoadOp op) { return emitter.emitLoad(op), true; } - bool visitOp(memref::StoreOp op) { return emitter.emitStore(op), true; } - bool visitOp(memref::DeallocOp op) { return true; } - - /// Tensor-related statements. - bool visitOp(memref::TensorLoadOp op) { - return emitter.emitTensorLoad(op), true; - } - bool visitOp(memref::TensorStoreOp op) { - return emitter.emitTensorStore(op), true; - } - bool visitOp(memref::BufferCastOp op) { - return emitter.emitTensorToMemref(op), true; - } - bool visitOp(memref::DimOp op) { return emitter.emitDim(op), true; } - bool visitOp(RankOp op) { return emitter.emitRank(op), true; } - - /// Structure operations. - bool visitOp(AssignOp op) { return emitter.emitAssign(op), true; } - bool visitOp(CastOp op) { return emitter.emitCast(op), true; } - bool visitOp(MulOp op) { return emitter.emitBinary(op, "*"), true; } - bool visitOp(AddOp op) { return emitter.emitBinary(op, "+"), true; } - -private: - ModuleEmitter &emitter; -}; -} // namespace - -namespace { -class ExprVisitor : public HLSCppVisitorBase { -public: - ExprVisitor(ModuleEmitter &emitter) : emitter(emitter) {} - - using HLSCppVisitorBase::visitOp; - /// Float binary expressions. - bool visitOp(CmpFOp op); - bool visitOp(AddFOp op) { return emitter.emitBinary(op, "+"), true; } - bool visitOp(SubFOp op) { return emitter.emitBinary(op, "-"), true; } - bool visitOp(MulFOp op) { return emitter.emitBinary(op, "*"), true; } - bool visitOp(DivFOp op) { return emitter.emitBinary(op, "/"), true; } - bool visitOp(RemFOp op) { return emitter.emitBinary(op, "%"), true; } - - /// Integer binary expressions. - bool visitOp(CmpIOp op); - bool visitOp(AddIOp op) { return emitter.emitBinary(op, "+"), true; } - bool visitOp(SubIOp op) { return emitter.emitBinary(op, "-"), true; } - bool visitOp(MulIOp op) { return emitter.emitBinary(op, "*"), true; } - bool visitOp(SignedDivIOp op) { return emitter.emitBinary(op, "/"), true; } - bool visitOp(SignedRemIOp op) { return emitter.emitBinary(op, "%"), true; } - bool visitOp(UnsignedDivIOp op) { return emitter.emitBinary(op, "/"), true; } - bool visitOp(UnsignedRemIOp op) { return emitter.emitBinary(op, "%"), true; } - bool visitOp(XOrOp op) { return emitter.emitBinary(op, "^"), true; } - bool visitOp(AndOp op) { return emitter.emitBinary(op, "&"), true; } - bool visitOp(OrOp op) { return emitter.emitBinary(op, "|"), true; } - bool visitOp(ShiftLeftOp op) { return emitter.emitBinary(op, "<<"), true; } - bool visitOp(SignedShiftRightOp op) { - return emitter.emitBinary(op, ">>"), true; - } - bool visitOp(UnsignedShiftRightOp op) { - return emitter.emitBinary(op, ">>"), true; - } - - /// Unary expressions. - bool visitOp(AbsFOp op) { return emitter.emitUnary(op, "abs"), true; } - bool visitOp(CeilFOp op) { return emitter.emitUnary(op, "ceil"), true; } - bool visitOp(NegFOp op) { return emitter.emitUnary(op, "-"), true; } - bool visitOp(math::CosOp op) { return emitter.emitUnary(op, "cos"), true; } - bool visitOp(math::SinOp op) { return emitter.emitUnary(op, "sin"), true; } - bool visitOp(math::TanhOp op) { return emitter.emitUnary(op, "tanh"), true; } - bool visitOp(math::SqrtOp op) { return emitter.emitUnary(op, "sqrt"), true; } - bool visitOp(math::RsqrtOp op) { - return emitter.emitUnary(op, "1.0 / sqrt"), true; - } - bool visitOp(math::ExpOp op) { return emitter.emitUnary(op, "exp"), true; } - bool visitOp(math::Exp2Op op) { return emitter.emitUnary(op, "exp2"), true; } - bool visitOp(math::LogOp op) { return emitter.emitUnary(op, "log"), true; } - bool visitOp(math::Log2Op op) { return emitter.emitUnary(op, "log2"), true; } - bool visitOp(math::Log10Op op) { - return emitter.emitUnary(op, "log10"), true; - } - - /// Special operations. - bool visitOp(SelectOp op) { return emitter.emitSelect(op), true; } - bool visitOp(ConstantOp op) { return emitter.emitConstant(op), true; } - bool visitOp(IndexCastOp op) { - return emitter.emitCast(op), true; - } - bool visitOp(UIToFPOp op) { return emitter.emitCast(op), true; } - bool visitOp(SIToFPOp op) { return emitter.emitCast(op), true; } - bool visitOp(FPToUIOp op) { return emitter.emitCast(op), true; } - bool visitOp(FPToSIOp op) { return emitter.emitCast(op), true; } - bool visitOp(CallOp op) { return emitter.emitCall(op), true; } - bool visitOp(ReturnOp op) { return true; } - -private: - ModuleEmitter &emitter; -}; -} // namespace - -namespace { -class KernelVisitor : public HLSKernelVisitorBase { -public: - KernelVisitor(ModuleEmitter &emitter) : emitter(emitter) {} - - using HLSKernelVisitorBase::visitOp; - /// IP operation. - bool visitOp(IPOp op) { return emitter.emitIP(op), true; } - -private: - ModuleEmitter &emitter; -}; -} // namespace - -bool ExprVisitor::visitOp(CmpFOp op) { - switch (op.getPredicate()) { - case CmpFPredicate::OEQ: - case CmpFPredicate::UEQ: - return emitter.emitBinary(op, "=="), true; - case CmpFPredicate::ONE: - case CmpFPredicate::UNE: - return emitter.emitBinary(op, "!="), true; - case CmpFPredicate::OLT: - case CmpFPredicate::ULT: - return emitter.emitBinary(op, "<"), true; - case CmpFPredicate::OLE: - case CmpFPredicate::ULE: - return emitter.emitBinary(op, "<="), true; - case CmpFPredicate::OGT: - case CmpFPredicate::UGT: - return emitter.emitBinary(op, ">"), true; - case CmpFPredicate::OGE: - case CmpFPredicate::UGE: - return emitter.emitBinary(op, ">="), true; - default: - op.emitError("has unsupported compare type."); - return false; - } -} - -bool ExprVisitor::visitOp(CmpIOp op) { - switch (op.getPredicate()) { - case CmpIPredicate::eq: - return emitter.emitBinary(op, "=="), true; - case CmpIPredicate::ne: - return emitter.emitBinary(op, "!="), true; - case CmpIPredicate::slt: - case CmpIPredicate::ult: - return emitter.emitBinary(op, "<"), true; - case CmpIPredicate::sle: - case CmpIPredicate::ule: - return emitter.emitBinary(op, "<="), true; - case CmpIPredicate::sgt: - case CmpIPredicate::ugt: - return emitter.emitBinary(op, ">"), true; - case CmpIPredicate::sge: - case CmpIPredicate::uge: - return emitter.emitBinary(op, ">="), true; - } -} - -//===----------------------------------------------------------------------===// -// ModuleEmitter Class Definition -//===----------------------------------------------------------------------===// - -/// SCF statement emitters. -void ModuleEmitter::emitScfFor(scf::ForOp op) { - indent(); - os << "for ("; - auto iterVar = op.getInductionVar(); - - // Emit lower bound. - emitValue(iterVar); - os << " = "; - emitValue(op.lowerBound()); - os << "; "; - - // Emit upper bound. - emitValue(iterVar); - os << " < "; - emitValue(op.upperBound()); - os << "; "; - - // Emit increase step. - emitValue(iterVar); - os << " += "; - emitValue(op.step()); - os << ") {"; - emitInfoAndNewLine(op); - - addIndent(); - - emitLoopDirectives(op); - emitBlock(*op.getBody()); - reduceIndent(); - - indent(); - os << "}\n"; -} - -void ModuleEmitter::emitScfIf(scf::IfOp op) { - // Declare all values returned by scf::YieldOp. They will be further handled - // by the scf::YieldOp emitter. - for (auto result : op.getResults()) { - if (!isDeclared(result)) { - indent(); - if (result.getType().isa()) - emitArrayDecl(result); - else - emitValue(result); - os << ";\n"; - } - } - - indent(); - os << "if ("; - emitValue(op.condition()); - os << ") {"; - emitInfoAndNewLine(op); - - addIndent(); - emitBlock(op.thenRegion().front()); - reduceIndent(); - - if (!op.elseRegion().empty()) { - indent(); - os << "} else {\n"; - addIndent(); - emitBlock(op.elseRegion().front()); - reduceIndent(); - } - - indent(); - os << "}\n"; -} - -void ModuleEmitter::emitScfYield(scf::YieldOp op) { - if (op.getNumOperands() == 0) - return; - - // For now, only and scf::If operations will use scf::Yield to return - // generated values. - if (auto parentOp = dyn_cast(op->getParentOp())) { - unsigned resultIdx = 0; - for (auto result : parentOp.getResults()) { - unsigned rank = emitNestedLoopHead(result); - indent(); - emitValue(result, rank); - os << " = "; - emitValue(op.getOperand(resultIdx++), rank); - os << ";"; - emitInfoAndNewLine(op); - emitNestedLoopTail(rank); - } - } -} - -/// Affine statement emitters. -void ModuleEmitter::emitAffineFor(AffineForOp op) { - indent(); - os << "for ("; - auto iterVar = op.getInductionVar(); - - // Emit lower bound. - emitValue(iterVar); - os << " = "; - auto lowerMap = op.getLowerBoundMap(); - AffineExprEmitter lowerEmitter(state, lowerMap.getNumDims(), - op.getLowerBoundOperands()); - if (lowerMap.getNumResults() == 1) - lowerEmitter.emitAffineExpr(lowerMap.getResult(0)); - else { - for (unsigned i = 0, e = lowerMap.getNumResults() - 1; i < e; ++i) - os << "max("; - lowerEmitter.emitAffineExpr(lowerMap.getResult(0)); - for (auto &expr : llvm::drop_begin(lowerMap.getResults(), 1)) { - os << ", "; - lowerEmitter.emitAffineExpr(expr); - os << ")"; - } - } - os << "; "; - - // Emit upper bound. - emitValue(iterVar); - os << " < "; - auto upperMap = op.getUpperBoundMap(); - AffineExprEmitter upperEmitter(state, upperMap.getNumDims(), - op.getUpperBoundOperands()); - if (upperMap.getNumResults() == 1) - upperEmitter.emitAffineExpr(upperMap.getResult(0)); - else { - for (unsigned i = 0, e = upperMap.getNumResults() - 1; i < e; ++i) - os << "min("; - upperEmitter.emitAffineExpr(upperMap.getResult(0)); - for (auto &expr : llvm::drop_begin(upperMap.getResults(), 1)) { - os << ", "; - upperEmitter.emitAffineExpr(expr); - os << ")"; - } - } - os << "; "; - - // Emit increase step. - emitValue(iterVar); - os << " += " << op.getStep() << ") {"; - emitInfoAndNewLine(op); - - addIndent(); - - emitLoopDirectives(op); - emitBlock(*op.getBody()); - reduceIndent(); - - indent(); - os << "}\n"; -} - -void ModuleEmitter::emitAffineIf(AffineIfOp op) { - // Declare all values returned by AffineYieldOp. They will be further - // handled by the AffineYieldOp emitter. - for (auto result : op.getResults()) { - if (!isDeclared(result)) { - indent(); - if (result.getType().isa()) - emitArrayDecl(result); - else - emitValue(result); - os << ";\n"; - } - } - - indent(); - os << "if ("; - auto constrSet = op.getIntegerSet(); - AffineExprEmitter constrEmitter(state, constrSet.getNumDims(), - op.getOperands()); - - // Emit all constraints. - unsigned constrIdx = 0; - for (auto &expr : constrSet.getConstraints()) { - constrEmitter.emitAffineExpr(expr); - if (constrSet.isEq(constrIdx)) - os << " == 0"; - else - os << " >= 0"; - - if (constrIdx++ != constrSet.getNumConstraints() - 1) - os << " && "; - } - os << ") {"; - emitInfoAndNewLine(op); - - addIndent(); - emitBlock(*op.getThenBlock()); - reduceIndent(); - - if (op.hasElse()) { - indent(); - os << "} else {\n"; - addIndent(); - emitBlock(*op.getElseBlock()); - reduceIndent(); - } - - indent(); - os << "}\n"; -} - -void ModuleEmitter::emitAffineParallel(AffineParallelOp op) { - // Declare all values returned by AffineParallelOp. They will be further - // handled by the AffineYieldOp emitter. - for (auto result : op.getResults()) { - if (!isDeclared(result)) { - indent(); - if (result.getType().isa()) - emitArrayDecl(result); - else - emitValue(result); - os << ";\n"; - } - } - - auto steps = getIntArrayAttrValue(op, op.getStepsAttrName()); - for (unsigned i = 0, e = op.getNumDims(); i < e; ++i) { - indent(); - os << "for ("; - auto iterVar = op.getBody()->getArgument(i); - - // Emit lower bound. - emitValue(iterVar); - os << " = "; - auto lowerMap = op.getLowerBoundsValueMap().getAffineMap(); - AffineExprEmitter lowerEmitter(state, lowerMap.getNumDims(), - op.getLowerBoundsOperands()); - lowerEmitter.emitAffineExpr(lowerMap.getResult(i)); - os << "; "; - - // Emit upper bound. - emitValue(iterVar); - os << " < "; - auto upperMap = op.getUpperBoundsValueMap().getAffineMap(); - AffineExprEmitter upperEmitter(state, upperMap.getNumDims(), - op.getUpperBoundsOperands()); - upperEmitter.emitAffineExpr(upperMap.getResult(i)); - os << "; "; - - // Emit increase step. - emitValue(iterVar); - os << " += " << steps[i] << ") {"; - emitInfoAndNewLine(op); - - addIndent(); - } - - emitBlock(*op.getBody()); - - for (unsigned i = 0, e = op.getNumDims(); i < e; ++i) { - reduceIndent(); - - indent(); - os << "}\n"; - } -} - -void ModuleEmitter::emitAffineApply(AffineApplyOp op) { - indent(); - emitValue(op.getResult()); - os << " = "; - auto affineMap = op.getAffineMap(); - AffineExprEmitter(state, affineMap.getNumDims(), op.getOperands()) - .emitAffineExpr(affineMap.getResult(0)); - os << ";"; - emitInfoAndNewLine(op); -} - -template -void ModuleEmitter::emitAffineMaxMin(OpType op, const char *syntax) { - indent(); - emitValue(op.getResult()); - os << " = "; - auto affineMap = op.getAffineMap(); - AffineExprEmitter affineEmitter(state, affineMap.getNumDims(), - op.getOperands()); - for (unsigned i = 0, e = affineMap.getNumResults() - 1; i < e; ++i) - os << syntax << "("; - affineEmitter.emitAffineExpr(affineMap.getResult(0)); - for (auto &expr : llvm::drop_begin(affineMap.getResults(), 1)) { - os << ", "; - affineEmitter.emitAffineExpr(expr); - os << ")"; - } - os << ";"; - emitInfoAndNewLine(op); -} - -void ModuleEmitter::emitAffineLoad(AffineLoadOp op) { - indent(); - emitValue(op.getResult()); - os << " = "; - emitValue(op.getMemRef()); - auto affineMap = op.getAffineMap(); - AffineExprEmitter affineEmitter(state, affineMap.getNumDims(), - op.getMapOperands()); - for (auto index : affineMap.getResults()) { - os << "["; - affineEmitter.emitAffineExpr(index); - os << "]"; - } - os << ";"; - emitInfoAndNewLine(op); -} - -void ModuleEmitter::emitAffineStore(AffineStoreOp op) { - indent(); - emitValue(op.getMemRef()); - auto affineMap = op.getAffineMap(); - AffineExprEmitter affineEmitter(state, affineMap.getNumDims(), - op.getMapOperands()); - for (auto index : affineMap.getResults()) { - os << "["; - affineEmitter.emitAffineExpr(index); - os << "]"; - } - os << " = "; - emitValue(op.getValueToStore()); - os << ";"; - emitInfoAndNewLine(op); -} - -// TODO: For now, all values created in the AffineIf region will be declared -// in the generated C++. However, values which will be returned by affine -// yield operation should not be declared again. How to "bind" the pair of -// values inside/outside of AffineIf region needs to be considered. -void ModuleEmitter::emitAffineYield(AffineYieldOp op) { - if (op.getNumOperands() == 0) - return; - - // For now, only AffineParallel and AffineIf operations will use - // AffineYield to return generated values. - if (auto parentOp = dyn_cast(op->getParentOp())) { - unsigned resultIdx = 0; - for (auto result : parentOp.getResults()) { - unsigned rank = emitNestedLoopHead(result); - indent(); - emitValue(result, rank); - os << " = "; - emitValue(op.getOperand(resultIdx++), rank); - os << ";"; - emitInfoAndNewLine(op); - emitNestedLoopTail(rank); - } - } else if (auto parentOp = dyn_cast(op->getParentOp())) { - indent(); - os << "if ("; - unsigned ivIdx = 0; - for (auto iv : parentOp.getBody()->getArguments()) { - emitValue(iv); - os << " == 0"; - if (ivIdx++ != parentOp.getBody()->getNumArguments() - 1) - os << " && "; - } - os << ") {\n"; - - // When all induction values are 0, generated values will be directly - // assigned to the current results, correspondingly. - addIndent(); - unsigned resultIdx = 0; - for (auto result : parentOp.getResults()) { - unsigned rank = emitNestedLoopHead(result); - indent(); - emitValue(result, rank); - os << " = "; - emitValue(op.getOperand(resultIdx++), rank); - os << ";"; - emitInfoAndNewLine(op); - emitNestedLoopTail(rank); - } - reduceIndent(); - - indent(); - os << "} else {\n"; - - // Otherwise, generated values will be accumulated/reduced to the - // current results with corresponding AtomicRMWKind operations. - addIndent(); - auto RMWAttrs = - getIntArrayAttrValue(parentOp, parentOp.getReductionsAttrName()); - resultIdx = 0; - for (auto result : parentOp.getResults()) { - unsigned rank = emitNestedLoopHead(result); - indent(); - emitValue(result, rank); - switch ((AtomicRMWKind)RMWAttrs[resultIdx]) { - case (AtomicRMWKind::addf): - case (AtomicRMWKind::addi): - os << " += "; - emitValue(op.getOperand(resultIdx++), rank); - break; - case (AtomicRMWKind::assign): - os << " = "; - emitValue(op.getOperand(resultIdx++), rank); - break; - case (AtomicRMWKind::maxf): - case (AtomicRMWKind::maxs): - case (AtomicRMWKind::maxu): - os << " = max("; - emitValue(result, rank); - os << ", "; - emitValue(op.getOperand(resultIdx++), rank); - os << ")"; - break; - case (AtomicRMWKind::minf): - case (AtomicRMWKind::mins): - case (AtomicRMWKind::minu): - os << " = min("; - emitValue(result, rank); - os << ", "; - emitValue(op.getOperand(resultIdx++), rank); - os << ")"; - break; - case (AtomicRMWKind::mulf): - case (AtomicRMWKind::muli): - os << " *= "; - emitValue(op.getOperand(resultIdx++), rank); - break; - } - os << ";"; - emitInfoAndNewLine(op); - emitNestedLoopTail(rank); - } - reduceIndent(); - - indent(); - os << "}\n"; - } -} - -/// Memref-related statement emitters. -template void ModuleEmitter::emitAlloc(OpType op) { - // A declared result indicates that the memref is output of the function, and - // has been declared in the function signature. - if (isDeclared(op.getResult())) - return; - - // Vivado HLS only supports static shape on-chip memory. - if (!op.getType().hasStaticShape()) - emitError(op, "is unranked or has dynamic shape."); - - indent(); - emitArrayDecl(op.getResult()); - os << ";"; - emitInfoAndNewLine(op); - emitArrayDirectives(op.getResult()); -} - -void ModuleEmitter::emitLoad(memref::LoadOp op) { - indent(); - emitValue(op.getResult()); - os << " = "; - emitValue(op.getMemRef()); - for (auto index : op.getIndices()) { - os << "["; - emitValue(index); - os << "]"; - } - os << ";"; - emitInfoAndNewLine(op); -} - -void ModuleEmitter::emitStore(memref::StoreOp op) { - indent(); - emitValue(op.getMemRef()); - for (auto index : op.getIndices()) { - os << "["; - emitValue(index); - os << "]"; - } - os << " = "; - emitValue(op.getValueToStore()); - os << ";"; - emitInfoAndNewLine(op); -} - -/// Tensor-related statement emitters. -void ModuleEmitter::emitTensorLoad(memref::TensorLoadOp op) { - auto rank = emitNestedLoopHead(op.getResult()); - indent(); - emitValue(op.getResult(), rank); - os << " = "; - emitValue(op.getOperand(), rank); - os << ";"; - emitInfoAndNewLine(op); - emitNestedLoopTail(rank); -} - -void ModuleEmitter::emitTensorStore(memref::TensorStoreOp op) { - auto rank = emitNestedLoopHead(op.getOperand(0)); - indent(); - emitValue(op.getOperand(1), rank); - os << " = "; - emitValue(op.getOperand(0), rank); - os << ";"; - emitInfoAndNewLine(op); - emitNestedLoopTail(rank); -} - -void ModuleEmitter::emitTensorToMemref(memref::BufferCastOp op) { - // A declared result indicates that the memref is output of the function, and - // has been declared in the function signature. - if (isDeclared(op.getResult())) { - auto rank = emitNestedLoopHead(op.getResult()); - indent(); - emitValue(op.getResult(), rank); - os << " = "; - emitValue(op.getOperand(), rank); - os << ";"; - emitInfoAndNewLine(op); - emitNestedLoopTail(rank); - } else { - addAlias(op.getOperand(), op.getResult()); - emitArrayDirectives(op.getResult()); - } -} - -void ModuleEmitter::emitDim(memref::DimOp op) { - if (auto constOp = dyn_cast(op.getOperand(1).getDefiningOp())) { - auto constVal = constOp.getValue().cast().getInt(); - auto type = op.getOperand(0).getType().cast(); - - if (type.hasStaticShape()) { - if (constVal >= 0 && constVal < (int64_t)type.getShape().size()) { - indent(); - emitValue(op.getResult()); - os << " = "; - os << type.getShape()[constVal] << ";"; - emitInfoAndNewLine(op); - } else - emitError(op, "index is out of range."); - } else - emitError(op, "is unranked or has dynamic shape."); - } else - emitError(op, "index is not a constant."); -} - -void ModuleEmitter::emitRank(RankOp op) { - auto type = op.getOperand().getType().cast(); - if (type.hasRank()) { - indent(); - emitValue(op.getResult()); - os << " = "; - os << type.getRank() << ";"; - emitInfoAndNewLine(op); - } else - emitError(op, "is unranked."); -} - -/// Standard expression emitters. -void ModuleEmitter::emitBinary(Operation *op, const char *syntax) { - auto rank = emitNestedLoopHead(op->getResult(0)); - indent(); - emitValue(op->getResult(0), rank); - os << " = "; - emitValue(op->getOperand(0), rank); - os << " " << syntax << " "; - emitValue(op->getOperand(1), rank); - os << ";"; - emitInfoAndNewLine(op); - emitNestedLoopTail(rank); -} - -void ModuleEmitter::emitUnary(Operation *op, const char *syntax) { - auto rank = emitNestedLoopHead(op->getResult(0)); - indent(); - emitValue(op->getResult(0), rank); - os << " = " << syntax << "("; - emitValue(op->getOperand(0), rank); - os << ");"; - emitInfoAndNewLine(op); - emitNestedLoopTail(rank); -} - -/// IP operation emitter. -void ModuleEmitter::emitIP(IPOp op) { - auto name = op.name(); - os << " __IP__" << name << "("; - - unsigned argIdx = 0; - for (auto arg : op.getOperands()) { - emitValue(arg); - if (argIdx++ != op.getOperands().size() - 1) { - os << ", "; - } - } - os << ");\n"; -} - -/// Special operation emitters. -void ModuleEmitter::emitSelect(SelectOp op) { - unsigned rank = emitNestedLoopHead(op.getResult()); - unsigned conditionRank = rank; - if (!op.getCondition().getType().isa()) - conditionRank = 0; - - indent(); - emitValue(op.getResult(), rank); - os << " = "; - emitValue(op.getCondition(), conditionRank); - os << " ? "; - os << "(" << getTypeName(op.getTrueValue()) << ")"; - emitValue(op.getTrueValue(), rank); - os << " : "; - os << "(" << getTypeName(op.getFalseValue()) << ")"; - emitValue(op.getFalseValue(), rank); - os << ";"; - emitInfoAndNewLine(op); - emitNestedLoopTail(rank); -} - -void ModuleEmitter::emitConstant(ConstantOp op) { - // This indicates the constant type is scalar (float, integer, or bool). - if (isDeclared(op.getResult())) - return; - - if (auto denseAttr = op.getValue().dyn_cast()) { - indent(); - emitArrayDecl(op.getResult()); - os << " = {"; - auto type = op.getResult().getType().cast().getElementType(); - - unsigned elementIdx = 0; - for (auto element : denseAttr.getAttributeValues()) { - if (type.isF32()) { - auto value = element.cast().getValue().convertToFloat(); - if (std::isfinite(value)) - os << value; - else if (value > 0) - os << "INFINITY"; - else - os << "-INFINITY"; - - } else if (type.isF64()) { - auto value = element.cast().getValue().convertToDouble(); - if (std::isfinite(value)) - os << value; - else if (value > 0) - os << "INFINITY"; - else - os << "-INFINITY"; - - } else if (type.isInteger(1)) - os << element.cast().getValue(); - else if (type.isIntOrIndex()) - os << element.cast().getValue(); - else - emitError(op, "array has unsupported element type."); - - if (elementIdx++ != denseAttr.getNumElements() - 1) - os << ", "; - } - os << "};"; - emitInfoAndNewLine(op); - } else - emitError(op, "has unsupported constant type."); -} - -template void ModuleEmitter::emitCast(CastOpType op) { - indent(); - emitValue(op.getResult()); - os << " = "; - emitValue(op.getOperand()); - os << ";"; - emitInfoAndNewLine(op); -} - -void ModuleEmitter::emitCall(CallOp op) { - // Handle returned value by the callee. - for (auto result : op.getResults()) { - if (!isDeclared(result)) { - indent(); - if (result.getType().isa()) - emitArrayDecl(result); - else - emitValue(result); - os << ";\n"; - } - } - - // Emit the function call. - indent(); - os << op.getCallee() << "("; - - // Handle input arguments. - unsigned argIdx = 0; - for (auto arg : op.getOperands()) { - emitValue(arg); - - if (argIdx++ != op.getNumOperands() - 1) - os << ", "; - } - - // Handle output arguments. - for (auto result : op.getResults()) { - // The address should be passed in for scalar result arguments. - if (result.getType().isa()) - os << ", "; - else - os << ", &"; - - emitValue(result); - } - - os << ");"; - emitInfoAndNewLine(op); -} - -/// Structure operation emitters. -void ModuleEmitter::emitAssign(AssignOp op) { - unsigned rank = emitNestedLoopHead(op.getResult()); - indent(); - emitValue(op.getResult(), rank); - os << " = "; - emitValue(op.getOperand(), rank); - os << ";"; - emitInfoAndNewLine(op); - emitNestedLoopTail(rank); -} - -/// C++ component emitters. -void ModuleEmitter::emitValue(Value val, unsigned rank, bool isPtr) { - assert(!(rank && isPtr) && "should be either an array or a pointer."); - - // Value has been declared before or is a constant number. - if (isDeclared(val)) { - os << getName(val); - for (unsigned i = 0; i < rank; ++i) - os << "[iv" << i << "]"; - return; - } - - os << getTypeName(val) << " "; - - // Add the new value to nameTable and emit its name. - os << addName(val, isPtr); - for (unsigned i = 0; i < rank; ++i) - os << "[iv" << i << "]"; -} - -void ModuleEmitter::emitArrayDecl(Value array) { - assert(!isDeclared(array) && "has been declared before."); - - auto arrayType = array.getType().cast(); - if (arrayType.hasStaticShape()) { - emitValue(array); - for (auto &shape : arrayType.getShape()) - os << "[" << shape << "]"; - } else - emitValue(array, /*rank=*/0, /*isPtr=*/true); -} - -unsigned ModuleEmitter::emitNestedLoopHead(Value val) { - unsigned rank = 0; - - if (auto type = val.getType().dyn_cast()) { - if (!type.hasStaticShape()) { - emitError(val.getDefiningOp(), "is unranked or has dynamic shape."); - return 0; - } - - // Declare a new array. - if (!isDeclared(val)) { - indent(); - emitArrayDecl(val); - os << ";\n"; - } - - // Create nested loop. - unsigned dimIdx = 0; - for (auto &shape : type.getShape()) { - indent(); - os << "for (int iv" << dimIdx << " = 0; "; - os << "iv" << dimIdx << " < " << shape << "; "; - os << "++iv" << dimIdx++ << ") {\n"; - - addIndent(); - } - rank = type.getRank(); - } - - return rank; -} - -void ModuleEmitter::emitNestedLoopTail(unsigned rank) { - for (unsigned i = 0; i < rank; ++i) { - reduceIndent(); - - indent(); - os << "}\n"; - } -} - -void ModuleEmitter::emitInfoAndNewLine(Operation *op) { - os << "\t//"; - // Print line number. - if (auto loc = op->getLoc().dyn_cast()) - os << " L" << loc.getLine(); - - // Print schedule information. - if (auto timing = getTiming(op)) - os << ", [" << timing.getBegin() << "," << timing.getEnd() << ")"; - - // Print loop information. - if (auto loopInfo = getLoopInfo(op)) - os << ", iterCycle=" << loopInfo.getIterLatency() - << ", II=" << loopInfo.getMinII(); - - os << "\n"; -} - -/// MLIR component and HLS C++ pragma emitters. -void ModuleEmitter::emitBlock(Block &block) { - for (auto &op : block) { - if (ExprVisitor(*this).dispatchVisitor(&op)) - continue; - - if (StmtVisitor(*this).dispatchVisitor(&op)) - continue; - - if (KernelVisitor(*this).dispatchVisitor(&op)) - continue; - - emitError(&op, "can't be correctly emitted."); - } -} - -void ModuleEmitter::emitLoopDirectives(Operation *op) { - auto loopDirect = getLoopDirective(op); - if (!loopDirect) - return; - - if (loopDirect.getPipeline()) { - indent(); - os << "#pragma HLS pipeline II=" << loopDirect.getTargetII() << "\n"; - - } else if (loopDirect.getDataflow()) { - indent(); - os << "#pragma HLS dataflow\n"; - } -} - -void ModuleEmitter::emitArrayDirectives(Value memref) { - bool emitPragmaFlag = false; - auto type = memref.getType().cast(); - - if (auto layoutMap = getLayoutMap(type)) { - // Emit array_partition pragma(s). - SmallVector factors; - getPartitionFactors(type, &factors); - - for (int64_t dim = 0; dim < type.getRank(); ++dim) { - if (factors[dim] != 1) { - emitPragmaFlag = true; - - indent(); - os << "#pragma HLS array_partition"; - os << " variable="; - emitValue(memref); - - // Emit partition type. - if (layoutMap.getResult(dim).getKind() == AffineExprKind::FloorDiv) - os << " block"; - else - os << " cyclic"; - - os << " factor=" << factors[dim]; - os << " dim=" << dim + 1 << "\n"; - } - } - } - - // Emit resource pragma when the array is not DRAM kind and is not fully - // partitioned. - auto kind = MemoryKind(type.getMemorySpaceAsInt()); - if (kind != MemoryKind::DRAM && !isFullyPartitioned(type)) { - emitPragmaFlag = true; - - indent(); - os << "#pragma HLS resource"; - os << " variable="; - emitValue(memref); - - os << " core="; - if (kind == MemoryKind::BRAM_1P) - os << "ram_1p_bram"; - else if (kind == MemoryKind::BRAM_S2P) - os << "ram_s2p_bram"; - else if (kind == MemoryKind::BRAM_T2P) - os << "ram_t2p_bram"; - else - os << "ram_s2p_bram"; - os << "\n"; - } - - // Emit an empty line. - if (emitPragmaFlag) - os << "\n"; -} - -void ModuleEmitter::emitFunctionDirectives(FuncOp func, - ArrayRef portList) { - auto funcDirect = getFuncDirective(func); - if (!funcDirect) - return; - - if (funcDirect.getPipeline()) { - indent(); - os << "#pragma HLS pipeline II=" << funcDirect.getTargetInterval() << "\n"; - - // An empty line. - os << "\n"; - } else if (funcDirect.getDataflow()) { - indent(); - os << "#pragma HLS dataflow\n"; - - // An empty line. - os << "\n"; - } - - // Only top function should emit interface pragmas. - if (funcDirect.getTopFunc()) { - indent(); - os << "#pragma HLS interface s_axilite port=return bundle=ctrl\n"; - - for (auto &port : portList) { - // Array ports and scalar ports are handled separately. Here, we only - // handle MemRef types since we assume the IR has be fully bufferized. - if (auto memrefType = port.getType().dyn_cast()) { - // Only emit interface pragma when the array is not fully partitioned. - if (!isFullyPartitioned(memrefType)) { - indent(); - os << "#pragma HLS interface"; - // For now, we set the offset of all m_axi interfaces as slave. - if (MemoryKind(memrefType.getMemorySpaceAsInt()) == MemoryKind::DRAM) - os << " m_axi offset=slave"; - else - os << " bram"; - - os << " port="; - emitValue(port); - os << "\n"; - } - } else { - indent(); - os << "#pragma HLS interface s_axilite"; - os << " port="; - - // TODO: This is a temporary solution. - auto name = getName(port); - if (name.front() == "*"[0]) - name.erase(name.begin()); - os << name; - os << " bundle=ctrl\n"; - } - } - - // An empty line. - os << "\n"; - - // Emit other pragmas for function ports. - for (auto &port : portList) - if (port.getType().isa()) - emitArrayDirectives(port); - } -} - -void ModuleEmitter::emitFunction(FuncOp func) { - if (func.getBlocks().size() != 1) - emitError(func, "has zero or more than one basic blocks."); - - if (auto funcDirect = getFuncDirective(func)) - if (funcDirect.getTopFunc()) - os << "/// This is top function.\n"; - - if (auto timing = getTiming(func)) { - os << "/// Latency=" << timing.getLatency(); - os << ", interval=" << timing.getInterval(); - os << "\n"; - } - - if (auto resource = getResource(func)) { - os << "/// DSP=" << resource.getDsp(); - // os << ", BRAM=" << resource.getBram(); - // os << ", LUT=" << resource.getLut(); - os << "\n"; - } - - // Emit function signature. - os << "void " << func.getName() << "(\n"; - addIndent(); - - // This vector is to record all ports of the function. - SmallVector portList; - - // Emit input arguments. - unsigned argIdx = 0; - for (auto &arg : func.getArguments()) { - indent(); - if (arg.getType().isa()) - emitArrayDecl(arg); - else - emitValue(arg); - - portList.push_back(arg); - if (argIdx++ != func.getNumArguments() - 1) - os << ",\n"; - } - - // Emit results. - if (auto funcReturn = dyn_cast(func.front().getTerminator())) { - for (auto result : funcReturn.getOperands()) { - os << ",\n"; - indent(); - // TODO: a known bug, cannot return a value twice, e.g. return %0, %0 : - // index, index. However, typically this should not happen. - if (result.getType().isa()) - emitArrayDecl(result); - else - // In Vivado HLS, pointer indicates the value is an output. - emitValue(result, /*rank=*/0, /*isPtr=*/true); - - portList.push_back(result); - } - } else - emitError(func, "doesn't have a return operation as terminator."); - - reduceIndent(); - os << "\n) {"; - emitInfoAndNewLine(func); - - // Emit function body. - addIndent(); - - emitFunctionDirectives(func, portList); - emitBlock(func.front()); - reduceIndent(); - os << "}\n"; - - // An empty line. - os << "\n"; -} - -/// Top-level MLIR module emitter. -void ModuleEmitter::emitModule(ModuleOp module) { - os << R"XXX( -//===------------------------------------------------------------*- C++ -*-===// -// -// Automatically generated file for High-level Synthesis (HLS). -// -//===----------------------------------------------------------------------===// - -#include -#include -#include -#include -#include -#include -#include -#include - -using namespace std; - -)XXX"; - - // Right now, decide to emit compiled HLS C++ to the ip library store without asking the user - std::fstream fio; - std::string line = "This is a test that the emitModule function can theoretically write compiled HLS code into the ip library file."; - fio.open("ip_library.txt", std::ios::trunc | std::ios::out | std::ios::in); - fio << line << std::endl; - - for (auto &op : *module.getBody()) { - if (auto func = dyn_cast(op)) - emitFunction(func); - else - emitError(&op, "is unsupported operation."); - } - - fio.close(); -} - -//===----------------------------------------------------------------------===// -// Entry of scalehls-translate -//===----------------------------------------------------------------------===// - LogicalResult scalehls::emitHLSCpp(ModuleOp module, llvm::raw_ostream &os) { ScaleHLSEmitterState state(os); ModuleEmitter(state).emitModule(module); diff --git a/lib/Translation/EmitIPLibrary.cpp b/lib/Translation/EmitIPLibrary.cpp new file mode 100644 index 00000000..dd8e6598 --- /dev/null +++ b/lib/Translation/EmitIPLibrary.cpp @@ -0,0 +1,57 @@ +#include "scalehls/Translation/EmitHLSCpp.h" +#include "mlir/Dialect/Affine/IR/AffineValueMap.h" +#include "mlir/IR/AffineExprVisitor.h" +#include "mlir/IR/IntegerSet.h" +#include "mlir/Translation.h" +#include "scalehls/Dialect/HLSCpp/Visitor.h" +#include "scalehls/Dialect/HLSKernel/Visitor.h" +#include "scalehls/InitAllDialects.h" +#include "scalehls/Support/Utils.h" +#include "llvm/Support/raw_ostream.h" + +#include "scalehls/Translation/EmissionMethods.h" +#include "scalehls/Translation/EmitIPLibrary.h" +#include "llvm/Support/JSON.h" +#include +#include + +using namespace mlir; +using namespace scalehls; + +LogicalResult scalehls::emitIPLibrary(ModuleOp module, llvm::raw_ostream &os) { + // TODO: Somehow we need to find a way to get a filename argument into this scope. How can I add a filename option to the "emit-ip" cmd-line arg? + + std::string filename = "iplibrary.json"; // This will need to be set by the command line + std::string optimized_source, json_file_contents; + llvm::raw_string_ostream stream_to_str(optimized_source); + + ScaleHLSEmitterState state(stream_to_str); + ModuleEmitter(state).emitModule(module, false); + + // std::cout << "Now emitting optimized source" << std::endl << optimized_source << std::endl; + + /* Build the JSON object representing the IP */ + // llvm::json::Object ip_in_json; + // ip_in_json.insert({"source", optimized_source}); + + /* JSON emission to string */ + llvm::raw_string_ostream stream_to_json_str(json_file_contents); + llvm::json::OStream json_os(stream_to_json_str); + json_os.object([&]{ + json_os.attribute("source", llvm::json::Value(optimized_source)); + }); + + /* Final emission to JSON file */ + std::fstream fio; + fio.open(filename, std::ios::trunc | std::ios::out | std::ios::in); + fio << json_file_contents; + + return failure(state.encounteredError); +} + +void scalehls::registerEmitIPLibraryTranslation() { + static TranslateFromMLIRRegistration toIPLibrary( + "emit-ip", emitIPLibrary, [&](DialectRegistry ®istry) { + scalehls::registerAllDialects(registry); + }); +} diff --git a/tools/scalehls-translate/scalehls-translate.cpp b/tools/scalehls-translate/scalehls-translate.cpp index c140dc46..72d006af 100644 --- a/tools/scalehls-translate/scalehls-translate.cpp +++ b/tools/scalehls-translate/scalehls-translate.cpp @@ -7,9 +7,11 @@ #include "mlir/IR/Diagnostics.h" #include "mlir/Translation.h" #include "scalehls/Translation/EmitHLSCpp.h" +#include "scalehls/Translation/EmitIPLibrary.h" int main(int argc, char **argv) { mlir::scalehls::registerEmitHLSCppTranslation(); + mlir::scalehls::registerEmitIPLibraryTranslation(); return mlir::failed( mlir::mlirTranslateMain(argc, argv, "ScaleHLS Translation Tool")); From 4efcce75506e5ef2e4313cd277dec53d4f32f50e Mon Sep 17 00:00:00 2001 From: Luke Jacobs Date: Sun, 21 Nov 2021 15:44:01 -0600 Subject: [PATCH 3/4] Remove optional header feature of EmitModule- not needed --- .../scalehls/Translation/EmissionMethods.h | 87 +++++----- lib/Translation/EmissionMethods.cpp | 157 ++++++++++-------- lib/Translation/EmitIPLibrary.cpp | 4 +- 3 files changed, 140 insertions(+), 108 deletions(-) diff --git a/include/scalehls/Translation/EmissionMethods.h b/include/scalehls/Translation/EmissionMethods.h index 2aa0fbd6..4e3f3ff4 100644 --- a/include/scalehls/Translation/EmissionMethods.h +++ b/include/scalehls/Translation/EmissionMethods.h @@ -2,11 +2,14 @@ #include "mlir/Dialect/Affine/IR/AffineValueMap.h" #include "mlir/IR/AffineExprVisitor.h" #include "mlir/IR/IntegerSet.h" +#include "mlir/Support/FileUtilities.h" #include "mlir/Translation.h" #include "scalehls/Dialect/HLSCpp/Visitor.h" #include "scalehls/Dialect/HLSKernel/Visitor.h" #include "scalehls/InitAllDialects.h" #include "scalehls/Support/Utils.h" +#include "llvm/Support/JSON.h" +#include "llvm/Support/MemoryBuffer.h" #include "llvm/Support/raw_ostream.h" #include @@ -127,16 +130,16 @@ class ModuleEmitter : public ScaleHLSEmitterBase { void emitIP(IPOp op); /// Special operation emitters. + void emitCall(CallOp op); void emitSelect(SelectOp op); - void emitConstant(ConstantOp op); + void emitConstant(arith::ConstantOp op); template void emitCast(CastOpType op); - void emitCall(CallOp op); /// Structure operations emitters. void emitAssign(AssignOp op); /// Top-level MLIR module emitter. - void emitModule(ModuleOp module, bool emitHeader = true); + void emitModule(ModuleOp module); private: /// C++ component emitters. @@ -293,7 +296,7 @@ class StmtVisitor : public HLSCppVisitorBase { bool visitOp(memref::DimOp op) { return emitter.emitDim(op), true; } bool visitOp(RankOp op) { return emitter.emitRank(op), true; } - /// Structure operations. + /// HLSCpp operations. bool visitOp(AssignOp op) { return emitter.emitAssign(op), true; } bool visitOp(CastOp op) { return emitter.emitCast(op), true; } bool visitOp(MulOp op) { return emitter.emitBinary(op, "*"), true; } @@ -309,37 +312,32 @@ class ExprVisitor : public HLSCppVisitorBase { using HLSCppVisitorBase::visitOp; /// Float binary expressions. - bool visitOp(CmpFOp op); - bool visitOp(AddFOp op) { return emitter.emitBinary(op, "+"), true; } - bool visitOp(SubFOp op) { return emitter.emitBinary(op, "-"), true; } - bool visitOp(MulFOp op) { return emitter.emitBinary(op, "*"), true; } - bool visitOp(DivFOp op) { return emitter.emitBinary(op, "/"), true; } - bool visitOp(RemFOp op) { return emitter.emitBinary(op, "%"), true; } + bool visitOp(arith::CmpFOp op); + bool visitOp(arith::AddFOp op) { return emitter.emitBinary(op, "+"), true; } + bool visitOp(arith::SubFOp op) { return emitter.emitBinary(op, "-"), true; } + bool visitOp(arith::MulFOp op) { return emitter.emitBinary(op, "*"), true; } + bool visitOp(arith::DivFOp op) { return emitter.emitBinary(op, "/"), true; } + bool visitOp(arith::RemFOp op) { return emitter.emitBinary(op, "%"), true; } /// Integer binary expressions. - bool visitOp(CmpIOp op); - bool visitOp(AddIOp op) { return emitter.emitBinary(op, "+"), true; } - bool visitOp(SubIOp op) { return emitter.emitBinary(op, "-"), true; } - bool visitOp(MulIOp op) { return emitter.emitBinary(op, "*"), true; } - bool visitOp(SignedDivIOp op) { return emitter.emitBinary(op, "/"), true; } - bool visitOp(SignedRemIOp op) { return emitter.emitBinary(op, "%"), true; } - bool visitOp(UnsignedDivIOp op) { return emitter.emitBinary(op, "/"), true; } - bool visitOp(UnsignedRemIOp op) { return emitter.emitBinary(op, "%"), true; } - bool visitOp(XOrOp op) { return emitter.emitBinary(op, "^"), true; } - bool visitOp(AndOp op) { return emitter.emitBinary(op, "&"), true; } - bool visitOp(OrOp op) { return emitter.emitBinary(op, "|"), true; } - bool visitOp(ShiftLeftOp op) { return emitter.emitBinary(op, "<<"), true; } - bool visitOp(SignedShiftRightOp op) { - return emitter.emitBinary(op, ">>"), true; - } - bool visitOp(UnsignedShiftRightOp op) { - return emitter.emitBinary(op, ">>"), true; - } + bool visitOp(arith::CmpIOp op); + bool visitOp(arith::AddIOp op) { return emitter.emitBinary(op, "+"), true; } + bool visitOp(arith::SubIOp op) { return emitter.emitBinary(op, "-"), true; } + bool visitOp(arith::MulIOp op) { return emitter.emitBinary(op, "*"), true; } + bool visitOp(arith::DivSIOp op) { return emitter.emitBinary(op, "/"), true; } + bool visitOp(arith::RemSIOp op) { return emitter.emitBinary(op, "%"), true; } + bool visitOp(arith::DivUIOp op) { return emitter.emitBinary(op, "/"), true; } + bool visitOp(arith::RemUIOp op) { return emitter.emitBinary(op, "%"), true; } + bool visitOp(arith::XOrIOp op) { return emitter.emitBinary(op, "^"), true; } + bool visitOp(arith::AndIOp op) { return emitter.emitBinary(op, "&"), true; } + bool visitOp(arith::OrIOp op) { return emitter.emitBinary(op, "|"), true; } + bool visitOp(arith::ShLIOp op) { return emitter.emitBinary(op, "<<"), true; } + bool visitOp(arith::ShRSIOp op) { return emitter.emitBinary(op, ">>"), true; } + bool visitOp(arith::ShRUIOp op) { return emitter.emitBinary(op, ">>"), true; } /// Unary expressions. - bool visitOp(AbsFOp op) { return emitter.emitUnary(op, "abs"), true; } - bool visitOp(CeilFOp op) { return emitter.emitUnary(op, "ceil"), true; } - bool visitOp(NegFOp op) { return emitter.emitUnary(op, "-"), true; } + bool visitOp(math::AbsOp op) { return emitter.emitUnary(op, "abs"), true; } + bool visitOp(math::CeilOp op) { return emitter.emitUnary(op, "ceil"), true; } bool visitOp(math::CosOp op) { return emitter.emitUnary(op, "cos"), true; } bool visitOp(math::SinOp op) { return emitter.emitUnary(op, "sin"), true; } bool visitOp(math::TanhOp op) { return emitter.emitUnary(op, "tanh"), true; } @@ -354,19 +352,28 @@ class ExprVisitor : public HLSCppVisitorBase { bool visitOp(math::Log10Op op) { return emitter.emitUnary(op, "log10"), true; } + bool visitOp(arith::NegFOp op) { return emitter.emitUnary(op, "-"), true; } /// Special operations. - bool visitOp(SelectOp op) { return emitter.emitSelect(op), true; } - bool visitOp(ConstantOp op) { return emitter.emitConstant(op), true; } - bool visitOp(IndexCastOp op) { - return emitter.emitCast(op), true; - } - bool visitOp(UIToFPOp op) { return emitter.emitCast(op), true; } - bool visitOp(SIToFPOp op) { return emitter.emitCast(op), true; } - bool visitOp(FPToUIOp op) { return emitter.emitCast(op), true; } - bool visitOp(FPToSIOp op) { return emitter.emitCast(op), true; } bool visitOp(CallOp op) { return emitter.emitCall(op), true; } bool visitOp(ReturnOp op) { return true; } + bool visitOp(SelectOp op) { return emitter.emitSelect(op), true; } + bool visitOp(arith::ConstantOp op) { return emitter.emitConstant(op), true; } + bool visitOp(arith::IndexCastOp op) { + return emitter.emitCast(op), true; + } + bool visitOp(arith::UIToFPOp op) { + return emitter.emitCast(op), true; + } + bool visitOp(arith::SIToFPOp op) { + return emitter.emitCast(op), true; + } + bool visitOp(arith::FPToUIOp op) { + return emitter.emitCast(op), true; + } + bool visitOp(arith::FPToSIOp op) { + return emitter.emitCast(op), true; + } private: ModuleEmitter &emitter; diff --git a/lib/Translation/EmissionMethods.cpp b/lib/Translation/EmissionMethods.cpp index f091f430..a536219a 100644 --- a/lib/Translation/EmissionMethods.cpp +++ b/lib/Translation/EmissionMethods.cpp @@ -101,8 +101,8 @@ SmallString<8> ScaleHLSEmitterBase::getName(Value val) { // For constant scalar operations, the constant number will be returned rather // than the value name. if (auto defOp = val.getDefiningOp()) { - if (auto constOp = dyn_cast(defOp)) { - auto constAttr = constOp.getValue(); + if (auto constOp = dyn_cast(defOp)) { + auto constAttr = constOp.value(); if (auto floatAttr = constAttr.dyn_cast()) { auto value = floatAttr.getValueAsDouble(); @@ -128,25 +128,25 @@ SmallString<8> ScaleHLSEmitterBase::getName(Value val) { // ExprVisitor Class Definition //===----------------------------------------------------------------------===// -bool ExprVisitor::visitOp(CmpFOp op) { +bool ExprVisitor::visitOp(arith::CmpFOp op) { switch (op.getPredicate()) { - case CmpFPredicate::OEQ: - case CmpFPredicate::UEQ: + case arith::CmpFPredicate::OEQ: + case arith::CmpFPredicate::UEQ: return emitter.emitBinary(op, "=="), true; - case CmpFPredicate::ONE: - case CmpFPredicate::UNE: + case arith::CmpFPredicate::ONE: + case arith::CmpFPredicate::UNE: return emitter.emitBinary(op, "!="), true; - case CmpFPredicate::OLT: - case CmpFPredicate::ULT: + case arith::CmpFPredicate::OLT: + case arith::CmpFPredicate::ULT: return emitter.emitBinary(op, "<"), true; - case CmpFPredicate::OLE: - case CmpFPredicate::ULE: + case arith::CmpFPredicate::OLE: + case arith::CmpFPredicate::ULE: return emitter.emitBinary(op, "<="), true; - case CmpFPredicate::OGT: - case CmpFPredicate::UGT: + case arith::CmpFPredicate::OGT: + case arith::CmpFPredicate::UGT: return emitter.emitBinary(op, ">"), true; - case CmpFPredicate::OGE: - case CmpFPredicate::UGE: + case arith::CmpFPredicate::OGE: + case arith::CmpFPredicate::UGE: return emitter.emitBinary(op, ">="), true; default: op.emitError("has unsupported compare type."); @@ -154,23 +154,23 @@ bool ExprVisitor::visitOp(CmpFOp op) { } } -bool ExprVisitor::visitOp(CmpIOp op) { +bool ExprVisitor::visitOp(arith::CmpIOp op) { switch (op.getPredicate()) { - case CmpIPredicate::eq: + case arith::CmpIPredicate::eq: return emitter.emitBinary(op, "=="), true; - case CmpIPredicate::ne: + case arith::CmpIPredicate::ne: return emitter.emitBinary(op, "!="), true; - case CmpIPredicate::slt: - case CmpIPredicate::ult: + case arith::CmpIPredicate::slt: + case arith::CmpIPredicate::ult: return emitter.emitBinary(op, "<"), true; - case CmpIPredicate::sle: - case CmpIPredicate::ule: + case arith::CmpIPredicate::sle: + case arith::CmpIPredicate::ule: return emitter.emitBinary(op, "<="), true; - case CmpIPredicate::sgt: - case CmpIPredicate::ugt: + case arith::CmpIPredicate::sgt: + case arith::CmpIPredicate::ugt: return emitter.emitBinary(op, ">"), true; - case CmpIPredicate::sge: - case CmpIPredicate::uge: + case arith::CmpIPredicate::sge: + case arith::CmpIPredicate::uge: return emitter.emitBinary(op, ">="), true; } } @@ -701,8 +701,9 @@ void ModuleEmitter::emitTensorToMemref(memref::BufferCastOp op) { } void ModuleEmitter::emitDim(memref::DimOp op) { - if (auto constOp = dyn_cast(op.getOperand(1).getDefiningOp())) { - auto constVal = constOp.getValue().cast().getInt(); + if (auto constOp = + dyn_cast(op.getOperand(1).getDefiningOp())) { + auto constVal = constOp.value().cast().getInt(); auto type = op.getOperand(0).getType().cast(); if (type.hasStaticShape()) { @@ -759,9 +760,35 @@ void ModuleEmitter::emitUnary(Operation *op, const char *syntax) { /// IP operation emitter. void ModuleEmitter::emitIP(IPOp op) { - auto name = op.name(); - os << " __IP__" << name << "("; + // Emit IP source from JSON if IP exists. + std::string errorMessage; + if (auto jsonFile = mlir::openInputFile(op.path(), &errorMessage)) { + if (auto json = llvm::json::parse(jsonFile->getBuffer())) { + if (auto O = json->getAsObject()) { + if (auto source = O->getObject("source")) { + for (auto line : *source->getArray("code")) { + auto l = line.getAsString()->str(); + for (size_t idx = 0; idx < source->getArray("params")->size(); idx++) { + auto p = source->getArray("params")->operator[](idx).getAsString()->str(); + auto o = getName(op.getOperands()[idx]).str().str(); + for (std::size_t pos = 0; l.npos != (pos = l.find(p, pos)); pos += o.length()) { + l.replace(pos, p.length(), o); + } + } + + indent(); + os << l << "\n"; + } + return; + } + } + } + //emitError(op, "IP JSON cannot be parsed."); + } + //emitError(op, "IP cannot be found."); + // Emit a regular function call if IP does not exist. + os << " __IP__" << op.name() << "("; unsigned argIdx = 0; for (auto arg : op.getOperands()) { emitValue(arg); @@ -794,19 +821,19 @@ void ModuleEmitter::emitSelect(SelectOp op) { emitNestedLoopTail(rank); } -void ModuleEmitter::emitConstant(ConstantOp op) { +void ModuleEmitter::emitConstant(arith::ConstantOp op) { // This indicates the constant type is scalar (float, integer, or bool). if (isDeclared(op.getResult())) return; - if (auto denseAttr = op.getValue().dyn_cast()) { + if (auto denseAttr = op.value().dyn_cast()) { indent(); emitArrayDecl(op.getResult()); os << " = {"; auto type = op.getResult().getType().cast().getElementType(); unsigned elementIdx = 0; - for (auto element : denseAttr.getAttributeValues()) { + for (auto element : denseAttr.getValues()) { if (type.isF32()) { auto value = element.cast().getValue().convertToFloat(); if (std::isfinite(value)) @@ -1228,45 +1255,45 @@ void ModuleEmitter::emitFunction(FuncOp func) { } /// Top-level MLIR module emitter. -void ModuleEmitter::emitModule(ModuleOp module, bool emitHeader) { - - // Don't emit the header if emitting to an IP library file - if (emitHeader) { - os << R"XXX( - //===------------------------------------------------------------*- C++ -*-===// - // - // Automatically generated file for High-level Synthesis (HLS). - // - //===----------------------------------------------------------------------===// - - #include - #include - #include - #include - #include - #include - #include - #include - - using namespace std; - - )XXX"; +void ModuleEmitter::emitModule(ModuleOp module) { + os << R"XXX( +//===------------------------------------------------------------*- C++ -*-===// +// +// Automatically generated file for High-level Synthesis (HLS). +// +//===----------------------------------------------------------------------===// + +#include +#include +#include +#include +#include +#include +#include +#include + +// Libraries included by user. +)XXX"; + + for (auto &op : *module.getBody()) { + if (auto include = dyn_cast(op)) { + for (auto library : include.libraries()) { + os << "#include <" << library.dyn_cast().getValue() << ">\n"; + } + } } - // Right now, decide to emit compiled HLS C++ to the ip library store without asking the user - std::fstream fio; - std::string line = "This is a test that the emitModule function that will, in the future, write compiled HLS code into the ip library file."; - fio.open("ip_library.txt", std::ios::trunc | std::ios::out | std::ios::in); - fio << line << std::endl; + os << R"XXX( +using namespace std; + +)XXX"; for (auto &op : *module.getBody()) { if (auto func = dyn_cast(op)) emitFunction(func); - else - emitError(&op, "is unsupported operation."); + //else + // emitError(&op, "is unsupported operation."); } - - fio.close(); } } diff --git a/lib/Translation/EmitIPLibrary.cpp b/lib/Translation/EmitIPLibrary.cpp index dd8e6598..10869980 100644 --- a/lib/Translation/EmitIPLibrary.cpp +++ b/lib/Translation/EmitIPLibrary.cpp @@ -26,9 +26,7 @@ LogicalResult scalehls::emitIPLibrary(ModuleOp module, llvm::raw_ostream &os) { llvm::raw_string_ostream stream_to_str(optimized_source); ScaleHLSEmitterState state(stream_to_str); - ModuleEmitter(state).emitModule(module, false); - - // std::cout << "Now emitting optimized source" << std::endl << optimized_source << std::endl; + ModuleEmitter(state).emitModule(module); /* Build the JSON object representing the IP */ // llvm::json::Object ip_in_json; From fff1aded164b9f9ddf8219f42a3860cd377b73dd Mon Sep 17 00:00:00 2001 From: Luke Jacobs Date: Sun, 21 Nov 2021 23:44:38 -0600 Subject: [PATCH 4/4] emit to IP library experimental features complete --- lib/Translation/EmitIPLibrary.cpp | 56 ++++++++++++++++++++++--------- 1 file changed, 41 insertions(+), 15 deletions(-) diff --git a/lib/Translation/EmitIPLibrary.cpp b/lib/Translation/EmitIPLibrary.cpp index 10869980..431290c2 100644 --- a/lib/Translation/EmitIPLibrary.cpp +++ b/lib/Translation/EmitIPLibrary.cpp @@ -22,34 +22,60 @@ LogicalResult scalehls::emitIPLibrary(ModuleOp module, llvm::raw_ostream &os) { // TODO: Somehow we need to find a way to get a filename argument into this scope. How can I add a filename option to the "emit-ip" cmd-line arg? std::string filename = "iplibrary.json"; // This will need to be set by the command line - std::string optimized_source, json_file_contents; - llvm::raw_string_ostream stream_to_str(optimized_source); - ScaleHLSEmitterState state(stream_to_str); - ModuleEmitter(state).emitModule(module); + // ===== Emit HLS code ===== + // ScaleHLSEmitterState state(stream_to_str); + // ModuleEmitter(state).emitModule(module); - /* Build the JSON object representing the IP */ - // llvm::json::Object ip_in_json; - // ip_in_json.insert({"source", optimized_source}); + // ===== Emit optimized MLIR given to us by scalehls-opt ===== + + llvm::json::Object ip_json; + ip_json["raw"] = llvm::json::Value(""); // Default raw (unoptimized) json entry + ip_json["source"] = llvm::json::Value(""); // Default source (optimized) json entry + + // Iterate through the MLIR functions in the MLIR module presented to us + // We assume there are two functions given, one having a "bypass = true" attribute + for (auto func : module.getOps()) { + FuncOpAdaptor func_adaptor(func); + DictionaryAttr attr_dict = func_adaptor.getAttributes(); + + // If this function contains "bypass = true", then emit it to the IP JSON under the "raw" field + Attribute bypass_attr = attr_dict.get("bypass"); + if (bypass_attr != Attribute()) { + std::string bypass_func_serialized; + llvm::raw_string_ostream bypass_func_ss(bypass_func_serialized); + bypass_func_ss << func; // Luke- I had using this string streaming idiom but it seems to be the only option + ip_json["raw"] = bypass_func_serialized; + } else { + std::string source_func_serialized; + llvm::raw_string_ostream source_func_ss(source_func_serialized); + source_func_ss << func; + ip_json["source"] = source_func_serialized; + } + } - /* JSON emission to string */ - llvm::raw_string_ostream stream_to_json_str(json_file_contents); - llvm::json::OStream json_os(stream_to_json_str); + /* JSON Object emission to string */ + std::string file_outputs; + llvm::raw_string_ostream file_output_ss(file_outputs); + llvm::json::OStream json_os(file_output_ss); json_os.object([&]{ - json_os.attribute("source", llvm::json::Value(optimized_source)); + for (auto attr_pair : ip_json) { + json_os.attribute(attr_pair.first, attr_pair.second); + } }); /* Final emission to JSON file */ std::fstream fio; fio.open(filename, std::ios::trunc | std::ios::out | std::ios::in); - fio << json_file_contents; + fio << file_outputs; - return failure(state.encounteredError); + return LogicalResult::success(); } void scalehls::registerEmitIPLibraryTranslation() { static TranslateFromMLIRRegistration toIPLibrary( - "emit-ip", emitIPLibrary, [&](DialectRegistry ®istry) { + "emit-ip", emitIPLibrary, [&](DialectRegistry ®istry) { scalehls::registerAllDialects(registry); - }); + } + ); }