[EmitHLSCpp] support to emit line number and schedule information; [HLSCpp] update all code related to storage_type attribute to align with Vivado 2019.1 settings

This commit is contained in:
Hanchen Ye 2020-12-20 17:52:56 -06:00
parent bf6c17f1ab
commit 53550db33a
7 changed files with 160 additions and 98 deletions

View File

@ -40,12 +40,12 @@ $ # Benchmark generation, dataflow-level optimization, and bufferization.
$ benchmark-gen -type "cnn" -config "config/cnn-config.ini" -number 1 \
| scalehls-opt -legalize-dataflow -split-function \
-hlskernel-bufferize -hlskernel-to-affine -func-bufferize -canonicalize
$
$ # HLSKernel lowering, loop-level and pragma-level optimizations, and performance estimation.
$ scalehls-opt test/Conversion/HLSKernelToAffine/test_gemm.mlir -hlskernel-to-affine \
-affine-loop-perfection -remove-var-loop-bound -partial-affine-loop-tile="tile-level=1 tile-size=4" \
-convert-to-hlscpp="top-function=test_gemm" -loop-pipelining="pipeline-level=1" \
-store-op-forward -simplify-memref-access -array-partition -canonicalize \
-store-op-forward -simplify-memref-access -array-partition -cse -canonicalize \
-qor-estimation="target-spec=config/target-spec.ini"
$ # HLS C++ code generation.

View File

@ -33,22 +33,32 @@ def PartitionTypeArrayAttr : TypedArrayAttrBase<PartitionTypeAttr, ""> {}
// Pragma bind_storage Attributes
//===----------------------------------------------------------------------===//
def StorageImplAttr : StrEnumAttr<"StorageImpl", "", [
StrEnumAttrCase<"bram", 0>,
StrEnumAttrCase<"lutram", 1>,
StrEnumAttrCase<"uram", 2>,
StrEnumAttrCase<"srl", 3>
]> {
let cppNamespace = "::mlir::scalehls::hlscpp";
}
// def StorageImplAttr : StrEnumAttr<"StorageImpl", "", [
// StrEnumAttrCase<"bram", 0>,
// StrEnumAttrCase<"lutram", 1>,
// StrEnumAttrCase<"uram", 2>,
// StrEnumAttrCase<"srl", 3>
// ]> {
// let cppNamespace = "::mlir::scalehls::hlscpp";
// }
// def StorageTypeAttr : StrEnumAttr<"StorageType", "", [
// StrEnumAttrCase<"fifo", 0>,
// StrEnumAttrCase<"ram_1p", 1>,
// StrEnumAttrCase<"ram_1wnr", 2>,
// StrEnumAttrCase<"ram_2p", 3>,
// StrEnumAttrCase<"ram_s2p", 4>,
// StrEnumAttrCase<"ram_t2p", 5>
// ]> {
// let cppNamespace = "::mlir::scalehls::hlscpp";
// }
// Currently we only support bram/lut based memory instance.
def StorageTypeAttr : StrEnumAttr<"StorageType", "", [
StrEnumAttrCase<"fifo", 0>,
StrEnumAttrCase<"ram_1p", 1>,
StrEnumAttrCase<"ram_1wnr", 2>,
StrEnumAttrCase<"ram_2p", 3>,
StrEnumAttrCase<"ram_s2p", 4>,
StrEnumAttrCase<"ram_t2p", 5>
StrEnumAttrCase<"ram_1p_bram", 0>,
StrEnumAttrCase<"ram_2p_bram", 1>,
StrEnumAttrCase<"ram_s2p_bram", 2>,
StrEnumAttrCase<"ram_t2p_bram", 3>
]> {
let cppNamespace = "::mlir::scalehls::hlscpp";
}

View File

