CIRCT  20.0.0git
ExpandWhens.cpp
Go to the documentation of this file.
1 //===- ExpandWhens.cpp - Expand WhenOps into muxed operations ---*- C++ -*-===//
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 // This file defines the ExpandWhens pass.
10 //
11 //===----------------------------------------------------------------------===//
12 
18 #include "circt/Support/FieldRef.h"
19 #include "mlir/Pass/Pass.h"
20 #include "llvm/ADT/MapVector.h"
21 #include "llvm/ADT/STLExtras.h"
22 
23 namespace circt {
24 namespace firrtl {
25 #define GEN_PASS_DEF_EXPANDWHENS
26 #include "circt/Dialect/FIRRTL/Passes.h.inc"
27 } // namespace firrtl
28 } // namespace circt
29 
30 using namespace circt;
31 using namespace firrtl;
32 
33 /// Move all operations from a source block in to a destination block. Leaves
34 /// the source block empty.
35 static void mergeBlock(Block &destination, Block::iterator insertPoint,
36  Block &source) {
37  destination.getOperations().splice(insertPoint, source.getOperations());
38 }
39 
40 /// This is a stack of hashtables, if lookup fails in the top-most hashtable,
41 /// it will attempt to lookup in lower hashtables. This class is used instead
42 /// of a ScopedHashTable so we can manually pop off a scope and keep it around.
43 ///
44 /// This only allows inserting into the outermost scope.
45 template <typename KeyT, typename ValueT>
47  using ScopeT = typename llvm::MapVector<KeyT, ValueT>;
48  using StackT = typename llvm::SmallVector<ScopeT, 3>;
49 
50  struct Iterator {
51  Iterator(typename StackT::iterator stackIt,
52  typename ScopeT::iterator scopeIt)
53  : stackIt(stackIt), scopeIt(scopeIt) {}
54 
55  bool operator==(const Iterator &rhs) const {
56  return stackIt == rhs.stackIt && scopeIt == rhs.scopeIt;
57  }
58 
59  bool operator!=(const Iterator &rhs) const { return !(*this == rhs); }
60 
61  std::pair<KeyT, ValueT> &operator*() const { return *scopeIt; }
62 
64  if (scopeIt == stackIt->end())
65  scopeIt = (++stackIt)->begin();
66  else
67  ++scopeIt;
68  return *this;
69  }
70 
71  typename StackT::iterator stackIt;
72  typename ScopeT::iterator scopeIt;
73  };
74 
76  // We require at least one scope.
77  pushScope();
78  }
79 
80  using iterator = Iterator;
81 
83  return Iterator(mapStack.begin(), mapStack.first().begin());
84  }
85 
86  iterator end() { return Iterator(mapStack.end() - 1, mapStack.back().end()); }
87 
88  iterator find(const KeyT &key) {
89  // Try to find a hashtable with the missing value.
90  for (auto i = mapStack.size(); i > 0; --i) {
91  auto &map = mapStack[i - 1];
92  auto it = map.find(key);
93  if (it != map.end())
94  return Iterator(mapStack.begin() + i - 1, it);
95  }
96  return end();
97  }
98 
99  ScopeT &getLastScope() { return mapStack.back(); }
100 
101  void pushScope() { mapStack.emplace_back(); }
102 
104  assert(mapStack.size() > 1 && "Cannot pop the last scope");
105  return mapStack.pop_back_val();
106  }
107 
108  // This class lets you insert into the top scope.
109  ValueT &operator[](const KeyT &key) { return mapStack.back()[key]; }
110 
111 private:
113 };
114 
115 /// This is a determistic mapping of a FieldRef to the last operation which set
116 /// a value to it.
119 
120 //===----------------------------------------------------------------------===//
121 // Last Connect Resolver
122 //===----------------------------------------------------------------------===//
123 
124 namespace {
125 /// This visitor visits process a block resolving last connect semantics
126 /// and recursively expanding WhenOps.
127 template <typename ConcreteT>
128 class LastConnectResolver : public FIRRTLVisitor<ConcreteT> {
129 protected:
130  /// Map of destinations and the operation which is driving a value to it in
131  /// the current scope. This is used for resolving last connect semantics, and
132  /// for retrieving the responsible connect operation.
133  ScopedDriverMap &driverMap;
134 
135 public:
136  LastConnectResolver(ScopedDriverMap &driverMap) : driverMap(driverMap) {}
137 
141 
142  /// Records a connection to a destination in the current scope.
143  /// If connection has static single connect behavior, this is all.
144  /// For connections with last-connect behavior, this will delete a previous
145  /// connection to a destination if there was one.
146  /// Returns true if an old connect was erased.
147  bool recordConnect(FieldRef dest, Operation *connection) {
148  // Try to insert, if it doesn't insert, replace the previous value.
149  auto itAndInserted = driverMap.getLastScope().insert({dest, connection});
150  if (isStaticSingleConnect(connection)) {
151  // There should be no non-null driver already, Verifier checks this.
152  assert(itAndInserted.second || !itAndInserted.first->second);
153  if (!itAndInserted.second)
154  itAndInserted.first->second = connection;
155  return false;
156  }
157  assert(isLastConnect(connection));
158  if (!std::get<1>(itAndInserted)) {
159  auto iterator = std::get<0>(itAndInserted);
160  auto changed = false;
161  // Delete the old connection if it exists. Null connections are inserted
162  // on declarations.
163  if (auto *oldConnect = iterator->second) {
164  oldConnect->erase();
165  changed = true;
166  }
167  iterator->second = connection;
168  return changed;
169  }
170  return false;
171  }
172 
173  /// Get the destination value from a connection. This supports any operation
174  /// which is capable of driving a value.
175  static Value getDestinationValue(Operation *op) {
176  return cast<FConnectLike>(op).getDest();
177  }
178 
179  /// Get the source value from a connection. This supports any operation which
180  /// is capable of driving a value.
181  static Value getConnectedValue(Operation *op) {
182  return cast<FConnectLike>(op).getSrc();
183  }
184 
185  /// Return whether the connection has static single connection behavior.
186  static bool isStaticSingleConnect(Operation *op) {
187  return cast<FConnectLike>(op).hasStaticSingleConnectBehavior();
188  }
189 
190  /// Return whether the connection has last-connect behavior.
191  /// Compared to static single connect behavior, with last-connect behavior
192  /// destinations can be connected to multiple times and are connected
193  /// conditionally when connecting out from under a 'when'.
194  static bool isLastConnect(Operation *op) {
195  return cast<FConnectLike>(op).hasLastConnectBehavior();
196  }
197 
198  /// For every leaf field in the sink, record that it exists and should be
199  /// initialized.
200  void declareSinks(Value value, Flow flow, bool local = false) {
201  auto type = value.getType();
202  unsigned id = 0;
203 
204  // Recurse through a bundle and declare each leaf sink node.
205  std::function<void(Type, Flow, bool)> declare = [&](Type type, Flow flow,
206  bool local) {
207  // If this is a class type, recurse to each of the fields.
208  if (auto classType = type_dyn_cast<ClassType>(type)) {
209  if (local) {
210  // If this is a local object declaration, then we are responsible for
211  // initializing its input ports.
212  for (auto &element : classType.getElements()) {
213  id++;
214  if (element.direction == Direction::Out)
215  declare(element.type, flow, false);
216  else
217  declare(element.type, swapFlow(flow), false);
218  }
219  } else {
220  // If this is a remote object, then the object itself is potentially
221  // a sink.
222  if (flow != Flow::Source)
223  driverMap[{value, id}] = nullptr;
224  // skip over its subfields--we are not responsible for initializing
225  // the object here.
226  id += classType.getMaxFieldID();
227  }
228  return;
229  }
230 
231  // If this is a bundle type, recurse to each of the fields.
232  if (auto bundleType = type_dyn_cast<BundleType>(type)) {
233  for (auto &element : bundleType.getElements()) {
234  id++;
235  if (element.isFlip)
236  declare(element.type, swapFlow(flow), false);
237  else
238  declare(element.type, flow, false);
239  }
240  return;
241  }
242 
243  // If this is a vector type, recurse to each of the elements.
244  if (auto vectorType = type_dyn_cast<FVectorType>(type)) {
245  for (unsigned i = 0; i < vectorType.getNumElements(); ++i) {
246  id++;
247  declare(vectorType.getElementType(), flow, false);
248  }
249  return;
250  }
251 
252  // If this is an analog type, it does not need to be tracked.
253  if (auto analogType = type_dyn_cast<AnalogType>(type))
254  return;
255 
256  // If it is a leaf node with Flow::Sink or Flow::Duplex, it must be
257  // initialized.
258  if (flow != Flow::Source)
259  driverMap[{value, id}] = nullptr;
260  };
261 
262  declare(type, flow, local);
263  }
264 
265  /// Take two connection operations and merge them into a new connect under a
266  /// condition. Destination of both connects should be `dest`.
267  ConnectOp flattenConditionalConnections(OpBuilder &b, Location loc,
268  Value dest, Value cond,
269  Operation *whenTrueConn,
270  Operation *whenFalseConn) {
271  assert(isLastConnect(whenTrueConn) && isLastConnect(whenFalseConn));
272  auto fusedLoc =
273  b.getFusedLoc({loc, whenTrueConn->getLoc(), whenFalseConn->getLoc()});
274  auto whenTrue = getConnectedValue(whenTrueConn);
275  auto trueIsInvalid =
276  isa_and_nonnull<InvalidValueOp>(whenTrue.getDefiningOp());
277  auto whenFalse = getConnectedValue(whenFalseConn);
278  auto falseIsInvalid =
279  isa_and_nonnull<InvalidValueOp>(whenFalse.getDefiningOp());
280  // If one of the branches of the mux is an invalid value, we optimize the
281  // mux to be the non-invalid value. This optimization can only be
282  // performed while lowering when-ops into muxes, and would not be legal as
283  // a more general mux folder.
284  // mux(cond, invalid, x) -> x
285  // mux(cond, x, invalid) -> x
286  Value newValue = whenTrue;
287  if (trueIsInvalid == falseIsInvalid)
288  newValue = b.createOrFold<MuxPrimOp>(fusedLoc, cond, whenTrue, whenFalse);
289  else if (trueIsInvalid)
290  newValue = whenFalse;
291  return b.create<ConnectOp>(loc, dest, newValue);
292  }
293 
294  void visitDecl(WireOp op) { declareSinks(op.getResult(), Flow::Duplex); }
295 
296  /// Take an aggregate value and construct ground subelements recursively.
297  /// And then apply function `fn`.
298  void foreachSubelement(OpBuilder &builder, Value value,
299  llvm::function_ref<void(Value)> fn) {
300  FIRRTLTypeSwitch<Type>(value.getType())
301  .template Case<BundleType>([&](BundleType bundle) {
302  for (auto i : llvm::seq(0u, (unsigned)bundle.getNumElements())) {
303  auto subfield =
304  builder.create<SubfieldOp>(value.getLoc(), value, i);
305  foreachSubelement(builder, subfield, fn);
306  }
307  })
308  .template Case<FVectorType>([&](FVectorType vector) {
309  for (auto i : llvm::seq((size_t)0, vector.getNumElements())) {
310  auto subindex =
311  builder.create<SubindexOp>(value.getLoc(), value, i);
312  foreachSubelement(builder, subindex, fn);
313  }
314  })
315  .Default([&](auto) { fn(value); });
316  }
317 
318  void visitDecl(RegOp op) {
319  // Registers are initialized to themselves. If the register has an
320  // aggergate type, connect each ground type element.
321  auto builder = OpBuilder(op->getBlock(), ++Block::iterator(op));
322  auto fn = [&](Value value) {
323  auto connect = builder.create<ConnectOp>(value.getLoc(), value, value);
324  driverMap[getFieldRefFromValue(value)] = connect;
325  };
326  foreachSubelement(builder, op.getResult(), fn);
327  }
328 
329  void visitDecl(RegResetOp op) {
330  // Registers are initialized to themselves. If the register has an
331  // aggergate type, connect each ground type element.
332  auto builder = OpBuilder(op->getBlock(), ++Block::iterator(op));
333  auto fn = [&](Value value) {
334  auto connect = builder.create<ConnectOp>(value.getLoc(), value, value);
335  driverMap[getFieldRefFromValue(value)] = connect;
336  };
337  foreachSubelement(builder, op.getResult(), fn);
338  }
339 
340  void visitDecl(InstanceOp op) {
341  // Track any instance inputs which need to be connected to for init
342  // coverage.
343  for (const auto &result : llvm::enumerate(op.getResults()))
344  if (op.getPortDirection(result.index()) == Direction::Out)
345  declareSinks(result.value(), Flow::Source);
346  else
347  declareSinks(result.value(), Flow::Sink);
348  }
349 
350  void visitDecl(ObjectOp op) {
351  declareSinks(op, Flow::Source, /*local=*/true);
352  }
353 
354  void visitDecl(MemOp op) {
355  // Track any memory inputs which require connections.
356  for (auto result : op.getResults())
357  if (!isa<RefType>(result.getType()))
358  declareSinks(result, Flow::Sink);
359  }
360 
361  void visitStmt(ConnectOp op) {
362  recordConnect(getFieldRefFromValue(op.getDest()), op);
363  }
364 
365  void visitStmt(MatchingConnectOp op) {
366  recordConnect(getFieldRefFromValue(op.getDest()), op);
367  }
368 
369  void visitStmt(RefDefineOp op) {
370  recordConnect(getFieldRefFromValue(op.getDest()), op);
371  }
372 
373  void visitStmt(PropAssignOp op) {
374  recordConnect(getFieldRefFromValue(op.getDest()), op);
375  }
376 
377  void processWhenOp(WhenOp whenOp, Value outerCondition);
378 
379  /// Combine the connect statements from each side of the block. There are 5
380  /// cases to consider. If all are set, last connect semantics dictate that it
381  /// is actually the third case.
382  ///
383  /// Prev | Then | Else | Outcome
384  /// -----|------|------|-------
385  /// | set | | then
386  /// | | set | else
387  /// set | set | set | mux(p, then, else)
388  /// | set | set | impossible
389  /// set | set | | mux(p, then, prev)
390  /// set | | set | mux(p, prev, else)
391  ///
392  /// If the value was declared in the block, then it does not need to have been
393  /// assigned a previous value. If the value was declared before the block,
394  /// then there is an incomplete initialization error.
395  void mergeScopes(Location loc, DriverMap &thenScope, DriverMap &elseScope,
396  Value thenCondition) {
397 
398  // Process all connects in the `then` block.
399  for (auto &destAndConnect : thenScope) {
400  auto dest = std::get<0>(destAndConnect);
401  auto thenConnect = std::get<1>(destAndConnect);
402 
403  auto outerIt = driverMap.find(dest);
404  if (outerIt == driverMap.end()) {
405  // `dest` is set in `then` only. This indicates it was created in the
406  // `then` block, so just copy it into the outer scope.
407  driverMap[dest] = thenConnect;
408  continue;
409  }
410 
411  auto elseIt = elseScope.find(dest);
412  if (elseIt != elseScope.end()) {
413  // `dest` is set in `then` and `else`. We need to combine them into and
414  // delete any previous connect.
415 
416  // Create a new connect with `mux(p, then, else)`.
417  auto &elseConnect = std::get<1>(*elseIt);
418  OpBuilder connectBuilder(elseConnect);
419  auto newConnect = flattenConditionalConnections(
420  connectBuilder, loc, getDestinationValue(thenConnect),
421  thenCondition, thenConnect, elseConnect);
422 
423  // Delete all old connections.
424  thenConnect->erase();
425  elseConnect->erase();
426  recordConnect(dest, newConnect);
427 
428  continue;
429  }
430 
431  auto &outerConnect = std::get<1>(*outerIt);
432  if (!outerConnect) {
433  if (isLastConnect(thenConnect)) {
434  // `dest` is null in the outer scope. This indicate an initialization
435  // problem: `mux(p, then, nullptr)`. Just delete the broken connect.
436  thenConnect->erase();
437  } else {
438  assert(isStaticSingleConnect(thenConnect));
439  driverMap[dest] = thenConnect;
440  }
441  continue;
442  }
443 
444  // `dest` is set in `then` and the outer scope. Create a new connect with
445  // `mux(p, then, outer)`.
446  OpBuilder connectBuilder(thenConnect);
447  auto newConnect = flattenConditionalConnections(
448  connectBuilder, loc, getDestinationValue(thenConnect), thenCondition,
449  thenConnect, outerConnect);
450 
451  // Delete all old connections.
452  thenConnect->erase();
453  recordConnect(dest, newConnect);
454  }
455 
456  // Process all connects in the `else` block.
457  for (auto &destAndConnect : elseScope) {
458  auto dest = std::get<0>(destAndConnect);
459  auto elseConnect = std::get<1>(destAndConnect);
460 
461  // If this destination was driven in the 'then' scope, then we will have
462  // already consumed the driver from the 'else' scope, and we must skip it.
463  if (thenScope.contains(dest))
464  continue;
465 
466  auto outerIt = driverMap.find(dest);
467  if (outerIt == driverMap.end()) {
468  // `dest` is set in `else` only. This indicates it was created in the
469  // `else` block, so just copy it into the outer scope.
470  driverMap[dest] = elseConnect;
471  continue;
472  }
473 
474  auto &outerConnect = std::get<1>(*outerIt);
475  if (!outerConnect) {
476  if (isLastConnect(elseConnect)) {
477  // `dest` is null in the outer scope. This indicates an initialization
478  // problem: `mux(p, then, nullptr)`. Just delete the broken connect.
479  elseConnect->erase();
480  } else {
481  assert(isStaticSingleConnect(elseConnect));
482  driverMap[dest] = elseConnect;
483  }
484  continue;
485  }
486 
487  // `dest` is set in the `else` and outer scope. Create a new connect with
488  // `mux(p, outer, else)`.
489  OpBuilder connectBuilder(elseConnect);
490  auto newConnect = flattenConditionalConnections(
491  connectBuilder, loc, getDestinationValue(outerConnect), thenCondition,
492  outerConnect, elseConnect);
493 
494  // Delete all old connections.
495  elseConnect->erase();
496  recordConnect(dest, newConnect);
497  }
498  }
499 };
500 } // namespace
501 
502 //===----------------------------------------------------------------------===//
503 // WhenOpVisitor
504 //===----------------------------------------------------------------------===//
505 
506 /// This extends the LastConnectVisitor to handle all Simulation related
507 /// constructs which do not need any processing at the module scope, but need to
508 /// be processed inside of a WhenOp.
509 namespace {
510 class WhenOpVisitor : public LastConnectResolver<WhenOpVisitor> {
511 
512 public:
513  WhenOpVisitor(ScopedDriverMap &driverMap, Value condition)
514  : LastConnectResolver<WhenOpVisitor>(driverMap), condition(condition) {}
515 
516  using LastConnectResolver<WhenOpVisitor>::visitExpr;
517  using LastConnectResolver<WhenOpVisitor>::visitDecl;
518  using LastConnectResolver<WhenOpVisitor>::visitStmt;
519  using LastConnectResolver<WhenOpVisitor>::visitStmtExpr;
520 
521  /// Process a block, recording each declaration, and expanding all whens.
522  void process(Block &block);
523 
524  /// Simulation Constructs.
525  void visitStmt(VerifAssertIntrinsicOp op);
526  void visitStmt(VerifAssumeIntrinsicOp op);
527  void visitStmt(VerifCoverIntrinsicOp op);
528  void visitStmt(AssertOp op);
529  void visitStmt(AssumeOp op);
530  void visitStmt(UnclockedAssumeIntrinsicOp op);
531  void visitStmt(CoverOp op);
532  void visitStmt(ModuleOp op);
533  void visitStmt(PrintFOp op);
534  void visitStmt(StopOp op);
535  void visitStmt(WhenOp op);
536  void visitStmt(LayerBlockOp op);
537  void visitStmt(RefForceOp op);
538  void visitStmt(RefForceInitialOp op);
539  void visitStmt(RefReleaseOp op);
540  void visitStmt(RefReleaseInitialOp op);
541  void visitStmtExpr(DPICallIntrinsicOp op);
542 
543 private:
544  /// And a 1-bit value with the current condition. If we are in the outer
545  /// scope, i.e. not in a WhenOp region, then there is no condition.
546  Value andWithCondition(Operation *op, Value value) {
547  // 'and' the value with the current condition.
548  return OpBuilder(op).createOrFold<AndPrimOp>(
549  condition.getLoc(), condition.getType(), condition, value);
550  }
551 
552  /// Concurrent and of a property with the current condition. If we are in
553  /// the outer scope, i.e. not in a WhenOp region, then there is no condition.
554  Value ltlAndWithCondition(Operation *op, Value property) {
555  // Look through nodes.
556  while (auto nodeOp = property.getDefiningOp<NodeOp>())
557  property = nodeOp.getInput();
558 
559  // Look through `ltl.clock` ops.
560  if (auto clockOp = property.getDefiningOp<LTLClockIntrinsicOp>()) {
561  auto input = ltlAndWithCondition(op, clockOp.getInput());
562  auto &newClockOp = createdLTLClockOps[{clockOp, input}];
563  if (!newClockOp) {
564  newClockOp = OpBuilder(op).cloneWithoutRegions(clockOp);
565  newClockOp.getInputMutable().assign(input);
566  }
567  return newClockOp;
568  }
569 
570  // Otherwise create a new `ltl.and` with the condition.
571  auto &newOp = createdLTLAndOps[{condition, property}];
572  if (!newOp)
573  newOp = OpBuilder(op).createOrFold<LTLAndIntrinsicOp>(
574  condition.getLoc(), property.getType(), condition, property);
575  return newOp;
576  }
577 
578  /// Overlapping implication with the condition as its antecedent and a given
579  /// property as the consequent. If we are in the outer scope, i.e. not in a
580  /// WhenOp region, then there is no condition.
581  Value ltlImplicationWithCondition(Operation *op, Value property) {
582  // Look through nodes.
583  while (auto nodeOp = property.getDefiningOp<NodeOp>())
584  property = nodeOp.getInput();
585 
586  // Look through `ltl.clock` ops.
587  if (auto clockOp = property.getDefiningOp<LTLClockIntrinsicOp>()) {
588  auto input = ltlImplicationWithCondition(op, clockOp.getInput());
589  auto &newClockOp = createdLTLClockOps[{clockOp, input}];
590  if (!newClockOp) {
591  newClockOp = OpBuilder(op).cloneWithoutRegions(clockOp);
592  newClockOp.getInputMutable().assign(input);
593  }
594  return newClockOp;
595  }
596 
597  // Merge condition into `ltl.implication` left-hand side.
598  if (auto implOp = property.getDefiningOp<LTLImplicationIntrinsicOp>()) {
599  auto lhs = ltlAndWithCondition(op, implOp.getLhs());
600  auto &newImplOp = createdLTLImplicationOps[{lhs, implOp.getRhs()}];
601  if (!newImplOp) {
602  auto clonedOp = OpBuilder(op).cloneWithoutRegions(implOp);
603  clonedOp.getLhsMutable().assign(lhs);
604  newImplOp = clonedOp;
605  }
606  return newImplOp;
607  }
608 
609  // Otherwise create a new `ltl.implication` with the condition on the LHS.
610  auto &newImplOp = createdLTLImplicationOps[{condition, property}];
611  if (!newImplOp)
612  newImplOp = OpBuilder(op).createOrFold<LTLImplicationIntrinsicOp>(
613  condition.getLoc(), property.getType(), condition, property);
614  return newImplOp;
615  }
616 
617 private:
618  /// The current wrapping condition. If null, we are in the outer scope.
619  Value condition;
620 
621  /// The `ltl.and` operations that have been created.
622  SmallDenseMap<std::pair<Value, Value>, Value> createdLTLAndOps;
623 
624  /// The `ltl.implication` operations that have been created.
625  SmallDenseMap<std::pair<Value, Value>, Value> createdLTLImplicationOps;
626 
627  /// The `ltl.clock` operations that have been created.
629  createdLTLClockOps;
630 };
631 } // namespace
632 
633 void WhenOpVisitor::process(Block &block) {
634  for (auto &op : llvm::make_early_inc_range(block)) {
635  dispatchVisitor(&op);
636  }
637 }
638 
639 void WhenOpVisitor::visitStmt(PrintFOp op) {
640  op.getCondMutable().assign(andWithCondition(op, op.getCond()));
641 }
642 
643 void WhenOpVisitor::visitStmt(StopOp op) {
644  op.getCondMutable().assign(andWithCondition(op, op.getCond()));
645 }
646 
647 void WhenOpVisitor::visitStmt(VerifAssertIntrinsicOp op) {
648  op.getPropertyMutable().assign(
649  ltlImplicationWithCondition(op, op.getProperty()));
650 }
651 
652 void WhenOpVisitor::visitStmt(VerifAssumeIntrinsicOp op) {
653  op.getPropertyMutable().assign(
654  ltlImplicationWithCondition(op, op.getProperty()));
655 }
656 
657 void WhenOpVisitor::visitStmt(VerifCoverIntrinsicOp op) {
658  op.getPropertyMutable().assign(ltlAndWithCondition(op, op.getProperty()));
659 }
660 
661 void WhenOpVisitor::visitStmt(AssertOp op) {
662  op.getEnableMutable().assign(andWithCondition(op, op.getEnable()));
663 }
664 
665 void WhenOpVisitor::visitStmt(AssumeOp op) {
666  op.getEnableMutable().assign(andWithCondition(op, op.getEnable()));
667 }
668 
669 void WhenOpVisitor::visitStmt(UnclockedAssumeIntrinsicOp op) {
670  op.getEnableMutable().assign(andWithCondition(op, op.getEnable()));
671 }
672 
673 void WhenOpVisitor::visitStmt(CoverOp op) {
674  op.getEnableMutable().assign(andWithCondition(op, op.getEnable()));
675 }
676 
677 void WhenOpVisitor::visitStmt(WhenOp whenOp) {
678  processWhenOp(whenOp, condition);
679 }
680 
681 // NOLINTNEXTLINE(misc-no-recursion)
682 void WhenOpVisitor::visitStmt(LayerBlockOp layerBlockOp) {
683  process(*layerBlockOp.getBody());
684 }
685 
686 void WhenOpVisitor::visitStmt(RefForceOp op) {
687  op.getPredicateMutable().assign(andWithCondition(op, op.getPredicate()));
688 }
689 
690 void WhenOpVisitor::visitStmt(RefForceInitialOp op) {
691  op.getPredicateMutable().assign(andWithCondition(op, op.getPredicate()));
692 }
693 
694 void WhenOpVisitor::visitStmt(RefReleaseOp op) {
695  op.getPredicateMutable().assign(andWithCondition(op, op.getPredicate()));
696 }
697 
698 void WhenOpVisitor::visitStmt(RefReleaseInitialOp op) {
699  op.getPredicateMutable().assign(andWithCondition(op, op.getPredicate()));
700 }
701 
702 void WhenOpVisitor::visitStmtExpr(DPICallIntrinsicOp op) {
703  if (op.getEnable())
704  op.getEnableMutable().assign(andWithCondition(op, op.getEnable()));
705  else
706  op.getEnableMutable().assign(condition);
707 }
708 
709 /// This is a common helper that is dispatched to by the concrete visitors.
710 /// This condition should be the conjunction of all surrounding WhenOp
711 /// condititions.
712 ///
713 /// This requires WhenOpVisitor to be fully defined.
714 template <typename ConcreteT>
715 void LastConnectResolver<ConcreteT>::processWhenOp(WhenOp whenOp,
716  Value outerCondition) {
717  OpBuilder b(whenOp);
718  auto loc = whenOp.getLoc();
719  Block *parentBlock = whenOp->getBlock();
720  auto condition = whenOp.getCondition();
721  auto ui1Type = condition.getType();
722 
723  // Process both sides of the WhenOp, fixing up all simulation constructs,
724  // and resolving last connect semantics in each block. This process returns
725  // the set of connects in each side of the when op.
726 
727  // Process the `then` block. If we are already in a whenblock, the we need to
728  // conjoin ('and') the outer conditions.
729  Value thenCondition = whenOp.getCondition();
730  if (outerCondition)
731  thenCondition =
732  b.createOrFold<AndPrimOp>(loc, ui1Type, outerCondition, thenCondition);
733 
734  auto &thenBlock = whenOp.getThenBlock();
735  driverMap.pushScope();
736  WhenOpVisitor(driverMap, thenCondition).process(thenBlock);
737  mergeBlock(*parentBlock, Block::iterator(whenOp), thenBlock);
738  auto thenScope = driverMap.popScope();
739 
740  // Process the `else` block.
741  DriverMap elseScope;
742  if (whenOp.hasElseRegion()) {
743  // Else condition is the complement of the then condition.
744  auto elseCondition =
745  b.createOrFold<NotPrimOp>(loc, condition.getType(), condition);
746  // Conjoin the when condition with the outer condition.
747  if (outerCondition)
748  elseCondition = b.createOrFold<AndPrimOp>(loc, ui1Type, outerCondition,
749  elseCondition);
750  auto &elseBlock = whenOp.getElseBlock();
751  driverMap.pushScope();
752  WhenOpVisitor(driverMap, elseCondition).process(elseBlock);
753  mergeBlock(*parentBlock, Block::iterator(whenOp), elseBlock);
754  elseScope = driverMap.popScope();
755  }
756 
757  mergeScopes(loc, thenScope, elseScope, condition);
758 
759  // Delete the now empty WhenOp.
760  whenOp.erase();
761 }
762 
763 //===----------------------------------------------------------------------===//
764 // ModuleOpVisitor
765 //===----------------------------------------------------------------------===//
766 
767 namespace {
768 /// This extends the LastConnectResolver to track if anything has changed.
769 class ModuleVisitor : public LastConnectResolver<ModuleVisitor> {
770 public:
771  ModuleVisitor() : LastConnectResolver<ModuleVisitor>(driverMap) {}
772 
773  using LastConnectResolver<ModuleVisitor>::visitExpr;
774  using LastConnectResolver<ModuleVisitor>::visitDecl;
775  using LastConnectResolver<ModuleVisitor>::visitStmt;
776  void visitStmt(WhenOp whenOp);
777  void visitStmt(ConnectOp connectOp);
778  void visitStmt(MatchingConnectOp connectOp);
779  void visitStmt(LayerBlockOp layerBlockOp);
780 
781  bool run(FModuleLike op);
782  LogicalResult checkInitialization();
783 
784 private:
785  /// The outermost scope of the module body.
786  ScopedDriverMap driverMap;
787 
788  /// Tracks if anything in the IR has changed.
789  bool anythingChanged = false;
790 };
791 } // namespace
792 
793 /// Run expand whens on the Module. This will emit an error for each
794 /// incomplete initialization found. If an initialiazation error was detected,
795 /// this will return failure and leave the IR in an inconsistent state.
796 bool ModuleVisitor::run(FModuleLike op) {
797  // We only lower whens inside of fmodule ops or class ops.
798  if (!isa<FModuleOp, ClassOp>(op))
799  return anythingChanged;
800 
801  for (auto &region : op->getRegions()) {
802  for (auto &block : region.getBlocks()) {
803  // Track any results (flipped arguments) of the module for init coverage.
804  for (const auto &[index, value] : llvm::enumerate(block.getArguments())) {
805  auto direction = op.getPortDirection(index);
806  auto flow = direction == Direction::In ? Flow::Source : Flow::Sink;
807  declareSinks(value, flow);
808  }
809 
810  // Process the body of the module.
811  for (auto &op : llvm::make_early_inc_range(block))
812  dispatchVisitor(&op);
813  }
814  }
815 
816  return anythingChanged;
817 }
818 
819 void ModuleVisitor::visitStmt(ConnectOp op) {
820  anythingChanged |= recordConnect(getFieldRefFromValue(op.getDest()), op);
821 }
822 
823 void ModuleVisitor::visitStmt(MatchingConnectOp op) {
824  anythingChanged |= recordConnect(getFieldRefFromValue(op.getDest()), op);
825 }
826 
827 void ModuleVisitor::visitStmt(WhenOp whenOp) {
828  // If we are deleting a WhenOp something definitely changed.
829  anythingChanged = true;
830  processWhenOp(whenOp, /*outerCondition=*/{});
831 }
832 
833 void ModuleVisitor::visitStmt(LayerBlockOp layerBlockOp) {
834  for (auto &op : llvm::make_early_inc_range(*layerBlockOp.getBody())) {
835  dispatchVisitor(&op);
836  }
837 }
838 
839 /// Perform initialization checking. This uses the built up state from
840 /// running on a module. Returns failure in the event of bad initialization.
841 LogicalResult ModuleVisitor::checkInitialization() {
842  bool failed = false;
843  for (auto destAndConnect : driverMap.getLastScope()) {
844  // If there is valid connection to this destination, everything is good.
845  auto *connect = std::get<1>(destAndConnect);
846  if (connect)
847  continue;
848 
849  // Get the op which defines the sink, and emit an error.
850  FieldRef dest = std::get<0>(destAndConnect);
851  auto loc = dest.getValue().getLoc();
852  auto *definingOp = dest.getDefiningOp();
853  if (auto mod = dyn_cast<FModuleLike>(definingOp))
854  mlir::emitError(loc) << "port \"" << getFieldName(dest).first
855  << "\" not fully initialized in \""
856  << mod.getModuleName() << "\"";
857  else
858  mlir::emitError(loc)
859  << "sink \"" << getFieldName(dest).first
860  << "\" not fully initialized in \""
861  << definingOp->getParentOfType<FModuleLike>().getModuleName() << "\"";
862  failed = true;
863  }
864  if (failed)
865  return failure();
866  return success();
867 }
868 
869 //===----------------------------------------------------------------------===//
870 // Pass Infrastructure
871 //===----------------------------------------------------------------------===//
872 
873 namespace {
874 class ExpandWhensPass
875  : public circt::firrtl::impl::ExpandWhensBase<ExpandWhensPass> {
876  void runOnOperation() override;
877 };
878 } // end anonymous namespace
879 
880 void ExpandWhensPass::runOnOperation() {
881  ModuleVisitor visitor;
882  if (!visitor.run(getOperation()))
883  markAllAnalysesPreserved();
884  if (failed(visitor.checkInitialization()))
885  signalPassFailure();
886 }
887 
888 std::unique_ptr<mlir::Pass> circt::firrtl::createExpandWhensPass() {
889  return std::make_unique<ExpandWhensPass>();
890 }
assert(baseType &&"element must be base type")
ScopedDriverMap::ScopeT DriverMap
static void mergeBlock(Block &destination, Block::iterator insertPoint, Block &source)
Move all operations from a source block in to a destination block.
Definition: ExpandWhens.cpp:35
This class represents a reference to a specific field or element of an aggregate value.
Definition: FieldRef.h:28
Value getValue() const
Get the Value which created this location.
Definition: FieldRef.h:37
Operation * getDefiningOp() const
Get the operation which defines this field.
Definition: FieldRef.cpp:19
This class implements the same functionality as TypeSwitch except that it uses firrtl::type_dyn_cast ...
Definition: FIRRTLTypes.h:520
FIRRTLVisitor allows you to visit all of the expr/stmt/decls with one class declaration.
def connect(destination, source)
Definition: support.py:39
Flow swapFlow(Flow flow)
Get a flow's reverse.
Definition: FIRRTLOps.cpp:182
FieldRef getFieldRefFromValue(Value value, bool lookThroughCasts=false)
Get the FieldRef from a value.
std::unique_ptr< mlir::Pass > createExpandWhensPass()
std::pair< std::string, bool > getFieldName(const FieldRef &fieldRef, bool nameSafe=false)
Get a string identifier representing the FieldRef.
The InstanceGraph op interface, see InstanceGraphInterface.td for more details.
Definition: DebugAnalysis.h:21
int run(Type[Generator] generator=CppGenerator, cmdline_args=sys.argv)
Definition: codegen.py:121
StackT::iterator stackIt
Definition: ExpandWhens.cpp:71
std::pair< KeyT, ValueT > & operator*() const
Definition: ExpandWhens.cpp:61
bool operator!=(const Iterator &rhs) const
Definition: ExpandWhens.cpp:59
bool operator==(const Iterator &rhs) const
Definition: ExpandWhens.cpp:55
ScopeT::iterator scopeIt
Definition: ExpandWhens.cpp:72
Iterator(typename StackT::iterator stackIt, typename ScopeT::iterator scopeIt)
Definition: ExpandWhens.cpp:51
This is a stack of hashtables, if lookup fails in the top-most hashtable, it will attempt to lookup i...
Definition: ExpandWhens.cpp:46
typename llvm::MapVector< KeyT, ValueT > ScopeT
Definition: ExpandWhens.cpp:47
iterator begin()
Definition: ExpandWhens.cpp:82
iterator find(const KeyT &key)
Definition: ExpandWhens.cpp:88
ScopeT popScope()
iterator end()
Definition: ExpandWhens.cpp:86
ValueT & operator[](const KeyT &key)
typename llvm::SmallVector< ScopeT, 3 > StackT
Definition: ExpandWhens.cpp:48
ScopeT & getLastScope()
Definition: ExpandWhens.cpp:99