[Scheduling] Define modulo scheduling problem. (#1886)

This commit is contained in:
Julian Oppermann 2021-09-28 10:08:44 +02:00 committed by GitHub
parent caa1f0be41
commit bff070fa32
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 164 additions and 4 deletions

View File

@ -287,6 +287,28 @@ protected:
virtual LogicalResult verifyOperatorType(OperatorType opr) override;
};
/// This class models the modulo scheduling problem as the composition of the
/// cyclic problem and the resource-constrained problem with fully-pipelined
/// shared operators.
///
/// A solution to this problem comprises an integer II and integer start times
/// for all registered operations, and is feasible iff:
/// (1) The precedence constraints implied by the `CyclicProblem`'s dependence
/// edges are satisfied, and
/// (2) The number of operations that use a certain limited operator type,
/// and start in the same congruence class (= start time *mod* II), does
/// not exceed the operator type's limit.
class ModuloProblem : public virtual CyclicProblem,
public virtual SharedPipelinedOperatorsProblem {
public:
ModuloProblem(Operation *containingOp)
: Problem(containingOp), CyclicProblem(containingOp),
SharedPipelinedOperatorsProblem(containingOp) {}
protected:
virtual LogicalResult verifyOperatorType(OperatorType opr) override;
};
} // namespace scheduling
} // namespace circt

View File

@ -213,6 +213,35 @@ SharedPipelinedOperatorsProblem::verifyOperatorType(OperatorType opr) {
return success();
}
//===----------------------------------------------------------------------===//
// ModuloProblem
//===----------------------------------------------------------------------===//
LogicalResult ModuloProblem::verifyOperatorType(OperatorType opr) {
// fail early if II is not set or invalid
if (!getInitiationInterval() || *getInitiationInterval() == 0)
return getContainingOp()->emitError("Invalid initiation interval");
auto limit = getLimit(opr);
if (!limit)
return success();
unsigned ii = *getInitiationInterval();
llvm::SmallDenseMap<unsigned, unsigned> nOpsPerCongruenceClass;
for (auto *op : getOperations())
if (opr == *getLinkedOperatorType(op))
++nOpsPerCongruenceClass[*getStartTime(op) % ii];
for (auto &kv : nOpsPerCongruenceClass)
if (kv.second > *limit)
return getContainingOp()->emitError()
<< "Operator type '" << opr << "' is oversubscribed."
<< "\n congruence class: " << kv.first
<< "\n #operations: " << kv.second << "\n limit: " << *limit;
return success();
}
//===----------------------------------------------------------------------===//
// Dependence
//===----------------------------------------------------------------------===//

View File

