CIRCT 24.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"
20#include "mlir/Dialect/SCF/IR/SCF.h"
21#include "mlir/Pass/Pass.h"
22#include "llvm/ADT/IndexedMap.h"
23#include "llvm/ADT/MapVector.h"
24#include "llvm/ADT/SetVector.h"
25#include "llvm/Support/Debug.h"
26
27#define DEBUG_TYPE "proceduralize-sim"
28
29namespace circt {
30namespace sim {
31#define GEN_PASS_DEF_PROCEDURALIZESIM
32#include "circt/Dialect/Sim/SimPasses.h.inc"
33} // namespace sim
34} // namespace circt
35
36using llvm::SmallDenseSet;
37using namespace circt;
38using namespace sim;
39
40namespace {
41static LogicalResult collectFormatStringFragments(
42 Value formatString, PrintFormattedOp anchorOp,
43 SmallVectorImpl<Operation *> &fragmentList,
44 SmallSetVector<Operation *, 8> &allFStringFragments,
45 SmallSetVector<Value, 4> &arguments) {
46 SmallVector<Value> flatString;
47 if (auto concatInput = formatString.getDefiningOp<FormatStringConcatOp>()) {
48 auto isAcyclic = concatInput.getFlattenedInputs(flatString);
49 if (failed(isAcyclic)) {
50 anchorOp.emitError("Cyclic format string cannot be proceduralized.");
51 return failure();
52 }
53 } else {
54 flatString.push_back(formatString);
55 }
56
57 assert(fragmentList.empty() && "format string visited twice");
58 for (auto fragment : flatString) {
59 auto *fmtOp = fragment.getDefiningOp();
60 if (!fmtOp) {
61 anchorOp.emitError("Proceduralization of format strings passed as block "
62 "argument is unsupported.");
63 return failure();
64 }
65 fragmentList.push_back(fmtOp);
66 allFStringFragments.insert(fmtOp);
67
68 // Value formatter fragments have to carry their formatted value into the
69 // triggered region. Contextual fragments such as sim.fmt.time or
70 // sim.fmt.hier_path do not require an extra argument.
71 if (auto fmtVal = getFormattedValue(fmtOp)) {
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
125 SmallMapVector<Value, SmallVector<PrintFormattedOp>, 2> printfOpMap;
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.
138 SmallDenseMap<PrintFormattedOp, SmallVector<Operation *>, 4> printFragmentMap;
139 // Map get_file ops -> flattened list of filename format-string fragments.
140 SmallDenseMap<GetFileOp, SmallVector<Operation *>, 4> fileNameFragmentMap;
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.
144 SmallSetVector<GetFileOp, 4> getFileOps;
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 }
237
238 // Materialize print inputs before creating any conditional blocks.
239 // Whether to actually construct strings eagerly/lazily is left to lowering
240 // backends.
241 SmallDenseMap<PrintFormattedOp, Value> procPrintInputMap;
242 // Insert after rematerialized fragments/get_file ops so operands dominate.
243 builder.setInsertionPointToEnd(trigOp.getBodyBlock());
244 for (auto printOp : livePrintOps) {
245 auto &printFragments = printFragmentMap[printOp];
246 procPrintInputMap[printOp] = ::rematerializeFormatStringFromFragments(
247 printFragments, builder, mapping, printOp.getLoc());
248 }
249
250 Value prevConditionValue;
251 Block *prevConditionBlock = nullptr;
252 for (auto printOp : livePrintOps) {
253 auto condArg = mapping.lookup(printOp.getCondition());
254 auto *condBlock =
255 ::getOrCreateConditionBlock(builder, trigOp, printOp.getLoc(), condArg,
256 prevConditionValue, prevConditionBlock);
257
258 builder.setInsertionPoint(condBlock->getTerminator());
259 Value procPrintInput = procPrintInputMap[printOp];
260
261 Value procPrintStream;
262 if (auto stream = printOp.getStream()) {
263 procPrintStream = mapping.lookupOrNull(stream);
264 if (!procPrintStream) {
265 printOp.emitError("proceduralization failed to rematerialize stream");
266 return failure();
267 }
268 }
269
270 PrintFormattedProcOp::create(builder, printOp.getLoc(), procPrintInput,
271 procPrintStream);
272 cleanupList.push_back(printOp);
273 }
274 return success();
275}
276
277// Prune the DAGs of formatting fragments left outside of the newly created
278// TriggeredOps.
279void ProceduralizeSimPass::cleanup() {
280 SparseOpSCC<OpSCCDirection::Backward> sccs([](Operation *op,
281 OpOperand &operand) {
282 return isa<FormatStringType, OutputStreamType>(operand.get().getType()) &&
283 isa_and_present<SimDialect>(op->getDialect());
284 });
285 sccs.visit(cleanupList);
286 assert(sccs.getNumCyclicSCCs() == 0 &&
287 "Cyclic graph should have been rejected");
288 for (auto scc : sccs.reverseTopological()) {
289 auto *op = cast<Operation *>(scc);
290 if (op->use_empty())
291 op->erase();
292 else
293 op->emitWarning("Operation has remaining (transitive) users outside of "
294 "procedural regions.");
295 }
296}
297
298void ProceduralizeSimPass::runOnOperation() {
299 LLVM_DEBUG(debugPassHeader(this) << "\n");
300 printfOpMap.clear();
301 cleanupList.clear();
302
303 auto theModule = getOperation();
304 // Collect printf operations grouped by their clock.
305 theModule.walk<mlir::WalkOrder::PreOrder>(
306 [&](PrintFormattedOp op) { printfOpMap[op.getClock()].push_back(op); });
307
308 // Create a hw::TriggeredOp for each clock
309 for (auto &[clock, printOps] : printfOpMap)
310 if (failed(proceduralizePrintOps(clock, printOps))) {
311 signalPassFailure();
312 return;
313 }
314
315 cleanup();
316}
assert(baseType &&"element must be base type")
false return failure()
Parse an optional keyword or string and set instance into 'result'.
return success()
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