@ -33,15 +33,15 @@ def ArrayOp : HLSCppOp<"array", [SameOperandsAndResultType]> {
// Interface-related attributes.
DefaultValuedAttr<BoolAttr, "false"> : $interface,
DefaultValuedAttr<InterfaceModeAttr, "bram"> : $interface_mode,
DefaultValuedAttr<PositiveUI32Attr, "1024"> : $interface_depth,
DefaultValuedAttr<PositiveUI32Attr, "1"> : $interface_depth,
// BindStorage-related attributes.
DefaultValuedAttr<BoolAttr, "false"> : $storage,
DefaultValuedAttr<StorageTypeAttr, "ram_2p"> : $storage_type,
DefaultValuedAttr<StorageImplAttr, "bram"> : $storage_impl,
DefaultValuedAttr<StorageTypeAttr, "ram_1p_bram"> : $storage_type,
// ArrayPartition-related attributes.
DefaultValuedAttr<BoolAttr, "false"> : $partition,
DefaultValuedAttr<PositiveUI32Attr, "1"> : $partition_num,
DefaultValuedAttr<PartitionTypeArrayAttr, "{}"> : $partition_type,
DefaultValuedAttr<PositiveUI32ArrayAttr, "{}"> : $partition_factor
);

View File

@ -245,9 +245,8 @@ unsigned HLSCppEstimator::getLoadStoreSchedule(Operation *op, unsigned begin) {
setAttrValue(op, "partition_index", partitionIdx);
auto arrayOp = getArrayOp(op);
auto partitionNum =
max((unsigned)1, getUIntAttrValue(arrayOp, "partition_num"));
auto storageType = getStrAttrValue(arrayOp, "storage_type");
auto partitionNum = arrayOp.partition_num();
auto storageType = arrayOp.storage_type();
// Try to avoid memory port violation until a legal schedule is found. Since
// an infinite length schedule cannot be generated, this while loop can be
@ -265,15 +264,16 @@ unsigned HLSCppEstimator::getLoadStoreSchedule(Operation *op, unsigned begin) {
unsigned wrPort = 0;
unsigned rdwrPort = 0;
if (storageType == "ram_s2p")
rdPort = 1, wrPort = 1;
else if (storageType == "ram_2p" || storageType == "ram_t2p")
rdwrPort = 2;
else if (storageType == "ram_1p")
if (storageType == "ram_1p_bram")
rdwrPort = 1;
else {
else if (storageType == "ram_2p_bram")
rdPort = 1, rdwrPort = 1;
else if (storageType == "ram_s2p_bram")
rdPort = 1, wrPort = 1;
else if (storageType == "ram_t2p_bram")
rdwrPort = 2;
else
rdwrPort = 2;
}
memPort.push_back(PortInfo(rdPort, wrPort, rdwrPort));
}
@ -346,7 +346,6 @@ unsigned HLSCppEstimator::getLoadStoreSchedule(Operation *op, unsigned begin) {
// else
// minII = getUIntAttrValue(op, "schedule_end") -
// getUIntAttrValue(op, "schedule_begin");
// II = max(II, minII);
// });
// return II;
@ -359,9 +358,8 @@ unsigned HLSCppEstimator::getResMinII(LoadStoresMap &map) {
for (auto &pair : map) {
auto arrayOp = getArrayOp(pair.first);
// Partition number should at least be 1.
auto partitionNum =
max((unsigned)1, getUIntAttrValue(arrayOp, "partition_num"));
auto storageType = getStrAttrValue(arrayOp, "storage_type");
auto partitionNum = arrayOp.partition_num();
auto storageType = arrayOp.storage_type();
SmallVector<unsigned, 16> readNum;
SmallVector<unsigned, 16> writeNum;
@ -376,12 +374,15 @@ unsigned HLSCppEstimator::getResMinII(LoadStoresMap &map) {
auto partitionIdx = getIntAttrValue(op, "partition_index");
if (partitionIdx == -1) {
unsigned accessNum = 2;
if (storageType == "ram_s2p")
if (storageType == "ram_1p_bram")
accessNum = 1;
else if (storageType == "ram_1p")
else if (storageType == "ram_2p_bram")
accessNum = 1;
else if (storageType == "ram_2p" || storageType == "ram_t2p" ||
storageType == "")
else if (storageType == "ram_s2p_bram")
accessNum = 1;
else if (storageType == "ram_t2p_bram")
accessNum = 1;
else
accessNum = 2;
// The rationale here is an undetermined partition access will introduce
@ -401,15 +402,15 @@ unsigned HLSCppEstimator::getResMinII(LoadStoresMap &map) {
}
unsigned minII = 1;
if (storageType == "ram_s2p")
if (storageType == "ram_s2p_bram")
minII = max({minII, *std::max_element(readNum.begin(), readNum.end()),
*std::max_element(writeNum.begin(), writeNum.end())});
else if (storageType == "ram_1p")
else if (storageType == "ram_1p_bram")
for (unsigned i = 0, e = partitionNum; i < e; ++i)
minII = max(minII, readNum[i] + writeNum[i]);
else if (storageType == "ram_2p" || storageType == "ram_t2p" ||
else if (storageType == "ram_2p_bram" || storageType == "ram_t2p_bram" ||
storageType == "")
for (unsigned i = 0, e = partitionNum; i < e; ++i)
minII = max(minII, (readNum[i] + writeNum[i] + 1) / 2);
@ -672,7 +673,7 @@ void HLSCppEstimator::estimateFunc() {
// setScheduleValue(op, latency - end, latency - begin);
// });
} else
setAttrValue(func, "latency", -1);
setAttrValue(func, "latency", std::string("unknown"));
}
//===----------------------------------------------------------------------===//

View File

@ -86,7 +86,7 @@ void ConvertToHLSCpp::runOnOperation() {
// an AssignOp, it will always not be annotated as interface. This
// is acceptable because AssignOp is only used to handle some weird
// corner cases that rarely happen.
if (!arrayOp.getAttr("interface") && func.getName() == topFunction) {
if (!arrayOp.interface() && func.getName() == topFunction) {
// Only if when the array is an block arguments or a returned
// value, it will be annotated as interface.
bool interfaceFlag =
@ -99,10 +99,13 @@ void ConvertToHLSCpp::runOnOperation() {
} else
arrayOp.setAttr("interface", builder.getBoolAttr(false));
if (!arrayOp.getAttr("storage"))
arrayOp.setAttr("storage", builder.getBoolAttr(false));
if (!arrayOp.storage()) {
arrayOp.setAttr("storage", builder.getBoolAttr(true));
arrayOp.setAttr("storage_type",
builder.getStringAttr("ram_1p_bram"));
}
if (!arrayOp.getAttr("partition"))
if (!arrayOp.partition())
arrayOp.setAttr("partition", builder.getBoolAttr(false));
}
}

View File

@ -210,6 +210,7 @@ private:
void emitArrayDecl(Value array);
unsigned emitNestedLoopHead(Value val);
void emitNestedLoopTail(unsigned rank);
void emitInfoAndNewLine(Operation *op);
/// MLIR component emitters.
void emitOperation(Operation *op);
@ -527,16 +528,16 @@ void ModuleEmitter::emitScfFor(scf::ForOp *op) {
emitValue(iterVar);
os << " += ";
emitValue(op->step());
os << ") {\n";
os << ") {";
emitInfoAndNewLine(*op);
addIndent();
if (auto pipeline = op->getAttrOfType<BoolAttr>("pipeline")) {
indent();
if (pipeline.getValue())
if (pipeline.getValue()) {
indent();
os << "#pragma HLS pipeline\n";
else
os << "#pragma HLS pipeline off\n";
}
}
// if (auto flatten = op->getAttrOfType<BoolAttr>("flatten")) {
@ -578,7 +579,9 @@ void ModuleEmitter::emitScfIf(scf::IfOp *op) {
indent();
os << "if (";
emitValue(op->condition());
os << ") {\n";
os << ") {";
emitInfoAndNewLine(*op);
addIndent();
emitBlock(op->thenRegion().front());
reduceIndent();
@ -609,7 +612,8 @@ void ModuleEmitter::emitScfYield(scf::YieldOp *op) {
emitValue(result, rank);
os << " = ";
emitValue(op->getOperand(resultIdx++), rank);
os << ";\n";
os << ";";
emitInfoAndNewLine(*op);
emitNestedLoopTail(rank);
}
}
@ -663,16 +667,16 @@ void ModuleEmitter::emitAffineFor(AffineForOp *op) {
// Emit increase step.
emitValue(iterVar);
os << " += " << op->getStep() << ") {\n";
os << " += " << op->getStep() << ") {";
emitInfoAndNewLine(*op);
addIndent();
if (auto pipeline = op->getAttrOfType<BoolAttr>("pipeline")) {
indent();
if (pipeline.getValue())
if (pipeline.getValue()) {
indent();
os << "#pragma HLS pipeline\n";
else
os << "#pragma HLS pipeline off\n";
}
}
// if (auto flatten = op->getAttrOfType<BoolAttr>("flatten")) {
@ -729,7 +733,9 @@ void ModuleEmitter::emitAffineIf(AffineIfOp *op) {
if (constrIdx++ != constrSet.getNumConstraints() - 1)
os << " && ";
}
os << ") {\n";
os << ") {";
emitInfoAndNewLine(*op);
addIndent();
emitBlock(*op->getThenBlock());
reduceIndent();
@ -799,8 +805,12 @@ void ModuleEmitter::emitAffineParallel(AffineParallelOp *op) {
reduceIndent();
indent();
os << "}\n";
if (i == e - 1)
os << "}";
else
os << "}\n";
}
emitInfoAndNewLine(*op);
}
void ModuleEmitter::emitAffineApply(AffineApplyOp *op) {
@ -810,7 +820,8 @@ void ModuleEmitter::emitAffineApply(AffineApplyOp *op) {
auto affineMap = op->getAffineMap();
AffineExprEmitter(state, affineMap.getNumDims(), op->getOperands())
.emitAffineExpr(affineMap.getResult(0));
os << ";\n";
os << ";";
emitInfoAndNewLine(*op);
}
template <typename OpType>
@ -829,7 +840,8 @@ void ModuleEmitter::emitAffineMaxMin(OpType *op, const char *syntax) {
affineEmitter.emitAffineExpr(expr);
os << ")";
}
os << ";\n";
os << ";";
emitInfoAndNewLine(*op);
}
void ModuleEmitter::emitAffineLoad(AffineLoadOp *op) {
@ -845,7 +857,8 @@ void ModuleEmitter::emitAffineLoad(AffineLoadOp *op) {
affineEmitter.emitAffineExpr(index);
os << "]";
}
os << ";\n";
os << ";";
emitInfoAndNewLine(*op);
}
void ModuleEmitter::emitAffineStore(AffineStoreOp *op) {
@ -861,7 +874,8 @@ void ModuleEmitter::emitAffineStore(AffineStoreOp *op) {
}
os << " = ";
emitValue(op->getValueToStore());
os << ";\n";
os << ";";
emitInfoAndNewLine(*op);
}
void ModuleEmitter::emitAffineVectorLoad(AffineVectorLoadOp *op) {
@ -890,7 +904,8 @@ void ModuleEmitter::emitAffineYield(AffineYieldOp *op) {
emitValue(result, rank);
os << " = ";
emitValue(op->getOperand(resultIdx++), rank);
os << ";\n";
os << ";";
emitInfoAndNewLine(*op);
emitNestedLoopTail(rank);
}
} else if (auto parentOp =
@ -916,7 +931,8 @@ void ModuleEmitter::emitAffineYield(AffineYieldOp *op) {
emitValue(result, rank);
os << " = ";
emitValue(op->getOperand(resultIdx++), rank);
os << ";\n";
os << ";";
emitInfoAndNewLine(*op);
emitNestedLoopTail(rank);
}
reduceIndent();
@ -970,7 +986,8 @@ void ModuleEmitter::emitAffineYield(AffineYieldOp *op) {
emitValue(op->getOperand(resultIdx++), rank);
break;
}
os << ";\n";
os << ";";
emitInfoAndNewLine(*op);
emitNestedLoopTail(rank);
}
reduceIndent();
@ -993,7 +1010,8 @@ template <typename OpType> void ModuleEmitter::emitAlloc(OpType *op) {
indent();
emitArrayDecl(op->getResult());
os << ";\n";
os << ";";
emitInfoAndNewLine(*op);
}
void ModuleEmitter::emitLoad(LoadOp *op) {
@ -1006,7 +1024,8 @@ void ModuleEmitter::emitLoad(LoadOp *op) {
emitValue(index);
os << "]";
}
os << ";\n";
os << ";";
emitInfoAndNewLine(*op);
}
void ModuleEmitter::emitStore(StoreOp *op) {
@ -1019,7 +1038,8 @@ void ModuleEmitter::emitStore(StoreOp *op) {
}
os << " = ";
emitValue(op->getValueToStore());
os << ";\n";
os << ";";
emitInfoAndNewLine(*op);
}
/// Tensor-related statement emitters.
@ -1029,7 +1049,8 @@ void ModuleEmitter::emitTensorLoad(TensorLoadOp *op) {
emitValue(op->getResult(), rank);
os << " = ";
emitValue(op->getOperand(), rank);
os << ";\n";
os << ";";
emitInfoAndNewLine(*op);
emitNestedLoopTail(rank);
}
@ -1039,7 +1060,8 @@ void ModuleEmitter::emitTensorStore(TensorStoreOp *op) {
emitValue(op->getOperand(1), rank);
os << " = ";
emitValue(op->getOperand(0), rank);
os << ";\n";
os << ";";
emitInfoAndNewLine(*op);
emitNestedLoopTail(rank);
}
@ -1065,7 +1087,8 @@ void ModuleEmitter::emitDim(DimOp *op) {
indent();
emitValue(op->getResult());
os << " = ";
os << type.getShape()[constVal] << ";\n";
os << type.getShape()[constVal] << ";";
emitInfoAndNewLine(*op);
} else
emitError(*op, "index is out of range.");
} else
@ -1080,7 +1103,8 @@ void ModuleEmitter::emitRank(RankOp *op) {
indent();
emitValue(op->getResult());
os << " = ";
os << type.getRank() << ";\n";
os << type.getRank() << ";";
emitInfoAndNewLine(*op);
} else
emitError(*op, "is unranked.");
}
@ -1094,7 +1118,8 @@ void ModuleEmitter::emitBinary(Operation *op, const char *syntax) {
emitValue(op->getOperand(0), rank);
os << " " << syntax << " ";
emitValue(op->getOperand(1), rank);
os << ";\n";
os << ";";
emitInfoAndNewLine(op);
emitNestedLoopTail(rank);
}
@ -1104,7 +1129,8 @@ void ModuleEmitter::emitUnary(Operation *op, const char *syntax) {
emitValue(op->getResult(0), rank);
os << " = " << syntax << "(";
emitValue(op->getOperand(0), rank);
os << ");\n";
os << ");";
emitInfoAndNewLine(op);
emitNestedLoopTail(rank);
}
@ -1123,7 +1149,8 @@ void ModuleEmitter::emitSelect(SelectOp *op) {
emitValue(op->getTrueValue(), rank);
os << " : ";
emitValue(op->getFalseValue(), rank);
os << ";\n";
os << ";";
emitInfoAndNewLine(*op);
emitNestedLoopTail(rank);
}
@ -1154,7 +1181,8 @@ void ModuleEmitter::emitConstant(ConstantOp *op) {
if (elementIdx++ != denseAttr.getNumElements() - 1)
os << ", ";
}
os << "};\n";
os << "};";
emitInfoAndNewLine(*op);
} else
emitError(*op, "has unsupported constant type.");
}
@ -1164,7 +1192,8 @@ void ModuleEmitter::emitIndexCast(IndexCastOp *op) {
emitValue(op->getResult());
os << " = ";
emitValue(op->getOperand());
os << ";\n";
os << ";";
emitInfoAndNewLine(*op);
}
void ModuleEmitter::emitCall(CallOp *op) {
@ -1204,7 +1233,8 @@ void ModuleEmitter::emitCall(CallOp *op) {
emitValue(result);
}
os << ");\n";
os << ");";
emitInfoAndNewLine(*op);
}
/// Structure operation emitters.
@ -1214,17 +1244,17 @@ void ModuleEmitter::emitAssign(AssignOp *op) {
emitValue(op->getResult(), rank);
os << " = ";
emitValue(op->getOperand(), rank);
os << ";\n";
os << ";";
emitInfoAndNewLine(*op);
emitNestedLoopTail(rank);
}
void ModuleEmitter::emitArray(ArrayOp *op) {
bool emitPragmaFlag = false;
// Emit interface pragma.
if (op->interface()) {
emitPragmaFlag = true;
// Emit interface pragma.
indent();
os << "#pragma HLS interface";
os << " " << op->interface_mode();
@ -1233,21 +1263,21 @@ void ModuleEmitter::emitArray(ArrayOp *op) {
if (op->interface_mode() == "m_axi") {
os << " depth=" << op->interface_depth();
os << " offset=slave";
} else if (op->storage())
os << " storage_type=" << op->storage_type();
}
os << "\n";
}
// Emit resource pragma.
if (op->storage()) {
emitPragmaFlag = true;
indent();
os << "#pragma HLS resource";
os << " variable=";
emitValue(op->getOperand());
os << " core=";
os << op->storage_type();
os << "\n";
}
// This branch is prepared for 2020.1 or higher version.
// else {
// Emit bind_storage pragma.
// indent();
// os << "#pragma HLS bind_storage";
// os << " variable=";
// emitValue(op->getOperand());
// os << " type=" << op->storage_type();
// os << " impl=" << op->storage_impl() << "\n";
//}
auto type = op->getOperand().getType().cast<ShapedType>();
if (op->partition() && type.hasStaticShape()) {
@ -1370,6 +1400,17 @@ void ModuleEmitter::emitNestedLoopTail(unsigned rank) {
}
}
void ModuleEmitter::emitInfoAndNewLine(Operation *op) {
os << "\t//";
if (auto loc = op->getLoc().dyn_cast<FileLineColLoc>())
os << " #L" << loc.getLine();
if (auto begin = op->getAttrOfType<IntegerAttr>("schedule_begin"))
os << ", [" << begin.getUInt();
if (auto end = op->getAttrOfType<IntegerAttr>("schedule_end"))
os << ", " << end.getUInt() << ")";
os << "\n";
}
/// MLIR component emitters.
void ModuleEmitter::emitOperation(Operation *op) {
if (ExprVisitor(*this).dispatchVisitor(op))
@ -1393,6 +1434,12 @@ void ModuleEmitter::emitFunction(FuncOp func) {
if (func.getBlocks().size() != 1)
emitError(func, "has zero or more than one basic blocks.");
if (auto top = func.getAttrOfType<BoolAttr>("top_function"))
os << "/// This is top function.\n";
if (auto latency = func.getAttrOfType<IntegerAttr>("latency"))
os << "/// Function latency is " << latency.getUInt() << ".\n";
// Emit function signature.
os << "void " << func.getName() << "(\n";
addIndent();
@ -1434,7 +1481,8 @@ void ModuleEmitter::emitFunction(FuncOp func) {
emitError(func, "doesn't have a return operation as terminator.");
reduceIndent();
os << "\n) {\n";
os << "\n) {";
emitInfoAndNewLine(func);
// Emit function body.
addIndent();

View File

@ -3,11 +3,11 @@
// CHECK-LABEL: func @test_conversion(
// CHECK-SAME: %arg0: f32, %arg1: memref<16xf32>) -> (f32, memref<16xf32>, i32, tensor<2x2xi32>) attributes {dataflow = false, top_function = true} {
func @test_conversion(%arg0: f32, %arg1: memref<16xf32>) -> (f32, memref<16xf32>, i32, tensor<2x2xi32>) {
// CHECK: %[[VAL_0:.*]] = "hlscpp.array"(%[[ARG_1:.*]]) {interface = true, partition = false, storage = false} : (memref<16xf32>) -> memref<16xf32>
// CHECK: %[[VAL_0:.*]] = "hlscpp.array"(%[[ARG_1:.*]]) {interface = true, partition = false, storage = true, storage_type = "ram_1p_bram"} : (memref<16xf32>) -> memref<16xf32>
%c11_i32 = constant 11 : i32
%cst = constant dense<[[11, 0], [0, -42]]> : tensor<2x2xi32>
// CHECK: %[[VAL_1:.*]] = "hlscpp.array"(%cst) {interface = false, partition = false, storage = false} : (tensor<2x2xi32>) -> tensor<2x2xi32>
// CHECK: %[[VAL_1:.*]] = "hlscpp.array"(%cst) {interface = false, partition = false, storage = true, storage_type = "ram_1p_bram"} : (tensor<2x2xi32>) -> tensor<2x2xi32>
// CHECK: %[[VAL_2:.*]] = "hlscpp.assign"(%arg0) : (f32) -> f32
// CHECK: %[[VAL_3:.*]] = "hlscpp.assign"(%[[VAL_0:.*]]) : (memref<16xf32>) -> memref<16xf32>
// CHECK: %[[VAL_4:.*]] = "hlscpp.assign"(%c11_i32) : (i32) -> i32