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 
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 value) {
555  // 'ltl.and' the value with the current condition.
556  return OpBuilder(op).createOrFold<LTLAndIntrinsicOp>(
557  condition.getLoc(), condition.getType(), condition, value);
558  }
559 
560  /// Overlapping implication with the condition as its antecedent and a given
561  /// property as the consequent. If we are in the outer scope, i.e. not in a
562  /// WhenOp region, then there is no condition.
563  Value ltlImplicationWithCondition(Operation *op, Value value) {
564  // 'and' the value with the current condition.
565  return OpBuilder(op).createOrFold<LTLImplicationIntrinsicOp>(
566  condition.getLoc(), condition.getType(), condition, value);
567  }
568 
569 private:
570  /// The current wrapping condition. If null, we are in the outer scope.
571  Value condition;
572 };
573 } // namespace
574 
575 void WhenOpVisitor::process(Block &block) {
576  for (auto &op : llvm::make_early_inc_range(block)) {
577  dispatchVisitor(&op);
578  }
579 }
580 
581 void WhenOpVisitor::visitStmt(PrintFOp op) {
582  op.getCondMutable().assign(andWithCondition(op, op.getCond()));
583 }
584 
585 void WhenOpVisitor::visitStmt(StopOp op) {
586  op.getCondMutable().assign(andWithCondition(op, op.getCond()));
587 }
588 
589 void WhenOpVisitor::visitStmt(VerifAssertIntrinsicOp op) {
590  op.getPropertyMutable().assign(
591  ltlImplicationWithCondition(op, op.getProperty()));
592 }
593 
594 void WhenOpVisitor::visitStmt(VerifAssumeIntrinsicOp op) {
595  op.getPropertyMutable().assign(
596  ltlImplicationWithCondition(op, op.getProperty()));
597 }
598 
599 void WhenOpVisitor::visitStmt(VerifCoverIntrinsicOp op) {
600  op.getPropertyMutable().assign(ltlAndWithCondition(op, op.getProperty()));
601 }
602 
603 void WhenOpVisitor::visitStmt(AssertOp op) {
604  op.getEnableMutable().assign(andWithCondition(op, op.getEnable()));
605 }
606 
607 void WhenOpVisitor::visitStmt(AssumeOp op) {
608  op.getEnableMutable().assign(andWithCondition(op, op.getEnable()));
609 }
610 
611 void WhenOpVisitor::visitStmt(UnclockedAssumeIntrinsicOp op) {
612  op.getEnableMutable().assign(andWithCondition(op, op.getEnable()));
613 }
614 
615 void WhenOpVisitor::visitStmt(CoverOp op) {
616  op.getEnableMutable().assign(andWithCondition(op, op.getEnable()));
617 }
618 
619 void WhenOpVisitor::visitStmt(WhenOp whenOp) {
620  processWhenOp(whenOp, condition);
621 }
622 
623 // NOLINTNEXTLINE(misc-no-recursion)
624 void WhenOpVisitor::visitStmt(LayerBlockOp layerBlockOp) {
625  process(*layerBlockOp.getBody());
626 }
627 
628 void WhenOpVisitor::visitStmt(RefForceOp op) {
629  op.getPredicateMutable().assign(andWithCondition(op, op.getPredicate()));
630 }
631 
632 void WhenOpVisitor::visitStmt(RefForceInitialOp op) {
633  op.getPredicateMutable().assign(andWithCondition(op, op.getPredicate()));
634 }
635 
636 void WhenOpVisitor::visitStmt(RefReleaseOp op) {
637  op.getPredicateMutable().assign(andWithCondition(op, op.getPredicate()));
638 }
639 
640 void WhenOpVisitor::visitStmt(RefReleaseInitialOp op) {
641  op.getPredicateMutable().assign(andWithCondition(op, op.getPredicate()));
642 }
643 
644 void WhenOpVisitor::visitStmtExpr(DPICallIntrinsicOp op) {
645  if (op.getEnable())
646  op.getEnableMutable().assign(andWithCondition(op, op.getEnable()));
647  else
648  op.getEnableMutable().assign(condition);
649 }
650 
651 /// This is a common helper that is dispatched to by the concrete visitors.
652 /// This condition should be the conjunction of all surrounding WhenOp
653 /// condititions.
654 ///
655 /// This requires WhenOpVisitor to be fully defined.
656 template <typename ConcreteT>
657 void LastConnectResolver<ConcreteT>::processWhenOp(WhenOp whenOp,
658  Value outerCondition) {
659  OpBuilder b(whenOp);
660  auto loc = whenOp.getLoc();
661  Block *parentBlock = whenOp->getBlock();
662  auto condition = whenOp.getCondition();
663  auto ui1Type = condition.getType();
664 
665  // Process both sides of the WhenOp, fixing up all simulation constructs,
666  // and resolving last connect semantics in each block. This process returns
667  // the set of connects in each side of the when op.
668 
669  // Process the `then` block. If we are already in a whenblock, the we need to
670  // conjoin ('and') the outer conditions.
671  Value thenCondition = whenOp.getCondition();
672  if (outerCondition)
673  thenCondition =
674  b.createOrFold<AndPrimOp>(loc, ui1Type, outerCondition, thenCondition);
675 
676  auto &thenBlock = whenOp.getThenBlock();
677  driverMap.pushScope();
678  WhenOpVisitor(driverMap, thenCondition).process(thenBlock);
679  mergeBlock(*parentBlock, Block::iterator(whenOp), thenBlock);
680  auto thenScope = driverMap.popScope();
681 
682  // Process the `else` block.
683  DriverMap elseScope;
684  if (whenOp.hasElseRegion()) {
685  // Else condition is the complement of the then condition.
686  auto elseCondition =
687  b.createOrFold<NotPrimOp>(loc, condition.getType(), condition);
688  // Conjoin the when condition with the outer condition.
689  if (outerCondition)
690  elseCondition = b.createOrFold<AndPrimOp>(loc, ui1Type, outerCondition,
691  elseCondition);
692  auto &elseBlock = whenOp.getElseBlock();
693  driverMap.pushScope();
694  WhenOpVisitor(driverMap, elseCondition).process(elseBlock);
695  mergeBlock(*parentBlock, Block::iterator(whenOp), elseBlock);
696  elseScope = driverMap.popScope();
697  }
698 
699  mergeScopes(loc, thenScope, elseScope, condition);
700 
701  // Delete the now empty WhenOp.
702  whenOp.erase();
703 }
704 
705 //===----------------------------------------------------------------------===//
706 // ModuleOpVisitor
707 //===----------------------------------------------------------------------===//
708 
709 namespace {
710 /// This extends the LastConnectResolver to track if anything has changed.
711 class ModuleVisitor : public LastConnectResolver<ModuleVisitor> {
712 public:
713  ModuleVisitor() : LastConnectResolver<ModuleVisitor>(driverMap) {}
714 
715  using LastConnectResolver<ModuleVisitor>::visitExpr;
716  using LastConnectResolver<ModuleVisitor>::visitDecl;
717  using LastConnectResolver<ModuleVisitor>::visitStmt;
718  void visitStmt(WhenOp whenOp);
719  void visitStmt(ConnectOp connectOp);
720  void visitStmt(MatchingConnectOp connectOp);
721  void visitStmt(LayerBlockOp layerBlockOp);
722 
723  bool run(FModuleLike op);
724  LogicalResult checkInitialization();
725 
726 private:
727  /// The outermost scope of the module body.
728  ScopedDriverMap driverMap;
729 
730  /// Tracks if anything in the IR has changed.
731  bool anythingChanged = false;
732 };
733 } // namespace
734 
735 /// Run expand whens on the Module. This will emit an error for each
736 /// incomplete initialization found. If an initialiazation error was detected,
737 /// this will return failure and leave the IR in an inconsistent state.
738 bool ModuleVisitor::run(FModuleLike op) {
739  // We only lower whens inside of fmodule ops or class ops.
740  if (!isa<FModuleOp, ClassOp>(op))
741  return anythingChanged;
742 
743  for (auto &region : op->getRegions()) {
744  for (auto &block : region.getBlocks()) {
745  // Track any results (flipped arguments) of the module for init coverage.
746  for (const auto &[index, value] : llvm::enumerate(block.getArguments())) {
747  auto direction = op.getPortDirection(index);
748  auto flow = direction == Direction::In ? Flow::Source : Flow::Sink;
749  declareSinks(value, flow);
750  }
751 
752  // Process the body of the module.
753  for (auto &op : llvm::make_early_inc_range(block))
754  dispatchVisitor(&op);
755  }
756  }
757 
758  return anythingChanged;
759 }
760 
761 void ModuleVisitor::visitStmt(ConnectOp op) {
762  anythingChanged |= recordConnect(getFieldRefFromValue(op.getDest()), op);
763 }
764 
765 void ModuleVisitor::visitStmt(MatchingConnectOp op) {
766  anythingChanged |= recordConnect(getFieldRefFromValue(op.getDest()), op);
767 }
768 
769 void ModuleVisitor::visitStmt(WhenOp whenOp) {
770  // If we are deleting a WhenOp something definitely changed.
771  anythingChanged = true;
772  processWhenOp(whenOp, /*outerCondition=*/{});
773 }
774 
775 void ModuleVisitor::visitStmt(LayerBlockOp layerBlockOp) {
776  for (auto &op : llvm::make_early_inc_range(*layerBlockOp.getBody())) {
777  dispatchVisitor(&op);
778  }
779 }
780 
781 /// Perform initialization checking. This uses the built up state from
782 /// running on a module. Returns failure in the event of bad initialization.
783 LogicalResult ModuleVisitor::checkInitialization() {
784  bool failed = false;
785  for (auto destAndConnect : driverMap.getLastScope()) {
786  // If there is valid connection to this destination, everything is good.
787  auto *connect = std::get<1>(destAndConnect);
788  if (connect)
789  continue;
790 
791  // Get the op which defines the sink, and emit an error.
792  FieldRef dest = std::get<0>(destAndConnect);
793  auto loc = dest.getValue().getLoc();
794  auto *definingOp = dest.getDefiningOp();
795  if (auto mod = dyn_cast<FModuleLike>(definingOp))
796  mlir::emitError(loc) << "port \"" << getFieldName(dest).first
797  << "\" not fully initialized in \""
798  << mod.getModuleName() << "\"";
799  else
800  mlir::emitError(loc)
801  << "sink \"" << getFieldName(dest).first
802  << "\" not fully initialized in \""
803  << definingOp->getParentOfType<FModuleLike>().getModuleName() << "\"";
804  failed = true;
805  }
806  if (failed)
807  return failure();
808  return success();
809 }
810 
811 //===----------------------------------------------------------------------===//
812 // Pass Infrastructure
813 //===----------------------------------------------------------------------===//
814 
815 namespace {
816 class ExpandWhensPass
817  : public circt::firrtl::impl::ExpandWhensBase<ExpandWhensPass> {
818  void runOnOperation() override;
819 };
820 } // end anonymous namespace
821 
822 void ExpandWhensPass::runOnOperation() {
823  ModuleVisitor visitor;
824  if (!visitor.run(getOperation()))
825  markAllAnalysesPreserved();
826  if (failed(visitor.checkInitialization()))
827  signalPassFailure();
828 }
829 
830 std::unique_ptr<mlir::Pass> circt::firrtl::createExpandWhensPass() {
831  return std::make_unique<ExpandWhensPass>();
832 }
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:37
Flow swapFlow(Flow flow)
Get a flow's reverse.
Definition: FIRRTLOps.cpp:179
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: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