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