[HLSCppDialect] add array operations, and corresponding modification in HLSCppEmitter, ConverToHLSCpp

This commit is contained in:
Hanchen Ye 2020-10-05 23:26:42 -05:00
parent 3555fb9d2d
commit f3c40918e3
11 changed files with 199 additions and 51 deletions

View File

@ -49,9 +49,17 @@ public:
explicit QoREstimator(ProcParam &procParam, MemParam &memParam,
std::string targetSpecPath, std::string opLatencyPath);
using ScheduleMap = llvm::SmallDenseMap<Operation *, unsigned, 8>;
// For storing the scheduled time stamp of operations;
using ScheduleMap = llvm::SmallDenseMap<Operation *, unsigned, 16>;
// For storing each memory access operations indexed by its targed memory
// value symbol.
using MemAccess = std::pair<Value, Operation *>;
using MemAccessList = SmallVector<MemAccess, 8>;
using MemAccessList = SmallVector<MemAccess, 16>;
// For storing required memory ports for each partition of each array.
using MemPort = SmallVector<unsigned, 16>;
using MemPortMap = llvm::SmallDenseMap<Value, MemPort, 16>;
// This flag indicates that currently the estimator is in a pipelined region,
// which will impact the estimation strategy.

View File

@ -85,7 +85,46 @@ enum class MemParamKind {
/// This class includes all possible parameters kind for "processes" (function,
/// for/parallel loop, and if).
class ProcParam : public ParamBase<ProcParamKind, Operation *> {};
class ProcParam : public ParamBase<ProcParamKind, Operation *> {
// Process-related pragam configurations.
unsigned getEnablePipeline(Operation *op) {
return get(op, ProcParamKind::EnablePipeline);
}
unsigned getUnrollFactor(Operation *op) {
return get(op, ProcParamKind::UnrollFactor);
}
// Process attributes.
unsigned getLowerBound(Operation *op) {
return get(op, ProcParamKind::LowerBound);
}
unsigned getUpperBound(Operation *op) {
return get(op, ProcParamKind::UpperBound);
}
unsigned getIterNumber(Operation *op) {
return get(op, ProcParamKind::IterNumber);
}
unsigned getIsPerfect(Operation *op) {
return get(op, ProcParamKind::IsPerfect);
}
// Performance parameters.
unsigned getInitInterval(Operation *op) {
return get(op, ProcParamKind::InitInterval);
}
unsigned getIterLatency(Operation *op) {
return get(op, ProcParamKind::IterLatency);
}
unsigned getPipeIterNumber(Operation *op) {
return get(op, ProcParamKind::PipeIterNumber);
}
unsigned getLatency(Operation *op) { return get(op, ProcParamKind::Latency); }
// Resource parameters.
unsigned getLUT(Operation *op) { return get(op, ProcParamKind::LUT); }
unsigned getBRAM(Operation *op) { return get(op, ProcParamKind::BRAM); }
unsigned getDSP(Operation *op) { return get(op, ProcParamKind::DSP); }
};
/// This class includes all possible parameters kind for memories (memref,
/// tensor, and vector).

View File

@ -1,13 +1,11 @@
//===-------------------------------------------------------*- tablegen -*-===//
//
// Deprecated
//===----------------------------------------------------------------------===//
#ifndef SCALEHLS_DIALECT_HLSCPP_PRAGMAOPS_TD
#define SCALEHLS_DIALECT_HLSCPP_PRAGMAOPS_TD
def ArrayPragmaOp : HLSCppOp<"array_pragma", [
PragmaOpInterface
]> {
def ArrayPragmaOp : HLSCppOp<"array_pragma", [PragmaOpInterface]> {
let summary = "Apply array pragmas";
let description = [{
This hlscpp.func_pragma operation represent pragmas for arrays, such as

View File

@ -18,6 +18,36 @@ def AssignOp : HLSCppOp<"assign", [SameOperandsAndResultType]> {
let results = (outs AnyType : $output);
}
def ArrayOp : HLSCppOp<"array", [SameOperandsAndResultType]> {
let summary = "A C++ array instance";
let description = [{
This hlscpp.array operation represent an array in C++. All shaped type value
(e.g., memref, tensor, and vector) should be passed through this operation
after declared by an allocation (e.g., Alloc, etc.) operation or in the
signature of a function. This will help the compiler to easily manage the
attributs and statistics of arrays.
}];
let arguments = (ins Type<IsShapedTypePred> : $input,
// Interface-related attributes.
OptionalAttr<BoolAttr> : $interface,
OptionalAttr<InterfaceModeAttr> : $interface_mode,
OptionalAttr<PositiveUI32Attr> : $interface_depth,
// BindStorage-related attributes.
OptionalAttr<StorageTypeAttr> : $storage_type,
OptionalAttr<StorageImplAttr> : $storage_impl,
// ArrayPartition-related attributes.
OptionalAttr<BoolAttr> : $partition,
OptionalAttr<PartitionTypeArrayAttr> : $partition_type,
OptionalAttr<PositiveUI32ArrayAttr> : $partition_factor
);
let results = (outs Type<IsShapedTypePred> : $output);
}
def EndOp : HLSCppOp<"end", [Terminator]> {
let summary = "Mark the end of a HLSCpp region";
let description = [{

View File

@ -47,7 +47,9 @@ public:
AddCFOp, SubCFOp, ImOp, ReOp, CreateComplexOp,
// Special operations.
SelectOp, ConstantOp, CopySignOp, TruncateIOp, ZeroExtendIOp,
SignExtendIOp, IndexCastOp, CallOp, ReturnOp, AssignOp, EndOp,
SignExtendIOp, IndexCastOp, CallOp, ReturnOp,
// Structure operations.
AssignOp, ArrayOp, EndOp,
// Pragma operations.
LoopPragmaOp, FuncPragmaOp, ArrayPragmaOp>(
[&](auto opNode) -> ResultType {
@ -171,7 +173,10 @@ public:
HANDLE(IndexCastOp);
HANDLE(CallOp);
HANDLE(ReturnOp);
// Structure operations.
HANDLE(AssignOp);
HANDLE(ArrayOp);
HANDLE(EndOp);
// Pragma operations.

View File

@ -13,6 +13,10 @@ using namespace mlir;
using namespace scalehls;
using namespace hlscpp;
//===----------------------------------------------------------------------===//
// Utils
//===----------------------------------------------------------------------===//
//===----------------------------------------------------------------------===//
// HLSCppAnalyzer Class Definition
//===----------------------------------------------------------------------===//
@ -252,6 +256,16 @@ bool QoREstimator::visitOp(AffineForOp op) {
unsigned initInterval = 1;
initInterval = getBlockII(body.front(), opScheduleMap, memLoadList,
memStoreList, initInterval);
// Calculate initial interval caused by limited memory ports. For now, we
// just consider the memory access inside of the pipeline region, aks the
// extra memory ports caused by unroll optimization out of the pipeline
// region are not calculated.
MemPortMap memLoadPortMap;
MemPortMap memStorePortMap;
for (auto &op : body.front()) {
}
procParam.set(op, ProcParamKind::InitInterval, initInterval);
procParam.set(op, ProcParamKind::Latency,

View File

@ -15,35 +15,83 @@ namespace {
class ConvertToHLSCppPass
: public mlir::PassWrapper<ConvertToHLSCppPass, OperationPass<ModuleOp>> {
public:
void runOnOperation() override {
for (auto &funcOp : getOperation()) {
if (auto func = dyn_cast<FuncOp>(funcOp)) {
if (func.getBlocks().size() != 1)
func.emitError("has zero or more than one basic blocks");
void runOnOperation() override;
};
} // namespace
if (auto returnOp = dyn_cast<ReturnOp>(func.front().getTerminator())) {
auto builder = OpBuilder(returnOp);
unsigned operandIdx = 0;
for (auto operand : returnOp.getOperands()) {
void ConvertToHLSCppPass::runOnOperation() {
for (auto func : getOperation().getOps<FuncOp>()) {
if (func.getBlocks().size() != 1)
func.emitError("has zero or more than one basic blocks");
if (operand.getKind() == Value::Kind::BlockArgument) {
auto newValue = builder.create<AssignOp>(
returnOp.getLoc(), operand.getType(), operand);
returnOp.setOperand(operandIdx, newValue);
} else if (isa<ConstantOp>(operand.getDefiningOp())) {
auto newValue = builder.create<AssignOp>(
returnOp.getLoc(), operand.getType(), operand);
returnOp.setOperand(operandIdx, newValue);
}
operandIdx += 1;
auto b = OpBuilder(func);
// Insert AssignOp.
if (auto returnOp = dyn_cast<ReturnOp>(func.front().getTerminator())) {
b.setInsertionPoint(returnOp);
unsigned idx = 0;
for (auto operand : returnOp.getOperands()) {
if (operand.getKind() == Value::Kind::BlockArgument) {
auto value =
b.create<AssignOp>(returnOp.getLoc(), operand.getType(), operand);
returnOp.setOperand(idx, value);
} else if (isa<ConstantOp>(operand.getDefiningOp())) {
auto value =
b.create<AssignOp>(returnOp.getLoc(), operand.getType(), operand);
returnOp.setOperand(idx, value);
}
idx += 1;
}
} else
func.emitError("doesn't have a return as terminator.");
// Set function pragma attributes.
func.setAttr("dataflow", b.getBoolAttr(false));
for (auto &op : func.front()) {
if (auto forOp = dyn_cast<AffineForOp>(op)) {
if (forOp.getLoopBody().getBlocks().size() != 1)
forOp.emitError("has zero or more than one basic blocks");
// Set loop pragma attributes.
forOp.setAttr("pipeline", b.getBoolAttr(false));
forOp.setAttr("pipeline_II", b.getUI32IntegerAttr(1));
forOp.setAttr("unroll_factor", b.getUI32IntegerAttr(1));
}
for (auto operand : op.getOperands()) {
if (auto arrayType = operand.getType().dyn_cast<ShapedType>()) {
bool insertArrayOp = false;
if (operand.getKind() == Value::Kind::BlockArgument)
insertArrayOp = true;
else if (!isa<ArrayOp>(operand.getDefiningOp())) {
insertArrayOp = true;
if (!arrayType.hasStaticShape())
operand.getDefiningOp()->emitError(
"is unranked or has dynamic shape which is illegal.");
}
} else
func.emitError("doesn't have a return operation as terminator");
if (insertArrayOp) {
// Insert array operation and set attributes.
b.setInsertionPointAfterValue(operand);
auto arrayOp =
b.create<ArrayOp>(op.getLoc(), operand.getType(), operand);
operand.replaceAllUsesExcept(arrayOp.getResult(),
SmallPtrSet<Operation *, 1>{arrayOp});
// Set array pragma attributes, default array instance is ram_1p
// bram. Other attributes are not set here since they requires more
// analysis to be determined.
arrayOp.setAttr("interface", b.getBoolAttr(false));
arrayOp.setAttr("storage_type", b.getStringAttr("ram_1p"));
arrayOp.setAttr("storage_impl", b.getStringAttr("bram"));
arrayOp.setAttr("partition", b.getBoolAttr(false));
}
}
}
}
}
};
} // namespace
}
void hlscpp::registerConvertToHLSCppPass() {
PassRegistration<ConvertToHLSCppPass>(

View File

@ -175,7 +175,10 @@ public:
void emitConstant(ConstantOp *op);
void emitIndexCast(IndexCastOp *op);
void emitCall(CallOp *op);
/// Structure operations emitters.
void emitAssign(AssignOp *op);
void emitArray(ArrayOp *op);
/// Pragma operation emitters.
void emitLoopPragma(LoopPragmaOp *op);
@ -401,8 +404,6 @@ public:
bool visitOp(IndexCastOp op) { return emitter.emitIndexCast(&op), true; }
bool visitOp(CallOp op) { return emitter.emitCall(&op), true; }
bool visitOp(ReturnOp op) { return true; }
bool visitOp(AssignOp op) { return emitter.emitAssign(&op), true; }
bool visitOp(EndOp op) { return true; }
private:
ModuleEmitter &emitter;
@ -466,6 +467,11 @@ public:
using HLSCppVisitorBase::visitOp;
/// Structure operations.
bool visitOp(AssignOp op) { return emitter.emitAssign(&op), true; }
bool visitOp(ArrayOp op) { return emitter.emitArray(&op), true; }
bool visitOp(EndOp op) { return true; }
/// Pragma operations.
bool visitOp(LoopPragmaOp op) { return emitter.emitLoopPragma(&op), true; }
bool visitOp(FuncPragmaOp op) { return emitter.emitFuncPragma(&op), true; }
@ -1013,6 +1019,7 @@ void ModuleEmitter::emitCall(CallOp *op) {
// TODO
}
/// Structure operation emitters.
void ModuleEmitter::emitAssign(AssignOp *op) {
unsigned rank = emitNestedLoopHead(op->getResult());
indent();
@ -1023,8 +1030,9 @@ void ModuleEmitter::emitAssign(AssignOp *op) {
emitNestedLoopTail(rank);
}
/// Pragma operation emitters.
void ModuleEmitter::emitArray(ArrayOp *op) {}
/// Pragma operation emitters.
void ModuleEmitter::emitLoopPragma(LoopPragmaOp *op) {
indent();
os << "#pragma HLS unroll";

View File

@ -2,15 +2,11 @@
// CHECK-LABEL: func @test_for
func @test_for(%arg0: memref<16x4x4xindex>, %arg1: memref<16x4x4xindex>) {
"hlscpp.func_pragma" () {dataflow = true} : () -> ()
"hlscpp.array_pragma" (%arg0) {partition=true, partition_type=["cyclic", "cyclic", "cyclic"], partition_factor=[4 : ui32, 2 : ui32, 4 : ui32], storage_type="ram_2p", interface=true, interface_mode="bram"} : (memref<16x4x4xindex>) -> ()
"hlscpp.array_pragma" (%arg1) {partition=true, partition_type=["cyclic", "cyclic", "cyclic"], partition_factor=[4 : ui32, 2 : ui32, 4 : ui32], storage_type="ram_2p", interface=true, interface_mode="bram"} : (memref<16x4x4xindex>) -> ()
affine.for %i = 0 to 16 {
"hlscpp.loop_pragma" () {unroll_factor=4 : ui32} : () -> ()
affine.for %j = 0 to 4 {
"hlscpp.loop_pragma" () {pipeline=true, unroll_factor=2 : ui32} : () -> ()
affine.for %k = 0 to 4 {
"hlscpp.loop_pragma" () {unroll_factor=4 : ui32} : () -> ()
%0 = affine.load %arg0[%i, %j, %k] : memref<16x4x4xindex>
%1 = affine.load %arg1[%i, %j, %k] : memref<16x4x4xindex>
%2 = muli %0, %1 : index

View File

@ -1,13 +0,0 @@
// RUN: scalehls-opt -convert-to-hlscpp %s | FileCheck %s
func @test_assign(%arg0: f32, %arg1: memref<16xf32>) -> (f32, memref<16xf32>, i32, tensor<2x2xi32>) {
%c11_i32 = constant 11 : i32
%cst = constant dense<[[11, 0], [0, -42]]> : tensor<2x2xi32>
// CHECK: %[[VAL_0:.*]] = "hlscpp.assign"(%[[ARG_0:.*]]) : (f32) -> f32
// CHECK: %[[VAL_1:.*]] = "hlscpp.assign"(%[[ARG_1:.*]]) : (memref<16xf32>) -> memref<16xf32>
// CHECK: %[[VAL_2:.*]] = "hlscpp.assign"(%c11_i32) : (i32) -> i32
// CHECK: %[[VAL_3:.*]] = "hlscpp.assign"(%cst) : (tensor<2x2xi32>) -> tensor<2x2xi32>
// CHECK: return %[[VAL_0:.*]], %[[VAL_1:.*]], %[[VAL_2:.*]], %[[VAL_3:.*]] : f32, memref<16xf32>, i32, tensor<2x2xi32>
return %arg0, %arg1, %c11_i32, %cst : f32, memref<16xf32>, i32, tensor<2x2xi32>
}

View File

@ -0,0 +1,15 @@
// RUN: scalehls-opt -convert-to-hlscpp %s | FileCheck %s
// CHECK-LABEL: func @test_conversion(
// CHECK-SAME: %arg0: f32, %arg1: memref<16xf32>) -> (f32, memref<16xf32>, i32, tensor<2x2xi32>) attributes {dataflow = false} {
func @test_conversion(%arg0: f32, %arg1: memref<16xf32>) -> (f32, memref<16xf32>, i32, tensor<2x2xi32>) {
// CHECK: %[[VAL_0:.*]] = "hlscpp.array"(%[[ARG_1:.*]]) {interface = false, partition = false, storage_impl = "bram", storage_type = "ram_1p"} : (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_impl = "bram", storage_type = "ram_1p"} : (tensor<2x2xi32>) -> tensor<2x2xi32>
// CHECK: %[[VAL_2:.*]] = "hlscpp.assign"(%[[ARG_0:.*]]) : (f32) -> f32
// CHECK: %[[VAL_2:.*]] = "hlscpp.assign"(%c11_i32) : (i32) -> i32
// CHECK: return %[[VAL_2:.*]], %[[VAL_0:.*]], %[[VAL_3:.*]], %[[VAL_1:.*]] : f32, memref<16xf32>, i32, tensor<2x2xi32>
return %arg0, %arg1, %c11_i32, %cst : f32, memref<16xf32>, i32, tensor<2x2xi32>
}