CIRCT  20.0.0git
LowerClocksToFuncs.cpp
Go to the documentation of this file.
1 //===- LowerClocksToFuncs.cpp ---------------------------------------------===//
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 
11 #include "mlir/Dialect/Func/IR/FuncOps.h"
12 #include "mlir/Dialect/SCF/IR/SCF.h"
13 #include "mlir/Pass/Pass.h"
14 #include "llvm/ADT/TypeSwitch.h"
15 #include "llvm/Support/Debug.h"
16 
17 #define DEBUG_TYPE "arc-lower-clocks-to-funcs"
18 
19 namespace circt {
20 namespace arc {
21 #define GEN_PASS_DEF_LOWERCLOCKSTOFUNCS
22 #include "circt/Dialect/Arc/ArcPasses.h.inc"
23 } // namespace arc
24 } // namespace circt
25 
26 using namespace mlir;
27 using namespace circt;
28 using namespace arc;
29 using namespace hw;
30 using mlir::OpTrait::ConstantLike;
31 
32 //===----------------------------------------------------------------------===//
33 // Pass Implementation
34 //===----------------------------------------------------------------------===//
35 
36 namespace {
37 struct LowerClocksToFuncsPass
38  : public arc::impl::LowerClocksToFuncsBase<LowerClocksToFuncsPass> {
39  LowerClocksToFuncsPass() = default;
40  LowerClocksToFuncsPass(const LowerClocksToFuncsPass &pass)
41  : LowerClocksToFuncsPass() {}
42 
43  void runOnOperation() override;
44  LogicalResult lowerModel(ModelOp modelOp);
45  LogicalResult lowerClock(Operation *clockOp, Value modelStorageArg,
46  OpBuilder &funcBuilder);
47  LogicalResult isolateClock(Operation *clockOp, Value modelStorageArg,
48  Value clockStorageArg);
49 
50  SymbolTable *symbolTable;
51 
52  Statistic numOpsCopied{this, "ops-copied", "Ops copied into clock trees"};
53  Statistic numOpsMoved{this, "ops-moved", "Ops moved into clock trees"};
54 
55 private:
56  bool hasPassthroughOp;
57 };
58 } // namespace
59 
60 void LowerClocksToFuncsPass::runOnOperation() {
61  symbolTable = &getAnalysis<SymbolTable>();
62  for (auto op : getOperation().getOps<ModelOp>())
63  if (failed(lowerModel(op)))
64  return signalPassFailure();
65 }
66 
67 LogicalResult LowerClocksToFuncsPass::lowerModel(ModelOp modelOp) {
68  LLVM_DEBUG(llvm::dbgs() << "Lowering clocks in `" << modelOp.getName()
69  << "`\n");
70 
71  // Find the clocks to extract.
72  SmallVector<InitialOp, 1> initialOps;
73  SmallVector<PassThroughOp, 1> passthroughOps;
74  SmallVector<Operation *> clocks;
75  modelOp.walk([&](Operation *op) {
76  TypeSwitch<Operation *, void>(op)
77  .Case<ClockTreeOp>([&](auto) { clocks.push_back(op); })
78  .Case<InitialOp>([&](auto initOp) {
79  initialOps.push_back(initOp);
80  clocks.push_back(initOp);
81  })
82  .Case<PassThroughOp>([&](auto ptOp) {
83  passthroughOps.push_back(ptOp);
84  clocks.push_back(ptOp);
85  });
86  });
87  hasPassthroughOp = !passthroughOps.empty();
88 
89  // Sanity check
90  if (passthroughOps.size() > 1) {
91  auto diag = modelOp.emitOpError()
92  << "containing multiple PassThroughOps cannot be lowered.";
93  for (auto ptOp : passthroughOps)
94  diag.attachNote(ptOp.getLoc()) << "Conflicting PassThroughOp:";
95  }
96  if (initialOps.size() > 1) {
97  auto diag = modelOp.emitOpError()
98  << "containing multiple InitialOps is currently unsupported.";
99  for (auto initOp : initialOps)
100  diag.attachNote(initOp.getLoc()) << "Conflicting InitialOp:";
101  }
102  if (passthroughOps.size() > 1 || initialOps.size() > 1)
103  return failure();
104 
105  // Perform the actual extraction.
106  OpBuilder funcBuilder(modelOp);
107  for (auto *op : clocks)
108  if (failed(lowerClock(op, modelOp.getBody().getArgument(0), funcBuilder)))
109  return failure();
110 
111  return success();
112 }
113 
114 LogicalResult LowerClocksToFuncsPass::lowerClock(Operation *clockOp,
115  Value modelStorageArg,
116  OpBuilder &funcBuilder) {
117  LLVM_DEBUG(llvm::dbgs() << "- Lowering clock " << clockOp->getName() << "\n");
118  assert((isa<ClockTreeOp, PassThroughOp, InitialOp>(clockOp)));
119 
120  // Add a `StorageType` block argument to the clock's body block which we are
121  // going to use to pass the storage pointer to the clock once it has been
122  // pulled out into a separate function.
123  Region &clockRegion = clockOp->getRegion(0);
124  Value clockStorageArg = clockRegion.addArgument(modelStorageArg.getType(),
125  modelStorageArg.getLoc());
126 
127  // Ensure the clock tree does not use any values defined outside of it.
128  if (failed(isolateClock(clockOp, modelStorageArg, clockStorageArg)))
129  return failure();
130 
131  // Add a return op to the end of the body.
132  auto builder = OpBuilder::atBlockEnd(&clockRegion.front());
133  builder.create<func::ReturnOp>(clockOp->getLoc());
134 
135  // Pick a name for the clock function.
136  SmallString<32> funcName;
137  auto modelOp = clockOp->getParentOfType<ModelOp>();
138  funcName.append(modelOp.getName());
139 
140  if (isa<PassThroughOp>(clockOp))
141  funcName.append("_passthrough");
142  else if (isa<InitialOp>(clockOp))
143  funcName.append("_initial");
144  else
145  funcName.append("_clock");
146 
147  auto funcOp = funcBuilder.create<func::FuncOp>(
148  clockOp->getLoc(), funcName,
149  builder.getFunctionType({modelStorageArg.getType()}, {}));
150  symbolTable->insert(funcOp); // uniquifies the name
151  LLVM_DEBUG(llvm::dbgs() << " - Created function `" << funcOp.getSymName()
152  << "`\n");
153 
154  // Create a call to the function within the model.
155  builder.setInsertionPoint(clockOp);
156  TypeSwitch<Operation *, void>(clockOp)
157  .Case<ClockTreeOp>([&](auto treeOp) {
158  auto ifOp = builder.create<scf::IfOp>(clockOp->getLoc(),
159  treeOp.getClock(), false);
160  auto builder = ifOp.getThenBodyBuilder();
161  builder.template create<func::CallOp>(clockOp->getLoc(), funcOp,
162  ValueRange{modelStorageArg});
163  })
164  .Case<PassThroughOp>([&](auto) {
165  builder.template create<func::CallOp>(clockOp->getLoc(), funcOp,
166  ValueRange{modelStorageArg});
167  })
168  .Case<InitialOp>([&](auto) {
169  if (modelOp.getInitialFn().has_value())
170  modelOp.emitWarning() << "Existing model initializer '"
171  << modelOp.getInitialFnAttr().getValue()
172  << "' will be overridden.";
173  modelOp.setInitialFnAttr(
174  FlatSymbolRefAttr::get(funcOp.getSymNameAttr()));
175  });
176 
177  // Move the clock's body block to the function and remove the old clock op.
178  funcOp.getBody().takeBody(clockRegion);
179 
180  if (isa<InitialOp>(clockOp) && hasPassthroughOp) {
181  // Call PassThroughOp after init
182  builder.setInsertionPoint(funcOp.getBlocks().front().getTerminator());
183  funcName.clear();
184  funcName.append(modelOp.getName());
185  funcName.append("_passthrough");
186  builder.create<func::CallOp>(clockOp->getLoc(), funcName, TypeRange{},
187  ValueRange{funcOp.getBody().getArgument(0)});
188  }
189 
190  clockOp->erase();
191  return success();
192 }
193 
194 /// Copy any external constants that the clock tree might be using into its
195 /// body. Anything besides constants should no longer exist after a proper run
196 /// of the pipeline.
197 LogicalResult LowerClocksToFuncsPass::isolateClock(Operation *clockOp,
198  Value modelStorageArg,
199  Value clockStorageArg) {
200  auto *clockRegion = &clockOp->getRegion(0);
201  auto builder = OpBuilder::atBlockBegin(&clockRegion->front());
202  DenseMap<Value, Value> copiedValues;
203  auto result = clockRegion->walk([&](Operation *op) {
204  for (auto &operand : op->getOpOperands()) {
205  // Block arguments are okay, since there's nothing we can move.
206  if (operand.get() == modelStorageArg) {
207  operand.set(clockStorageArg);
208  continue;
209  }
210  if (isa<BlockArgument>(operand.get())) {
211  auto d = op->emitError(
212  "operation in clock tree uses external block argument");
213  d.attachNote() << "clock trees can only use external constant values";
214  d.attachNote() << "see operand #" << operand.getOperandNumber();
215  d.attachNote(clockOp->getLoc()) << "clock tree:";
216  return WalkResult::interrupt();
217  }
218 
219  // Check if the value is defined outside of the clock op.
220  auto *definingOp = operand.get().getDefiningOp();
221  assert(definingOp && "block arguments ruled out above");
222  Region *definingRegion = definingOp->getParentRegion();
223  if (clockRegion->isAncestor(definingRegion))
224  continue;
225 
226  // The op is defined outside the clock, so we need to create a copy of the
227  // defining inside the clock tree.
228  if (auto copiedValue = copiedValues.lookup(operand.get())) {
229  operand.set(copiedValue);
230  continue;
231  }
232 
233  // Check that we can actually copy this definition inside.
234  if (!definingOp->hasTrait<ConstantLike>()) {
235  auto d = op->emitError("operation in clock tree uses external value");
236  d.attachNote() << "clock trees can only use external constant values";
237  d.attachNote(definingOp->getLoc()) << "external value defined here:";
238  d.attachNote(clockOp->getLoc()) << "clock tree:";
239  return WalkResult::interrupt();
240  }
241 
242  // Copy the op inside the clock tree (or move it if all uses are within
243  // the clock tree).
244  bool canMove = llvm::all_of(definingOp->getUsers(), [&](Operation *user) {
245  return clockRegion->isAncestor(user->getParentRegion());
246  });
247  Operation *clonedOp;
248  if (canMove) {
249  definingOp->remove();
250  clonedOp = definingOp;
251  ++numOpsMoved;
252  } else {
253  clonedOp = definingOp->cloneWithoutRegions();
254  ++numOpsCopied;
255  }
256  builder.insert(clonedOp);
257  if (!canMove) {
258  for (auto [outerResult, innerResult] :
259  llvm::zip(definingOp->getResults(), clonedOp->getResults())) {
260  copiedValues.insert({outerResult, innerResult});
261  if (operand.get() == outerResult)
262  operand.set(innerResult);
263  }
264  }
265  }
266  return WalkResult::advance();
267  });
268  return success(!result.wasInterrupted());
269 }
270 
271 std::unique_ptr<Pass> arc::createLowerClocksToFuncsPass() {
272  return std::make_unique<LowerClocksToFuncsPass>();
273 }
assert(baseType &&"element must be base type")
std::unique_ptr< mlir::Pass > createLowerClocksToFuncsPass()
Direction get(bool isOutput)
Returns an output direction if isOutput is true, otherwise returns an input direction.
Definition: CalyxOps.cpp:55
The InstanceGraph op interface, see InstanceGraphInterface.td for more details.
Definition: DebugAnalysis.h:21
Definition: hw.py:1