CIRCT  19.0.0git
ScheduleLinearPipeline.cpp
Go to the documentation of this file.
1 //===- ScheduleLinearPipeline.cpp - Linear pipeline scheduling pass -----*-===//
2 //
3 // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4 // See https://llvm.org/LICENSE.txt for license information.
5 // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6 //
7 //===----------------------------------------------------------------------===//
8 //
9 // Contains the definitions linear pipeline scheduling pass.
10 //
11 //===----------------------------------------------------------------------===//
12 
13 #include "PassDetails.h"
22 
23 #define DEBUG_TYPE "pipeline-schedule-linear"
24 
25 using namespace mlir;
26 using namespace circt;
27 using namespace circt::scheduling;
28 using namespace pipeline;
29 
30 namespace {
31 
32 class ScheduleLinearPipelinePass
33  : public ScheduleLinearPipelineBase<ScheduleLinearPipelinePass> {
34 public:
35  void runOnOperation() override;
36 
37 private:
38  LogicalResult schedulePipeline(UnscheduledPipelineOp pipeline);
39 };
40 
41 } // end anonymous namespace
42 
43 // Returns true if 'op' should be ignored in the scheduling problem.
44 static bool ignoreOp(Operation *op) {
45  return op->hasTrait<OpTrait::ConstantLike>();
46 }
47 
48 LogicalResult
49 ScheduleLinearPipelinePass::schedulePipeline(UnscheduledPipelineOp pipeline) {
50  // Get operator library for the pipeline - assume it's placed in the top level
51  // module.
52  auto opLibAttr = pipeline->getAttrOfType<FlatSymbolRefAttr>("operator_lib");
53  if (!opLibAttr)
54  return pipeline.emitError("missing 'operator_lib' attribute");
55  auto parentModule = pipeline->getParentOfType<ModuleOp>();
56  auto opLib = parentModule.lookupSymbol<ssp::OperatorLibraryOp>(opLibAttr);
57  if (!opLib)
58  return pipeline.emitError("operator library '")
59  << opLibAttr << "' not found";
60 
61  // Load operator info from attribute.
62  auto problem = Problem::get(pipeline);
63 
64  DenseMap<SymbolRefAttr, Problem::OperatorType> operatorTypes;
66 
67  // Set operation operator types.
68  auto returnOp =
69  cast<pipeline::ReturnOp>(pipeline.getEntryStage()->getTerminator());
70  for (auto &op : pipeline.getOps()) {
71  // Skip if is a known non-functional operator
72  if (ignoreOp(&op))
73  continue;
74 
75  Problem::OperatorType operatorType;
76  bool isReturnOp = &op == returnOp.getOperation();
77  if (isReturnOp) {
78  // Construct an operator type for the return op (not an externally defined
79  // operator type since it is intrinsic to this pass).
80  operatorType = problem.getOrInsertOperatorType("return");
81  problem.setLatency(operatorType, 0);
82  } else {
83  // Lookup operator info.
84  auto operatorTypeAttr =
85  op.getAttrOfType<SymbolRefAttr>("ssp.operator_type");
86  if (!operatorTypeAttr)
87  return op.emitError()
88  << "Expected 'ssp.operator_type' attribute on operation.";
89 
90  auto operatorTypeIt = operatorTypes.find(operatorTypeAttr);
91  if (operatorTypeIt == operatorTypes.end()) {
92  // First time seeing operator type - load it into the problem.
93  auto opTypeOp =
94  opLib.lookupSymbol<ssp::OperatorTypeOp>(operatorTypeAttr);
95  if (!opTypeOp)
96  return op.emitError() << "Operator type '" << operatorTypeAttr
97  << "' not found in operator library.";
98 
99  auto insertRes = operatorTypes.try_emplace(
100  operatorTypeAttr, ssp::loadOperatorType<Problem, ssp::LatencyAttr>(
101  problem, opTypeOp, oprIds));
102  operatorTypeIt = insertRes.first;
103  }
104  operatorType = operatorTypeIt->second;
105  }
106 
107  problem.insertOperation(&op);
108  problem.setLinkedOperatorType(&op, operatorType);
109 
110  // We want the return op to be a sink node for the dependence graph, i.e. it
111  // should (transitively) depend on every other op. This is done by inserting
112  // auxiliary dependences from ops without users, complementing the implicit
113  // dependences from the return op's operands.
114  if (!isReturnOp && op.use_empty()) {
115  if (failed(problem.insertDependence({&op, returnOp.getOperation()})))
116  return op.emitError()
117  << "Failed to insert dependence from operation to return op.";
118  }
119  }
120 
121  // Solve!
122  assert(succeeded(problem.check()));
123  if (failed(scheduling::scheduleSimplex(problem, returnOp.getOperation())))
124  return pipeline.emitError("Failed to schedule pipeline.");
125 
126  assert(succeeded(problem.verify()));
127 
128  // Gather stage results.
129  using StageIdx = unsigned;
130 
131  OpBuilder b(pipeline.getContext());
132 
133  // Maintain a mapping of {start time : [operations]}, that contains the
134  // operations scheduled to a given start time. This is an ordered map, so that
135  // we can iterate over the stages in order.
136  std::map<StageIdx, llvm::SmallVector<Operation *>> stageMap;
137  llvm::SmallVector<Operation *, 4> otherOps;
138 
139  // Create the scheduled pipeline.
140  b.setInsertionPoint(pipeline);
141  auto schedPipeline = b.template create<pipeline::ScheduledPipelineOp>(
142  pipeline.getLoc(), pipeline.getDataOutputs().getTypes(),
143  pipeline.getInputs(), pipeline.getInputNames(), pipeline.getOutputNames(),
144  pipeline.getClock(), pipeline.getReset(), pipeline.getGo(),
145  pipeline.getStall(), pipeline.getNameAttr());
146 
147  Block *currentStage = schedPipeline.getStage(0);
148 
149  for (auto [oldBArg, newBArg] :
150  llvm::zip(pipeline.getEntryStage()->getArguments(),
151  currentStage->getArguments()))
152  oldBArg.replaceAllUsesWith(newBArg);
153 
154  // Iterate over the ops in the pipeline, and add them to the stage map.
155  // While doing so, we also build the pipeline stage operations.
156  unsigned currentEndTime = 0;
157  for (auto &op : pipeline.getOps()) {
158  if (ignoreOp(&op)) {
159  otherOps.push_back(&op);
160  continue;
161  }
162  unsigned startTime = *problem.getStartTime(&op);
163  stageMap[startTime].push_back(&op);
164 
165  auto oldEndTime = currentEndTime;
166  currentEndTime = std::max(currentEndTime, *problem.getEndTime(&op));
167  for (unsigned i = oldEndTime; i < currentEndTime; ++i) {
168  Block *newStage = schedPipeline.addStage();
169 
170  // Create a StageOp in the new stage, and branch it to the newly created
171  // stage.
172  b.setInsertionPointToEnd(currentStage);
173  b.create<pipeline::StageOp>(pipeline.getLoc(), newStage, ValueRange{},
174  ValueRange{});
175  currentStage = newStage;
176  }
177  }
178 
179  // Move the return op to the last stage in the scheduled pipeline.
180  returnOp->moveBefore(currentStage, currentStage->end());
181 
182  // Reorder pipeline. Initially place unscheduled ops at the entry stage, and
183  // then all following ops in their assigned stage.
184  Block *entryStage = schedPipeline.getStage(0);
185  Operation *entryStageTerminator = entryStage->getTerminator();
186  for (auto *op : otherOps)
187  op->moveBefore(entryStageTerminator);
188 
189  for (auto [startTime, ops] : stageMap) {
190  Block *stage = schedPipeline.getStage(startTime);
191  assert(stage && "Stage not found");
192  Operation *stageTerminator = stage->getTerminator();
193  for (auto *op : ops)
194  op->moveBefore(stageTerminator);
195  }
196 
197  // Remove the unscheduled pipeline
198  pipeline.replaceAllUsesWith(schedPipeline);
199  pipeline.erase();
200  return success();
201 }
202 
203 void ScheduleLinearPipelinePass::runOnOperation() {
204  for (auto &region : getOperation()->getRegions()) {
205  for (auto pipeline :
206  llvm::make_early_inc_range(region.getOps<UnscheduledPipelineOp>())) {
207  if (failed(schedulePipeline(pipeline)))
208  return signalPassFailure();
209  }
210  }
211 }
212 
213 std::unique_ptr<mlir::Pass>
215  return std::make_unique<ScheduleLinearPipelinePass>();
216 }
assert(baseType &&"element must be base type")
static bool ignoreOp(Operation *op)
mlir::StringAttr OperatorType
Operator types are distinguished by name (chosen by the client).
Definition: Problems.h:104
Direction get(bool isOutput)
Returns an output direction if isOutput is true, otherwise returns an input direction.
Definition: CalyxOps.cpp:54
std::unique_ptr< mlir::Pass > createScheduleLinearPipelinePass()
LogicalResult scheduleSimplex(Problem &prob, Operation *lastOp)
Solve the basic problem using linear programming and a handwritten implementation of the simplex algo...
The InstanceGraph op interface, see InstanceGraphInterface.td for more details.
Definition: DebugAnalysis.h:21