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