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 } // namespace
56 
57 void LowerClocksToFuncsPass::runOnOperation() {
58  symbolTable = &getAnalysis<SymbolTable>();
59  for (auto op : getOperation().getOps<ModelOp>())
60  if (failed(lowerModel(op)))
61  return signalPassFailure();
62 }
63 
64 LogicalResult LowerClocksToFuncsPass::lowerModel(ModelOp modelOp) {
65  LLVM_DEBUG(llvm::dbgs() << "Lowering clocks in `" << modelOp.getName()
66  << "`\n");
67 
68  // Find the clocks to extract.
69  SmallVector<InitialOp, 1> initialOps;
70  SmallVector<FinalOp, 1> finalOps;
71  SmallVector<Operation *> clocks;
72  modelOp.walk([&](Operation *op) {
73  TypeSwitch<Operation *, void>(op)
74  .Case<InitialOp>([&](auto initOp) {
75  initialOps.push_back(initOp);
76  clocks.push_back(initOp);
77  })
78  .Case<FinalOp>([&](auto op) {
79  finalOps.push_back(op);
80  clocks.push_back(op);
81  });
82  });
83 
84  // Sanity check
85  if (initialOps.size() > 1) {
86  auto diag = modelOp.emitOpError()
87  << "containing multiple InitialOps is currently unsupported.";
88  for (auto initOp : initialOps)
89  diag.attachNote(initOp.getLoc()) << "Conflicting InitialOp:";
90  }
91  if (finalOps.size() > 1) {
92  auto diag = modelOp.emitOpError()
93  << "containing multiple FinalOps is currently unsupported.";
94  for (auto op : finalOps)
95  diag.attachNote(op.getLoc()) << "Conflicting FinalOp:";
96  }
97  if (initialOps.size() > 1 || finalOps.size() > 1)
98  return failure();
99 
100  // Perform the actual extraction.
101  OpBuilder funcBuilder(modelOp);
102  for (auto *op : clocks)
103  if (failed(lowerClock(op, modelOp.getBody().getArgument(0), funcBuilder)))
104  return failure();
105 
106  return success();
107 }
108 
109 LogicalResult LowerClocksToFuncsPass::lowerClock(Operation *clockOp,
110  Value modelStorageArg,
111  OpBuilder &funcBuilder) {
112  LLVM_DEBUG(llvm::dbgs() << "- Lowering clock " << clockOp->getName() << "\n");
113  assert((isa<InitialOp, FinalOp>(clockOp)));
114 
115  // Add a `StorageType` block argument to the clock's body block which we are
116  // going to use to pass the storage pointer to the clock once it has been
117  // pulled out into a separate function.
118  Region &clockRegion = clockOp->getRegion(0);
119  Value clockStorageArg = clockRegion.addArgument(modelStorageArg.getType(),
120  modelStorageArg.getLoc());
121 
122  // Ensure the clock tree does not use any values defined outside of it.
123  if (failed(isolateClock(clockOp, modelStorageArg, clockStorageArg)))
124  return failure();
125 
126  // Add a return op to the end of the body.
127  auto builder = OpBuilder::atBlockEnd(&clockRegion.front());
128  builder.create<func::ReturnOp>(clockOp->getLoc());
129 
130  // Pick a name for the clock function.
131  SmallString<32> funcName;
132  auto modelOp = clockOp->getParentOfType<ModelOp>();
133  funcName.append(modelOp.getName());
134 
135  if (isa<InitialOp>(clockOp))
136  funcName.append("_initial");
137  else if (isa<FinalOp>(clockOp))
138  funcName.append("_final");
139 
140  auto funcOp = funcBuilder.create<func::FuncOp>(
141  clockOp->getLoc(), funcName,
142  builder.getFunctionType({modelStorageArg.getType()}, {}));
143  symbolTable->insert(funcOp); // uniquifies the name
144  LLVM_DEBUG(llvm::dbgs() << " - Created function `" << funcOp.getSymName()
145  << "`\n");
146 
147  // Create a call to the function within the model.
148  builder.setInsertionPoint(clockOp);
149  TypeSwitch<Operation *, void>(clockOp)
150  .Case<InitialOp>([&](auto) {
151  if (modelOp.getInitialFn().has_value())
152  modelOp.emitWarning() << "Existing model initializer '"
153  << modelOp.getInitialFnAttr().getValue()
154  << "' will be overridden.";
155  modelOp.setInitialFnAttr(
156  FlatSymbolRefAttr::get(funcOp.getSymNameAttr()));
157  })
158  .Case<FinalOp>([&](auto) {
159  if (modelOp.getFinalFn().has_value())
160  modelOp.emitWarning()
161  << "Existing model finalizer '"
162  << modelOp.getFinalFnAttr().getValue() << "' will be overridden.";
163  modelOp.setFinalFnAttr(FlatSymbolRefAttr::get(funcOp.getSymNameAttr()));
164  });
165 
166  // Move the clock's body block to the function and remove the old clock op.
167  funcOp.getBody().takeBody(clockRegion);
168 
169  clockOp->erase();
170  return success();
171 }
172 
173 /// Copy any external constants that the clock tree might be using into its
174 /// body. Anything besides constants should no longer exist after a proper run
175 /// of the pipeline.
176 LogicalResult LowerClocksToFuncsPass::isolateClock(Operation *clockOp,
177  Value modelStorageArg,
178  Value clockStorageArg) {
179  auto *clockRegion = &clockOp->getRegion(0);
180  auto builder = OpBuilder::atBlockBegin(&clockRegion->front());
181  DenseMap<Value, Value> copiedValues;
182  auto result = clockRegion->walk([&](Operation *op) {
183  for (auto &operand : op->getOpOperands()) {
184  // Block arguments are okay, since there's nothing we can move.
185  if (operand.get() == modelStorageArg) {
186  operand.set(clockStorageArg);
187  continue;
188  }
189  if (isa<BlockArgument>(operand.get())) {
190  auto d = op->emitError(
191  "operation in clock tree uses external block argument");
192  d.attachNote() << "clock trees can only use external constant values";
193  d.attachNote() << "see operand #" << operand.getOperandNumber();
194  d.attachNote(clockOp->getLoc()) << "clock tree:";
195  return WalkResult::interrupt();
196  }
197 
198  // Check if the value is defined outside of the clock op.
199  auto *definingOp = operand.get().getDefiningOp();
200  assert(definingOp && "block arguments ruled out above");
201  Region *definingRegion = definingOp->getParentRegion();
202  if (clockRegion->isAncestor(definingRegion))
203  continue;
204 
205  // The op is defined outside the clock, so we need to create a copy of the
206  // defining inside the clock tree.
207  if (auto copiedValue = copiedValues.lookup(operand.get())) {
208  operand.set(copiedValue);
209  continue;
210  }
211 
212  // Check that we can actually copy this definition inside.
213  if (!definingOp->hasTrait<ConstantLike>()) {
214  auto d = op->emitError("operation in clock tree uses external value");
215  d.attachNote() << "clock trees can only use external constant values";
216  d.attachNote(definingOp->getLoc()) << "external value defined here:";
217  d.attachNote(clockOp->getLoc()) << "clock tree:";
218  return WalkResult::interrupt();
219  }
220 
221  // Copy the op inside the clock tree (or move it if all uses are within
222  // the clock tree).
223  bool canMove = llvm::all_of(definingOp->getUsers(), [&](Operation *user) {
224  return clockRegion->isAncestor(user->getParentRegion());
225  });
226  Operation *clonedOp;
227  if (canMove) {
228  definingOp->remove();
229  clonedOp = definingOp;
230  ++numOpsMoved;
231  } else {
232  clonedOp = definingOp->cloneWithoutRegions();
233  ++numOpsCopied;
234  }
235  builder.insert(clonedOp);
236  if (!canMove) {
237  for (auto [outerResult, innerResult] :
238  llvm::zip(definingOp->getResults(), clonedOp->getResults())) {
239  copiedValues.insert({outerResult, innerResult});
240  if (operand.get() == outerResult)
241  operand.set(innerResult);
242  }
243  }
244  }
245  return WalkResult::advance();
246  });
247  return success(!result.wasInterrupted());
248 }
249 
250 std::unique_ptr<Pass> arc::createLowerClocksToFuncsPass() {
251  return std::make_unique<LowerClocksToFuncsPass>();
252 }
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