diff --git a/include/Analysis/Passes.td b/include/Analysis/Passes.td index bf7ddf0..72b57f2 100644 --- a/include/Analysis/Passes.td +++ b/include/Analysis/Passes.td @@ -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"> ]; } diff --git a/include/Analysis/QoREstimation.h b/include/Analysis/QoREstimation.h index 4d933d6..9db9262 100644 --- a/include/Analysis/QoREstimation.h +++ b/include/Analysis/QoREstimation.h @@ -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, - 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 //===----------------------------------------------------------------------===// diff --git a/include/Conversion/Passes.td b/include/Conversion/Passes.td index ca6945d..e049d07 100644 --- a/include/Conversion/Passes.td +++ b/include/Conversion/Passes.td @@ -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">, ]; } diff --git a/include/Transforms/Passes.h b/include/Transforms/Passes.h index f9bade8..8def5b8 100644 --- a/include/Transforms/Passes.h +++ b/include/Transforms/Passes.h @@ -17,7 +17,7 @@ namespace scalehls { /// Pragma optimization passes. std::unique_ptr createPragmaDSEPass(); -std::unique_ptr createInsertPipelinePragmaPass(); +std::unique_ptr createLoopPipeliningPass(); std::unique_ptr createArrayPartitionPass(); /// Loop optimization passes. diff --git a/include/Transforms/Passes.td b/include/Transforms/Passes.td index b6d9695..5678f0b 100644 --- a/include/Transforms/Passes.td +++ b/include/Transforms/Passes.td @@ -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)"> ]; } diff --git a/lib/Analysis/QoREstimation.cpp b/lib/Analysis/QoREstimation.cpp index 74007b5..bc37b8e 100644 --- a/lib/Analysis/QoREstimation.cpp +++ b/lib/Analysis/QoREstimation.cpp @@ -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().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(bodyOp)) - opNum += 1; - if (isa(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().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(bodyOp)) + opNum += 1; + if (isa(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 { 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()) + estimator.estimateFunc(func); } }; } // namespace diff --git a/lib/Conversion/ConvertToHLSCpp.cpp b/lib/Conversion/ConvertToHLSCpp.cpp index 6d84093..ba4c0bc 100644 --- a/lib/Conversion/ConvertToHLSCpp.cpp +++ b/lib/Conversion/ConvertToHLSCpp.cpp @@ -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 = diff --git a/lib/Transforms/ArrayPartition.cpp b/lib/Transforms/ArrayPartition.cpp index 501e4a4..13e537e 100644 --- a/lib/Transforms/ArrayPartition.cpp +++ b/lib/Transforms/ArrayPartition.cpp @@ -111,24 +111,24 @@ void ArrayPartition::runOnOperation() { // Apply array partition. for (auto forOp : func.getOps()) { - auto outermost = getPipelineLoop(forOp); + if (auto outermost = getPipelineLoop(forOp)) { + // Collect memory access information. + MemAccessDict loadDict; + outermost.walk([&](mlir::AffineLoadOp loadOp) { + auto arrayOp = cast(loadOp.getMemRef().getDefiningOp()); + loadDict[arrayOp].push_back(loadOp); + }); - // Collect memory access information. - MemAccessDict loadDict; - outermost.walk([&](mlir::AffineLoadOp loadOp) { - auto arrayOp = cast(loadOp.getMemRef().getDefiningOp()); - loadDict[arrayOp].push_back(loadOp); - }); + MemAccessDict storeDict; + outermost.walk([&](mlir::AffineStoreOp storeOp) { + auto arrayOp = cast(storeOp.getMemRef().getDefiningOp()); + storeDict[arrayOp].push_back(storeOp); + }); - MemAccessDict storeDict; - outermost.walk([&](mlir::AffineStoreOp storeOp) { - auto arrayOp = cast(storeOp.getMemRef().getDefiningOp()); - storeDict[arrayOp].push_back(storeOp); - }); - - // Apply array partition pragma. - applyArrayPartition(loadDict, builder); - applyArrayPartition(storeDict, builder); + // Apply array partition pragma. + applyArrayPartition(loadDict, builder); + applyArrayPartition(storeDict, builder); + } } } diff --git a/lib/Transforms/InsertPipelinePragma.cpp b/lib/Transforms/LoopPipelining.cpp similarity index 80% rename from lib/Transforms/InsertPipelinePragma.cpp rename to lib/Transforms/LoopPipelining.cpp index a4019d0..b8c4a11 100644 --- a/lib/Transforms/InsertPipelinePragma.cpp +++ b/lib/Transforms/LoopPipelining.cpp @@ -14,13 +14,12 @@ using namespace mlir; using namespace scalehls; namespace { -struct InsertPipelinePragma - : public InsertPipelinePragmaBase { +struct LoopPipelining : public LoopPipeliningBase { 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 scalehls::createInsertPipelinePragmaPass() { - return std::make_unique(); +std::unique_ptr scalehls::createLoopPipeliningPass() { + return std::make_unique(); } diff --git a/lib/Transforms/RemoveVarLoopBound.cpp b/lib/Transforms/RemoveVarLoopBound.cpp index e848127..504ae49 100644 --- a/lib/Transforms/RemoveVarLoopBound.cpp +++ b/lib/Transforms/RemoveVarLoopBound.cpp @@ -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. diff --git a/samples/ablation_test_run.sh b/samples/ablation_test_run.sh index 16c46e9..c28ad6c 100755 --- a/samples/ablation_test_run.sh +++ b/samples/ablation_test_run.sh @@ -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 diff --git a/test/Transforms/test_insert_pipeline_pragma.mlir b/test/Transforms/test_insert_pipeline_pragma.mlir deleted file mode 100644 index 007a77c..0000000 --- a/test/Transforms/test_insert_pipeline_pragma.mlir +++ /dev/null @@ -1,6 +0,0 @@ -// RUN: scalehls-opt -insert-pipeline-pragma %s | FileCheck %s - -// CHECK-LABEL: func @test_for -func @test_for() { - return -} diff --git a/test/Transforms/test_loop_pipelining.mlir b/test/Transforms/test_loop_pipelining.mlir new file mode 100644 index 0000000..cca1645 --- /dev/null +++ b/test/Transforms/test_loop_pipelining.mlir @@ -0,0 +1,6 @@ +// RUN: scalehls-opt -loop-pipelining %s | FileCheck %s + +// CHECK-LABEL: func @test_for +func @test_for() { + return +}