Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Pipeline] Extend Pipeline Dialect's pipeline-schedule-linear pass to support more types of scheduling problems #8209

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions include/circt/Dialect/Pipeline/PipelinePasses.td
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,20 @@ def ScheduleLinearPipeline : Pass<"pipeline-schedule-linear"> {
}];
let dependentDialects = ["hw::HWDialect", "seq::SeqDialect"];
let constructor = "circt::pipeline::createScheduleLinearPipelinePass()";

let options = [
Option<"problemType", "problem-type", "std::string",
/*default=*/"\"base\"",
"Select between base problem and chaining problem ">,

Option<"cycleTime", "cycle-time", "float",
/*default=*/"1.0",
"Cycle time, only applicable when chaining problem is selected ">,

Option<"initInterval", "init-interval", "int",
/*default=*/"1",
"II">,
];
}


Expand Down
112 changes: 100 additions & 12 deletions lib/Dialect/Pipeline/Transforms/ScheduleLinearPipeline.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -15,17 +15,23 @@
#include "circt/Dialect/HW/HWOpInterfaces.h"
#include "circt/Dialect/Pipeline/PipelineOps.h"
#include "circt/Dialect/Pipeline/PipelinePasses.h"
#include "circt/Dialect/SSP/SSPAttributes.h"
#include "circt/Dialect/SSP/SSPOps.h"
#include "circt/Dialect/SSP/Utilities.h"
#include "circt/Scheduling/Algorithms.h"
#include "circt/Scheduling/Problems.h"
#include "circt/Scheduling/Utilities.h"
#include "circt/Support/BackedgeBuilder.h"
#include "mlir/Pass/Pass.h"
#include "llvm/Support/raw_ostream.h"
#include <optional>
#include <type_traits>

#define DEBUG_TYPE "pipeline-schedule-linear"

namespace circt {
namespace pipeline {
#define GEN_PASS_DECL_SCHEDULELINEARPIPELINE
#define GEN_PASS_DEF_SCHEDULELINEARPIPELINE
#include "circt/Dialect/Pipeline/PipelinePasses.h.inc"
} // namespace pipeline
Expand All @@ -45,7 +51,11 @@ class ScheduleLinearPipelinePass
void runOnOperation() override;

private:
LogicalResult schedulePipeline(UnscheduledPipelineOp pipeline);
template <typename ProblemTy>
LogicalResult
schedulePipeline(UnscheduledPipelineOp pipeline,
std::optional<float> cycleTime = std::nullopt,
std::optional<int> initiationInterval = std::nullopt);
};

} // end anonymous namespace
Expand All @@ -55,8 +65,10 @@ static bool ignoreOp(Operation *op) {
return op->hasTrait<OpTrait::ConstantLike>();
}

