CIRCT 20.0.0git
Loading...
Searching...
No Matches
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
19namespace circt {
20namespace arc {
21#define GEN_PASS_DEF_LOWERCLOCKSTOFUNCS
22#include "circt/Dialect/Arc/ArcPasses.h.inc"
23} // namespace arc
24} // namespace circt
25
26using namespace mlir;
27using namespace circt;
28using namespace arc;
29using namespace hw;
30using mlir::OpTrait::ConstantLike;
31
32//===----------------------------------------------------------------------===//
33// Pass Implementation
34//===----------------------------------------------------------------------===//
35
36namespace {
37struct 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
57void LowerClocksToFuncsPass::runOnOperation() {
58 symbolTable = &getAnalysis<SymbolTable>();
59 for (auto op : getOperation().getOps<ModelOp>())
60 if (failed(lowerModel(op)))
61 return signalPassFailure();
62}
63
64LogicalResult 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
109LogicalResult 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.
176LogicalResult 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
250std::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()
The InstanceGraph op interface, see InstanceGraphInterface.td for more details.
Definition hw.py:1