CIRCT 23.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 {
40static LogicalResult collectFormatStringFragments(
41 Value formatString, PrintFormattedOp anchorOp,
42 SmallVectorImpl<Operation *> &fragmentList,
43 SmallSetVector<Operation *, 8> &allFStringFragments,
44 SmallSetVector<Value, 4> &arguments) {
45 SmallVector<Value> flatString;
46 if (auto concatInput = formatString.getDefiningOp<FormatStringConcatOp>()) {
47 auto isAcyclic = concatInput.getFlattenedInputs(flatString);
48 if (failed(isAcyclic)) {
49 anchorOp.emitError("Cyclic format string cannot be proceduralized.");
50 return failure();
51 }
52 } else {
53 flatString.push_back(formatString);
54 }
55
56 assert(fragmentList.empty() && "format string visited twice");
57 for (auto fragment : flatString) {
58 auto *fmtOp = fragment.getDefiningOp();
59 if (!fmtOp) {
60 anchorOp.emitError("Proceduralization of format strings passed as block "
61 "argument is unsupported.");
62 return failure();
63 }
64 fragmentList.push_back(fmtOp);
65 allFStringFragments.insert(fmtOp);
66
67 // For non-literal fragments, the value to be formatted has to become a
68 // triggered region argument.
69 if (!llvm::isa<FormatLiteralOp>(fmtOp)) {
70 auto fmtVal = getFormattedValue(fmtOp);
71 assert(!!fmtVal && "Unexpected formatting fragment op.");
72 arguments.insert(fmtVal);
73 }
74 }
75 return success();
76}
77
78static Value
79rematerializeFormatStringFromFragments(ArrayRef<Operation *> fragments,
80 OpBuilder &builder, IRMapping &mapping,
81 Location loc) {
82 SmallVector<Value> operands;
83 operands.reserve(fragments.size());
84 for (auto *fragment : fragments) {
85 auto cloned = mapping.lookupOrNull(fragment->getResult(0));
86 assert(cloned && "missing cloned fragment");
87 operands.push_back(cloned);
88 }
89 if (operands.size() == 1)
90 return operands.front();
91 return builder.createOrFold<FormatStringConcatOp>(loc, operands);
92}
93
94static Block *getOrCreateConditionBlock(OpBuilder &builder,
95 hw::TriggeredOp trigOp, Location loc,
96 Value condition,
97 Value &prevConditionValue,
98 Block *&prevConditionBlock) {
99 if (condition != prevConditionValue)
100 prevConditionBlock = nullptr;
101
102 if (prevConditionBlock)
103 return prevConditionBlock;
104
105 builder.setInsertionPointToEnd(trigOp.getBodyBlock());
106 auto ifOp = mlir::scf::IfOp::create(builder, loc, TypeRange{}, condition,
107 true, false);
108 builder.setInsertionPointToStart(&ifOp.getThenRegion().front());
109 mlir::scf::YieldOp::create(builder, loc);
110 prevConditionValue = condition;
111 prevConditionBlock = builder.getBlock();
112 return prevConditionBlock;
113}
114
115struct ProceduralizeSimPass : impl::ProceduralizeSimBase<ProceduralizeSimPass> {
116public:
117 void runOnOperation() override;
118
119private:
120 LogicalResult proceduralizePrintOps(Value clock,
121 ArrayRef<PrintFormattedOp> printOps);
122 void cleanup();
123
124 // Mapping Clock -> List of printf ops
126
127 // List of formatting ops to be pruned after proceduralization.
128 SmallVector<Operation *> cleanupList;
129};
130} // namespace
131
132LogicalResult ProceduralizeSimPass::proceduralizePrintOps(
133 Value clock, ArrayRef<PrintFormattedOp> printOps) {
134
135 // List of uniqued values to become arguments of the TriggeredOp.
136 SmallSetVector<Value, 4> arguments;
137 // Map print ops -> flattened list of format-string fragments.
139 // Map get_file ops -> flattened list of filename format-string fragments.
141 // All non-concat format-string fragment ops needed in the triggered body.
142 SmallSetVector<Operation *, 8> allFStringFragments;
143 // Keep get_file ops in first-use order.
145 SmallVector<Location> locs;
146 SmallDenseSet<Value, 1> alwaysEnabledConditions;
147 SmallVector<PrintFormattedOp> livePrintOps;
148
149 locs.reserve(printOps.size());
150 for (auto printOp : printOps) {
151 if (auto cstCond = printOp.getCondition().getDefiningOp<hw::ConstantOp>()) {
152 if (cstCond.getValue().isZero()) {
153 printOp.erase();
154 continue;
155 }
156 if (cstCond.getValue().isAllOnes())
157 alwaysEnabledConditions.insert(printOp.getCondition());
158 } else {
159 arguments.insert(printOp.getCondition());
160 }
161
162 livePrintOps.push_back(printOp);
163 locs.push_back(printOp.getLoc());
164
165 auto &printFragments = printFragmentMap[printOp];
166 if (failed(::collectFormatStringFragments(printOp.getInput(), printOp,
167 printFragments,
168 allFStringFragments, arguments)))
169 return failure();
170
171 if (auto stream = printOp.getStream()) {
172 auto getFileOp = stream.getDefiningOp<GetFileOp>();
173 if (!getFileOp) {
174 if (!stream.getDefiningOp())
175 printOp.emitError("proceduralization requires stream to be produced "
176 "by sim.get_file, block arguments are unsupported");
177 else
178 printOp.emitError("proceduralization requires stream to be produced "
179 "by sim.get_file");
180 return failure();
181 }
182 getFileOps.insert(getFileOp);
183 auto &fileNameFragments = fileNameFragmentMap[getFileOp];
184 if (fileNameFragments.empty() &&
185 failed(::collectFormatStringFragments(
186 getFileOp.getFileName(), printOp, fileNameFragments,
187 allFStringFragments, arguments)))
188 return failure();
189 }
190 }
191
192 if (livePrintOps.empty())
193 return success();
194
195 OpBuilder builder(livePrintOps.back());
196 auto fusedLoc = builder.getFusedLoc(locs);
197 SmallVector<Value> argVec = arguments.takeVector();
198
199 auto clockConv = builder.createOrFold<seq::FromClockOp>(fusedLoc, clock);
200 auto trigOp = hw::TriggeredOp::create(
201 builder, fusedLoc,
202 hw::EventControlAttr::get(builder.getContext(),
203 hw::EventControl::AtPosEdge),
204 clockConv, argVec);
205
206 IRMapping mapping;
207 for (auto [idx, arg] : llvm::enumerate(argVec))
208 mapping.map(arg, trigOp.getBodyBlock()->getArgument(idx));
209
210 builder.setInsertionPointToStart(trigOp.getBodyBlock());
211 if (!alwaysEnabledConditions.empty()) {
212 auto cstTrue = builder.createOrFold<hw::ConstantOp>(
213 fusedLoc, IntegerAttr::get(builder.getI1Type(), 1));
214 for (auto cstCond : alwaysEnabledConditions)
215 mapping.map(cstCond, cstTrue);
216 }
217
218 for (auto *fragment : allFStringFragments) {
219 auto original = fragment->getResult(0);
220 if (mapping.lookupOrNull(original))
221 continue;
222 auto *cloned = builder.clone(*fragment, mapping);
223 mapping.map(original, cloned->getResult(0));
224 }
225
226 for (auto getFileOp : getFileOps) {
227 auto &fileNameFragments = fileNameFragmentMap[getFileOp];
228 Value clonedFileName = ::rematerializeFormatStringFromFragments(
229 fileNameFragments, builder, mapping, getFileOp.getLoc());
230
231 auto clonedGetFile =
232 GetFileOp::create(builder, getFileOp.getLoc(), clonedFileName);
233 mapping.map(getFileOp.getResult(), clonedGetFile.getResult());
234
235 cleanupList.push_back(getFileOp);
236 cleanupList.push_back(getFileOp.getFileName().getDefiningOp());
237 }
238
239 // Materialize print inputs before creating any conditional blocks.
240 // Whether to actually construct strings eagerly/lazily is left to lowering
241 // backends.
243 // Insert after rematerialized fragments/get_file ops so operands dominate.
244 builder.setInsertionPointToEnd(trigOp.getBodyBlock());
245 for (auto printOp : livePrintOps) {
246 auto &printFragments = printFragmentMap[printOp];
247 procPrintInputMap[printOp] = ::rematerializeFormatStringFromFragments(
248 printFragments, builder, mapping, printOp.getLoc());
249 }
250
251 Value prevConditionValue;
252 Block *prevConditionBlock = nullptr;
253 for (auto printOp : livePrintOps) {
254 auto condArg = mapping.lookup(printOp.getCondition());
255 auto *condBlock =
256 ::getOrCreateConditionBlock(builder, trigOp, printOp.getLoc(), condArg,
257 prevConditionValue, prevConditionBlock);
258
259 builder.setInsertionPoint(condBlock->getTerminator());
260 Value procPrintInput = procPrintInputMap[printOp];
261
262 Value procPrintStream;
263 if (auto stream = printOp.getStream()) {
264 procPrintStream = mapping.lookupOrNull(stream);
265 if (!procPrintStream) {
266 printOp.emitError("proceduralization failed to rematerialize stream");
267 return failure();
268 }
269 }
270
271 PrintFormattedProcOp::create(builder, printOp.getLoc(), procPrintInput,
272 procPrintStream);
273 cleanupList.push_back(printOp.getInput().getDefiningOp());
274 printOp.erase();
275 }
276 return success();
277}
278
279// Prune the DAGs of formatting fragments left outside of the newly created
280// TriggeredOps.
281void ProceduralizeSimPass::cleanup() {
282 SmallVector<Operation *> cleanupNextList;
283 SmallDenseSet<Operation *> erasedOps;
284
285 bool noChange = true;
286 while (!cleanupList.empty() || !cleanupNextList.empty()) {
287
288 if (cleanupList.empty()) {
289 if (noChange)
290 break;
291 cleanupList = std::move(cleanupNextList);
292 cleanupNextList = {};
293 noChange = true;
294 }
295
296 auto *opToErase = cleanupList.pop_back_val();
297 if (erasedOps.contains(opToErase))
298 continue;
299
300 if (opToErase->getUses().empty()) {
301 // Remove a dead op. If it is a concat remove its operands, too.
302 if (auto concat = dyn_cast<FormatStringConcatOp>(opToErase))
303 for (auto operand : concat.getInputs())
304 cleanupNextList.push_back(operand.getDefiningOp());
305 opToErase->erase();
306 erasedOps.insert(opToErase);
307 noChange = false;
308 } else {
309 // Op still has uses, revisit later.
310 cleanupNextList.push_back(opToErase);
311 }
312 }
313}
314
315void ProceduralizeSimPass::runOnOperation() {
316 LLVM_DEBUG(debugPassHeader(this) << "\n");
317 printfOpMap.clear();
318 cleanupList.clear();
319
320 auto theModule = getOperation();
321 // Collect printf operations grouped by their clock.
322 theModule.walk<mlir::WalkOrder::PreOrder>(
323 [&](PrintFormattedOp op) { printfOpMap[op.getClock()].push_back(op); });
324
325 // Create a hw::TriggeredOp for each clock
326 for (auto &[clock, printOps] : printfOpMap)
327 if (failed(proceduralizePrintOps(clock, printOps))) {
328 signalPassFailure();
329 return;
330 }
331
332 cleanup();
333}
assert(baseType &&"element must be base type")
static Block * getBodyBlock(FModuleLike mod)
The InstanceGraph op interface, see InstanceGraphInterface.td for more details.
llvm::raw_ostream & debugPassHeader(const mlir::Pass *pass, unsigned width=80)
Write a boilerplate header for a pass to the debug stream.
Definition Debug.cpp:31
Definition sim.py:1