LogicalResult
ScheduleLinearPipelinePass::schedulePipeline(UnscheduledPipelineOp pipeline) {
template <typename ProblemTy>
LogicalResult ScheduleLinearPipelinePass::schedulePipeline(
UnscheduledPipelineOp pipeline, std::optional<float> cycleTime,
std::optional<int> initiationInterval) {
// Get operator library for the pipeline - assume it's placed in the top level
// module.
auto opLibAttr = pipeline->getAttrOfType<FlatSymbolRefAttr>("operator_lib");
Expand All @@ -69,9 +81,9 @@ ScheduleLinearPipelinePass::schedulePipeline(UnscheduledPipelineOp pipeline) {
<< opLibAttr << "' not found";

// Load operator info from attribute.
Problem problem(pipeline);
ProblemTy problem(pipeline);

DenseMap<SymbolRefAttr, Problem::OperatorType> operatorTypes;
DenseMap<SymbolRefAttr, typename ProblemTy::OperatorType> operatorTypes;
SmallDenseMap<StringAttr, unsigned> oprIds;

// Set operation operator types.
Expand All @@ -82,13 +94,20 @@ ScheduleLinearPipelinePass::schedulePipeline(UnscheduledPipelineOp pipeline) {
if (ignoreOp(&op))
continue;

Problem::OperatorType operatorType;
typename ProblemTy::OperatorType operatorType;
bool isReturnOp = &op == returnOp.getOperation();
if (isReturnOp) {
// Construct an operator type for the return op (not an externally defined
// operator type since it is intrinsic to this pass).
operatorType = problem.getOrInsertOperatorType("return");
problem.setLatency(operatorType, 0);

// set delay to 0 for return op in chaining related problems
if constexpr (std::is_same_v<ProblemTy, ChainingProblem> ||
std::is_same_v<ProblemTy, ChainingCyclicProblem>) {
problem.setIncomingDelay(operatorType, 0);
problem.setOutgoingDelay(operatorType, 0);
}
} else {
// Lookup operator info.
auto operatorTypeAttr =
Expand All @@ -106,14 +125,45 @@ ScheduleLinearPipelinePass::schedulePipeline(UnscheduledPipelineOp pipeline) {
return op.emitError() << "Operator type '" << operatorTypeAttr
<< "' not found in operator library.";

auto insertRes = operatorTypes.try_emplace(
operatorTypeAttr, ssp::loadOperatorType<Problem, ssp::LatencyAttr>(
problem, opTypeOp, oprIds));
operatorTypeIt = insertRes.first;
if constexpr (std::is_same_v<ProblemTy, Problem> ||
std::is_same_v<ProblemTy, CyclicProblem>) {
auto insertRes = operatorTypes.try_emplace(
operatorTypeAttr,
ssp::loadOperatorType<ProblemTy, ssp::LatencyAttr>(
problem, opTypeOp, oprIds));
operatorTypeIt = insertRes.first;
}

else if constexpr (std::is_same_v<ProblemTy, SharedOperatorsProblem> ||
std::is_same_v<ProblemTy, ModuloProblem>) {
auto insertRes = operatorTypes.try_emplace(
operatorTypeAttr,
ssp::loadOperatorType<ProblemTy, ssp::LatencyAttr,
ssp::LimitAttr>(problem, opTypeOp, oprIds));
operatorTypeIt = insertRes.first;
}

else if constexpr (std::is_same_v<ProblemTy, ChainingProblem> ||
std::is_same_v<ProblemTy, ChainingCyclicProblem>) {
auto insertRes = operatorTypes.try_emplace(
operatorTypeAttr,
ssp::loadOperatorType<ProblemTy, ssp::LatencyAttr,
ssp::IncomingDelayAttr,
ssp::OutgoingDelayAttr>(problem, opTypeOp,
oprIds));
operatorTypeIt = insertRes.first;
}
}
operatorType = operatorTypeIt->second;
}

// set initiation interval for relevant problems
if constexpr (std::is_same_v<ProblemTy, CyclicProblem> ||
std::is_same_v<ProblemTy, ModuloProblem> ||
std::is_same_v<ProblemTy, ChainingCyclicProblem>) {
problem.setInitiationInterval(initiationInterval.value());
}

problem.insertOperation(&op);
problem.setLinkedOperatorType(&op, operatorType);

Expand All @@ -130,7 +180,21 @@ ScheduleLinearPipelinePass::schedulePipeline(UnscheduledPipelineOp pipeline) {

// Solve!
assert(succeeded(problem.check()));
if (failed(scheduling::scheduleSimplex(problem, returnOp.getOperation())))

LogicalResult result = failure();
if constexpr (std::is_same_v<ProblemTy, Problem> ||
std::is_same_v<ProblemTy, CyclicProblem> ||
std::is_same_v<ProblemTy, SharedOperatorsProblem> ||
std::is_same_v<ProblemTy, ModuloProblem>) {
result = scheduling::scheduleSimplex(problem, returnOp.getOperation());
}
if constexpr (std::is_same_v<ProblemTy, ChainingProblem> ||
std::is_same_v<ProblemTy, ChainingCyclicProblem>) {
result = scheduling::scheduleSimplex(problem, returnOp.getOperation(),
cycleTime.value());
}

if (failed(result))
return pipeline.emitError("Failed to schedule pipeline.");

assert(succeeded(problem.verify()));
Expand Down Expand Up @@ -214,8 +278,32 @@ void ScheduleLinearPipelinePass::runOnOperation() {
for (auto &region : getOperation()->getRegions()) {
for (auto pipeline :
llvm::make_early_inc_range(region.getOps<UnscheduledPipelineOp>())) {
if (failed(schedulePipeline(pipeline)))

if (this->problemType == "base") {
if (failed(schedulePipeline<Problem>(pipeline)))
return signalPassFailure();
} else if (this->problemType == "cyclic") {
if (failed(schedulePipeline<CyclicProblem>(pipeline, std::nullopt,
this->initInterval)))
return signalPassFailure();
} else if (this->problemType == "shared_operators") {
if (failed(schedulePipeline<SharedOperatorsProblem>(pipeline)))
return signalPassFailure();
} else if (this->problemType == "modulo") {
if (failed(schedulePipeline<ModuloProblem>(pipeline, std::nullopt,
this->initInterval)))
return signalPassFailure();
} else if (this->problemType == "chaining") {
if (failed(
schedulePipeline<ChainingProblem>(pipeline, this->cycleTime)))
return signalPassFailure();
} else if (this->problemType == "cyclic_chaining") {
if (failed(schedulePipeline<ChainingCyclicProblem>(pipeline,
this->cycleTime)))
return signalPassFailure();
} else {
return signalPassFailure();
}
}
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
// RUN: circt-opt --pass-pipeline='builtin.module(any(pipeline-schedule-linear{problem-type="chaining" cycle-time=1}))' %s | FileCheck %s

// CHECK-LABEL: hw.module @pipeline(
// CHECK-SAME: in %[[VAL_0:.*]] : i32, in %[[VAL_1:.*]] : i32, in %[[GO:.*]] : i1, in %[[CLOCK:.*]] : !seq.clock, in %[[RESET:.*]] : i1, out out : i32) {
// CHECK: %[[VAL_5:.*]], %[[VAL_6:.*]] = pipeline.scheduled(%[[VAL_7:.*]] : i32 = %[[VAL_0]], %[[VAL_8:.*]] : i32 = %[[VAL_1]]) clock(%[[CLOCK]]) reset(%[[RESET]]) go(%[[GO]]) entryEn(%[[VAL_9:.*]]) -> (out : i32) {
// CHECK: %[[VAL_10:.*]] = comb.add %[[VAL_7]], %[[VAL_8]] {ssp.operator_type = @add1} : i32
// CHECK: %[[VAL_11:.*]] = comb.mul %[[VAL_7]], %[[VAL_10]] {ssp.operator_type = @mul2} : i32

module {
ssp.library @lib {
operator_type @add1 [latency<0>, incDelay<0.33>, outDelay<0.33>]
operator_type @mul2 [latency<0>, incDelay<0.66>, outDelay<0.66>]
}

hw.module @pipeline(in %arg0 : i32, in %arg1 : i32, in %go : i1, in %clk : !seq.clock, in %rst : i1, out out: i32) {
%0:2 = pipeline.unscheduled(%a0 : i32 = %arg0, %a1 : i32 = %arg1) clock(%clk) reset(%rst) go(%go) entryEn(%s0_enable)
{operator_lib = @lib} -> (out: i32) {
%0 = comb.add %a0, %a1 {ssp.operator_type = @add1} : i32
%1 = comb.mul %a0, %0 {ssp.operator_type = @mul2} : i32
%2 = comb.add %a1, %a0 {ssp.operator_type = @add1} : i32
%3 = comb.add %1, %2 {ssp.operator_type = @add1} : i32
pipeline.return %0 : i32
}
hw.output %0#0 : i32
}
}

Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
// RUN: circt-opt --pass-pipeline='builtin.module(any(pipeline-schedule-linear{problem-type="shared_operators"}))' %s | FileCheck %s

// CHECK-LABEL: hw.module @pipeline(
// CHECK-SAME: in %[[VAL_0:.*]] : i32, in %[[VAL_1:.*]] : i32, in %[[GO:.*]] : i1, in %[[CLOCK:.*]] : !seq.clock, in %[[RESET:.*]] : i1, out out : i32) {
// CHECK: %[[VAL_5:.*]], %[[VAL_6:.*]] = pipeline.scheduled(%[[VAL_7:.*]] : i32 = %[[VAL_0]], %[[VAL_8:.*]] : i32 = %[[VAL_1]]) clock(%[[CLOCK]]) reset(%[[RESET]]) go(%[[GO]]) entryEn(%[[VAL_9:.*]]) -> (out : i32) {
// CHECK: %[[VAL_10:.*]] = comb.add %[[VAL_7]], %[[VAL_8]] {ssp.operator_type = @add1} : i32
// CHECK: pipeline.stage ^bb1

ssp.library @lib {
operator_type @add1 [latency<2>, limit<1>]
operator_type @mul2 [latency<3>]
}

hw.module @pipeline(in %arg0 : i32, in %arg1 : i32, in %go : i1, in %clk : !seq.clock, in %rst : i1, out out: i32) {
%0:2 = pipeline.unscheduled(%a0 : i32 = %arg0, %a1 : i32 = %arg1) clock(%clk) reset(%rst) go(%go) entryEn(%s0_enable)
{operator_lib = @lib} -> (out: i32) {
%0 = comb.add %a0, %a1 {ssp.operator_type = @add1} : i32
%1 = comb.mul %a0, %0 {ssp.operator_type = @mul2} : i32
%2 = comb.add %a1, %a0 {ssp.operator_type = @add1} : i32
%3 = comb.add %1, %2 {ssp.operator_type = @add1} : i32
pipeline.return %3 : i32
}
hw.output %0#0 : i32
}

14 changes: 10 additions & 4 deletions test/Dialect/Pipeline/Transforms/schedule-linear-pipeline.mlir
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
// RUN: circt-opt --pass-pipeline='builtin.module(any(pipeline-schedule-linear))' %s | FileCheck %s

// CHECK-LABEL: hw.module @pipeline(
// CHECK-SAME: in %[[VAL_0:.*]] : i32, in %[[VAL_1:.*]] : i32, in %[[GO:.*]] : i1, in %[[CLOCK:.*]] : !seq.clock, in %[[RESET:.*]] : i1, out out : i32) {
// RUN: circt-opt --pass-pipeline='builtin.module(any(pipeline-schedule-linear))' %s | FileCheck %s
// RUN: circt-opt --pass-pipeline='builtin.module(any(pipeline-schedule-linear{problem-type="cyclic" cycle-time=1}))' %s | FileCheck %s
// RUN: circt-opt --pass-pipeline='builtin.module(any(pipeline-schedule-linear{problem-type="modulo" init-interval=1}))' %s | FileCheck %s
// RUN: circt-opt --pass-pipeline='builtin.module(any(pipeline-schedule-linear{problem-type="shared_operators" init-interval=4}))' %s | FileCheck %s

// CHECK-LABEL: hw.module @pipeline(
// CHECK-SAME: in %[[VAL_0:.*]] : i32, in %[[VAL_1:.*]] : i32, in %[[GO:.*]] : i1, in %[[CLOCK:.*]] : !seq.clock, in %[[RESET:.*]] : i1, out out : i32) {
// CHECK: %[[VAL_5:.*]], %[[VAL_6:.*]] = pipeline.scheduled(%[[VAL_7:.*]] : i32 = %[[VAL_0]], %[[VAL_8:.*]] : i32 = %[[VAL_1]]) clock(%[[CLOCK]]) reset(%[[RESET]]) go(%[[GO]]) entryEn(%[[VAL_9:.*]]) -> (out : i32) {
// CHECK: %[[VAL_10:.*]] = comb.add %[[VAL_7]], %[[VAL_8]] {ssp.operator_type = @add1} : i32
// CHECK: %[[VAL_11:.*]] = comb.add %[[VAL_8]], %[[VAL_7]] {ssp.operator_type = @add1} : i32
Expand Down Expand Up @@ -44,3 +47,6 @@ module {
hw.output %0#0 : i32
}
}