CIRCT 20.0.0git
Loading...
Searching...
No Matches
ProceduralizeSim.cpp
Go to the documentation of this file.
1//===- ProceduralizeSim.cpp - Conversion to procedural operations ---------===//
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// Transform non-procedural simulation operations with clock and enable to
10// procedural operations wrapped in a procedural region.
11//
12//===----------------------------------------------------------------------===//
13
18#include "circt/Support/Debug.h"
19#include "mlir/Dialect/SCF/IR/SCF.h"
20#include "mlir/Pass/Pass.h"
21#include "llvm/ADT/IndexedMap.h"
22#include "llvm/ADT/MapVector.h"
23#include "llvm/ADT/SetVector.h"
24#include "llvm/Support/Debug.h"
25
26#define DEBUG_TYPE "proceduralize-sim"
27
28namespace circt {
29namespace sim {
30#define GEN_PASS_DEF_PROCEDURALIZESIM
31#include "circt/Dialect/Sim/SimPasses.h.inc"
32} // namespace sim
33} // namespace circt
34
35using namespace llvm;
36using namespace circt;
37using namespace sim;
38
39namespace {
40struct ProceduralizeSimPass : impl::ProceduralizeSimBase<ProceduralizeSimPass> {
41public:
42 void runOnOperation() override;
43
44private:
45 LogicalResult proceduralizePrintOps(Value clock,
46 ArrayRef<PrintFormattedOp> printOps);
47 SmallVector<Operation *> getPrintFragments(PrintFormattedOp op);
48 void cleanup();
49
50 // Mapping Clock -> List of printf ops
51 SmallMapVector<Value, SmallVector<PrintFormattedOp>, 2> printfOpMap;
52
53 // List of formatting ops to be pruned after proceduralization.
54 SmallVector<Operation *> cleanupList;
55};
56} // namespace
57
58LogicalResult ProceduralizeSimPass::proceduralizePrintOps(
59 Value clock, ArrayRef<PrintFormattedOp> printOps) {
60
61 // List of uniqued values to become arguments of the TriggeredOp.
62 SmallSetVector<Value, 4> arguments;
63 // Map printf ops -> flattened list of fragments
65 SmallVector<Location> locs;
66 SmallDenseSet<Value, 1> alwaysEnabledConditions;
67
68 locs.reserve(printOps.size());
69
70 for (auto printOp : printOps) {
71 // Handle the print condition value. If it is not constant, it has to become
72 // a region argument. If it is constant false, skip the operation.
73 if (auto cstCond = printOp.getCondition().getDefiningOp<hw::ConstantOp>()) {
74 if (cstCond.getValue().isAllOnes())
75 alwaysEnabledConditions.insert(printOp.getCondition());
76 else
77 continue;
78 } else {
79 arguments.insert(printOp.getCondition());
80 }
81
82 // Accumulate locations
83 locs.push_back(printOp.getLoc());
84
85 // Get the flat list of formatting fragments and collect leaf fragments
86 SmallVector<Value> flatString;
87 if (auto concatInput =
88 printOp.getInput().getDefiningOp<FormatStringConcatOp>()) {
89
90 auto isAcyclic = concatInput.getFlattenedInputs(flatString);
91 if (failed(isAcyclic)) {
92 printOp.emitError("Cyclic format string cannot be proceduralized.");
93 return failure();
94 }
95 } else {
96 flatString.push_back(printOp.getInput());
97 }
98
99 auto &fragmentList = fragmentMap[printOp];
100 assert(fragmentList.empty() && "printf operation visited twice.");
101
102 for (auto &fragment : flatString) {
103 auto *fmtOp = fragment.getDefiningOp();
104 if (!fmtOp) {
105 printOp.emitError("Proceduralization of format strings passed as block "
106 "argument is unsupported.");
107 return failure();
108 }
109 fragmentList.push_back(fmtOp);
110 // For non-literal fragments, the value to be formatted has to become an
111 // argument.
112 if (!llvm::isa<FormatLitOp>(fmtOp)) {
113 auto fmtVal = getFormattedValue(fmtOp);
114 assert(!!fmtVal && "Unexpected foramtting fragment op.");
115 arguments.insert(fmtVal);
116 }
117 }
118 }
119
120 // Build the hw::TriggeredOp
121 OpBuilder builder(printOps.back());
122 auto fusedLoc = builder.getFusedLoc(locs);
123
124 SmallVector<Value> argVec = arguments.takeVector();
125
126 auto clockConv = builder.createOrFold<seq::FromClockOp>(fusedLoc, clock);
127 auto trigOp = builder.create<hw::TriggeredOp>(
128 fusedLoc,
129 hw::EventControlAttr::get(builder.getContext(),
130 hw::EventControl::AtPosEdge),
131 clockConv, argVec);
132
133 // Map the collected arguments to the newly created block arguments.
134 IRMapping argumentMapper;
135 unsigned idx = 0;
136 for (auto arg : argVec) {
137 argumentMapper.map(arg, trigOp.getBodyBlock()->getArgument(idx));
138 idx++;
139 }
140
141 // Materialize and map a 'true' constant within the TriggeredOp if required.
142 builder.setInsertionPointToStart(trigOp.getBodyBlock());
143 if (!alwaysEnabledConditions.empty()) {
144 auto cstTrue = builder.createOrFold<hw::ConstantOp>(
145 fusedLoc, IntegerAttr::get(builder.getI1Type(), 1));
146 for (auto cstCond : alwaysEnabledConditions)
147 argumentMapper.map(cstCond, cstTrue);
148 }
149
151 Value prevConditionValue;
152 Block *prevConditionBlock;
153
154 for (auto printOp : printOps) {
155
156 // Throw away disabled prints
157 if (auto cstCond = printOp.getCondition().getDefiningOp<hw::ConstantOp>()) {
158 if (cstCond.getValue().isZero()) {
159 printOp.erase();
160 continue;
161 }
162 }
163
164 // Create a copy of the required fragment operations within the
165 // TriggeredOp's body.
166 auto fragments = fragmentMap[printOp];
167 SmallVector<Value> clonedOperands;
168 builder.setInsertionPointToStart(trigOp.getBodyBlock());
169 for (auto *fragment : fragments) {
170 auto &fmtCloned = cloneMap[fragment];
171 if (!fmtCloned)
172 fmtCloned = builder.clone(*fragment, argumentMapper);
173 clonedOperands.push_back(fmtCloned->getResult(0));
174 }
175 // Concatenate fragments to a single value if necessary.
176 Value procPrintInput;
177 if (clonedOperands.size() != 1)
178 procPrintInput = builder.createOrFold<FormatStringConcatOp>(
179 printOp.getLoc(), clonedOperands);
180 else
181 procPrintInput = clonedOperands.front();
182
183 // Check if we can reuse the previous conditional block.
184 auto condArg = argumentMapper.lookup(printOp.getCondition());
185 if (condArg != prevConditionValue)
186 prevConditionBlock = nullptr;
187 auto *condBlock = prevConditionBlock;
188
189 // If not, create a new scf::IfOp for the condition.
190 if (!condBlock) {
191 builder.setInsertionPointToEnd(trigOp.getBodyBlock());
192 auto ifOp = builder.create<mlir::scf::IfOp>(printOp.getLoc(), TypeRange{},
193 condArg, true, false);
194 builder.setInsertionPointToStart(&ifOp.getThenRegion().front());
195 builder.create<mlir::scf::YieldOp>(printOp.getLoc());
196 condBlock = builder.getBlock();
197 prevConditionValue = condArg;
198 prevConditionBlock = condBlock;
199 }
200
201 // Create the procedural print operation and prune the operations outside of
202 // the TriggeredOp.
203 builder.setInsertionPoint(condBlock->getTerminator());
204 builder.create<PrintFormattedProcOp>(printOp.getLoc(), procPrintInput);
205 cleanupList.push_back(printOp.getInput().getDefiningOp());
206 printOp.erase();
207 }
208 return success();
209}
210
211// Prune the DAGs of formatting fragments left outside of the newly created
212// TriggeredOps.
213void ProceduralizeSimPass::cleanup() {
214 SmallVector<Operation *> cleanupNextList;
215 SmallDenseSet<Operation *> erasedOps;
216
217 bool noChange = true;
218 while (!cleanupList.empty() || !cleanupNextList.empty()) {
219
220 if (cleanupList.empty()) {
221 if (noChange)
222 break;
223 cleanupList = std::move(cleanupNextList);
224 cleanupNextList = {};
225 noChange = true;
226 }
227
228 auto *opToErase = cleanupList.pop_back_val();
229 if (erasedOps.contains(opToErase))
230 continue;
231
232 if (opToErase->getUses().empty()) {
233 // Remove a dead op. If it is a concat remove its operands, too.
234 if (auto concat = dyn_cast<FormatStringConcatOp>(opToErase))
235 for (auto operand : concat.getInputs())
236 cleanupNextList.push_back(operand.getDefiningOp());
237 opToErase->erase();
238 erasedOps.insert(opToErase);
239 noChange = false;
240 } else {
241 // Op still has uses, revisit later.
242 cleanupNextList.push_back(opToErase);
243 }
244 }
245}
246
247void ProceduralizeSimPass::runOnOperation() {
248 LLVM_DEBUG(debugPassHeader(this) << "\n");
249 printfOpMap.clear();
250 cleanupList.clear();
251
252 auto theModule = getOperation();
253 // Collect printf operations grouped by their clock.
254 theModule.walk<mlir::WalkOrder::PreOrder>(
255 [&](PrintFormattedOp op) { printfOpMap[op.getClock()].push_back(op); });
256
257 // Create a hw::TriggeredOp for each clock
258 for (auto &[clock, printOps] : printfOpMap)
259 if (failed(proceduralizePrintOps(clock, printOps))) {
260 signalPassFailure();
261 return;
262 }
263
264 cleanup();
265}
assert(baseType &&"element must be base type")
static SmallVector< T > concat(const SmallVectorImpl< T > &a, const SmallVectorImpl< T > &b)
Returns a new vector containing the concatenation of vectors a and b.
Definition CalyxOps.cpp:540
static mlir::Value getFormattedValue(mlir::Operation *fmtOp)
Returns the value operand of a value formatting operation.
Definition SimOps.h:37
The InstanceGraph op interface, see InstanceGraphInterface.td for more details.
llvm::raw_ostream & debugPassHeader(const mlir::Pass *pass, int width=80)
Write a boilerplate header for a pass to the debug stream.
Definition Debug.cpp:31