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