[Passes] small bugs fixed; remove HLSCppAnalyzer; insert-pipeline-pragma to loop-pipelining

This commit is contained in:
Hanchen Ye 2020-12-14 13:45:13 -06:00
parent c24b418c4a
commit 3f6d8e8560
13 changed files with 109 additions and 199 deletions

View File

@ -7,7 +7,7 @@
include "mlir/Pass/PassBase.td"
def QoREstimation : Pass<"qor-estimation", "FuncOp"> {
def QoREstimation : Pass<"qor-estimation", "ModuleOp"> {
let summary = "Estimate the performance and resource utilization";
let description = [{
This qor-estimation pass will analyze the input CDFG and pragma operations
@ -21,6 +21,8 @@ def QoREstimation : Pass<"qor-estimation", "FuncOp"> {
Option<"targetSpec", "target-spec", "std::string",
/*default=*/"\"../config/target-spec.ini\"",
"File path: target backend specifications and configurations">,
Option<"topFunction", "top-function", "std::string", /*default=*/"",
"The top function for HLS synthesis">
];
}

View File

@ -16,7 +16,7 @@ namespace mlir {
namespace scalehls {
//===----------------------------------------------------------------------===//
// HLSCppToolBase Class Declaration
// HLSCppToolBase Class Declaration and Definition
//===----------------------------------------------------------------------===//
class HLSCppToolBase {
@ -99,26 +99,6 @@ public:
OpBuilder &builder;
};
//===----------------------------------------------------------------------===//
// HLSCppAnalyzer Class Declaration
//===----------------------------------------------------------------------===//
class HLSCppAnalyzer : public HLSCppVisitorBase<HLSCppAnalyzer, bool>,
public HLSCppToolBase {
public:
explicit HLSCppAnalyzer(OpBuilder &builder) : HLSCppToolBase(builder) {}
bool visitUnhandledOp(Operation *op) { return true; }
using HLSCppVisitorBase::visitOp;
bool visitOp(ArrayOp op);
bool visitOp(AffineForOp op);
bool visitOp(AffineIfOp op);
void analyzeBlock(Block &block);
void analyzeFunc(FuncOp func);
};
//===----------------------------------------------------------------------===//
// HLSCppEstimator Class Declaration
//===----------------------------------------------------------------------===//

View File

@ -17,7 +17,7 @@ def ConvertToHLSCpp : Pass<"convert-to-hlscpp", "FuncOp"> {
let constructor = "mlir::scalehls::createConvertToHLSCppPass()";
let options = [
Option<"TopFunction", "top-function", "std::string", /*default=*/"\"main\"",
Option<"topFunction", "top-function", "std::string", /*default=*/"\"main\"",
"The top function for HLS synthesis">,
];
}

View File

@ -17,7 +17,7 @@ namespace scalehls {
/// Pragma optimization passes.
std::unique_ptr<mlir::Pass> createPragmaDSEPass();
std::unique_ptr<mlir::Pass> createInsertPipelinePragmaPass();
std::unique_ptr<mlir::Pass> createLoopPipeliningPass();
std::unique_ptr<mlir::Pass> createArrayPartitionPass();
/// Loop optimization passes.

View File

@ -35,17 +35,17 @@ def ArrayPartition : Pass<"array-partition", "FuncOp"> {
let constructor = "mlir::scalehls::createArrayPartitionPass()";
}
def InsertPipelinePragma : Pass<"insert-pipeline-pragma", "FuncOp"> {
let summary = "Insert pipeline pragma";
def LoopPipelining : Pass<"loop-pipelining", "FuncOp"> {
let summary = "Apply loop pipelining";
let description = [{
This insert-pipeline-pragma pass will insert pipeline pragma to the
innermost loop level.
This loop-pipelining pass will insert pipeline pragma to the innermost n-th
loop level, and automatically unroll all inner loops.
}];
let constructor = "mlir::scalehls::createInsertPipelinePragmaPass()";
let constructor = "mlir::scalehls::createLoopPipeliningPass()";
let options = [
Option<"insertLevel", "insert-level", "unsigned", /*default=*/"1",
Option<"pipelineLevel", "pipeline-level", "unsigned", /*default=*/"1",
"Positive number: loop level to be pipelined (from innermost)">
];
}

View File

@ -7,6 +7,7 @@
#include "Dialect/HLSCpp/HLSCpp.h"
#include "mlir/Analysis/AffineAnalysis.h"
#include "mlir/Analysis/AffineStructures.h"
#include "mlir/Analysis/LoopAnalysis.h"
#include "mlir/IR/Operation.h"
#include "mlir/IR/PatternMatch.h"
#include "mlir/Transforms/GreedyPatternRewriteDriver.h"
@ -16,107 +17,6 @@ using namespace mlir;
using namespace scalehls;
using namespace hlscpp;
//===----------------------------------------------------------------------===//
// HLSCppAnalyzer Class Definition
//===----------------------------------------------------------------------===//
bool HLSCppAnalyzer::visitOp(ArrayOp op) {
unsigned factor = 1;
if (getBoolAttrValue(op, "partition")) {
for (unsigned i = 0, e = op.getType().cast<ShapedType>().getRank(); i < e;
++i)
factor *= getPartitionFactor(op, i);
}
setAttrValue(op, "partition_num", factor);
return true;
}
bool HLSCppAnalyzer::visitOp(AffineForOp op) {
// If the current loop is annotated as unroll, all inner loops and itself are
// automatically unrolled.
if (getBoolAttrValue(op, "unroll")) {
op.walk([&](AffineForOp forOp) {
if (forOp.getLoopBody().getBlocks().size() != 1)
op.emitError("has zero or more than one basic blocks.");
if (failed(loopUnrollFull(forOp))) {
forOp.emitError("failed to be fully unrolled.");
return;
}
});
return true;
}
// If the current loop is annotated as pipeline, all intter loops are
// automatically unrolled.
if (getBoolAttrValue(op, "pipeline")) {
op.walk([&](AffineForOp forOp) {
if (forOp != op) {
if (forOp.getLoopBody().getBlocks().size() != 1)
op.emitError("has zero or more than one basic blocks.");
if (failed(loopUnrollFull(forOp))) {
forOp.emitError("failed to be fully unrolled.");
return;
}
}
});
}
// We assume loop contains a single basic block.
auto &body = op.getLoopBody();
if (body.getBlocks().size() != 1)
op.emitError("has zero or more than one basic blocks.");
// Recursively analyze all inner loops.
analyzeBlock(body.front());
// Set an attribute indicating the trip count. For now, we assume all loops
// have static loop bound.
if (!op.hasConstantLowerBound() || !op.hasConstantUpperBound())
op.emitError("has variable upper or lower bound.");
unsigned tripCount =
(op.getConstantUpperBound() - op.getConstantLowerBound()) / op.getStep();
setAttrValue(op, "trip_count", tripCount);
// Set attributes indicating this loop can be flatten or not.
unsigned opNum = 0;
unsigned forNum = 0;
bool innerFlatten = false;
for (auto &bodyOp : body.front()) {
if (!isa<AffineYieldOp>(bodyOp))
opNum += 1;
if (isa<AffineForOp>(bodyOp)) {
forNum += 1;
innerFlatten = getBoolAttrValue(&bodyOp, "flatten");
}
}
if (forNum == 0 || (opNum == 1 && innerFlatten))
setAttrValue(op, "flatten", true);
else
setAttrValue(op, "flatten", false);
return true;
}
bool HLSCppAnalyzer::visitOp(AffineIfOp op) { return true; }
void HLSCppAnalyzer::analyzeBlock(Block &block) {
for (auto &op : block) {
if (dispatchVisitor(&op))
continue;
op.emitError("can't be correctly analyzed.");
}
}
void HLSCppAnalyzer::analyzeFunc(FuncOp func) {
if (func.getBlocks().size() != 1)
func.emitError("has zero or more than one basic blocks.");
analyzeBlock(func.front());
}
//===----------------------------------------------------------------------===//
// HLSCppEstimator Class Definition
//===----------------------------------------------------------------------===//
@ -505,6 +405,48 @@ void HLSCppEstimator::estimateFunc(FuncOp func) {
if (func.getBlocks().size() != 1)
func.emitError("has zero or more than one basic blocks.");
// Extract all static parameters and current pragma configurations.
func.walk([&](ArrayOp op) {
unsigned factor = 1;
if (getBoolAttrValue(op, "partition")) {
for (unsigned i = 0, e = op.getType().cast<ShapedType>().getRank(); i < e;
++i)
factor *= getPartitionFactor(op, i);
}
setAttrValue(op, "partition_num", factor);
});
func.walk([&](AffineForOp op) {
// We assume loop contains a single basic block.
auto &body = op.getLoopBody();
if (body.getBlocks().size() != 1)
op.emitError("has zero or more than one basic blocks.");
// Set an attribute indicating the trip count. For now, we assume all
// loops have static loop bound.
unsigned tripCount = getConstantTripCount(op).getValue();
setAttrValue(op, "trip_count", tripCount);
// Set attributes indicating this loop can be flatten or not.
unsigned opNum = 0;
unsigned forNum = 0;
bool innerFlatten = false;
for (auto &bodyOp : body.front()) {
if (!isa<AffineYieldOp>(bodyOp))
opNum += 1;
if (isa<AffineForOp>(bodyOp)) {
forNum += 1;
innerFlatten = getBoolAttrValue(&bodyOp, "flatten");
}
}
if (forNum == 0 || (opNum == 1 && innerFlatten))
setAttrValue(op, "flatten", true);
else
setAttrValue(op, "flatten", false);
});
estimateBlock(func.front());
MemAccessDict dict;
@ -521,25 +463,13 @@ void HLSCppEstimator::estimateFunc(FuncOp func) {
namespace {
struct QoREstimation : public scalehls::QoREstimationBase<QoREstimation> {
void runOnOperation() override {
auto builder = OpBuilder(getOperation());
// Extract all static parameters and current pragma configurations.
HLSCppAnalyzer analyzer(builder);
analyzer.analyzeFunc(getOperation());
// Canonicalize the analyzed IR.
OwningRewritePatternList patterns;
auto *context = &getContext();
for (auto *op : context->getRegisteredOperations())
op->getCanonicalizationPatterns(patterns, context);
Operation *op = getOperation();
applyPatternsAndFoldGreedily(op->getRegions(), std::move(patterns));
auto module = getOperation();
auto builder = OpBuilder(module);
// Estimate performance and resource utilization.
HLSCppEstimator estimator(builder, targetSpec);
estimator.estimateFunc(getOperation());
for (auto func : module.getOps<FuncOp>())
estimator.estimateFunc(func);
}
};
} // namespace

View File

@ -29,7 +29,7 @@ void ConvertToHLSCpp::runOnOperation() {
if (!func.getAttr("dataflow"))
func.setAttr("dataflow", builder.getBoolAttr(false));
if (func.getName() == TopFunction)
if (func.getName() == topFunction)
func.setAttr("top_function", builder.getBoolAttr(true));
else
func.setAttr("top_function", builder.getBoolAttr(false));
@ -87,7 +87,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.getAttr("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 =

View File

@ -111,8 +111,7 @@ void ArrayPartition::runOnOperation() {
// Apply array partition.
for (auto forOp : func.getOps<mlir::AffineForOp>()) {
auto outermost = getPipelineLoop(forOp);
if (auto outermost = getPipelineLoop(forOp)) {
// Collect memory access information.
MemAccessDict loadDict;
outermost.walk([&](mlir::AffineLoadOp loadOp) {
@ -131,6 +130,7 @@ void ArrayPartition::runOnOperation() {
applyArrayPartition<mlir::AffineStoreOp>(storeDict, builder);
}
}
}
std::unique_ptr<mlir::Pass> scalehls::createArrayPartitionPass() {
return std::make_unique<ArrayPartition>();

View File

@ -14,13 +14,12 @@ using namespace mlir;
using namespace scalehls;
namespace {
struct InsertPipelinePragma
: public InsertPipelinePragmaBase<InsertPipelinePragma> {
struct LoopPipelining : public LoopPipeliningBase<LoopPipelining> {
void runOnOperation() override;
};
} // namespace
void InsertPipelinePragma::runOnOperation() {
void LoopPipelining::runOnOperation() {
auto func = getOperation();
auto builder = OpBuilder(func);
@ -30,8 +29,8 @@ void InsertPipelinePragma::runOnOperation() {
forOp.walk([&](mlir::AffineForOp loop) { nestedLoops.push_back(loop); });
auto targetLoop = nestedLoops.back();
if (nestedLoops.size() > insertLevel)
targetLoop = *std::next(nestedLoops.begin(), insertLevel);
if (nestedLoops.size() > pipelineLevel)
targetLoop = *std::next(nestedLoops.begin(), pipelineLevel);
targetLoop.setAttr("pipeline", builder.getBoolAttr(true));
@ -53,6 +52,6 @@ void InsertPipelinePragma::runOnOperation() {
std::move(patterns));
}
std::unique_ptr<mlir::Pass> scalehls::createInsertPipelinePragmaPass() {
return std::make_unique<InsertPipelinePragma>();
std::unique_ptr<mlir::Pass> scalehls::createLoopPipeliningPass() {
return std::make_unique<LoopPipelining>();
}

View File

@ -40,7 +40,6 @@ void RemoveVarLoopBound::runOnOperation() {
// For now, only if the variable bound is the induction variable of
// one of the outer loops, the removal is possible.
unsigned idx = 0;
if (auto valOwner = getForInductionVarOwner(val)) {
if (valOwner.hasConstantUpperBound()) {
// Set new constant loop bound.

View File

@ -34,10 +34,10 @@ alp=-affine-loop-perfection
rvb=-remove-var-loop-bound
par=-array-partition
p0=-insert-pipeline-pragma="insert-level=0"
p1=-insert-pipeline-pragma="insert-level=1"
p2=-insert-pipeline-pragma="insert-level=2"
p3=-insert-pipeline-pragma="insert-level=3"
p0=-loop-pipelining="pipeline-level=0"
p1=-loop-pipelining="pipeline-level=1"
p2=-loop-pipelining="pipeline-level=2"
p3=-loop-pipelining="pipeline-level=3"
u1=-affine-loop-unroll="unroll-full unroll-num-reps=1"
u2=-affine-loop-unroll="unroll-full unroll-num-reps=2"
@ -96,32 +96,32 @@ do
16) scalehls-opt $hta $alp $rvb $cth="$name" "$p3" $par $can $file | scalehls-translate $emit -o $output ;;
# Apply ... + 1st-level loop tiling + pipeline.
17) scalehls-opt $hta $alp $rvb "$t1s2" $cth="$name" "$p1" "$u1" $can $file | scalehls-translate $emit -o $output ;;
18) scalehls-opt $hta $alp $rvb "$t1s4" $cth="$name" "$p1" "$u1" $can $file | scalehls-translate $emit -o $output ;;
19) scalehls-opt $hta $alp $rvb "$t1s8" $cth="$name" "$p1" "$u1" $can $file | scalehls-translate $emit -o $output ;;
17) scalehls-opt $hta $alp $rvb "$t1s2" $cth="$name" "$p1" $can $file | scalehls-translate $emit -o $output ;;
18) scalehls-opt $hta $alp $rvb "$t1s4" $cth="$name" "$p1" $can $file | scalehls-translate $emit -o $output ;;
19) scalehls-opt $hta $alp $rvb "$t1s8" $cth="$name" "$p1" $can $file | scalehls-translate $emit -o $output ;;
# Apply ... + 1st-level loop tiling + pipeline + array partition.
20) scalehls-opt $hta $alp $rvb "$t1s2" $cth="$name" "$p1" "$u1" $par $can $file | scalehls-translate $emit -o $output ;;
21) scalehls-opt $hta $alp $rvb "$t1s4" $cth="$name" "$p1" "$u1" $par $can $file | scalehls-translate $emit -o $output ;;
22) scalehls-opt $hta $alp $rvb "$t1s8" $cth="$name" "$p1" "$u1" $par $can $file | scalehls-translate $emit -o $output ;;
20) scalehls-opt $hta $alp $rvb "$t1s2" $cth="$name" "$p1" $par $can $file | scalehls-translate $emit -o $output ;;
21) scalehls-opt $hta $alp $rvb "$t1s4" $cth="$name" "$p1" $par $can $file | scalehls-translate $emit -o $output ;;
22) scalehls-opt $hta $alp $rvb "$t1s8" $cth="$name" "$p1" $par $can $file | scalehls-translate $emit -o $output ;;
# Apply ... + 2nd-level loop tiling + pipeline.
23) scalehls-opt $hta $alp $rvb "$t2s2" $cth="$name" "$p2" "$u2" $can $file | scalehls-translate $emit -o $output ;;
24) scalehls-opt $hta $alp $rvb "$t2s4" $cth="$name" "$p2" "$u2" $can $file | scalehls-translate $emit -o $output ;;
25) scalehls-opt $hta $alp $rvb "$t2s8" $cth="$name" "$p2" "$u2" $can $file | scalehls-translate $emit -o $output ;;
23) scalehls-opt $hta $alp $rvb "$t2s2" $cth="$name" "$p2" $can $file | scalehls-translate $emit -o $output ;;
24) scalehls-opt $hta $alp $rvb "$t2s4" $cth="$name" "$p2" $can $file | scalehls-translate $emit -o $output ;;
25) scalehls-opt $hta $alp $rvb "$t2s8" $cth="$name" "$p2" $can $file | scalehls-translate $emit -o $output ;;
# Apply ... + 2nd-level loop tiling + pipeline + array partition.
26) scalehls-opt $hta $alp $rvb "$t2s2" $cth="$name" "$p2" "$u2" $par $can $file | scalehls-translate $emit -o $output ;;
27) scalehls-opt $hta $alp $rvb "$t2s4" $cth="$name" "$p2" "$u2" $par $can $file | scalehls-translate $emit -o $output ;;
28) scalehls-opt $hta $alp $rvb "$t2s8" $cth="$name" "$p2" "$u2" $par $can $file | scalehls-translate $emit -o $output ;;
26) scalehls-opt $hta $alp $rvb "$t2s2" $cth="$name" "$p2" $par $can $file | scalehls-translate $emit -o $output ;;
27) scalehls-opt $hta $alp $rvb "$t2s4" $cth="$name" "$p2" $par $can $file | scalehls-translate $emit -o $output ;;
28) scalehls-opt $hta $alp $rvb "$t2s8" $cth="$name" "$p2" $par $can $file | scalehls-translate $emit -o $output ;;
# Apply ... + 3rd-level loop tiling + pipeline.
29) scalehls-opt $hta $alp $rvb "$t3s2" $cth="$name" "$p3" "$u3" $can $file | scalehls-translate $emit -o $output ;;
30) scalehls-opt $hta $alp $rvb "$t3s4" $cth="$name" "$p3" "$u3" $can $file | scalehls-translate $emit -o $output ;;
29) scalehls-opt $hta $alp $rvb "$t3s2" $cth="$name" "$p3" $can $file | scalehls-translate $emit -o $output ;;
30) scalehls-opt $hta $alp $rvb "$t3s4" $cth="$name" "$p3" $can $file | scalehls-translate $emit -o $output ;;
# Apply ... + 3rd-level loop tiling + pipeline + array partition.
31) scalehls-opt $hta $alp $rvb "$t3s2" $cth="$name" "$p3" "$u3" $par $can $file | scalehls-translate $emit -o $output ;;
32) scalehls-opt $hta $alp $rvb "$t3s4" $cth="$name" "$p3" "$u3" $par $can $file | scalehls-translate $emit -o $output ;;
31) scalehls-opt $hta $alp $rvb "$t3s2" $cth="$name" "$p3" $par $can $file | scalehls-translate $emit -o $output ;;
32) scalehls-opt $hta $alp $rvb "$t3s4" $cth="$name" "$p3" $par $can $file | scalehls-translate $emit -o $output ;;
esac
done

View File

@ -1,6 +0,0 @@
// RUN: scalehls-opt -insert-pipeline-pragma %s | FileCheck %s
// CHECK-LABEL: func @test_for
func @test_for() {
return
}

View File

@ -0,0 +1,6 @@
// RUN: scalehls-opt -loop-pipelining %s | FileCheck %s
// CHECK-LABEL: func @test_for
func @test_for() {
return
}