[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" 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 summary = "Estimate the performance and resource utilization";
let description = [{ let description = [{
This qor-estimation pass will analyze the input CDFG and pragma operations 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", Option<"targetSpec", "target-spec", "std::string",
/*default=*/"\"../config/target-spec.ini\"", /*default=*/"\"../config/target-spec.ini\"",
"File path: target backend specifications and configurations">, "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 { namespace scalehls {
//===----------------------------------------------------------------------===// //===----------------------------------------------------------------------===//
// HLSCppToolBase Class Declaration // HLSCppToolBase Class Declaration and Definition
//===----------------------------------------------------------------------===// //===----------------------------------------------------------------------===//
class HLSCppToolBase { class HLSCppToolBase {
@ -99,26 +99,6 @@ public:
OpBuilder &builder; 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 // HLSCppEstimator Class Declaration
//===----------------------------------------------------------------------===// //===----------------------------------------------------------------------===//

View File

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

View File

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

View File

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

View File

@ -7,6 +7,7 @@
#include "Dialect/HLSCpp/HLSCpp.h" #include "Dialect/HLSCpp/HLSCpp.h"
#include "mlir/Analysis/AffineAnalysis.h" #include "mlir/Analysis/AffineAnalysis.h"
#include "mlir/Analysis/AffineStructures.h" #include "mlir/Analysis/AffineStructures.h"
#include "mlir/Analysis/LoopAnalysis.h"
#include "mlir/IR/Operation.h" #include "mlir/IR/Operation.h"
#include "mlir/IR/PatternMatch.h" #include "mlir/IR/PatternMatch.h"
#include "mlir/Transforms/GreedyPatternRewriteDriver.h" #include "mlir/Transforms/GreedyPatternRewriteDriver.h"
@ -16,107 +17,6 @@ using namespace mlir;
using namespace scalehls; using namespace scalehls;
using namespace hlscpp; 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 // HLSCppEstimator Class Definition
//===----------------------------------------------------------------------===// //===----------------------------------------------------------------------===//
@ -505,6 +405,48 @@ void HLSCppEstimator::estimateFunc(FuncOp func) {
if (func.getBlocks().size() != 1) if (func.getBlocks().size() != 1)
func.emitError("has zero or more than one basic blocks."); 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()); estimateBlock(func.front());
MemAccessDict dict; MemAccessDict dict;
@ -521,25 +463,13 @@ void HLSCppEstimator::estimateFunc(FuncOp func) {
namespace { namespace {
struct QoREstimation : public scalehls::QoREstimationBase<QoREstimation> { struct QoREstimation : public scalehls::QoREstimationBase<QoREstimation> {
void runOnOperation() override { void runOnOperation() override {
auto builder = OpBuilder(getOperation()); auto module = getOperation();
auto builder = OpBuilder(module);
// 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));
// Estimate performance and resource utilization. // Estimate performance and resource utilization.
HLSCppEstimator estimator(builder, targetSpec); HLSCppEstimator estimator(builder, targetSpec);
estimator.estimateFunc(getOperation()); for (auto func : module.getOps<FuncOp>())
estimator.estimateFunc(func);
} }
}; };
} // namespace } // namespace

View File

@ -29,7 +29,7 @@ void ConvertToHLSCpp::runOnOperation() {
if (!func.getAttr("dataflow")) if (!func.getAttr("dataflow"))
func.setAttr("dataflow", builder.getBoolAttr(false)); func.setAttr("dataflow", builder.getBoolAttr(false));
if (func.getName() == TopFunction) if (func.getName() == topFunction)
func.setAttr("top_function", builder.getBoolAttr(true)); func.setAttr("top_function", builder.getBoolAttr(true));
else else
func.setAttr("top_function", builder.getBoolAttr(false)); 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 // an AssignOp, it will always not be annotated as interface. This
// is acceptable because AssignOp is only used to handle some weird // is acceptable because AssignOp is only used to handle some weird
// corner cases that rarely happen. // 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 // Only if when the array is an block arguments or a returned
// value, it will be annotated as interface. // value, it will be annotated as interface.
bool interfaceFlag = bool interfaceFlag =

View File

@ -111,24 +111,24 @@ void ArrayPartition::runOnOperation() {
// Apply array partition. // Apply array partition.
for (auto forOp : func.getOps<mlir::AffineForOp>()) { 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) {
auto arrayOp = cast<ArrayOp>(loadOp.getMemRef().getDefiningOp());
loadDict[arrayOp].push_back(loadOp);
});
// Collect memory access information. MemAccessDict storeDict;
MemAccessDict loadDict; outermost.walk([&](mlir::AffineStoreOp storeOp) {
outermost.walk([&](mlir::AffineLoadOp loadOp) { auto arrayOp = cast<ArrayOp>(storeOp.getMemRef().getDefiningOp());
auto arrayOp = cast<ArrayOp>(loadOp.getMemRef().getDefiningOp()); storeDict[arrayOp].push_back(storeOp);
loadDict[arrayOp].push_back(loadOp); });
});
MemAccessDict storeDict; // Apply array partition pragma.
outermost.walk([&](mlir::AffineStoreOp storeOp) { applyArrayPartition<mlir::AffineLoadOp>(loadDict, builder);
auto arrayOp = cast<ArrayOp>(storeOp.getMemRef().getDefiningOp()); applyArrayPartition<mlir::AffineStoreOp>(storeDict, builder);
storeDict[arrayOp].push_back(storeOp); }
});
// Apply array partition pragma.
applyArrayPartition<mlir::AffineLoadOp>(loadDict, builder);
applyArrayPartition<mlir::AffineStoreOp>(storeDict, builder);
} }
} }

View File

@ -14,13 +14,12 @@ using namespace mlir;
using namespace scalehls; using namespace scalehls;
namespace { namespace {
struct InsertPipelinePragma struct LoopPipelining : public LoopPipeliningBase<LoopPipelining> {
: public InsertPipelinePragmaBase<InsertPipelinePragma> {
void runOnOperation() override; void runOnOperation() override;
}; };
} // namespace } // namespace
void InsertPipelinePragma::runOnOperation() { void LoopPipelining::runOnOperation() {
auto func = getOperation(); auto func = getOperation();
auto builder = OpBuilder(func); auto builder = OpBuilder(func);
@ -30,8 +29,8 @@ void InsertPipelinePragma::runOnOperation() {
forOp.walk([&](mlir::AffineForOp loop) { nestedLoops.push_back(loop); }); forOp.walk([&](mlir::AffineForOp loop) { nestedLoops.push_back(loop); });
auto targetLoop = nestedLoops.back(); auto targetLoop = nestedLoops.back();
if (nestedLoops.size() > insertLevel) if (nestedLoops.size() > pipelineLevel)
targetLoop = *std::next(nestedLoops.begin(), insertLevel); targetLoop = *std::next(nestedLoops.begin(), pipelineLevel);
targetLoop.setAttr("pipeline", builder.getBoolAttr(true)); targetLoop.setAttr("pipeline", builder.getBoolAttr(true));
@ -53,6 +52,6 @@ void InsertPipelinePragma::runOnOperation() {
std::move(patterns)); std::move(patterns));
} }
std::unique_ptr<mlir::Pass> scalehls::createInsertPipelinePragmaPass() { std::unique_ptr<mlir::Pass> scalehls::createLoopPipeliningPass() {
return std::make_unique<InsertPipelinePragma>(); 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 // For now, only if the variable bound is the induction variable of
// one of the outer loops, the removal is possible. // one of the outer loops, the removal is possible.
unsigned idx = 0;
if (auto valOwner = getForInductionVarOwner(val)) { if (auto valOwner = getForInductionVarOwner(val)) {
if (valOwner.hasConstantUpperBound()) { if (valOwner.hasConstantUpperBound()) {
// Set new constant loop bound. // Set new constant loop bound.

View File

@ -34,10 +34,10 @@ alp=-affine-loop-perfection
rvb=-remove-var-loop-bound rvb=-remove-var-loop-bound
par=-array-partition par=-array-partition
p0=-insert-pipeline-pragma="insert-level=0" p0=-loop-pipelining="pipeline-level=0"
p1=-insert-pipeline-pragma="insert-level=1" p1=-loop-pipelining="pipeline-level=1"
p2=-insert-pipeline-pragma="insert-level=2" p2=-loop-pipelining="pipeline-level=2"
p3=-insert-pipeline-pragma="insert-level=3" p3=-loop-pipelining="pipeline-level=3"
u1=-affine-loop-unroll="unroll-full unroll-num-reps=1" u1=-affine-loop-unroll="unroll-full unroll-num-reps=1"
u2=-affine-loop-unroll="unroll-full unroll-num-reps=2" 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 ;; 16) scalehls-opt $hta $alp $rvb $cth="$name" "$p3" $par $can $file | scalehls-translate $emit -o $output ;;
# Apply ... + 1st-level loop tiling + pipeline. # Apply ... + 1st-level loop tiling + pipeline.
17) scalehls-opt $hta $alp $rvb "$t1s2" $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" "$u1" $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" "$u1" $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. # 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 ;; 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" "$u1" $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" "$u1" $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. # Apply ... + 2nd-level loop tiling + pipeline.
23) scalehls-opt $hta $alp $rvb "$t2s2" $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" "$u2" $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" "$u2" $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. # 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 ;; 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" "$u2" $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" "$u2" $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. # Apply ... + 3rd-level loop tiling + pipeline.
29) scalehls-opt $hta $alp $rvb "$t3s2" $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" "$u3" $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. # 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 ;; 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" "$u3" $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 esac
done 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
}