@ -86,8 +86,6 @@ static void constructProblem(Problem &prob, FuncOp func) {
}
static void constructCyclicProblem(CyclicProblem &prob, FuncOp func) {
constructProblem(prob, func);
// parse auxiliary dependences in the testcase (again), in order to set the
// optional distance in the cyclic problem
if (auto attr = func->getAttrOfType<ArrayAttr>("auxdeps")) {
@ -105,8 +103,6 @@ static void constructCyclicProblem(CyclicProblem &prob, FuncOp func) {
static void constructSPOProblem(SharedPipelinedOperatorsProblem &prob,
FuncOp func) {
constructProblem(prob, func);
// parse operator type info (again) to extract optional operator limit
if (auto attr = func->getAttrOfType<ArrayAttr>("operatortypes")) {
for (auto &elem : parseArrayOfDicts(attr, "limit")) {
@ -179,6 +175,7 @@ void TestCyclicProblemPass::runOnFunction() {
auto func = getFunction();
CyclicProblem prob(func);
constructProblem(prob, func);
constructCyclicProblem(prob, func);
if (failed(prob.check())) {
@ -221,6 +218,7 @@ void TestSPOProblemPass::runOnFunction() {
auto func = getFunction();
SharedPipelinedOperatorsProblem prob(func);
constructProblem(prob, func);
constructSPOProblem(prob, func);
if (failed(prob.check())) {
@ -239,6 +237,49 @@ void TestSPOProblemPass::runOnFunction() {
}
}
//===----------------------------------------------------------------------===//
// ModuloProblem
//===----------------------------------------------------------------------===//
namespace {
struct TestModuloProblemPass
: public PassWrapper<TestModuloProblemPass, FunctionPass> {
void runOnFunction() override;
StringRef getArgument() const override { return "test-modulo-problem"; }
StringRef getDescription() const override {
return "Import a solution for the modulo problem encoded as attributes";
}
};
} // namespace
void TestModuloProblemPass::runOnFunction() {
auto func = getFunction();
ModuloProblem prob(func);
constructProblem(prob, func);
constructCyclicProblem(prob, func);
constructSPOProblem(prob, func);
if (failed(prob.check())) {
func->emitError("problem check failed");
return signalPassFailure();
}
// get II from the test case
if (auto attr = func->getAttrOfType<IntegerAttr>("problemInitiationInterval"))
prob.setInitiationInterval(attr.getInt());
// get schedule from the test case
for (auto *op : prob.getOperations())
if (auto startTimeAttr = op->getAttrOfType<IntegerAttr>("problemStartTime"))
prob.setStartTime(op, startTimeAttr.getInt());
if (failed(prob.verify())) {
func->emitError("problem verification failed");
return signalPassFailure();
}
}
//===----------------------------------------------------------------------===//
// ASAPScheduler
//===----------------------------------------------------------------------===//
@ -319,6 +360,7 @@ void TestSimplexSchedulerPass::runOnFunction() {
if (problemToTest == "CyclicProblem") {
CyclicProblem prob(func);
constructProblem(prob, func);
constructCyclicProblem(prob, func);
assert(succeeded(prob.check()));
@ -377,6 +419,9 @@ void registerSchedulingTestPasses() {
mlir::registerPass([]() -> std::unique_ptr<::mlir::Pass> {
return std::make_unique<TestSPOProblemPass>();
});
mlir::registerPass([]() -> std::unique_ptr<::mlir::Pass> {
return std::make_unique<TestModuloProblemPass>();
});
mlir::registerPass([]() -> std::unique_ptr<::mlir::Pass> {
return std::make_unique<TestASAPSchedulerPass>();
});

View File

@ -0,0 +1,14 @@
// RUN: circt-opt %s -test-modulo-problem -verify-diagnostics -split-input-file
// expected-error@+2 {{Operator type 'limited' is oversubscribed}}
// expected-error@+1 {{problem verification failed}}
func @oversubscribed(%a0 : i32, %a1 : i32, %a2 : i32) -> i32 attributes {
problemInitiationInterval = 2,
operatortypes = [ { name = "limited", latency = 1, limit = 2} ]
} {
%0 = addi %a0, %a0 { problemStartTime = 0 } : i32
%1 = addi %a1, %0 { opr = "limited", problemStartTime = 1 } : i32
%2 = addi %0, %a2 { opr = "limited", problemStartTime = 3 } : i32
%3 = addi %0, %0 { opr = "limited", problemStartTime = 5 } : i32
return { problemStartTime = 6 } %3 : i32
}

View File

@ -0,0 +1,50 @@
// RUN: circt-opt %s -test-modulo-problem -allow-unregistered-dialect
func @canis14_fig2() attributes {
problemInitiationInterval = 3,
auxdeps = [ [2,0,1], [3,4] ],
operatortypes = [
{ name = "mem_port", latency = 1, limit = 1 },
{ name = "add", latency = 1 }
] } {
%0 = "dummy.load_A"() { opr = "mem_port", problemStartTime = 2 } : () -> i32
%1 = "dummy.load_B"() { opr = "mem_port", problemStartTime = 0 } : () -> i32
%2 = addi %0, %1 { opr = "add", problemStartTime = 3 } : i32
"dummy.store_A"(%2) { opr = "mem_port", problemStartTime = 4 } : (i32) -> ()
return { problemStartTime = 5 }
}
func @minII_feasible() attributes {
problemInitiationInterval = 3,
auxdeps = [ [6,1,5], [5,2,3], [6,7] ],
operatortypes = [
{ name = "const", latency = 0 },
{ name = "phi", latency = 2 },
{ name = "xor", latency = 1 },
{ name = "sub", latency = 3, limit = 1}
] } {
%0 = constant { opr = "const", problemStartTime = 0 } -1 : i32
%1 = "dummy.phi"() { opr = "phi", problemStartTime = 0 } : () -> i32
%2 = "dummy.phi"() { opr = "phi", problemStartTime = 1 } : () -> i32
%3 = xor %1, %0 { opr = "xor", problemStartTime = 2 } : i32
%4 = subi %3, %2 { opr = "sub", problemStartTime = 3 } : i32
%5 = subi %0, %4 { opr = "sub", problemStartTime = 7 } : i32
%6 = subi %4, %5 { opr = "sub", problemStartTime = 11 } : i32
return { problemStartTime = 14 }
}
func @minII_infeasible() -> i32 attributes {
problemInitiationInterval = 4,
auxdeps = [ [0,1], [5,1,1] ],
operatortypes = [
{ name = "unlimited", latency = 1 },
{ name = "limited", latency = 1, limit = 2 }
] } {
%0 = constant { opr = "unlimited", problemStartTime = 0 } 42 : i32
%1 = "dummy.phi"() { opr = "unlimited", problemStartTime = 1 } : () -> i32
%2 = "dummy.op"(%1) { opr = "limited", problemStartTime = 2 } : (i32) -> i32
%3 = "dummy.op"(%1) { opr = "limited", problemStartTime = 3 } : (i32) -> i32
%4 = "dummy.op"(%1) { opr = "limited", problemStartTime = 2 } : (i32) -> i32
%5 = "dummy.mux"(%2, %3, %4) { opr = "unlimited", problemStartTime = 4 } : (i32, i32, i32) -> i32
return { opr = "unlimited", problemStartTime = 5 } %5 : i32
}