CIRCT  20.0.0git
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 
14 #include "circt/Dialect/HW/HWOps.h"
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 
28 namespace circt {
29 namespace sim {
30 #define GEN_PASS_DEF_PROCEDURALIZESIM
31 #include "circt/Dialect/Sim/SimPasses.h.inc"
32 } // namespace sim
33 } // namespace circt
34 
35 using namespace llvm;
36 using namespace circt;
37 using namespace sim;
38 
39 namespace {
40 struct ProceduralizeSimPass : impl::ProceduralizeSimBase<ProceduralizeSimPass> {
41 public:
42  void runOnOperation() override;
43 
44 private:
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 
58 LogicalResult 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.
213 void 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 
247 void 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
Direction get(bool isOutput)
Returns an output direction if isOutput is true, otherwise returns an input direction.
Definition: CalyxOps.cpp:55
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.
Definition: DebugAnalysis.h:21
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