CIRCT  20.0.0git
InferResets.cpp
Go to the documentation of this file.
1 //===- InferResets.cpp - Infer resets and add full reset --------*- 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 InferResets pass.
10 //
11 //===----------------------------------------------------------------------===//
12 
19 #include "circt/Support/Debug.h"
20 #include "circt/Support/FieldRef.h"
23 #include "mlir/IR/Dominance.h"
24 #include "mlir/IR/ImplicitLocOpBuilder.h"
25 #include "mlir/IR/Threading.h"
26 #include "mlir/Pass/Pass.h"
27 #include "llvm/ADT/EquivalenceClasses.h"
28 #include "llvm/ADT/SetVector.h"
29 #include "llvm/ADT/TinyPtrVector.h"
30 #include "llvm/ADT/TypeSwitch.h"
31 #include "llvm/Support/Debug.h"
32 
33 #define DEBUG_TYPE "infer-resets"
34 
35 namespace circt {
36 namespace firrtl {
37 #define GEN_PASS_DEF_INFERRESETS
38 #include "circt/Dialect/FIRRTL/Passes.h.inc"
39 } // namespace firrtl
40 } // namespace circt
41 
42 using circt::igraph::InstanceOpInterface;
45 using llvm::BumpPtrAllocator;
46 using llvm::MapVector;
47 using llvm::SmallDenseSet;
48 using llvm::SmallSetVector;
49 using mlir::FailureOr;
50 using mlir::InferTypeOpInterface;
51 using mlir::WalkOrder;
52 
53 using namespace circt;
54 using namespace firrtl;
55 
56 //===----------------------------------------------------------------------===//
57 // Utilities
58 //===----------------------------------------------------------------------===//
59 
60 namespace {
61 /// A reset domain.
62 struct ResetDomain {
63  /// Whether this is the root of the reset domain.
64  bool isTop = false;
65  /// The reset signal for this domain. A null value indicates that this domain
66  /// explicitly has no reset.
67  Value reset;
68 
69  // Implementation details for this domain.
70  Value existingValue;
71  std::optional<unsigned> existingPort;
72  StringAttr newPortName;
73 
74  ResetDomain(Value reset) : reset(reset) {}
75 };
76 } // namespace
77 
78 inline bool operator==(const ResetDomain &a, const ResetDomain &b) {
79  return (a.isTop == b.isTop && a.reset == b.reset);
80 }
81 inline bool operator!=(const ResetDomain &a, const ResetDomain &b) {
82  return !(a == b);
83 }
84 
85 /// Return the name and parent module of a reset. The reset value must either be
86 /// a module port or a wire/node operation.
87 static std::pair<StringAttr, FModuleOp> getResetNameAndModule(Value reset) {
88  if (auto arg = dyn_cast<BlockArgument>(reset)) {
89  auto module = cast<FModuleOp>(arg.getParentRegion()->getParentOp());
90  return {module.getPortNameAttr(arg.getArgNumber()), module};
91  } else {
92  auto op = reset.getDefiningOp();
93  return {op->getAttrOfType<StringAttr>("name"),
94  op->getParentOfType<FModuleOp>()};
95  }
96 }
97 
98 /// Return the name of a reset. The reset value must either be a module port or
99 /// a wire/node operation.
100 static inline StringAttr getResetName(Value reset) {
101  return getResetNameAndModule(reset).first;
102 }
103 
104 /// Construct a zero value of the given type using the given builder.
105 static Value createZeroValue(ImplicitLocOpBuilder &builder, FIRRTLBaseType type,
107  // The zero value's type is a const version of `type`.
108  type = type.getConstType(true);
109  auto it = cache.find(type);
110  if (it != cache.end())
111  return it->second;
112  auto nullBit = [&]() {
113  return createZeroValue(
114  builder, UIntType::get(builder.getContext(), 1, /*isConst=*/true),
115  cache);
116  };
117  auto value =
119  .Case<ClockType>([&](auto type) {
120  return builder.create<AsClockPrimOp>(nullBit());
121  })
122  .Case<AsyncResetType>([&](auto type) {
123  return builder.create<AsAsyncResetPrimOp>(nullBit());
124  })
125  .Case<SIntType, UIntType>([&](auto type) {
126  return builder.create<ConstantOp>(
127  type, APInt::getZero(type.getWidth().value_or(1)));
128  })
129  .Case<BundleType>([&](auto type) {
130  auto wireOp = builder.create<WireOp>(type);
131  for (unsigned i = 0, e = type.getNumElements(); i < e; ++i) {
132  auto fieldType = type.getElementTypePreservingConst(i);
133  auto zero = createZeroValue(builder, fieldType, cache);
134  auto acc =
135  builder.create<SubfieldOp>(fieldType, wireOp.getResult(), i);
136  emitConnect(builder, acc, zero);
137  }
138  return wireOp.getResult();
139  })
140  .Case<FVectorType>([&](auto type) {
141  auto wireOp = builder.create<WireOp>(type);
142  auto zero = createZeroValue(
143  builder, type.getElementTypePreservingConst(), cache);
144  for (unsigned i = 0, e = type.getNumElements(); i < e; ++i) {
145  auto acc = builder.create<SubindexOp>(zero.getType(),
146  wireOp.getResult(), i);
147  emitConnect(builder, acc, zero);
148  }
149  return wireOp.getResult();
150  })
151  .Case<ResetType, AnalogType>(
152  [&](auto type) { return builder.create<InvalidValueOp>(type); })
153  .Default([](auto) {
154  llvm_unreachable("switch handles all types");
155  return Value{};
156  });
157  cache.insert({type, value});
158  return value;
159 }
160 
161 /// Construct a null value of the given type using the given builder.
162 static Value createZeroValue(ImplicitLocOpBuilder &builder,
163  FIRRTLBaseType type) {
165  return createZeroValue(builder, type, cache);
166 }
167 
168 /// Helper function that inserts reset multiplexer into all `ConnectOp`s
169 /// with the given target. Looks through `SubfieldOp`, `SubindexOp`,
170 /// and `SubaccessOp`, and inserts multiplexers into connects to
171 /// these subaccesses as well. Modifies the insertion location of the builder.
172 /// Returns true if the `resetValue` was used in any way, false otherwise.
173 static bool insertResetMux(ImplicitLocOpBuilder &builder, Value target,
174  Value reset, Value resetValue) {
175  // Indicates whether the `resetValue` was assigned to in some way. We use this
176  // to erase unused subfield/subindex/subaccess ops on the reset value if they
177  // end up unused.
178  bool resetValueUsed = false;
179 
180  for (auto &use : target.getUses()) {
181  Operation *useOp = use.getOwner();
182  builder.setInsertionPoint(useOp);
183  TypeSwitch<Operation *>(useOp)
184  // Insert a mux on the value connected to the target:
185  // connect(dst, src) -> connect(dst, mux(reset, resetValue, src))
186  .Case<ConnectOp, MatchingConnectOp>([&](auto op) {
187  if (op.getDest() != target)
188  return;
189  LLVM_DEBUG(llvm::dbgs() << " - Insert mux into " << op << "\n");
190  auto muxOp =
191  builder.create<MuxPrimOp>(reset, resetValue, op.getSrc());
192  op.getSrcMutable().assign(muxOp);
193  resetValueUsed = true;
194  })
195  // Look through subfields.
196  .Case<SubfieldOp>([&](auto op) {
197  auto resetSubValue =
198  builder.create<SubfieldOp>(resetValue, op.getFieldIndexAttr());
199  if (insertResetMux(builder, op, reset, resetSubValue))
200  resetValueUsed = true;
201  else
202  resetSubValue.erase();
203  })
204  // Look through subindices.
205  .Case<SubindexOp>([&](auto op) {
206  auto resetSubValue =
207  builder.create<SubindexOp>(resetValue, op.getIndexAttr());
208  if (insertResetMux(builder, op, reset, resetSubValue))
209  resetValueUsed = true;
210  else
211  resetSubValue.erase();
212  })
213  // Look through subaccesses.
214  .Case<SubaccessOp>([&](auto op) {
215  if (op.getInput() != target)
216  return;
217  auto resetSubValue =
218  builder.create<SubaccessOp>(resetValue, op.getIndex());
219  if (insertResetMux(builder, op, reset, resetSubValue))
220  resetValueUsed = true;
221  else
222  resetSubValue.erase();
223  });
224  }
225  return resetValueUsed;
226 }
227 
228 //===----------------------------------------------------------------------===//
229 // Reset Network
230 //===----------------------------------------------------------------------===//
231 
232 namespace {
233 
234 /// A reset signal.
235 ///
236 /// This essentially combines the exact `FieldRef` of the signal in question
237 /// with a type to be used for error reporting and inferring the reset kind.
238 struct ResetSignal {
239  ResetSignal(FieldRef field, FIRRTLBaseType type) : field(field), type(type) {}
240  bool operator<(const ResetSignal &other) const { return field < other.field; }
241  bool operator==(const ResetSignal &other) const {
242  return field == other.field;
243  }
244  bool operator!=(const ResetSignal &other) const { return !(*this == other); }
245 
246  FieldRef field;
247  FIRRTLBaseType type;
248 };
249 
250 /// A connection made to or from a reset network.
251 ///
252 /// These drives are tracked for each reset network, and are used for error
253 /// reporting to the user.
254 struct ResetDrive {
255  /// What's being driven.
256  ResetSignal dst;
257  /// What's driving.
258  ResetSignal src;
259  /// The location to use for diagnostics.
260  Location loc;
261 };
262 
263 /// A list of connections to a reset network.
264 using ResetDrives = SmallVector<ResetDrive, 1>;
265 
266 /// All signals connected together into a reset network.
267 using ResetNetwork = llvm::iterator_range<
268  llvm::EquivalenceClasses<ResetSignal>::member_iterator>;
269 
270 /// Whether a reset is sync or async.
271 enum class ResetKind { Async, Sync };
272 
273 static StringRef resetKindToStringRef(const ResetKind &kind) {
274  switch (kind) {
275  case ResetKind::Async:
276  return "async";
277  case ResetKind::Sync:
278  return "sync";
279  }
280  llvm_unreachable("unhandled reset kind");
281 }
282 } // namespace
283 
284 namespace llvm {
285 template <>
286 struct DenseMapInfo<ResetSignal> {
287  static inline ResetSignal getEmptyKey() {
288  return ResetSignal{DenseMapInfo<FieldRef>::getEmptyKey(), {}};
289  }
290  static inline ResetSignal getTombstoneKey() {
291  return ResetSignal{DenseMapInfo<FieldRef>::getTombstoneKey(), {}};
292  }
293  static unsigned getHashValue(const ResetSignal &x) {
294  return circt::hash_value(x.field);
295  }
296  static bool isEqual(const ResetSignal &lhs, const ResetSignal &rhs) {
297  return lhs == rhs;
298  }
299 };
300 } // namespace llvm
301 
302 template <typename T>
303 static T &operator<<(T &os, const ResetKind &kind) {
304  switch (kind) {
305  case ResetKind::Async:
306  return os << "async";
307  case ResetKind::Sync:
308  return os << "sync";
309  }
310  return os;
311 }
312 
313 //===----------------------------------------------------------------------===//
314 // Pass Infrastructure
315 //===----------------------------------------------------------------------===//
316 
317 namespace {
318 /// Infer concrete reset types and insert full reset.
319 ///
320 /// This pass replaces `reset` types in the IR with a concrete `asyncreset` or
321 /// `uint<1>` depending on how the reset is used, and adds resets to registers
322 /// in modules marked with the corresponding `FullResetAnnotation`.
323 ///
324 /// On a high level, the first stage of the pass that deals with reset inference
325 /// operates as follows:
326 ///
327 /// 1. Build a global graph of the resets in the design by tracing reset signals
328 /// through instances. This uses the `ResetNetwork` utilities and boils down
329 /// to finding groups of values in the IR that are part of the same reset
330 /// network (i.e., somehow attached together through ports, wires, instances,
331 /// and connects). We use LLVM's `EquivalenceClasses` data structure to do
332 /// this efficiently.
333 ///
334 /// 2. Infer the type of each reset network found in step 1 by looking at the
335 /// type of values connected to the network. This results in the network
336 /// being declared a sync (`uint<1>`) or async (`asyncreset`) network. If the
337 /// reset is never driven by a concrete type, an error is emitted.
338 ///
339 /// 3. Walk the IR and update the type of wires and ports with the reset types
340 /// found in step 2. This will replace all `reset` types in the IR with
341 /// a concrete type.
342 ///
343 /// The second stage that deals with the addition of full resets operates as
344 /// follows:
345 ///
346 /// 4. Visit every module in the design and determine if it has an explicit
347 /// reset annotated. Ports of and wires in the module can have a
348 /// `FullResetAnnotation`, which marks that port or wire as the reset for
349 /// the module. A module may also carry a `ExcludeFromFullResetAnnotation`,
350 /// which marks it as being explicitly not in a reset domain. These
351 /// annotations are sparse; it is very much possible that just the top-level
352 /// module in the design has a full reset annotation. A module can only
353 /// ever carry one of these annotations, which puts it into one of three
354 /// categories from a full reset inference perspective:
355 ///
356 /// a. unambiguously marks a port or wire as the module's full reset
357 /// b. explicitly marks it as not to have any full resets added
358 /// c. inherit reset
359 ///
360 /// 5. For every module in the design, determine the full full reset domain it
361 /// is in. Note that this very narrowly deals with the inference of a
362 /// "default" full reset, which basically goes through the IR and attaches
363 /// all non-reset registers to a default full reset signal. If a module
364 /// carries one of the annotations mentioned in (4), the annotated port or
365 /// wire is used as its reset domain. Otherwise, it inherits the reset domain
366 /// from parent modules. This conceptually involves looking at all the places
367 /// where a module is instantiated, and recursively determining the reset
368 /// domain at the instantiation site. A module can only ever be in one reset
369 /// domain. In case it is inferred to lie in multiple ones, e.g., if it is
370 /// instantiated in different reset domains, an error is emitted. If
371 /// successful, every module is associated with a reset signal, either one of
372 /// its local ports or wires, or a port or wire within one of its parent
373 /// modules.
374 ///
375 /// 6. For every module in the design, determine how full resets shall be
376 /// implemented. This step handles the following distinct cases:
377 ///
378 /// a. Skip a module because it is marked as having no reset domain.
379 /// b. Use a port or wire in the module itself as reset. This is possible
380 /// if the module is at the "top" of its reset domain, which means that
381 /// it itself carried a reset annotation, and the reset value is either
382 /// a port or wire of the module itself.
383 /// c. Route a parent module's reset through a module port and use that
384 /// port as the reset. This happens if the module is *not* at the "top"
385 /// of its reset domain, but rather refers to a value in a parent module
386 /// as its reset.
387 ///
388 /// As a result, a module's reset domain is annotated with the existing local
389 /// value to reuse (port or wire), the index of an existing port to reuse,
390 /// and the name of an additional port to insert into its port list.
391 ///
392 /// 7. For every module in the design, full resets are implemented. This
393 /// determines the local value to use as the reset signal and updates the
394 /// `reg` and `regreset` operations in the design. If the register already
395 /// has an async reset, or if the type of the full reset is sync, the
396 /// register's reset is left unchanged. If it has a sync reset and the full
397 /// reset is async, the sync reset is moved into a `mux` operation on all
398 /// `connect`s to the register (which the Scala code base called the
399 /// `RemoveResets` pass). Finally the register is replaced with a `regreset`
400 /// operation, with the reset signal determined earlier, and a "zero" value
401 /// constructed for the register's type.
402 ///
403 /// Determining the local reset value is trivial if step 6 found a module to
404 /// be of case a or b. Case c is the non-trivial one, because it requires
405 /// modifying the port list of the module. This is done by first determining
406 /// the name of the reset signal in the parent module, which is either the
407 /// name of the port or wire declaration. We then look for an existing
408 /// port of the same type in the port list and reuse that as reset. If no
409 /// port with that name was found, or the existing port is of the wrong type,
410 /// a new port is inserted into the port list.
411 ///
412 /// TODO: This logic is *very* brittle and error-prone. It may make sense to
413 /// just add an additional port for the inferred reset in any case, with an
414 /// optimization to use an existing port if all of the module's
415 /// instantiations have that port connected to the desired signal already.
416 ///
417 struct InferResetsPass
418  : public circt::firrtl::impl::InferResetsBase<InferResetsPass> {
419  void runOnOperation() override;
420  void runOnOperationInner();
421 
422  // Copy creates a new empty pass (because ResetMap has no copy constructor).
423  using InferResetsBase::InferResetsBase;
424  InferResetsPass(const InferResetsPass &other) : InferResetsBase(other) {}
425 
426  //===--------------------------------------------------------------------===//
427  // Reset type inference
428 
429  void traceResets(CircuitOp circuit);
430  void traceResets(InstanceOp inst);
431  void traceResets(Value dst, Value src, Location loc);
432  void traceResets(Value value);
433  void traceResets(Type dstType, Value dst, unsigned dstID, Type srcType,
434  Value src, unsigned srcID, Location loc);
435 
436  LogicalResult inferAndUpdateResets();
437  FailureOr<ResetKind> inferReset(ResetNetwork net);
438  LogicalResult updateReset(ResetNetwork net, ResetKind kind);
439  bool updateReset(FieldRef field, FIRRTLBaseType resetType);
440 
441  //===--------------------------------------------------------------------===//
442  // Full reset implementation
443 
444  LogicalResult collectAnnos(CircuitOp circuit);
445  // Collect reset annotations in the module and return a reset signal.
446  // Return `failure()` if there was an error in the annotation processing.
447  // Return `std::nullopt` if there was no reset annotation.
448  // Return `nullptr` if there was `ignore` annotation.
449  // Return a non-null Value if the reset was actually provided.
450  FailureOr<std::optional<Value>> collectAnnos(FModuleOp module);
451 
452  LogicalResult buildDomains(CircuitOp circuit);
453  void buildDomains(FModuleOp module, const InstancePath &instPath,
454  Value parentReset, InstanceGraph &instGraph,
455  unsigned indent = 0);
456 
457  void determineImpl();
458  void determineImpl(FModuleOp module, ResetDomain &domain);
459 
460  LogicalResult implementFullReset();
461  LogicalResult implementFullReset(FModuleOp module, ResetDomain &domain);
462  void implementFullReset(Operation *op, FModuleOp module, Value actualReset);
463 
464  LogicalResult verifyNoAbstractReset();
465 
466  //===--------------------------------------------------------------------===//
467  // Utilities
468 
469  /// Get the reset network a signal belongs to.
470  ResetNetwork getResetNetwork(ResetSignal signal) {
471  return llvm::make_range(resetClasses.findLeader(signal),
472  resetClasses.member_end());
473  }
474 
475  /// Get the drives of a reset network.
476  ResetDrives &getResetDrives(ResetNetwork net) {
477  return resetDrives[*net.begin()];
478  }
479 
480  /// Guess the root node of a reset network, such that we have something for
481  /// the user to make sense of.
482  ResetSignal guessRoot(ResetNetwork net);
483  ResetSignal guessRoot(ResetSignal signal) {
484  return guessRoot(getResetNetwork(signal));
485  }
486 
487  //===--------------------------------------------------------------------===//
488  // Analysis data
489 
490  /// A map of all traced reset networks in the circuit.
491  llvm::EquivalenceClasses<ResetSignal> resetClasses;
492 
493  /// A map of all connects to and from a reset.
494  DenseMap<ResetSignal, ResetDrives> resetDrives;
495 
496  /// The annotated reset for a module. A null value indicates that the module
497  /// is explicitly annotated with `ignore`. Otherwise the port/wire/node
498  /// annotated as reset within the module is stored.
499  DenseMap<Operation *, Value> annotatedResets;
500 
501  /// The reset domain for a module. In case of conflicting domain membership,
502  /// the vector for a module contains multiple elements.
503  MapVector<FModuleOp, SmallVector<std::pair<ResetDomain, InstancePath>, 1>>
504  domains;
505 
506  /// Cache of modules symbols
507  InstanceGraph *instanceGraph;
508 
509  /// Cache of instance paths.
510  std::unique_ptr<InstancePathCache> instancePathCache;
511 };
512 } // namespace
513 
514 void InferResetsPass::runOnOperation() {
515  runOnOperationInner();
516  resetClasses = llvm::EquivalenceClasses<ResetSignal>();
517  resetDrives.clear();
518  annotatedResets.clear();
519  domains.clear();
520  instancePathCache.reset(nullptr);
521  markAnalysesPreserved<InstanceGraph>();
522 }
523 
524 void InferResetsPass::runOnOperationInner() {
525  instanceGraph = &getAnalysis<InstanceGraph>();
526  instancePathCache = std::make_unique<InstancePathCache>(*instanceGraph);
527 
528  // Trace the uninferred reset networks throughout the design.
529  traceResets(getOperation());
530 
531  // Infer the type of the traced resets and update the IR.
532  if (failed(inferAndUpdateResets()))
533  return signalPassFailure();
534 
535  // Gather the reset annotations throughout the modules.
536  if (failed(collectAnnos(getOperation())))
537  return signalPassFailure();
538 
539  // Build the reset domains in the design.
540  if (failed(buildDomains(getOperation())))
541  return signalPassFailure();
542 
543  // Determine how each reset shall be implemented.
544  determineImpl();
545 
546  // Implement the full resets.
547  if (failed(implementFullReset()))
548  return signalPassFailure();
549 
550  // Require that no Abstract Resets exist on ports in the design.
551  if (failed(verifyNoAbstractReset()))
552  return signalPassFailure();
553 }
554 
555 std::unique_ptr<mlir::Pass> circt::firrtl::createInferResetsPass() {
556  return std::make_unique<InferResetsPass>();
557 }
558 
559 ResetSignal InferResetsPass::guessRoot(ResetNetwork net) {
560  ResetDrives &drives = getResetDrives(net);
561  ResetSignal bestSignal = *net.begin();
562  unsigned bestNumDrives = -1;
563 
564  for (auto signal : net) {
565  // Don't consider `invalidvalue` for reporting as a root.
566  if (isa_and_nonnull<InvalidValueOp>(
567  signal.field.getValue().getDefiningOp()))
568  continue;
569 
570  // Count the number of times this particular signal in the reset network is
571  // assigned to.
572  unsigned numDrives = 0;
573  for (auto &drive : drives)
574  if (drive.dst == signal)
575  ++numDrives;
576 
577  // Keep track of the signal with the lowest number of assigns. These tend to
578  // be the signals further up the reset tree. This will usually resolve to
579  // the root of the reset tree far up in the design hierarchy.
580  if (numDrives < bestNumDrives) {
581  bestNumDrives = numDrives;
582  bestSignal = signal;
583  }
584  }
585  return bestSignal;
586 }
587 
588 //===----------------------------------------------------------------------===//
589 // Custom Field IDs
590 //===----------------------------------------------------------------------===//
591 
592 // The following functions implement custom field IDs specifically for the use
593 // in reset inference. They look much more like tracking fields on types than
594 // individual values. For example, vectors don't carry separate IDs for each of
595 // their elements. Instead they have one set of IDs for the entire vector, since
596 // the element type is uniform across all elements.
597 
598 static unsigned getMaxFieldID(FIRRTLBaseType type) {
600  .Case<BundleType>([](auto type) {
601  unsigned id = 0;
602  for (auto e : type.getElements())
603  id += getMaxFieldID(e.type) + 1;
604  return id;
605  })
606  .Case<FVectorType>(
607  [](auto type) { return getMaxFieldID(type.getElementType()) + 1; })
608  .Default([](auto) { return 0; });
609 }
610 
611 static unsigned getFieldID(BundleType type, unsigned index) {
612  assert(index < type.getNumElements());
613  unsigned id = 1;
614  for (unsigned i = 0; i < index; ++i)
615  id += getMaxFieldID(type.getElementType(i)) + 1;
616  return id;
617 }
618 
619 static unsigned getFieldID(FVectorType type) { return 1; }
620 
621 static unsigned getIndexForFieldID(BundleType type, unsigned fieldID) {
622  assert(type.getNumElements() && "Bundle must have >0 fields");
623  --fieldID;
624  for (const auto &e : llvm::enumerate(type.getElements())) {
625  auto numSubfields = getMaxFieldID(e.value().type) + 1;
626  if (fieldID < numSubfields)
627  return e.index();
628  fieldID -= numSubfields;
629  }
630  assert(false && "field id outside bundle");
631  return 0;
632 }
633 
634 // If a field is pointing to a child of a zero-length vector, it is useless.
635 static bool isUselessVec(FIRRTLBaseType oldType, unsigned fieldID) {
636  if (oldType.isGround()) {
637  assert(fieldID == 0);
638  return false;
639  }
640 
641  // If this is a bundle type, recurse.
642  if (auto bundleType = type_dyn_cast<BundleType>(oldType)) {
643  unsigned index = getIndexForFieldID(bundleType, fieldID);
644  return isUselessVec(bundleType.getElementType(index),
645  fieldID - getFieldID(bundleType, index));
646  }
647 
648  // If this is a vector type, check if it is zero length. Anything in a
649  // zero-length vector is useless.
650  if (auto vectorType = type_dyn_cast<FVectorType>(oldType)) {
651  if (vectorType.getNumElements() == 0)
652  return true;
653  return isUselessVec(vectorType.getElementType(),
654  fieldID - getFieldID(vectorType));
655  }
656 
657  return false;
658 }
659 
660 // If a field is pointing to a child of a zero-length vector, it is useless.
661 static bool isUselessVec(FieldRef field) {
662  return isUselessVec(
663  getBaseType(type_cast<FIRRTLType>(field.getValue().getType())),
664  field.getFieldID());
665 }
666 
667 static bool getDeclName(Value value, SmallString<32> &string) {
668  if (auto arg = dyn_cast<BlockArgument>(value)) {
669  auto module = cast<FModuleOp>(arg.getOwner()->getParentOp());
670  string += module.getPortName(arg.getArgNumber());
671  return true;
672  }
673 
674  auto *op = value.getDefiningOp();
675  return TypeSwitch<Operation *, bool>(op)
676  .Case<InstanceOp, MemOp>([&](auto op) {
677  string += op.getName();
678  string += ".";
679  string +=
680  op.getPortName(cast<OpResult>(value).getResultNumber()).getValue();
681  return true;
682  })
683  .Case<WireOp, NodeOp, RegOp, RegResetOp>([&](auto op) {
684  string += op.getName();
685  return true;
686  })
687  .Default([](auto) { return false; });
688 }
689 
690 static bool getFieldName(const FieldRef &fieldRef, SmallString<32> &string) {
691  SmallString<64> name;
692  auto value = fieldRef.getValue();
693  if (!getDeclName(value, string))
694  return false;
695 
696  auto type = value.getType();
697  auto localID = fieldRef.getFieldID();
698  while (localID) {
699  if (auto bundleType = type_dyn_cast<BundleType>(type)) {
700  auto index = getIndexForFieldID(bundleType, localID);
701  // Add the current field string, and recurse into a subfield.
702  auto &element = bundleType.getElements()[index];
703  if (!string.empty())
704  string += ".";
705  string += element.name.getValue();
706  // Recurse in to the element type.
707  type = element.type;
708  localID = localID - getFieldID(bundleType, index);
709  } else if (auto vecType = type_dyn_cast<FVectorType>(type)) {
710  string += "[]";
711  // Recurse in to the element type.
712  type = vecType.getElementType();
713  localID = localID - getFieldID(vecType);
714  } else {
715  // If we reach here, the field ref is pointing inside some aggregate type
716  // that isn't a bundle or a vector. If the type is a ground type, then the
717  // localID should be 0 at this point, and we should have broken from the
718  // loop.
719  llvm_unreachable("unsupported type");
720  }
721  }
722  return true;
723 }
724 
725 //===----------------------------------------------------------------------===//
726 // Reset Tracing
727 //===----------------------------------------------------------------------===//
728 
729 /// Check whether a type contains a `ResetType`.
730 static bool typeContainsReset(Type type) {
731  return TypeSwitch<Type, bool>(type)
732  .Case<FIRRTLType>([](auto type) {
733  return type.getRecursiveTypeProperties().hasUninferredReset;
734  })
735  .Default([](auto) { return false; });
736 }
737 
738 /// Iterate over a circuit and follow all signals with `ResetType`, aggregating
739 /// them into reset nets. After this function returns, the `resetMap` is
740 /// populated with the reset networks in the circuit, alongside information on
741 /// drivers and their types that contribute to the reset.
742 void InferResetsPass::traceResets(CircuitOp circuit) {
743  LLVM_DEBUG({
744  llvm::dbgs() << "\n";
745  debugHeader("Tracing uninferred resets") << "\n\n";
746  });
747 
748  SmallVector<std::pair<FModuleOp, SmallVector<Operation *>>> moduleToOps;
749 
750  for (auto module : circuit.getOps<FModuleOp>())
751  moduleToOps.push_back({module, {}});
752 
753  hw::InnerRefNamespace irn{getAnalysis<SymbolTable>(),
754  getAnalysis<hw::InnerSymbolTableCollection>()};
755 
756  mlir::parallelForEach(circuit.getContext(), moduleToOps, [](auto &e) {
757  e.first.walk([&](Operation *op) {
758  // We are only interested in operations which are related to abstract
759  // reset.
760  if (llvm::any_of(
761  op->getResultTypes(),
762  [](mlir::Type type) { return typeContainsReset(type); }) ||
763  llvm::any_of(op->getOperandTypes(), typeContainsReset))
764  e.second.push_back(op);
765  });
766  });
767 
768  for (auto &[_, ops] : moduleToOps)
769  for (auto *op : ops) {
770  TypeSwitch<Operation *>(op)
771  .Case<FConnectLike>([&](auto op) {
772  traceResets(op.getDest(), op.getSrc(), op.getLoc());
773  })
774  .Case<InstanceOp>([&](auto op) { traceResets(op); })
775  .Case<RefSendOp>([&](auto op) {
776  // Trace using base types.
777  traceResets(op.getType().getType(), op.getResult(), 0,
778  op.getBase().getType().getPassiveType(), op.getBase(),
779  0, op.getLoc());
780  })
781  .Case<RefResolveOp>([&](auto op) {
782  // Trace using base types.
783  traceResets(op.getType(), op.getResult(), 0,
784  op.getRef().getType().getType(), op.getRef(), 0,
785  op.getLoc());
786  })
787  .Case<Forceable>([&](Forceable op) {
788  if (auto node = dyn_cast<NodeOp>(op.getOperation()))
789  traceResets(node.getResult(), node.getInput(), node.getLoc());
790  // Trace reset into rwprobe. Avoid invalid IR.
791  if (op.isForceable())
792  traceResets(op.getDataType(), op.getData(), 0, op.getDataType(),
793  op.getDataRef(), 0, op.getLoc());
794  })
795  .Case<RWProbeOp>([&](RWProbeOp op) {
796  auto ist = irn.lookup(op.getTarget());
797  assert(ist);
798  auto ref = getFieldRefForTarget(ist);
799  auto baseType = op.getType().getType();
800  traceResets(baseType, op.getResult(), 0, baseType.getPassiveType(),
801  ref.getValue(), ref.getFieldID(), op.getLoc());
802  })
803  .Case<UninferredResetCastOp, ConstCastOp, RefCastOp>([&](auto op) {
804  traceResets(op.getResult(), op.getInput(), op.getLoc());
805  })
806  .Case<InvalidValueOp>([&](auto op) {
807  // Uniquify `InvalidValueOp`s that are contributing to multiple
808  // reset networks. These are tricky to handle because passes
809  // like CSE will generally ensure that there is only a single
810  // `InvalidValueOp` per type. However, a `reset` invalid value
811  // may be connected to two reset networks that end up being
812  // inferred as `asyncreset` and `uint<1>`. In that case, we need
813  // a distinct `InvalidValueOp` for each reset network in order
814  // to assign it the correct type.
815  auto type = op.getType();
816  if (!typeContainsReset(type) || op->hasOneUse() || op->use_empty())
817  return;
818  LLVM_DEBUG(llvm::dbgs() << "Uniquify " << op << "\n");
819  ImplicitLocOpBuilder builder(op->getLoc(), op);
820  for (auto &use :
821  llvm::make_early_inc_range(llvm::drop_begin(op->getUses()))) {
822  // - `make_early_inc_range` since `getUses()` is invalidated
823  // upon
824  // `use.set(...)`.
825  // - `drop_begin` such that the first use can keep the
826  // original op.
827  auto newOp = builder.create<InvalidValueOp>(type);
828  use.set(newOp);
829  }
830  })
831 
832  .Case<SubfieldOp>([&](auto op) {
833  // Associate the input bundle's resets with the output field's
834  // resets.
835  BundleType bundleType = op.getInput().getType();
836  auto index = op.getFieldIndex();
837  traceResets(op.getType(), op.getResult(), 0,
838  bundleType.getElements()[index].type, op.getInput(),
839  getFieldID(bundleType, index), op.getLoc());
840  })
841 
842  .Case<SubindexOp, SubaccessOp>([&](auto op) {
843  // Associate the input vector's resets with the output field's
844  // resets.
845  //
846  // This collapses all elements in vectors into one shared
847  // element which will ensure that reset inference provides a
848  // uniform result for all elements.
849  //
850  // CAVEAT: This may infer reset networks that are too big, since
851  // unrelated resets in the same vector end up looking as if they
852  // were connected. However for the sake of type inference, this
853  // is indistinguishable from them having to share the same type
854  // (namely the vector element type).
855  FVectorType vectorType = op.getInput().getType();
856  traceResets(op.getType(), op.getResult(), 0,
857  vectorType.getElementType(), op.getInput(),
858  getFieldID(vectorType), op.getLoc());
859  })
860 
861  .Case<RefSubOp>([&](RefSubOp op) {
862  // Trace through ref.sub.
863  auto aggType = op.getInput().getType().getType();
864  uint64_t fieldID = TypeSwitch<FIRRTLBaseType, uint64_t>(aggType)
865  .Case<FVectorType>([](auto type) {
866  return getFieldID(type);
867  })
868  .Case<BundleType>([&](auto type) {
869  return getFieldID(type, op.getIndex());
870  });
871  traceResets(op.getType(), op.getResult(), 0,
872  op.getResult().getType(), op.getInput(), fieldID,
873  op.getLoc());
874  });
875  }
876 }
877 
878 /// Trace reset signals through an instance. This essentially associates the
879 /// instance's port values with the target module's port values.
880 void InferResetsPass::traceResets(InstanceOp inst) {
881  // Lookup the referenced module. Nothing to do if its an extmodule.
882  auto module = inst.getReferencedModule<FModuleOp>(*instanceGraph);
883  if (!module)
884  return;
885  LLVM_DEBUG(llvm::dbgs() << "Visiting instance " << inst.getName() << "\n");
886 
887  // Establish a connection between the instance ports and module ports.
888  for (const auto &it : llvm::enumerate(inst.getResults())) {
889  auto dir = module.getPortDirection(it.index());
890  Value dstPort = module.getArgument(it.index());
891  Value srcPort = it.value();
892  if (dir == Direction::Out)
893  std::swap(dstPort, srcPort);
894  traceResets(dstPort, srcPort, it.value().getLoc());
895  }
896 }
897 
898 /// Analyze a connect of one (possibly aggregate) value to another.
899 /// Each drive involving a `ResetType` is recorded.
900 void InferResetsPass::traceResets(Value dst, Value src, Location loc) {
901  // Analyze the actual connection.
902  traceResets(dst.getType(), dst, 0, src.getType(), src, 0, loc);
903 }
904 
905 /// Analyze a connect of one (possibly aggregate) value to another.
906 /// Each drive involving a `ResetType` is recorded.
907 void InferResetsPass::traceResets(Type dstType, Value dst, unsigned dstID,
908  Type srcType, Value src, unsigned srcID,
909  Location loc) {
910  if (auto dstBundle = type_dyn_cast<BundleType>(dstType)) {
911  auto srcBundle = type_cast<BundleType>(srcType);
912  for (unsigned dstIdx = 0, e = dstBundle.getNumElements(); dstIdx < e;
913  ++dstIdx) {
914  auto dstField = dstBundle.getElements()[dstIdx].name;
915  auto srcIdx = srcBundle.getElementIndex(dstField);
916  if (!srcIdx)
917  continue;
918  auto &dstElt = dstBundle.getElements()[dstIdx];
919  auto &srcElt = srcBundle.getElements()[*srcIdx];
920  if (dstElt.isFlip) {
921  traceResets(srcElt.type, src, srcID + getFieldID(srcBundle, *srcIdx),
922  dstElt.type, dst, dstID + getFieldID(dstBundle, dstIdx),
923  loc);
924  } else {
925  traceResets(dstElt.type, dst, dstID + getFieldID(dstBundle, dstIdx),
926  srcElt.type, src, srcID + getFieldID(srcBundle, *srcIdx),
927  loc);
928  }
929  }
930  return;
931  }
932 
933  if (auto dstVector = type_dyn_cast<FVectorType>(dstType)) {
934  auto srcVector = type_cast<FVectorType>(srcType);
935  auto srcElType = srcVector.getElementType();
936  auto dstElType = dstVector.getElementType();
937  // Collapse all elements into one shared element. See comment in traceResets
938  // above for some context. Note that we are directly passing on the field ID
939  // of the vector itself as a stand-in for its element type. This is not
940  // really what `FieldRef` is designed to do, but tends to work since all the
941  // places that need to reason about the resulting weird IDs are inside this
942  // file. Normally you would pick a specific index from the vector, which
943  // would also move the field ID forward by some amount. However, we can't
944  // distinguish individual elements for the sake of type inference *and* we
945  // have to support zero-length vectors for which the only available ID is
946  // the vector itself. Therefore we always just pick the vector itself for
947  // the field ID and make sure in `updateType` that we handle vectors
948  // accordingly.
949  traceResets(dstElType, dst, dstID + getFieldID(dstVector), srcElType, src,
950  srcID + getFieldID(srcVector), loc);
951  return;
952  }
953 
954  // Handle connecting ref's. Other uses trace using base type.
955  if (auto dstRef = type_dyn_cast<RefType>(dstType)) {
956  auto srcRef = type_cast<RefType>(srcType);
957  return traceResets(dstRef.getType(), dst, dstID, srcRef.getType(), src,
958  srcID, loc);
959  }
960 
961  // Handle reset connections.
962  auto dstBase = type_dyn_cast<FIRRTLBaseType>(dstType);
963  auto srcBase = type_dyn_cast<FIRRTLBaseType>(srcType);
964  if (!dstBase || !srcBase)
965  return;
966  if (!type_isa<ResetType>(dstBase) && !type_isa<ResetType>(srcBase))
967  return;
968 
969  FieldRef dstField(dst, dstID);
970  FieldRef srcField(src, srcID);
971  LLVM_DEBUG(llvm::dbgs() << "Visiting driver '" << dstField << "' = '"
972  << srcField << "' (" << dstType << " = " << srcType
973  << ")\n");
974 
975  // Determine the leaders for the dst and src reset networks before we make
976  // the connection. This will allow us to later detect if dst got merged
977  // into src, or src into dst.
978  ResetSignal dstLeader =
979  *resetClasses.findLeader(resetClasses.insert({dstField, dstBase}));
980  ResetSignal srcLeader =
981  *resetClasses.findLeader(resetClasses.insert({srcField, srcBase}));
982 
983  // Unify the two reset networks.
984  ResetSignal unionLeader = *resetClasses.unionSets(dstLeader, srcLeader);
985  assert(unionLeader == dstLeader || unionLeader == srcLeader);
986 
987  // If dst got merged into src, append dst's drives to src's, or vice
988  // versa. Also, remove dst's or src's entry in resetDrives, because they
989  // will never come up as a leader again.
990  if (dstLeader != srcLeader) {
991  auto &unionDrives = resetDrives[unionLeader]; // needed before finds
992  auto mergedDrivesIt =
993  resetDrives.find(unionLeader == dstLeader ? srcLeader : dstLeader);
994  if (mergedDrivesIt != resetDrives.end()) {
995  unionDrives.append(mergedDrivesIt->second);
996  resetDrives.erase(mergedDrivesIt);
997  }
998  }
999 
1000  // Keep note of this drive so we can point the user at the right location
1001  // in case something goes wrong.
1002  resetDrives[unionLeader].push_back(
1003  {{dstField, dstBase}, {srcField, srcBase}, loc});
1004 }
1005 
1006 //===----------------------------------------------------------------------===//
1007 // Reset Inference
1008 //===----------------------------------------------------------------------===//
1009 
1010 LogicalResult InferResetsPass::inferAndUpdateResets() {
1011  LLVM_DEBUG({
1012  llvm::dbgs() << "\n";
1013  debugHeader("Infer reset types") << "\n\n";
1014  });
1015  for (auto it = resetClasses.begin(), end = resetClasses.end(); it != end;
1016  ++it) {
1017  if (!it->isLeader())
1018  continue;
1019  ResetNetwork net = llvm::make_range(resetClasses.member_begin(it),
1020  resetClasses.member_end());
1021 
1022  // Infer whether this should be a sync or async reset.
1023  auto kind = inferReset(net);
1024  if (failed(kind))
1025  return failure();
1026 
1027  // Update the types in the IR to match the inferred kind.
1028  if (failed(updateReset(net, *kind)))
1029  return failure();
1030  }
1031  return success();
1032 }
1033 
1034 FailureOr<ResetKind> InferResetsPass::inferReset(ResetNetwork net) {
1035  LLVM_DEBUG(llvm::dbgs() << "Inferring reset network with "
1036  << std::distance(net.begin(), net.end())
1037  << " nodes\n");
1038 
1039  // Go through the nodes and track the involved types.
1040  unsigned asyncDrives = 0;
1041  unsigned syncDrives = 0;
1042  unsigned invalidDrives = 0;
1043  for (ResetSignal signal : net) {
1044  // Keep track of whether this signal contributes a vote for async or sync.
1045  if (type_isa<AsyncResetType>(signal.type))
1046  ++asyncDrives;
1047  else if (type_isa<UIntType>(signal.type))
1048  ++syncDrives;
1049  else if (isUselessVec(signal.field) ||
1050  isa_and_nonnull<InvalidValueOp>(
1051  signal.field.getValue().getDefiningOp()))
1052  ++invalidDrives;
1053  }
1054  LLVM_DEBUG(llvm::dbgs() << "- Found " << asyncDrives << " async, "
1055  << syncDrives << " sync, " << invalidDrives
1056  << " invalid drives\n");
1057 
1058  // Handle the case where we have no votes for either kind.
1059  if (asyncDrives == 0 && syncDrives == 0 && invalidDrives == 0) {
1060  ResetSignal root = guessRoot(net);
1061  auto diag = mlir::emitError(root.field.getValue().getLoc())
1062  << "reset network never driven with concrete type";
1063  for (ResetSignal signal : net)
1064  diag.attachNote(signal.field.getLoc()) << "here: ";
1065  return failure();
1066  }
1067 
1068  // Handle the case where we have votes for both kinds.
1069  if (asyncDrives > 0 && syncDrives > 0) {
1070  ResetSignal root = guessRoot(net);
1071  bool majorityAsync = asyncDrives >= syncDrives;
1072  auto diag = mlir::emitError(root.field.getValue().getLoc())
1073  << "reset network";
1074  SmallString<32> fieldName;
1075  if (getFieldName(root.field, fieldName))
1076  diag << " \"" << fieldName << "\"";
1077  diag << " simultaneously connected to async and sync resets";
1078  diag.attachNote(root.field.getValue().getLoc())
1079  << "majority of connections to this reset are "
1080  << (majorityAsync ? "async" : "sync");
1081  for (auto &drive : getResetDrives(net)) {
1082  if ((type_isa<AsyncResetType>(drive.dst.type) && !majorityAsync) ||
1083  (type_isa<AsyncResetType>(drive.src.type) && !majorityAsync) ||
1084  (type_isa<UIntType>(drive.dst.type) && majorityAsync) ||
1085  (type_isa<UIntType>(drive.src.type) && majorityAsync))
1086  diag.attachNote(drive.loc)
1087  << (type_isa<AsyncResetType>(drive.src.type) ? "async" : "sync")
1088  << " drive here:";
1089  }
1090  return failure();
1091  }
1092 
1093  // At this point we know that the type of the reset is unambiguous. If there
1094  // are any votes for async, we make the reset async. Otherwise we make it
1095  // sync.
1096  auto kind = (asyncDrives ? ResetKind::Async : ResetKind::Sync);
1097  LLVM_DEBUG(llvm::dbgs() << "- Inferred as " << kind << "\n");
1098  return kind;
1099 }
1100 
1101 //===----------------------------------------------------------------------===//
1102 // Reset Updating
1103 //===----------------------------------------------------------------------===//
1104 
1105 LogicalResult InferResetsPass::updateReset(ResetNetwork net, ResetKind kind) {
1106  LLVM_DEBUG(llvm::dbgs() << "Updating reset network with "
1107  << std::distance(net.begin(), net.end())
1108  << " nodes to " << kind << "\n");
1109 
1110  // Determine the final type the reset should have.
1111  FIRRTLBaseType resetType;
1112  if (kind == ResetKind::Async)
1113  resetType = AsyncResetType::get(&getContext());
1114  else
1115  resetType = UIntType::get(&getContext(), 1);
1116 
1117  // Update all those values in the network that cannot be inferred from
1118  // operands. If we change the type of a module port (i.e. BlockArgument), add
1119  // the module to a module worklist since we need to update its function type.
1120  SmallSetVector<Operation *, 16> worklist;
1121  SmallDenseSet<Operation *> moduleWorklist;
1122  SmallDenseSet<std::pair<Operation *, Operation *>> extmoduleWorklist;
1123  for (auto signal : net) {
1124  Value value = signal.field.getValue();
1125  if (!isa<BlockArgument>(value) &&
1126  !isa_and_nonnull<WireOp, RegOp, RegResetOp, InstanceOp, InvalidValueOp,
1127  ConstCastOp, RefCastOp, UninferredResetCastOp,
1128  RWProbeOp>(value.getDefiningOp()))
1129  continue;
1130  if (updateReset(signal.field, resetType)) {
1131  for (auto user : value.getUsers())
1132  worklist.insert(user);
1133  if (auto blockArg = dyn_cast<BlockArgument>(value))
1134  moduleWorklist.insert(blockArg.getOwner()->getParentOp());
1135  else if (auto instOp = value.getDefiningOp<InstanceOp>()) {
1136  if (auto extmodule =
1137  instOp.getReferencedModule<FExtModuleOp>(*instanceGraph))
1138  extmoduleWorklist.insert({extmodule, instOp});
1139  } else if (auto uncast = value.getDefiningOp<UninferredResetCastOp>()) {
1140  uncast.replaceAllUsesWith(uncast.getInput());
1141  uncast.erase();
1142  }
1143  }
1144  }
1145 
1146  // Process the worklist of operations that have their type changed, pushing
1147  // types down the SSA dataflow graph. This is important because we change the
1148  // reset types in aggregates, and then need all the subindex, subfield, and
1149  // subaccess operations to be updated as appropriate.
1150  while (!worklist.empty()) {
1151  auto *wop = worklist.pop_back_val();
1152  SmallVector<Type, 2> types;
1153  if (auto op = dyn_cast<InferTypeOpInterface>(wop)) {
1154  // Determine the new result types.
1155  SmallVector<Type, 2> types;
1156  if (failed(op.inferReturnTypes(op->getContext(), op->getLoc(),
1157  op->getOperands(), op->getAttrDictionary(),
1158  op->getPropertiesStorage(),
1159  op->getRegions(), types)))
1160  return failure();
1161 
1162  // Update the results and add the changed ones to the
1163  // worklist.
1164  for (auto it : llvm::zip(op->getResults(), types)) {
1165  auto newType = std::get<1>(it);
1166  if (std::get<0>(it).getType() == newType)
1167  continue;
1168  std::get<0>(it).setType(newType);
1169  for (auto *user : std::get<0>(it).getUsers())
1170  worklist.insert(user);
1171  }
1172  LLVM_DEBUG(llvm::dbgs() << "- Inferred " << *op << "\n");
1173  } else if (auto uop = dyn_cast<UninferredResetCastOp>(wop)) {
1174  for (auto *user : uop.getResult().getUsers())
1175  worklist.insert(user);
1176  uop.replaceAllUsesWith(uop.getInput());
1177  LLVM_DEBUG(llvm::dbgs() << "- Inferred " << uop << "\n");
1178  uop.erase();
1179  }
1180  }
1181 
1182  // Update module types based on the type of the block arguments.
1183  for (auto *op : moduleWorklist) {
1184  auto module = dyn_cast<FModuleOp>(op);
1185  if (!module)
1186  continue;
1187 
1188  SmallVector<Attribute> argTypes;
1189  argTypes.reserve(module.getNumPorts());
1190  for (auto arg : module.getArguments())
1191  argTypes.push_back(TypeAttr::get(arg.getType()));
1192 
1193  module.setPortTypesAttr(ArrayAttr::get(op->getContext(), argTypes));
1194  LLVM_DEBUG(llvm::dbgs()
1195  << "- Updated type of module '" << module.getName() << "'\n");
1196  }
1197 
1198  // Update extmodule types based on their instantiation.
1199  for (auto pair : extmoduleWorklist) {
1200  auto module = cast<FExtModuleOp>(pair.first);
1201  auto instOp = cast<InstanceOp>(pair.second);
1202 
1203  SmallVector<Attribute> types;
1204  for (auto type : instOp.getResultTypes())
1205  types.push_back(TypeAttr::get(type));
1206 
1207  module.setPortTypesAttr(ArrayAttr::get(module->getContext(), types));
1208  LLVM_DEBUG(llvm::dbgs()
1209  << "- Updated type of extmodule '" << module.getName() << "'\n");
1210  }
1211 
1212  return success();
1213 }
1214 
1215 /// Update the type of a single field within a type.
1216 static FIRRTLBaseType updateType(FIRRTLBaseType oldType, unsigned fieldID,
1217  FIRRTLBaseType fieldType) {
1218  // If this is a ground type, simply replace it, preserving constness.
1219  if (oldType.isGround()) {
1220  assert(fieldID == 0);
1221  return fieldType.getConstType(oldType.isConst());
1222  }
1223 
1224  // If this is a bundle type, update the corresponding field.
1225  if (auto bundleType = type_dyn_cast<BundleType>(oldType)) {
1226  unsigned index = getIndexForFieldID(bundleType, fieldID);
1227  SmallVector<BundleType::BundleElement> fields(bundleType.begin(),
1228  bundleType.end());
1229  fields[index].type = updateType(
1230  fields[index].type, fieldID - getFieldID(bundleType, index), fieldType);
1231  return BundleType::get(oldType.getContext(), fields, bundleType.isConst());
1232  }
1233 
1234  // If this is a vector type, update the element type.
1235  if (auto vectorType = type_dyn_cast<FVectorType>(oldType)) {
1236  auto newType = updateType(vectorType.getElementType(),
1237  fieldID - getFieldID(vectorType), fieldType);
1238  return FVectorType::get(newType, vectorType.getNumElements(),
1239  vectorType.isConst());
1240  }
1241 
1242  llvm_unreachable("unknown aggregate type");
1243  return oldType;
1244 }
1245 
1246 /// Update the reset type of a specific field.
1247 bool InferResetsPass::updateReset(FieldRef field, FIRRTLBaseType resetType) {
1248  // Compute the updated type.
1249  auto oldType = type_cast<FIRRTLType>(field.getValue().getType());
1250  FIRRTLType newType = mapBaseType(oldType, [&](auto base) {
1251  return updateType(base, field.getFieldID(), resetType);
1252  });
1253 
1254  // Update the type if necessary.
1255  if (oldType == newType)
1256  return false;
1257  LLVM_DEBUG(llvm::dbgs() << "- Updating '" << field << "' from " << oldType
1258  << " to " << newType << "\n");
1259  field.getValue().setType(newType);
1260  return true;
1261 }
1262 
1263 //===----------------------------------------------------------------------===//
1264 // Reset Annotations
1265 //===----------------------------------------------------------------------===//
1266 
1267 LogicalResult InferResetsPass::collectAnnos(CircuitOp circuit) {
1268  LLVM_DEBUG({
1269  llvm::dbgs() << "\n";
1270  debugHeader("Gather reset annotations") << "\n\n";
1271  });
1272  SmallVector<std::pair<FModuleOp, std::optional<Value>>> results;
1273  for (auto module : circuit.getOps<FModuleOp>())
1274  results.push_back({module, {}});
1275  // Collect annotations parallelly.
1276  if (failed(mlir::failableParallelForEach(
1277  circuit.getContext(), results, [&](auto &moduleAndResult) {
1278  auto result = collectAnnos(moduleAndResult.first);
1279  if (failed(result))
1280  return failure();
1281  moduleAndResult.second = *result;
1282  return success();
1283  })))
1284  return failure();
1285 
1286  for (auto [module, reset] : results)
1287  if (reset.has_value())
1288  annotatedResets.insert({module, *reset});
1289  return success();
1290 }
1291 
1292 FailureOr<std::optional<Value>>
1293 InferResetsPass::collectAnnos(FModuleOp module) {
1294  bool anyFailed = false;
1295  SmallSetVector<std::pair<Annotation, Location>, 4> conflictingAnnos;
1296 
1297  // Consume a possible "ignore" annotation on the module itself, which
1298  // explicitly assigns it no reset domain.
1299  bool ignore = false;
1300  AnnotationSet::removeAnnotations(module, [&](Annotation anno) {
1302  ignore = true;
1303  conflictingAnnos.insert({anno, module.getLoc()});
1304  return true;
1305  }
1306  if (anno.isClass(fullResetAnnoClass)) {
1307  anyFailed = true;
1308  module.emitError("''FullResetAnnotation' cannot target module; must "
1309  "target port or wire/node instead");
1310  return true;
1311  }
1312  return false;
1313  });
1314  if (anyFailed)
1315  return failure();
1316 
1317  // Consume any reset annotations on module ports.
1318  Value reset;
1319  // Helper for checking annotations and determining the reset
1320  auto checkAnnotations = [&](Annotation anno, Value arg) {
1321  if (anno.isClass(fullResetAnnoClass)) {
1322  ResetKind expectedResetKind;
1323  if (auto rt = anno.getMember<StringAttr>("resetType")) {
1324  if (rt == "sync") {
1325  expectedResetKind = ResetKind::Sync;
1326  } else if (rt == "async") {
1327  expectedResetKind = ResetKind::Async;
1328  } else {
1329  mlir::emitError(arg.getLoc(),
1330  "'FullResetAnnotation' requires resetType == 'sync' "
1331  "| 'async', but got resetType == ")
1332  << rt;
1333  anyFailed = true;
1334  return true;
1335  }
1336  } else {
1337  mlir::emitError(arg.getLoc(),
1338  "'FullResetAnnotation' requires resetType == "
1339  "'sync' | 'async', but got no resetType");
1340  anyFailed = true;
1341  return true;
1342  }
1343  // Check that the type is well-formed
1344  bool isAsync = expectedResetKind == ResetKind::Async;
1345  bool validUint = false;
1346  if (auto uintT = dyn_cast<UIntType>(arg.getType()))
1347  validUint = uintT.getWidth() == 1;
1348  if ((isAsync && !isa<AsyncResetType>(arg.getType())) ||
1349  (!isAsync && !validUint)) {
1350  auto kind = resetKindToStringRef(expectedResetKind);
1351  mlir::emitError(arg.getLoc(),
1352  "'FullResetAnnotation' with resetType == '")
1353  << kind << "' must target " << kind << " reset, but targets "
1354  << arg.getType();
1355  anyFailed = true;
1356  return true;
1357  }
1358 
1359  reset = arg;
1360  conflictingAnnos.insert({anno, reset.getLoc()});
1361 
1362  return false;
1363  }
1365  anyFailed = true;
1366  mlir::emitError(arg.getLoc(),
1367  "'ExcludeFromFullResetAnnotation' cannot "
1368  "target port/wire/node; must target module instead");
1369  return true;
1370  }
1371  return false;
1372  };
1373 
1374  AnnotationSet::removePortAnnotations(module,
1375  [&](unsigned argNum, Annotation anno) {
1376  Value arg = module.getArgument(argNum);
1377  return checkAnnotations(anno, arg);
1378  });
1379  if (anyFailed)
1380  return failure();
1381 
1382  // Consume any reset annotations on wires in the module body.
1383  module.getBody().walk([&](Operation *op) {
1384  // Reset annotations must target wire/node ops.
1385  if (!isa<WireOp, NodeOp>(op)) {
1386  if (AnnotationSet::hasAnnotation(op, fullResetAnnoClass,
1388  anyFailed = true;
1389  op->emitError(
1390  "reset annotations must target module, port, or wire/node");
1391  }
1392  return;
1393  }
1394 
1395  // At this point we know that we have a WireOp/NodeOp. Process the reset
1396  // annotations.
1397  AnnotationSet::removeAnnotations(op, [&](Annotation anno) {
1398  auto arg = op->getResult(0);
1399  return checkAnnotations(anno, arg);
1400  });
1401  });
1402  if (anyFailed)
1403  return failure();
1404 
1405  // If we have found no annotations, there is nothing to do. We just leave
1406  // this module unannotated, which will cause it to inherit a reset domain
1407  // from its instantiation sites.
1408  if (!ignore && !reset) {
1409  LLVM_DEBUG(llvm::dbgs()
1410  << "No reset annotation for " << module.getName() << "\n");
1411  return std::optional<Value>();
1412  }
1413 
1414  // If we have found multiple annotations, emit an error and abort.
1415  if (conflictingAnnos.size() > 1) {
1416  auto diag = module.emitError("multiple reset annotations on module '")
1417  << module.getName() << "'";
1418  for (auto &annoAndLoc : conflictingAnnos)
1419  diag.attachNote(annoAndLoc.second)
1420  << "conflicting " << annoAndLoc.first.getClassAttr() << ":";
1421  return failure();
1422  }
1423 
1424  // Dump some information in debug builds.
1425  LLVM_DEBUG({
1426  llvm::dbgs() << "Annotated reset for " << module.getName() << ": ";
1427  if (ignore)
1428  llvm::dbgs() << "no domain\n";
1429  else if (auto arg = dyn_cast<BlockArgument>(reset))
1430  llvm::dbgs() << "port " << module.getPortName(arg.getArgNumber()) << "\n";
1431  else
1432  llvm::dbgs() << "wire "
1433  << reset.getDefiningOp()->getAttrOfType<StringAttr>("name")
1434  << "\n";
1435  });
1436 
1437  // Store the annotated reset for this module.
1438  assert(ignore || reset);
1439  return std::optional<Value>(reset);
1440 }
1441 
1442 //===----------------------------------------------------------------------===//
1443 // Domain Construction
1444 //===----------------------------------------------------------------------===//
1445 
1446 /// Gather the reset domains present in a circuit. This traverses the instance
1447 /// hierarchy of the design, making instances either live in a new reset
1448 /// domain if so annotated, or inherit their parent's domain. This can go
1449 /// wrong in some cases, mainly when a module is instantiated multiple times
1450 /// within different reset domains.
1451 LogicalResult InferResetsPass::buildDomains(CircuitOp circuit) {
1452  LLVM_DEBUG({
1453  llvm::dbgs() << "\n";
1454  debugHeader("Build full reset domains") << "\n\n";
1455  });
1456 
1457  // Gather the domains.
1458  auto &instGraph = getAnalysis<InstanceGraph>();
1459  auto module = dyn_cast<FModuleOp>(*instGraph.getTopLevelNode()->getModule());
1460  if (!module) {
1461  LLVM_DEBUG(llvm::dbgs()
1462  << "Skipping circuit because main module is no `firrtl.module`");
1463  return success();
1464  }
1465  buildDomains(module, InstancePath{}, Value{}, instGraph);
1466 
1467  // Report any domain conflicts among the modules.
1468  bool anyFailed = false;
1469  for (auto &it : domains) {
1470  auto module = cast<FModuleOp>(it.first);
1471  auto &domainConflicts = it.second;
1472  if (domainConflicts.size() <= 1)
1473  continue;
1474 
1475  anyFailed = true;
1476  SmallDenseSet<Value> printedDomainResets;
1477  auto diag = module.emitError("module '")
1478  << module.getName()
1479  << "' instantiated in different reset domains";
1480  for (auto &it : domainConflicts) {
1481  ResetDomain &domain = it.first;
1482  const auto &path = it.second;
1483  auto inst = path.leaf();
1484  auto loc = path.empty() ? module.getLoc() : inst.getLoc();
1485  auto &note = diag.attachNote(loc);
1486 
1487  // Describe the instance itself.
1488  if (path.empty())
1489  note << "root instance";
1490  else {
1491  note << "instance '";
1492  llvm::interleave(
1493  path,
1494  [&](InstanceOpInterface inst) { note << inst.getInstanceName(); },
1495  [&]() { note << "/"; });
1496  note << "'";
1497  }
1498 
1499  // Describe the reset domain the instance is in.
1500  note << " is in";
1501  if (domain.reset) {
1502  auto nameAndModule = getResetNameAndModule(domain.reset);
1503  note << " reset domain rooted at '" << nameAndModule.first.getValue()
1504  << "' of module '" << nameAndModule.second.getName() << "'";
1505 
1506  // Show where the domain reset is declared (once per reset).
1507  if (printedDomainResets.insert(domain.reset).second) {
1508  diag.attachNote(domain.reset.getLoc())
1509  << "reset domain '" << nameAndModule.first.getValue()
1510  << "' of module '" << nameAndModule.second.getName()
1511  << "' declared here:";
1512  }
1513  } else
1514  note << " no reset domain";
1515  }
1516  }
1517  return failure(anyFailed);
1518 }
1519 
1520 void InferResetsPass::buildDomains(FModuleOp module,
1521  const InstancePath &instPath,
1522  Value parentReset, InstanceGraph &instGraph,
1523  unsigned indent) {
1524  LLVM_DEBUG({
1525  llvm::dbgs().indent(indent * 2) << "Visiting ";
1526  if (instPath.empty())
1527  llvm::dbgs() << "$root";
1528  else
1529  llvm::dbgs() << instPath.leaf().getInstanceName();
1530  llvm::dbgs() << " (" << module.getName() << ")\n";
1531  });
1532 
1533  // Assemble the domain for this module.
1534  ResetDomain domain(parentReset);
1535  auto it = annotatedResets.find(module);
1536  if (it != annotatedResets.end()) {
1537  domain.isTop = true;
1538  domain.reset = it->second;
1539  }
1540 
1541  // Associate the domain with this module. If the module already has an
1542  // associated domain, it must be identical. Otherwise we'll have to report
1543  // the conflicting domains to the user.
1544  auto &entries = domains[module];
1545  if (llvm::all_of(entries,
1546  [&](const auto &entry) { return entry.first != domain; }))
1547  entries.push_back({domain, instPath});
1548 
1549  // Traverse the child instances.
1550  for (auto *record : *instGraph[module]) {
1551  auto submodule = dyn_cast<FModuleOp>(*record->getTarget()->getModule());
1552  if (!submodule)
1553  continue;
1554  auto childPath =
1555  instancePathCache->appendInstance(instPath, record->getInstance());
1556  buildDomains(submodule, childPath, domain.reset, instGraph, indent + 1);
1557  }
1558 }
1559 
1560 /// Determine how the reset for each module shall be implemented.
1561 void InferResetsPass::determineImpl() {
1562  LLVM_DEBUG({
1563  llvm::dbgs() << "\n";
1564  debugHeader("Determine implementation") << "\n\n";
1565  });
1566  for (auto &it : domains) {
1567  auto module = cast<FModuleOp>(it.first);
1568  auto &domain = it.second.back().first;
1569  determineImpl(module, domain);
1570  }
1571 }
1572 
1573 /// Determine how the reset for a module shall be implemented. This function
1574 /// fills in the `existingValue`, `existingPort`, and `newPortName` fields of
1575 /// the given reset domain.
1576 ///
1577 /// Generally it does the following:
1578 /// - If the domain has explicitly no reset ("ignore"), leaves everything
1579 /// empty.
1580 /// - If the domain is the place where the reset is defined ("top"), fills in
1581 /// the existing port/wire/node as reset.
1582 /// - If the module already has a port with the reset's name:
1583 /// - If the port has the same type as the reset domain, reuses that port.
1584 /// - Otherwise appends a `_N` suffix with increasing N to create a
1585 /// yet-unused
1586 /// port name, and marks that as to be created.
1587 /// - Otherwise indicates that a port with the reset's name should be created.
1588 ///
1589 void InferResetsPass::determineImpl(FModuleOp module, ResetDomain &domain) {
1590  if (!domain.reset)
1591  return; // nothing to do if the module needs no reset
1592  LLVM_DEBUG(llvm::dbgs() << "Planning reset for " << module.getName() << "\n");
1593 
1594  // If this is the root of a reset domain, we don't need to add any ports
1595  // and can just simply reuse the existing values.
1596  if (domain.isTop) {
1597  LLVM_DEBUG(llvm::dbgs() << "- Rooting at local value "
1598  << getResetName(domain.reset) << "\n");
1599  domain.existingValue = domain.reset;
1600  if (auto blockArg = dyn_cast<BlockArgument>(domain.reset))
1601  domain.existingPort = blockArg.getArgNumber();
1602  return;
1603  }
1604 
1605  // Otherwise, check if a port with this name and type already exists and
1606  // reuse that where possible.
1607  auto neededName = getResetName(domain.reset);
1608  auto neededType = domain.reset.getType();
1609  LLVM_DEBUG(llvm::dbgs() << "- Looking for existing port " << neededName
1610  << "\n");
1611  auto portNames = module.getPortNames();
1612  auto ports = llvm::zip(portNames, module.getArguments());
1613  auto portIt = llvm::find_if(
1614  ports, [&](auto port) { return std::get<0>(port) == neededName; });
1615  if (portIt != ports.end() && std::get<1>(*portIt).getType() == neededType) {
1616  LLVM_DEBUG(llvm::dbgs()
1617  << "- Reusing existing port " << neededName << "\n");
1618  domain.existingValue = std::get<1>(*portIt);
1619  domain.existingPort = std::distance(ports.begin(), portIt);
1620  return;
1621  }
1622 
1623  // If we have found a port but the types don't match, pick a new name for
1624  // the reset port.
1625  //
1626  // CAVEAT: The Scala FIRRTL compiler just throws an error in this case. This
1627  // seems unnecessary though, since the compiler can just insert a new reset
1628  // signal as needed.
1629  if (portIt != ports.end()) {
1630  LLVM_DEBUG(llvm::dbgs()
1631  << "- Existing " << neededName << " has incompatible type "
1632  << std::get<1>(*portIt).getType() << "\n");
1633  StringAttr newName;
1634  unsigned suffix = 0;
1635  do {
1636  newName =
1637  StringAttr::get(&getContext(), Twine(neededName.getValue()) +
1638  Twine("_") + Twine(suffix++));
1639  } while (llvm::is_contained(portNames, newName));
1640  LLVM_DEBUG(llvm::dbgs()
1641  << "- Creating uniquified port " << newName << "\n");
1642  domain.newPortName = newName;
1643  return;
1644  }
1645 
1646  // At this point we know that there is no such port, and we can safely
1647  // create one as needed.
1648  LLVM_DEBUG(llvm::dbgs() << "- Creating new port " << neededName << "\n");
1649  domain.newPortName = neededName;
1650 }
1651 
1652 //===----------------------------------------------------------------------===//
1653 // Full Reset Implementation
1654 //===----------------------------------------------------------------------===//
1655 
1656 /// Implement the annotated resets gathered in the pass' `domains` map.
1657 LogicalResult InferResetsPass::implementFullReset() {
1658  LLVM_DEBUG({
1659  llvm::dbgs() << "\n";
1660  debugHeader("Implement full resets") << "\n\n";
1661  });
1662  for (auto &it : domains)
1663  if (failed(implementFullReset(cast<FModuleOp>(it.first),
1664  it.second.back().first)))
1665  return failure();
1666  return success();
1667 }
1668 
1669 /// Implement the async resets for a specific module.
1670 ///
1671 /// This will add ports to the module as appropriate, update the register ops
1672 /// in the module, and update any instantiated submodules with their
1673 /// corresponding reset implementation details.
1674 LogicalResult InferResetsPass::implementFullReset(FModuleOp module,
1675  ResetDomain &domain) {
1676  LLVM_DEBUG(llvm::dbgs() << "Implementing full reset for " << module.getName()
1677  << "\n");
1678 
1679  // Nothing to do if the module was marked explicitly with no reset domain.
1680  if (!domain.reset) {
1681  LLVM_DEBUG(llvm::dbgs()
1682  << "- Skipping because module explicitly has no domain\n");
1683  return success();
1684  }
1685 
1686  // Add an annotation indicating that this module belongs to a reset domain.
1687  auto *context = module.getContext();
1688  AnnotationSet annotations(module);
1689  annotations.addAnnotations(DictionaryAttr::get(
1690  context, NamedAttribute(StringAttr::get(context, "class"),
1691  StringAttr::get(context, fullResetAnnoClass))));
1692  annotations.applyToOperation(module);
1693 
1694  // If needed, add a reset port to the module.
1695  Value actualReset = domain.existingValue;
1696  if (domain.newPortName) {
1697  PortInfo portInfo{domain.newPortName,
1698  domain.reset.getType(),
1699  Direction::In,
1700  {},
1701  domain.reset.getLoc()};
1702  module.insertPorts({{0, portInfo}});
1703  actualReset = module.getArgument(0);
1704  LLVM_DEBUG(llvm::dbgs()
1705  << "- Inserted port " << domain.newPortName << "\n");
1706  }
1707  assert(actualReset);
1708  LLVM_DEBUG({
1709  llvm::dbgs() << "- Using ";
1710  if (auto blockArg = dyn_cast<BlockArgument>(actualReset))
1711  llvm::dbgs() << "port #" << blockArg.getArgNumber() << " ";
1712  else
1713  llvm::dbgs() << "wire/node ";
1714  llvm::dbgs() << getResetName(actualReset) << "\n";
1715  });
1716 
1717  // Gather a list of operations in the module that need to be updated with
1718  // the new reset.
1719  SmallVector<Operation *> opsToUpdate;
1720  module.walk([&](Operation *op) {
1721  if (isa<InstanceOp, RegOp, RegResetOp>(op))
1722  opsToUpdate.push_back(op);
1723  });
1724 
1725  // If the reset is a local wire or node, move it upwards such that it
1726  // dominates all the operations that it will need to attach to. In the case
1727  // of a node this might not be easily possible, so we just spill into a wire
1728  // in that case.
1729  if (!isa<BlockArgument>(actualReset)) {
1730  mlir::DominanceInfo dom(module);
1731  // The first op in `opsToUpdate` is the top-most op in the module, since
1732  // the ops and blocks are traversed in a depth-first, top-to-bottom order
1733  // in `walk`. So we can simply check if the local reset declaration is
1734  // before the first op to find out if we need to move anything.
1735  auto *resetOp = actualReset.getDefiningOp();
1736  if (!opsToUpdate.empty() && !dom.dominates(resetOp, opsToUpdate[0])) {
1737  LLVM_DEBUG(llvm::dbgs()
1738  << "- Reset doesn't dominate all uses, needs to be moved\n");
1739 
1740  // If the node can't be moved because its input doesn't dominate the
1741  // target location, convert it to a wire.
1742  auto nodeOp = dyn_cast<NodeOp>(resetOp);
1743  if (nodeOp && !dom.dominates(nodeOp.getInput(), opsToUpdate[0])) {
1744  LLVM_DEBUG(llvm::dbgs()
1745  << "- Promoting node to wire for move: " << nodeOp << "\n");
1746  auto builder = ImplicitLocOpBuilder::atBlockBegin(nodeOp.getLoc(),
1747  nodeOp->getBlock());
1748  auto wireOp = builder.create<WireOp>(
1749  nodeOp.getResult().getType(), nodeOp.getNameAttr(),
1750  nodeOp.getNameKindAttr(), nodeOp.getAnnotationsAttr(),
1751  nodeOp.getInnerSymAttr(), nodeOp.getForceableAttr());
1752  // Don't delete the node, since it might be in use in worklists.
1753  nodeOp->replaceAllUsesWith(wireOp);
1754  nodeOp->removeAttr(nodeOp.getInnerSymAttrName());
1755  nodeOp.setName("");
1756  // Leave forcable alone, since we cannot remove a result. It will be
1757  // cleaned up in canonicalization since it is dead. As will this node.
1758  nodeOp.setNameKind(NameKindEnum::DroppableName);
1759  nodeOp.setAnnotationsAttr(ArrayAttr::get(builder.getContext(), {}));
1760  builder.setInsertionPointAfter(nodeOp);
1761  emitConnect(builder, wireOp.getResult(), nodeOp.getResult());
1762  resetOp = wireOp;
1763  actualReset = wireOp.getResult();
1764  domain.existingValue = wireOp.getResult();
1765  }
1766 
1767  // Determine the block into which the reset declaration needs to be
1768  // moved.
1769  Block *targetBlock = dom.findNearestCommonDominator(
1770  resetOp->getBlock(), opsToUpdate[0]->getBlock());
1771  LLVM_DEBUG({
1772  if (targetBlock != resetOp->getBlock())
1773  llvm::dbgs() << "- Needs to be moved to different block\n";
1774  });
1775 
1776  // At this point we have to figure out in front of which operation in
1777  // the target block the reset declaration has to be moved. The reset
1778  // declaration and the first op it needs to dominate may be buried
1779  // inside blocks of other operations (e.g. `WhenOp`), so we have to look
1780  // through their parent operations until we find the one that lies
1781  // within the target block.
1782  auto getParentInBlock = [](Operation *op, Block *block) {
1783  while (op && op->getBlock() != block)
1784  op = op->getParentOp();
1785  return op;
1786  };
1787  auto *resetOpInTarget = getParentInBlock(resetOp, targetBlock);
1788  auto *firstOpInTarget = getParentInBlock(opsToUpdate[0], targetBlock);
1789 
1790  // Move the operation upwards. Since there are situations where the
1791  // reset declaration does not dominate the first use, but the `WhenOp`
1792  // it is nested within actually *does* come before that use, we have to
1793  // consider moving the reset declaration in front of its parent op.
1794  if (resetOpInTarget->isBeforeInBlock(firstOpInTarget))
1795  resetOp->moveBefore(resetOpInTarget);
1796  else
1797  resetOp->moveBefore(firstOpInTarget);
1798  }
1799  }
1800 
1801  // Update the operations.
1802  for (auto *op : opsToUpdate)
1803  implementFullReset(op, module, actualReset);
1804 
1805  return success();
1806 }
1807 
1808 /// Modify an operation in a module to implement an full reset for that
1809 /// module.
1810 void InferResetsPass::implementFullReset(Operation *op, FModuleOp module,
1811  Value actualReset) {
1812  ImplicitLocOpBuilder builder(op->getLoc(), op);
1813 
1814  // Handle instances.
1815  if (auto instOp = dyn_cast<InstanceOp>(op)) {
1816  // Lookup the reset domain of the instantiated module. If there is no
1817  // reset domain associated with that module, or the module is explicitly
1818  // marked as being in no domain, simply skip.
1819  auto refModule = instOp.getReferencedModule<FModuleOp>(*instanceGraph);
1820  if (!refModule)
1821  return;
1822  auto domainIt = domains.find(refModule);
1823  if (domainIt == domains.end())
1824  return;
1825  auto &domain = domainIt->second.back().first;
1826  if (!domain.reset)
1827  return;
1828  LLVM_DEBUG(llvm::dbgs()
1829  << "- Update instance '" << instOp.getName() << "'\n");
1830 
1831  // If needed, add a reset port to the instance.
1832  Value instReset;
1833  if (domain.newPortName) {
1834  LLVM_DEBUG(llvm::dbgs() << " - Adding new result as reset\n");
1835 
1836  auto newInstOp = instOp.cloneAndInsertPorts(
1837  {{/*portIndex=*/0,
1838  {domain.newPortName,
1839  type_cast<FIRRTLBaseType>(actualReset.getType()),
1840  Direction::In}}});
1841  instReset = newInstOp.getResult(0);
1842 
1843  // Update the uses over to the new instance and drop the old instance.
1844  instOp.replaceAllUsesWith(newInstOp.getResults().drop_front());
1845  instanceGraph->replaceInstance(instOp, newInstOp);
1846  instOp->erase();
1847  instOp = newInstOp;
1848  } else if (domain.existingPort.has_value()) {
1849  auto idx = *domain.existingPort;
1850  instReset = instOp.getResult(idx);
1851  LLVM_DEBUG(llvm::dbgs() << " - Using result #" << idx << " as reset\n");
1852  }
1853 
1854  // If there's no reset port on the instance to connect, we're done. This
1855  // can happen if the instantiated module has a reset domain, but that
1856  // domain is e.g. rooted at an internal wire.
1857  if (!instReset)
1858  return;
1859 
1860  // Connect the instance's reset to the actual reset.
1861  assert(instReset && actualReset);
1862  builder.setInsertionPointAfter(instOp);
1863  emitConnect(builder, instReset, actualReset);
1864  return;
1865  }
1866 
1867  // Handle reset-less registers.
1868  if (auto regOp = dyn_cast<RegOp>(op)) {
1869  if (AnnotationSet::removeAnnotations(regOp, excludeMemToRegAnnoClass))
1870  return;
1871 
1872  LLVM_DEBUG(llvm::dbgs() << "- Adding full reset to " << regOp << "\n");
1873  auto zero = createZeroValue(builder, regOp.getResult().getType());
1874  auto newRegOp = builder.create<RegResetOp>(
1875  regOp.getResult().getType(), regOp.getClockVal(), actualReset, zero,
1876  regOp.getNameAttr(), regOp.getNameKindAttr(), regOp.getAnnotations(),
1877  regOp.getInnerSymAttr(), regOp.getForceableAttr());
1878  regOp.getResult().replaceAllUsesWith(newRegOp.getResult());
1879  if (regOp.getForceable())
1880  regOp.getRef().replaceAllUsesWith(newRegOp.getRef());
1881  regOp->erase();
1882  return;
1883  }
1884 
1885  // Handle registers with reset.
1886  if (auto regOp = dyn_cast<RegResetOp>(op)) {
1887  // If the register already has an async reset or if the type of the added
1888  // reset is sync, leave it alone.
1889  if (type_isa<AsyncResetType>(regOp.getResetSignal().getType()) ||
1890  type_isa<UIntType>(actualReset.getType())) {
1891  LLVM_DEBUG(llvm::dbgs() << "- Skipping (has reset) " << regOp << "\n");
1892  // The following performs the logic of `CheckResets` in the original
1893  // Scala source code.
1894  if (failed(regOp.verifyInvariants()))
1895  signalPassFailure();
1896  return;
1897  }
1898  LLVM_DEBUG(llvm::dbgs() << "- Updating reset of " << regOp << "\n");
1899 
1900  auto reset = regOp.getResetSignal();
1901  auto value = regOp.getResetValue();
1902 
1903  // If we arrive here, the register has a sync reset and the added reset is
1904  // async. In order to add an async reset, we have to move the sync reset
1905  // into a mux in front of the register.
1906  insertResetMux(builder, regOp.getResult(), reset, value);
1907  builder.setInsertionPointAfterValue(regOp.getResult());
1908  auto mux = builder.create<MuxPrimOp>(reset, value, regOp.getResult());
1909  emitConnect(builder, regOp.getResult(), mux);
1910 
1911  // Replace the existing reset with the async reset.
1912  builder.setInsertionPoint(regOp);
1913  auto zero = createZeroValue(builder, regOp.getResult().getType());
1914  regOp.getResetSignalMutable().assign(actualReset);
1915  regOp.getResetValueMutable().assign(zero);
1916  }
1917 }
1918 
1919 LogicalResult InferResetsPass::verifyNoAbstractReset() {
1920  bool hasAbstractResetPorts = false;
1921  for (FModuleLike module :
1922  getOperation().getBodyBlock()->getOps<FModuleLike>()) {
1923  for (PortInfo port : module.getPorts()) {
1924  if (getBaseOfType<ResetType>(port.type)) {
1925  auto diag = emitError(port.loc)
1926  << "a port \"" << port.getName()
1927  << "\" with abstract reset type was unable to be "
1928  "inferred by InferResets (is this a top-level port?)";
1929  diag.attachNote(module->getLoc())
1930  << "the module with this uninferred reset port was defined here";
1931  hasAbstractResetPorts = true;
1932  }
1933  }
1934  }
1935 
1936  if (hasAbstractResetPorts)
1937  return failure();
1938  return success();
1939 }
assert(baseType &&"element must be base type")
static Value createZeroValue(ImplicitLocOpBuilder &builder, FIRRTLBaseType type, SmallDenseMap< FIRRTLBaseType, Value > &cache)
Construct a zero value of the given type using the given builder.
static unsigned getFieldID(BundleType type, unsigned index)
static std::pair< StringAttr, FModuleOp > getResetNameAndModule(Value reset)
Return the name and parent module of a reset.
Definition: InferResets.cpp:87
static unsigned getIndexForFieldID(BundleType type, unsigned fieldID)
static FIRRTLBaseType updateType(FIRRTLBaseType oldType, unsigned fieldID, FIRRTLBaseType fieldType)
Update the type of a single field within a type.
static bool isUselessVec(FIRRTLBaseType oldType, unsigned fieldID)
static StringAttr getResetName(Value reset)
Return the name of a reset.
static bool insertResetMux(ImplicitLocOpBuilder &builder, Value target, Value reset, Value resetValue)
Helper function that inserts reset multiplexer into all ConnectOps with the given target.
static bool getFieldName(const FieldRef &fieldRef, SmallString< 32 > &string)
static bool typeContainsReset(Type type)
Check whether a type contains a ResetType.
static bool getDeclName(Value value, SmallString< 32 > &string)
static unsigned getMaxFieldID(FIRRTLBaseType type)
static InstancePath empty
static Block * getBodyBlock(FModuleLike mod)
This class represents a reference to a specific field or element of an aggregate value.
Definition: FieldRef.h:28
unsigned getFieldID() const
Get the field ID of this FieldRef, which is a unique identifier mapped to a specific field in a bundl...
Definition: FieldRef.h:59
Value getValue() const
Get the Value which created this location.
Definition: FieldRef.h:37
This class provides a read-only projection over the MLIR attributes that represent a set of annotatio...
This class provides a read-only projection of an annotation.
AttrClass getMember(StringAttr name) const
Return a member of the annotation.
bool isClass(Args... names) const
Return true if this annotation matches any of the specified class names.
bool isConst()
Returns true if this is a 'const' type that can only hold compile-time constant values.
FIRRTLBaseType getConstType(bool isConst)
Return a 'const' or non-'const' version of this type.
This class implements the same functionality as TypeSwitch except that it uses firrtl::type_dyn_cast ...
Definition: FIRRTLTypes.h:520
FIRRTLTypeSwitch< T, ResultT > & Case(CallableT &&caseFn)
Add a case on the given type.
Definition: FIRRTLTypes.h:530
This graph tracks modules and where they are instantiated.
An instance path composed of a series of instances.
InstanceOpInterface leaf() const
Direction get(bool isOutput)
Returns an output direction if isOutput is true, otherwise returns an input direction.
Definition: CalyxOps.cpp:55
constexpr const char * excludeFromFullResetAnnoClass
Annotation that marks a module as not belonging to any reset domain.
FieldRef getFieldRefForTarget(const hw::InnerSymTarget &ist)
Get FieldRef pointing to the specified inner symbol target, which must be valid.
constexpr const char * excludeMemToRegAnnoClass
igraph::InstancePathCache InstancePathCache
FIRRTLBaseType getBaseType(Type type)
If it is a base type, return it as is.
Definition: FIRRTLUtils.h:222
FIRRTLType mapBaseType(FIRRTLType type, function_ref< FIRRTLBaseType(FIRRTLBaseType)> fn)
Return a FIRRTLType with its base type component mutated by the given function.
Definition: FIRRTLUtils.h:238
constexpr const char * fullResetAnnoClass
Annotation that marks a reset (port or wire) and domain.
std::pair< std::string, bool > getFieldName(const FieldRef &fieldRef, bool nameSafe=false)
Get a string identifier representing the FieldRef.
llvm::raw_ostream & operator<<(llvm::raw_ostream &os, const InstanceInfo::LatticeValue &value)
void emitConnect(OpBuilder &builder, Location loc, Value lhs, Value rhs)
Emit a connect between two values.
Definition: FIRRTLUtils.cpp:25
std::unique_ptr< mlir::Pass > createInferResetsPass()
The InstanceGraph op interface, see InstanceGraphInterface.td for more details.
Definition: DebugAnalysis.h:21
llvm::raw_ostream & debugHeader(llvm::StringRef str, int width=80)
Write a "header"-like string to the debug stream with a certain width.
Definition: Debug.cpp:18
inline ::llvm::hash_code hash_value(const FieldRef &fieldRef)
Get a hash code for a FieldRef.
Definition: FieldRef.h:92
bool operator==(uint64_t a, const FVInt &b)
Definition: FVInt.h:649
bool operator!=(uint64_t a, const FVInt &b)
Definition: FVInt.h:650
bool operator<(const AppID &a, const AppID &b)
Definition: Manifest.cpp:714
This holds the name and type that describes the module's ports.
static ResetSignal getEmptyKey()
static ResetSignal getTombstoneKey()
static bool isEqual(const ResetSignal &lhs, const ResetSignal &rhs)
static unsigned getHashValue(const ResetSignal &x)