Program Listing for File EmitHLSToCpp.cpp
↰ Return to documentation for file (lib/Target/EmitHLSToCpp/EmitHLSToCpp.cpp)
// Implementation of translation EmitHLSToCpp
//
// @file
// @author Jiahong Bi (jiahong.bi@tu-dresden.de)
#include "laksa-mlir/Dialect/EmitHLS/IR/EmitHLS.h"
#include "laksa-mlir/Target/EmitHLSToCpp/HLSCppEmitter.h"
#include "mlir/IR/BuiltinOps.h"
#include "mlir/IR/Diagnostics.h"
#include "mlir/Support/IndentedOstream.h"
#include "llvm/ADT/APFloat.h"
#include "llvm/ADT/APInt.h"
#include "llvm/ADT/DenseMap.h"
#include "llvm/ADT/STLExtras.h"
#include "llvm/ADT/Sequence.h"
#include "llvm/ADT/SmallPtrSet.h"
#include "llvm/ADT/SmallString.h"
#include "llvm/ADT/SmallVector.h"
#include "llvm/ADT/StringMap.h"
#include "llvm/ADT/TypeSwitch.h"
#include "llvm/Support/FormatVariadic.h"
using namespace mlir;
using namespace mlir::emithls;
using llvm::formatv;
namespace {
// Calls eachFn on every element of c, printing a comma between elements,
// and stops at the first failure.
template<typename Container, typename UnaryFunctor>
static LogicalResult interleaveCommaWithError(
const Container &c,
raw_ostream &os,
UnaryFunctor eachFn)
{
auto it = c.begin();
if (it == c.end()) return success();
if (failed(eachFn(*it))) return failure();
for (++it; it != c.end(); ++it) {
os << ", ";
if (failed(eachFn(*it))) return failure();
}
return success();
}
// Binding power used to decide whether a nested expression operand needs
// parentheses. Only operations that can appear inside an expression tree
// are covered here.
static FailureOr<int> getOperatorPrecedence(Operation* operation)
{
return llvm::TypeSwitch<Operation*, FailureOr<int>>(operation)
.Case<ArithAddOp>([](auto) { return 12; })
.Case<ArithSubOp>([](auto) { return 12; })
.Case<ArithMulOp>([](auto) { return 13; })
.Case<ArithDivOp>([](auto) { return 13; })
.Case<ArithRemOp>([](auto) { return 13; })
.Case<ArithAndOp>([](auto) { return 7; })
.Case<ArithOrOp>([](auto) { return 5; })
.Case<ArithLogicalAndOp>([](auto) { return 4; })
.Case<ArithLogicalOrOp>([](auto) { return 3; })
.Case<ArithShlOp>([](auto) { return 11; })
.Case<ArithShrOp>([](auto) { return 11; })
.Case<ArithCastOp>([](auto) { return 15; })
.Case<ArithDataRangeOp>([](auto) { return 1; })
.Case<ArithCmpOp>([](auto op) -> FailureOr<int> {
switch (op.getPredicate()) {
case CmpPredicate::eq:
case CmpPredicate::ne: return 8;
case CmpPredicate::lt:
case CmpPredicate::le:
case CmpPredicate::gt:
case CmpPredicate::ge: return 9;
}
return op->emitError("unsupported cmp predicate");
})
.Case<ArithSelectOp>([](auto) { return 2; })
.Case<CallOp>([](auto) { return 16; })
.Case<ExpressionOp>([](auto) { return 17; })
.Default([](auto op) {
return op->emitError("unsupported operation in expression");
});
}
//===----------------------------------------------------------------------===//
// HLSCppEmitter
//===----------------------------------------------------------------------===//
// Emits a program in EmitHLS IR as HLS C++ source
class HLSCppEmitter {
public:
explicit HLSCppEmitter(raw_ostream &os) : os(os) {}
// Emits the given operation, dispatching to the operation-specific
// printer. Appends a trailing semicolon/newline for statements when
// requested.
LogicalResult emitOperation(Operation &op, bool trailingSemicolon);
// Emitter helpers. Implemented alongside the operations that need them.
LogicalResult emitType(Location loc, Type type);
LogicalResult
emitAttribute(Location loc, Attribute attr, bool printConstant);
LogicalResult emitVariableDeclaration(OpResult result);
LogicalResult emitAssignPrefix(Operation &op);
LogicalResult emitExpression(ExpressionOp exprOp, bool isNested);
LogicalResult emitOperand(Value value);
raw_indented_ostream &getOS() { return os; }
Operation* getCurrentOperation() { return currentOperation; }
// Whether varOp should be emitted as a bare literal at its use sites
// instead of being declared as a named C++ variable.
bool shouldEmitConstant(VariableOp varOp)
{
if (!varOp.getIsConst()) return false;
if (isa<ArrayType>(varOp.getType())) return false;
return true;
}
//===------------------------------------------------------------------===//
// Value naming
//===------------------------------------------------------------------===//
StringRef getOrCreateName(Value value);
//===------------------------------------------------------------------===//
// Expression precedence. Tracks the enclosing expression and its
// operator precedence so nested expressions know when they need
// parentheses.
//===------------------------------------------------------------------===//
ExpressionOp getEmittedExpression() { return emittedExpression; }
bool isPartOfCurrentExpression(Value value)
{
if (!emittedExpression) return false;
Operation* defOp = value.getDefiningOp();
if (!defOp) return false;
auto exprOp = dyn_cast<ExpressionOp>(defOp->getParentOp());
return exprOp == emittedExpression;
}
void pushEmittedExpression(ExpressionOp exprOp)
{
expressionStack.push_back(emittedExpression);
emittedExpression = exprOp;
}
void restoreEmittedExpression()
{
if (!expressionStack.empty()) {
emittedExpression = expressionStack.back();
expressionStack.pop_back();
} else {
emittedExpression = nullptr;
}
}
void pushExpressionPrecedence(int precedence)
{ emittedExpressionPrecedence.push_back(precedence); }
void popExpressionPrecedence() { emittedExpressionPrecedence.pop_back(); }
static int lowestPrecedence() { return 0; }
int getExpressionPrecedence()
{
if (emittedExpressionPrecedence.empty()) return lowestPrecedence();
return emittedExpressionPrecedence.back();
}
// RAII helper that drops the value names introduced within a block
// once it goes out of scope.
struct BlockScope {
BlockScope(HLSCppEmitter &emitter, Block*) : emitter(emitter)
{
oldValues =
llvm::to_vector(llvm::make_first_range(emitter.valueNames));
}
~BlockScope()
{
llvm::SmallPtrSet<Value, 16> oldValueSet(
oldValues.begin(),
oldValues.end());
llvm::DenseMap<Value, std::string> newValueNames;
for (const auto &pair : emitter.valueNames)
if (oldValueSet.contains(pair.first))
newValueNames.insert(pair);
emitter.valueNames = std::move(newValueNames);
}
private:
HLSCppEmitter &emitter;
SmallVector<Value> oldValues;
};
private:
// Picks the prefix used when generating a name for an operation's
// result, keyed by the operation name without its dialect prefix.
struct NameManager {
NameManager() = default;
std::string getPrefix(Operation* op)
{
if (!op) return "v";
auto opName = op->getName().getStringRef();
size_t firstDotPos = opName.find('.');
if (firstDotPos != StringRef::npos)
opName = opName.substr(firstDotPos + 1);
auto it = prefixMap.find(opName);
if (it != prefixMap.end()) return it->second;
return "v";
}
private:
const llvm::StringMap<std::string> prefixMap = {
// ArithOps
{ "arith.add", "sum"},
{ "arith.cast", "cast"},
{ "arith.cmp", "cmp"},
{ "arith.div", "quot"},
{ "arith.mul", "prod"},
{ "arith.or", "or"},
{ "arith.rem", "rem"},
{ "arith.select", "mux"},
{ "arith.sub", "diff"},
{ "arith.shr", "shr"},
{ "arith.shl", "shl"},
{ "arith.max", "max"},
{ "arith.min", "min"},
// ArrayOps
{ "array.read", "elem"},
{"array.ptr_read", "elem"},
// StreamOps
{ "stream.read", "data"},
};
};
StringRef addNameForValue(Value value, const std::string &baseName)
{
unsigned suffix = 0;
std::string nameStr;
do {
nameStr = formatv("{0}{1}", baseName, suffix++);
} while (llvm::any_of(valueNames, [&](const auto &entry) {
return entry.second == nameStr;
}));
valueNames[value] = nameStr;
return valueNames[value];
}
// Operations
Operation* currentOperation = nullptr;
ExpressionOp emittedExpression = nullptr;
SmallVector<ExpressionOp> expressionStack;
SmallVector<int> emittedExpressionPrecedence;
// Printer
raw_indented_ostream os;
// Helper for name generation
llvm::DenseMap<Value, std::string> valueNames;
NameManager nameManager;
};
} // namespace
//===----------------------------------------------------------------------===//
// Print different operations
//===----------------------------------------------------------------------===//
static LogicalResult printOperation(HLSCppEmitter &emitter, ModuleOp moduleOp)
{
for (Operation &op : moduleOp.getBody()->getOperations())
if (failed(emitter.emitOperation(op, true))) return failure();
return success();
}
static LogicalResult printOperation(HLSCppEmitter &emitter, IncludeOp includeOp)
{
raw_indented_ostream &os = emitter.getOS();
os << "#include \"" << includeOp.getHeader() << "\"";
return success();
}
static LogicalResult
printOperation(HLSCppEmitter &emitter, VariableOp variableOp)
{
raw_indented_ostream &os = emitter.getOS();
bool hasInitNumber = static_cast<bool>(variableOp.getInitNumber());
Value initValue = variableOp.getInitValue();
if (hasInitNumber || initValue) {
if (variableOp.getIsConst()) {
if (emitter.shouldEmitConstant(variableOp)) return success();
os << "const ";
}
if (failed(emitter.emitAssignPrefix(*variableOp.getOperation())))
return failure();
if (hasInitNumber
&& failed(emitter.emitAttribute(
variableOp->getLoc(),
variableOp.getInitNumberAttr(),
false)))
return failure();
if (initValue && failed(emitter.emitOperand(initValue)))
return failure();
} else if (
failed(emitter.emitVariableDeclaration(
variableOp.getOperation()->getResult(0)))) {
return failure();
}
return success();
}
static LogicalResult printOperation(HLSCppEmitter &emitter, UpdateOp updateOp)
{
raw_indented_ostream &os = emitter.getOS();
if (failed(emitter.emitOperand(updateOp.getVariable()))) return failure();
for (auto idx : updateOp.getIndices()) {
os << "[";
if (failed(emitter.emitOperand(idx))) return failure();
os << "]";
}
os << " = ";
if (failed(emitter.emitOperand(updateOp.getNewValue()))) return failure();
for (auto idx : updateOp.getNewValueIndices()) {
os << "[";
if (failed(emitter.emitOperand(idx))) return failure();
os << "]";
}
return success();
}
static LogicalResult printOperation(HLSCppEmitter &emitter, FuncOp funcOp)
{
raw_indented_ostream &os = emitter.getOS();
HLSCppEmitter::BlockScope scope(emitter, &funcOp.getBody().front());
os << "void " << funcOp.getSymName() << "(";
if (failed(interleaveCommaWithError(
funcOp.getArguments(),
os,
[&](BlockArgument arg) -> LogicalResult {
auto argTy = arg.getType();
if (failed(emitter.emitType(funcOp.getLoc(), argTy)))
return failure();
os << " ";
if (isa<StreamType>(argTy))
os << "&";
else if (isa<PointerType>(argTy))
os << "*";
os << emitter.getOrCreateName(arg);
if (auto arrTy = dyn_cast<ArrayType>(argTy))
for (auto dim : arrTy.getShape()) os << "[" << dim << "]";
return success();
})))
return failure();
os << ")\n{\n";
os.indent();
for (Operation &opi : funcOp.getBody().getOps())
if (failed(emitter.emitOperation(opi, true))) return failure();
os.unindent() << "}";
return success();
}
static LogicalResult printOperation(HLSCppEmitter &emitter, CallOp callOp)
{
raw_indented_ostream &os = emitter.getOS();
os << callOp.getCallee() << "(";
if (failed(interleaveCommaWithError(
callOp.getArgOperands(),
os,
[&](Value arg) -> LogicalResult {
return emitter.emitOperand(arg);
})))
return failure();
os << ")";
return success();
}
static LogicalResult printOperation(HLSCppEmitter &emitter, IfOp ifOp)
{
raw_indented_ostream &os = emitter.getOS();
HLSCppEmitter::BlockScope scope(emitter, &ifOp.getThenRegion().front());
os << "if (";
if (failed(emitter.emitOperand(ifOp.getCondition()))) return failure();
os << ") {\n";
os.indent();
for (Operation &opi : ifOp.getThenRegion().getOps())
if (failed(emitter.emitOperation(opi, true))) return failure();
os.unindent();
os << "}";
if (!ifOp.getElseRegion().empty()) {
os << " else {\n";
os.indent();
for (Operation &opi : ifOp.getElseRegion().getOps())
if (failed(emitter.emitOperation(opi, true))) return failure();
os.unindent();
os << "}";
}
return success();
}
static LogicalResult printOperation(HLSCppEmitter &emitter, ForOp forOp)
{
raw_indented_ostream &os = emitter.getOS();
HLSCppEmitter::BlockScope scope(emitter, &forOp.getBody().front());
Value iVar = forOp.getInductionVariable();
os << "for (";
if (failed(emitter.emitType(forOp.getLoc(), iVar.getType())))
return failure();
os << " " << emitter.getOrCreateName(iVar) << " = " << forOp.getLowerBound()
<< ";";
os << " " << emitter.getOrCreateName(iVar) << " < " << forOp.getUpperBound()
<< ";";
os << " " << emitter.getOrCreateName(iVar) << " += " << forOp.getStep()
<< ") {\n";
os.indent();
for (Operation &opi : forOp.getBody().getOps())
if (failed(emitter.emitOperation(opi, true))) return failure();
os.unindent();
os << "}";
return success();
}
static LogicalResult printOperation(HLSCppEmitter &, ExpressionOp)
{
// The contents of an expression are never visited generically. They get
// emitted inline, at the point of use, by emitOperand and emitExpression.
return success();
}
//===----------------------------------------------------------------------===//
// ArithOps
//===----------------------------------------------------------------------===//
static LogicalResult printBinaryOperation(
HLSCppEmitter &emitter,
Operation* operation,
StringRef binaryOperator)
{
raw_indented_ostream &os = emitter.getOS();
if (failed(emitter.emitAssignPrefix(*operation))) return failure();
if (failed(emitter.emitOperand(operation->getOperand(0)))) return failure();
os << " " << binaryOperator << " ";
if (failed(emitter.emitOperand(operation->getOperand(1)))) return failure();
return success();
}
static LogicalResult printLogicalOperation(
HLSCppEmitter &emitter,
Operation* operation,
StringRef logicalOperator)
{
raw_indented_ostream &os = emitter.getOS();
if (failed(emitter.emitAssignPrefix(*operation))) return failure();
SmallVector<Value> operands = operation->getOperands();
for (Value operand : operands) {
if (failed(emitter.emitOperand(operand))) return failure();
if (operand != operands.back()) os << " " << logicalOperator << " ";
}
return success();
}
static LogicalResult printOperation(HLSCppEmitter &emitter, ArithAddOp addOp)
{ return printBinaryOperation(emitter, addOp.getOperation(), "+"); }
static LogicalResult printOperation(HLSCppEmitter &emitter, ArithSubOp subOp)
{ return printBinaryOperation(emitter, subOp.getOperation(), "-"); }
static LogicalResult printOperation(HLSCppEmitter &emitter, ArithMulOp mulOp)
{ return printBinaryOperation(emitter, mulOp.getOperation(), "*"); }
static LogicalResult printOperation(HLSCppEmitter &emitter, ArithDivOp divOp)
{ return printBinaryOperation(emitter, divOp.getOperation(), "/"); }
static LogicalResult printOperation(HLSCppEmitter &emitter, ArithRemOp remOp)
{ return printBinaryOperation(emitter, remOp.getOperation(), "%"); }
static LogicalResult printOperation(HLSCppEmitter &emitter, ArithAndOp andOp)
{ return printBinaryOperation(emitter, andOp.getOperation(), "&"); }
static LogicalResult printOperation(HLSCppEmitter &emitter, ArithOrOp orOp)
{ return printBinaryOperation(emitter, orOp.getOperation(), "|"); }
static LogicalResult printOperation(HLSCppEmitter &emitter, ArithShlOp shlOp)
{ return printBinaryOperation(emitter, shlOp.getOperation(), "<<"); }
static LogicalResult printOperation(HLSCppEmitter &emitter, ArithShrOp shrOp)
{ return printBinaryOperation(emitter, shrOp.getOperation(), ">>"); }
static LogicalResult printOperation(HLSCppEmitter &emitter, ArithMaxOp maxOp)
{
raw_indented_ostream &os = emitter.getOS();
if (failed(emitter.emitAssignPrefix(*maxOp.getOperation())))
return failure();
os << "std::max(";
if (failed(emitter.emitOperand(maxOp.getLhs()))) return failure();
os << ", ";
if (failed(emitter.emitOperand(maxOp.getRhs()))) return failure();
os << ")";
return success();
}
static LogicalResult printOperation(HLSCppEmitter &emitter, ArithMinOp minOp)
{
raw_indented_ostream &os = emitter.getOS();
if (failed(emitter.emitAssignPrefix(*minOp.getOperation())))
return failure();
os << "std::min(";
if (failed(emitter.emitOperand(minOp.getLhs()))) return failure();
os << ", ";
if (failed(emitter.emitOperand(minOp.getRhs()))) return failure();
os << ")";
return success();
}
static LogicalResult
printOperation(HLSCppEmitter &emitter, ArithLogicalAndOp andOp)
{ return printLogicalOperation(emitter, andOp.getOperation(), "&&"); }
static LogicalResult
printOperation(HLSCppEmitter &emitter, ArithLogicalOrOp orOp)
{ return printLogicalOperation(emitter, orOp.getOperation(), "||"); }
static LogicalResult
printOperation(HLSCppEmitter &emitter, ArithFusedOp fusedOp)
{
raw_indented_ostream &os = emitter.getOS();
if (failed(emitter.emitOperand(fusedOp.getAcc()))) return failure();
os << " ";
switch (fusedOp.getOpCode()) {
case FusedOperator::add: os << "+"; break;
case FusedOperator::sub: os << "-"; break;
case FusedOperator::mul: os << "*"; break;
}
os << "= ";
if (failed(emitter.emitOperand(fusedOp.getVal()))) return failure();
return success();
}
static LogicalResult printOperation(HLSCppEmitter &emitter, ArithCastOp castOp)
{
raw_indented_ostream &os = emitter.getOS();
if (failed(emitter.emitAssignPrefix(*castOp.getOperation())))
return failure();
os << "(";
if (failed(emitter.emitType(castOp.getLoc(), castOp.getType())))
return failure();
os << ")";
if (failed(emitter.emitOperand(castOp.getFrom()))) return failure();
return success();
}
static LogicalResult printOperation(HLSCppEmitter &emitter, ArithCmpOp cmpOp)
{
raw_indented_ostream &os = emitter.getOS();
if (failed(emitter.emitAssignPrefix(*cmpOp.getOperation())))
return failure();
if (failed(emitter.emitOperand(cmpOp.getLhs()))) return failure();
switch (cmpOp.getPredicate()) {
case CmpPredicate::eq: os << " == "; break;
case CmpPredicate::ne: os << " != "; break;
case CmpPredicate::lt: os << " < "; break;
case CmpPredicate::le: os << " <= "; break;
case CmpPredicate::gt: os << " > "; break;
case CmpPredicate::ge: os << " >= "; break;
}
if (failed(emitter.emitOperand(cmpOp.getRhs()))) return failure();
return success();
}
static LogicalResult
printOperation(HLSCppEmitter &emitter, ArithSelectOp selectOp)
{
raw_indented_ostream &os = emitter.getOS();
if (failed(emitter.emitAssignPrefix(*selectOp.getOperation())))
return failure();
if (failed(emitter.emitOperand(selectOp.getCondition()))) return failure();
os << " ? ";
if (failed(emitter.emitOperand(selectOp.getTrueValue()))) return failure();
os << " : ";
if (failed(emitter.emitOperand(selectOp.getFalseValue()))) return failure();
return success();
}
static LogicalResult
printOperation(HLSCppEmitter &emitter, ArithDataRangeOp rangeOp)
{
raw_indented_ostream &os = emitter.getOS();
if (failed(emitter.emitAssignPrefix(*rangeOp.getOperation())))
return failure();
os << emitter.getOrCreateName(rangeOp.getData());
os << ".range(";
if (failed(emitter.emitOperand(rangeOp.getHighBit()))) return failure();
os << ", ";
if (failed(emitter.emitOperand(rangeOp.getLowBit()))) return failure();
os << ")";
return success();
}
//===----------------------------------------------------------------------===//
// ArrayOps
//===----------------------------------------------------------------------===//
static LogicalResult printOperation(HLSCppEmitter &emitter, ArrayReadOp readOp)
{
raw_indented_ostream &os = emitter.getOS();
if (failed(emitter.emitAssignPrefix(*readOp.getOperation())))
return failure();
os << emitter.getOrCreateName(readOp.getArray());
for (auto idx : readOp.getIndices()) {
os << "[";
if (failed(emitter.emitOperand(idx))) return failure();
os << "]";
}
return success();
}
static LogicalResult
printOperation(HLSCppEmitter &emitter, ArrayWriteOp writeOp)
{
raw_indented_ostream &os = emitter.getOS();
os << emitter.getOrCreateName(writeOp.getArray());
for (auto idx : writeOp.getIndices()) {
os << "[";
if (failed(emitter.emitOperand(idx))) return failure();
os << "]";
}
os << " = ";
if (failed(emitter.emitOperand(writeOp.getValue()))) return failure();
return success();
}
static LogicalResult
printOperation(HLSCppEmitter &emitter, ArrayPointerReadOp readOp)
{
raw_indented_ostream &os = emitter.getOS();
if (failed(emitter.emitAssignPrefix(*readOp.getOperation())))
return failure();
os << emitter.getOrCreateName(readOp.getPointer());
for (auto idx : readOp.getIndices()) {
os << "[";
if (failed(emitter.emitOperand(idx))) return failure();
os << "]";
}
return success();
}
static LogicalResult
printOperation(HLSCppEmitter &emitter, ArrayPointerWriteOp writeOp)
{
raw_indented_ostream &os = emitter.getOS();
os << emitter.getOrCreateName(writeOp.getPointer());
for (auto idx : writeOp.getIndices()) {
os << "[";
if (failed(emitter.emitOperand(idx))) return failure();
os << "]";
}
os << " = ";
if (failed(emitter.emitOperand(writeOp.getValue()))) return failure();
return success();
}
//===----------------------------------------------------------------------===//
// StreamOps
//===----------------------------------------------------------------------===//
static LogicalResult printOperation(HLSCppEmitter &emitter, StreamReadOp readOp)
{
raw_indented_ostream &os = emitter.getOS();
if (failed(emitter.emitAssignPrefix(*readOp.getOperation())))
return failure();
os << emitter.getOrCreateName(readOp.getStream());
for (auto idx : readOp.getIndices()) {
os << "[";
if (failed(emitter.emitOperand(idx))) return failure();
os << "]";
}
os << ".read()";
return success();
}
static LogicalResult
printOperation(HLSCppEmitter &emitter, StreamWriteOp writeOp)
{
raw_indented_ostream &os = emitter.getOS();
os << emitter.getOrCreateName(writeOp.getStream());
for (auto idx : writeOp.getIndices()) {
os << "[";
if (failed(emitter.emitOperand(idx))) return failure();
os << "]";
}
os << ".write(";
if (failed(emitter.emitOperand(writeOp.getData()))) return failure();
os << ")";
return success();
}
//===----------------------------------------------------------------------===//
// PragmaOps
//===----------------------------------------------------------------------===//
static LogicalResult
printOperation(HLSCppEmitter &emitter, PragmaArrayPartitionOp arrayPartOp)
{
raw_indented_ostream &os = emitter.getOS();
os << "#pragma HLS ARRAY_PARTITION variable="
<< emitter.getOrCreateName(arrayPartOp.getVariable());
os << " type=" << arrayPartOp.getPartType();
if (auto factor = arrayPartOp.getPartFactor()) os << " factor=" << *factor;
if (auto dim = arrayPartOp.getPartDim()) os << " dim=" << *dim;
return success();
}
static LogicalResult
printOperation(HLSCppEmitter &emitter, PragmaBindStorageOp bindStorageOp)
{
raw_indented_ostream &os = emitter.getOS();
os << "#pragma HLS BIND_STORAGE variable="
<< emitter.getOrCreateName(bindStorageOp.getVariable());
os << " type=" << bindStorageOp.getStorageType()
<< " impl=" << bindStorageOp.getStorageImpl();
return success();
}
static LogicalResult
printOperation(HLSCppEmitter &emitter, PragmaDataflowOp dataflowOp)
{
raw_indented_ostream &os = emitter.getOS();
os << "\n#pragma HLS DATAFLOW\n";
for (Operation &opi : dataflowOp.getBody().getOps())
if (failed(emitter.emitOperation(opi, true))) return failure();
return success();
}
static LogicalResult
printOperation(HLSCppEmitter &emitter, PragmaInlineOp inlineOp)
{
raw_indented_ostream &os = emitter.getOS();
os << "#pragma HLS INLINE";
if (inlineOp.getOff()) os << " off";
return success();
}
static LogicalResult
printOperation(HLSCppEmitter &emitter, PragmaPipelineOp pipelineOp)
{
raw_indented_ostream &os = emitter.getOS();
os << "#pragma HLS PIPELINE II=" << pipelineOp.getInterval();
os << " style=" << pipelineOp.getStyle();
return success();
}
static LogicalResult
printOperation(HLSCppEmitter &emitter, PragmaStreamOp streamOp)
{
raw_indented_ostream &os = emitter.getOS();
os << "#pragma HLS STREAM variable="
<< emitter.getOrCreateName(streamOp.getVariable());
os << " depth=" << streamOp.getDepth();
return success();
}
static LogicalResult
printOperation(HLSCppEmitter &emitter, PragmaUnrollOp unrollOp)
{
raw_indented_ostream &os = emitter.getOS();
os << "#pragma HLS UNROLL";
if (auto factor = unrollOp.getFactor()) os << " factor=" << *factor;
return success();
}
// This op only marks where the interface pragmas belong. Function arguments
// are always named arg0, arg1, ... in order, so the port names can be
// reconstructed just by counting the enclosing FuncOp's arguments.
static LogicalResult
printOperation(HLSCppEmitter &emitter, PragmaTopInterfaceOp topInterfaceOp)
{
raw_indented_ostream &os = emitter.getOS();
auto funcOp = topInterfaceOp->getParentOfType<FuncOp>();
if (!funcOp)
return topInterfaceOp->emitOpError(
"expected to be nested inside a FuncOp");
for (unsigned i : llvm::seq(funcOp.getNumArguments())) {
os << "#pragma HLS INTERFACE mode=m_axi port=arg" << i
<< " offset=slave bundle=gmem_arg" << i << "\n";
os << "#pragma HLS INTERFACE mode=s_axilite port=arg" << i
<< " bundle=control\n";
}
os << "#pragma HLS INTERFACE mode=s_axilite port=return bundle=control";
return success();
}
//===----------------------------------------------------------------------===//
// Emitter helper methods
//===----------------------------------------------------------------------===//
LogicalResult HLSCppEmitter::emitType(Location loc, Type type)
{
raw_indented_ostream &os = getOS();
if (auto intTy = dyn_cast<IntegerType>(type)) {
auto width = intTy.getWidth();
if (width == 1) {
os << "bool";
return success();
}
if (intTy.getSignedness() == IntegerType::Unsigned)
os << "ap_uint<" << width << ">";
else
os << "ap_int<" << width << ">";
return success();
}
if (isa<IndexType>(type)) {
os << "size_t";
return success();
}
if (auto floatTy = dyn_cast<FloatType>(type)) {
switch (floatTy.getIntOrFloatBitWidth()) {
case 16: os << "half"; break;
case 32: os << "float"; break;
case 64: os << "double"; break;
default: return emitError(loc, "unsupported float width for HLS C++");
}
return success();
}
if (auto arrayTy = dyn_cast<ArrayType>(type))
return emitType(loc, arrayTy.getElementType());
if (auto streamTy = dyn_cast<StreamType>(type)) {
os << "hls::stream<";
if (failed(emitType(loc, streamTy.getElementType()))) return failure();
os << ">";
return success();
}
if (auto ptrTy = dyn_cast<PointerType>(type))
return emitType(loc, ptrTy.getElementType());
return emitError(loc, "cannot emit type ") << type;
}
LogicalResult
HLSCppEmitter::emitAttribute(Location loc, Attribute attr, bool printConstant)
{
raw_indented_ostream &os = getOS();
auto printInt = [&](const APInt &val, bool isUnsigned) {
if (val.getBitWidth() == 1) {
os << (val.getBoolValue() ? "true" : "false");
} else {
SmallString<128> strValue;
val.toString(strValue, 10, !isUnsigned, false);
bool shouldPrintParen = printConstant && val.isNegative();
if (shouldPrintParen) os << "(";
os << strValue;
if (shouldPrintParen) os << ")";
}
};
auto printFloat = [&](const APFloat &val) {
if (val.isFinite()) {
SmallString<128> strValue;
val.toString(strValue);
os << strValue;
} else if (val.isNaN()) {
os << "NAN";
} else if (val.isInfinity()) {
if (val.isNegative()) os << "-";
os << "INFINITY";
}
};
// Print integer attributes.
if (auto iAttr = dyn_cast<IntegerAttr>(attr)) {
if (auto iTy = dyn_cast<IntegerType>(iAttr.getType())) {
printInt(
iAttr.getValue(),
iTy.getSignedness() == IntegerType::Unsigned);
return success();
}
if (isa<IndexType>(iAttr.getType())) {
printInt(iAttr.getValue(), false);
return success();
}
}
if (auto dense = dyn_cast<DenseIntElementsAttr>(attr)) {
if (auto iTy = dyn_cast<IntegerType>(dense.getElementType())) {
os << '{';
bool isUnsigned = iTy.getSignedness() == IntegerType::Unsigned;
if (dense.isSplat())
printInt(*dense.getValues<APInt>().begin(), isUnsigned);
else
llvm::interleaveComma(
dense.getValues<APInt>(),
os,
[&](const APInt &val) { printInt(val, isUnsigned); });
os << '}';
return success();
}
if (isa<IndexType>(dense.getElementType())) {
os << '{';
if (dense.isSplat())
printInt(*dense.getValues<APInt>().begin(), false);
else
llvm::interleaveComma(
dense.getValues<APInt>(),
os,
[&](const APInt &val) { printInt(val, false); });
os << '}';
return success();
}
}
// Print floating point attributes.
if (auto fAttr = dyn_cast<FloatAttr>(attr)) {
if (!isa<Float16Type, Float32Type, Float64Type>(fAttr.getType()))
return emitError(
loc,
"expected floating point attribute to be f16, f32 or f64");
printFloat(fAttr.getValue());
return success();
}
if (auto dense = dyn_cast<DenseFPElementsAttr>(attr)) {
if (!isa<Float16Type, Float32Type, Float64Type>(dense.getElementType()))
return emitError(
loc,
"expected floating point attribute to be f16, f32 or f64");
os << '{';
if (dense.isSplat())
printFloat(*dense.getValues<APFloat>().begin());
else
llvm::interleaveComma(
dense.getValues<APFloat>(),
os,
[&](const APFloat &val) { printFloat(val); });
os << '}';
return success();
}
// Print symbolic reference attributes.
if (auto sAttr = dyn_cast<SymbolRefAttr>(attr)) {
if (sAttr.getNestedReferences().size() > 1)
return emitError(loc, "attribute has more than 1 nested reference");
os << sAttr.getRootReference().getValue();
return success();
}
// Print type attributes.
if (auto typeAttr = dyn_cast<TypeAttr>(attr))
return emitType(loc, typeAttr.getValue());
return emitError(loc, "cannot emit attribute: ") << attr;
}
LogicalResult HLSCppEmitter::emitVariableDeclaration(OpResult result)
{
raw_indented_ostream &os = getOS();
if (valueNames.contains(result))
return result.getDefiningOp()->emitError(
"result variable for the operation already declared");
if (failed(emitType(result.getOwner()->getLoc(), result.getType())))
return failure();
os << " " << getOrCreateName(result);
if (auto arrayTy = dyn_cast<ArrayType>(result.getType()))
for (auto dim : arrayTy.getShape()) os << "[" << dim << "]";
return success();
}
LogicalResult HLSCppEmitter::emitAssignPrefix(Operation &op)
{
if (getEmittedExpression()) return success();
if (op.getNumResults() != 1) return failure();
if (failed(emitVariableDeclaration(op.getResult(0)))) return failure();
getOS() << " = ";
return success();
}
LogicalResult HLSCppEmitter::emitExpression(ExpressionOp exprOp, bool isNested)
{
if (!isNested)
assert(
emittedExpressionPrecedence.empty()
&& "expect precedence stack to be empty");
Operation* rootOp = exprOp.getRootOp();
pushEmittedExpression(exprOp);
FailureOr<int> precedence = getOperatorPrecedence(rootOp);
if (failed(precedence)) return failure();
pushExpressionPrecedence(*precedence);
if (failed(emitOperation(*rootOp, false))) return failure();
popExpressionPrecedence();
if (!isNested)
assert(
emittedExpressionPrecedence.empty()
&& "expected precedence stack to be empty");
restoreEmittedExpression();
return success();
}
LogicalResult HLSCppEmitter::emitOperand(Value value)
{
raw_indented_ostream &os = getOS();
if (isPartOfCurrentExpression(value)) {
Operation* defOp = value.getDefiningOp();
assert(defOp && "value must be defined by an operation");
FailureOr<int> precedence = getOperatorPrecedence(defOp);
if (failed(precedence)) return failure();
if (auto nestedExpr = dyn_cast<ExpressionOp>(defOp))
return emitExpression(nestedExpr, true);
bool encloseInParenthesis = *precedence <= getExpressionPrecedence();
if (encloseInParenthesis) os << "(";
pushExpressionPrecedence(*precedence);
if (failed(emitOperation(*defOp, false))) return failure();
if (encloseInParenthesis) os << ")";
popExpressionPrecedence();
return success();
}
if (!isa<BlockArgument>(value)) {
Operation* defOp = value.getDefiningOp();
if (auto exprOp = dyn_cast<ExpressionOp>(defOp))
return emitExpression(exprOp, false);
if (auto varOp = dyn_cast<VariableOp>(defOp)) {
if (shouldEmitConstant(varOp)) {
if (isa<ArithMaxOp, ArithMinOp, ArithShlOp>(
getCurrentOperation())) {
os << "(";
if (failed(emitType(varOp.getLoc(), varOp.getType())))
return failure();
os << ")";
}
return emitAttribute(
varOp->getLoc(),
varOp.getInitNumberAttr(),
true);
}
}
}
os << getOrCreateName(value);
return success();
}
StringRef HLSCppEmitter::getOrCreateName(Value value)
{
auto it = valueNames.find(value);
if (it != valueNames.end()) return it->second;
Operation* definingOp = value.getDefiningOp();
// Treat FuncOp and ForOp block arguments differently. If there is no
// defining operation, it's a block argument.
if (!definingOp) {
if (auto blkArg = dyn_cast<BlockArgument>(value)) {
Block* parentBlock = blkArg.getParentBlock();
if (Operation* parentOp = parentBlock->getParentOp()) {
if (isa<FuncOp>(parentOp)) return addNameForValue(value, "arg");
if (isa<ForOp>(parentOp)) return addNameForValue(value, "idx");
return addNameForValue(value, "v");
}
}
return "";
}
// Otherwise, it's defined by another operation.
std::string prefix;
if (auto varOp = dyn_cast<VariableOp>(definingOp)) {
if (varOp.getIsConst())
prefix = "cst";
else if (auto arrTy = dyn_cast<ArrayType>(varOp.getType()))
prefix =
isa<StreamType>(arrTy.getElementType()) ? "stream" : "array";
else if (isa<StreamType>(varOp.getType()))
prefix = "stream";
else
prefix = "var";
} else {
prefix = nameManager.getPrefix(definingOp);
}
if (!isa<FuncOp>(value.getParentBlock()->getParentOp())) prefix += "_tmp";
return addNameForValue(value, prefix);
}
//===----------------------------------------------------------------------===//
// HLSCppEmitter::emitOperation
//===----------------------------------------------------------------------===//
LogicalResult
HLSCppEmitter::emitOperation(Operation &op, bool trailingSemicolon)
{
currentOperation = &op;
LogicalResult status =
llvm::TypeSwitch<Operation*, LogicalResult>(&op)
.Case<ModuleOp>([&](auto op) { return printOperation(*this, op); })
.Case<
IncludeOp,
FuncOp,
CallOp,
VariableOp,
UpdateOp,
IfOp,
ForOp,
ExpressionOp>(
[&](auto op) { return printOperation(*this, op); })
.Case<YieldOp>([](auto op) {
return op->emitError(
"YieldOp should never be emitted directly");
})
.Case<
ArithAddOp,
ArithSubOp,
ArithMulOp,
ArithDivOp,
ArithRemOp,
ArithAndOp,
ArithOrOp,
ArithShlOp,
ArithShrOp,
ArithMinOp,
ArithMaxOp,
ArithLogicalAndOp,
ArithLogicalOrOp,
ArithFusedOp,
ArithCastOp,
ArithCmpOp,
ArithSelectOp,
ArithDataRangeOp>(
[&](auto op) { return printOperation(*this, op); })
.Case<
ArrayReadOp,
ArrayWriteOp,
ArrayPointerReadOp,
ArrayPointerWriteOp>(
[&](auto op) { return printOperation(*this, op); })
.Case<StreamReadOp, StreamWriteOp>(
[&](auto op) { return printOperation(*this, op); })
.Case<
PragmaArrayPartitionOp,
PragmaBindStorageOp,
PragmaDataflowOp,
PragmaPipelineOp,
PragmaInlineOp,
PragmaStreamOp,
PragmaTopInterfaceOp,
PragmaUnrollOp>(
[&](auto op) { return printOperation(*this, op); })
.Case<HelperLineBufferOp, HelperWindowOp, HelperAccumulateOp>(
[](auto op) {
return op->emitError(
"expect no helper operations before translation");
})
.Default([](Operation* op) {
return op->emitError("unsupported operation '")
<< op->getName() << "'";
});
if (failed(status)) return failure();
raw_indented_ostream &resultOS = getOS();
trailingSemicolon &= !isa<
ModuleOp,
ExpressionOp,
ForOp,
FuncOp,
IfOp,
IncludeOp,
PragmaInterface>(op);
bool trailingNewLine = !isa<ModuleOp, PragmaDataflowOp, ExpressionOp>(op)
&& !op.getParentOfType<ExpressionOp>();
if (auto varOp = dyn_cast<VariableOp>(op))
if (shouldEmitConstant(varOp)) {
trailingSemicolon = false;
trailingNewLine = false;
}
resultOS << (trailingSemicolon ? ";" : "");
resultOS << (trailingNewLine ? "\n" : "");
if (isa<ArithMulOp>(op) && trailingNewLine)
resultOS << "#pragma HLS BIND_OP variable="
<< getOrCreateName(op.getResult(0)) << " op=mul impl=dsp\n";
return success();
}
//===----------------------------------------------------------------------===//
// Translation entry point
//===----------------------------------------------------------------------===//
LogicalResult emithls::translateEmitHLSToCpp(Operation* op, raw_ostream &os)
{
HLSCppEmitter emitter(os);
return emitter.emitOperation(*op, false);
}