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  for (const auto &it : llvm::enumerate(inst.getResults())) {
861  auto dir = module.getPortDirection(it.index());
862  Value dstPort = module.getArgument(it.index());
863  Value srcPort = it.value();
864  if (dir == Direction::Out)
865  std::swap(dstPort, srcPort);
866  traceResets(dstPort, srcPort, it.value().getLoc());
867  }
868 }
869 
870 /// Analyze a connect of one (possibly aggregate) value to another.
871 /// Each drive involving a `ResetType` is recorded.
872 void InferResetsPass::traceResets(Value dst, Value src, Location loc) {
873  // Analyze the actual connection.
874  traceResets(dst.getType(), dst, 0, src.getType(), src, 0, loc);
875 }
876 
877 /// Analyze a connect of one (possibly aggregate) value to another.
878 /// Each drive involving a `ResetType` is recorded.
879 void InferResetsPass::traceResets(Type dstType, Value dst, unsigned dstID,
880  Type srcType, Value src, unsigned srcID,
881  Location loc) {
882  if (auto dstBundle = type_dyn_cast<BundleType>(dstType)) {
883  auto srcBundle = type_cast<BundleType>(srcType);
884  for (unsigned dstIdx = 0, e = dstBundle.getNumElements(); dstIdx < e;
885  ++dstIdx) {
886  auto dstField = dstBundle.getElements()[dstIdx].name;
887  auto srcIdx = srcBundle.getElementIndex(dstField);
888  if (!srcIdx)
889  continue;
890  auto &dstElt = dstBundle.getElements()[dstIdx];
891  auto &srcElt = srcBundle.getElements()[*srcIdx];
892  if (dstElt.isFlip) {
893  traceResets(srcElt.type, src, srcID + getFieldID(srcBundle, *srcIdx),
894  dstElt.type, dst, dstID + getFieldID(dstBundle, dstIdx),
895  loc);
896  } else {
897  traceResets(dstElt.type, dst, dstID + getFieldID(dstBundle, dstIdx),
898  srcElt.type, src, srcID + getFieldID(srcBundle, *srcIdx),
899  loc);
900  }
901  }
902  return;
903  }
904 
905  if (auto dstVector = type_dyn_cast<FVectorType>(dstType)) {
906  auto srcVector = type_cast<FVectorType>(srcType);
907  auto srcElType = srcVector.getElementType();
908  auto dstElType = dstVector.getElementType();
909  // Collapse all elements into one shared element. See comment in traceResets
910  // above for some context. Note that we are directly passing on the field ID
911  // of the vector itself as a stand-in for its element type. This is not
912  // really what `FieldRef` is designed to do, but tends to work since all the
913  // places that need to reason about the resulting weird IDs are inside this
914  // file. Normally you would pick a specific index from the vector, which
915  // would also move the field ID forward by some amount. However, we can't
916  // distinguish individual elements for the sake of type inference *and* we
917  // have to support zero-length vectors for which the only available ID is
918  // the vector itself. Therefore we always just pick the vector itself for
919  // the field ID and make sure in `updateType` that we handle vectors
920  // accordingly.
921  traceResets(dstElType, dst, dstID + getFieldID(dstVector), srcElType, src,
922  srcID + getFieldID(srcVector), loc);
923  return;
924  }
925 
926  // Handle connecting ref's. Other uses trace using base type.
927  if (auto dstRef = type_dyn_cast<RefType>(dstType)) {
928  auto srcRef = type_cast<RefType>(srcType);
929  return traceResets(dstRef.getType(), dst, dstID, srcRef.getType(), src,
930  srcID, loc);
931  }
932 
933  // Handle reset connections.
934  auto dstBase = type_dyn_cast<FIRRTLBaseType>(dstType);
935  auto srcBase = type_dyn_cast<FIRRTLBaseType>(srcType);
936  if (!dstBase || !srcBase)
937  return;
938  if (!type_isa<ResetType>(dstBase) && !type_isa<ResetType>(srcBase))
939  return;
940 
941  FieldRef dstField(dst, dstID);
942  FieldRef srcField(src, srcID);
943  LLVM_DEBUG(llvm::dbgs() << "Visiting driver '" << dstField << "' = '"
944  << srcField << "' (" << dstType << " = " << srcType
945  << ")\n");
946 
947  // Determine the leaders for the dst and src reset networks before we make
948  // the connection. This will allow us to later detect if dst got merged
949  // into src, or src into dst.
950  ResetSignal dstLeader =
951  *resetClasses.findLeader(resetClasses.insert({dstField, dstBase}));
952  ResetSignal srcLeader =
953  *resetClasses.findLeader(resetClasses.insert({srcField, srcBase}));
954 
955  // Unify the two reset networks.
956  ResetSignal unionLeader = *resetClasses.unionSets(dstLeader, srcLeader);
957  assert(unionLeader == dstLeader || unionLeader == srcLeader);
958 
959  // If dst got merged into src, append dst's drives to src's, or vice
960  // versa. Also, remove dst's or src's entry in resetDrives, because they
961  // will never come up as a leader again.
962  if (dstLeader != srcLeader) {
963  auto &unionDrives = resetDrives[unionLeader]; // needed before finds
964  auto mergedDrivesIt =
965  resetDrives.find(unionLeader == dstLeader ? srcLeader : dstLeader);
966  if (mergedDrivesIt != resetDrives.end()) {
967  unionDrives.append(mergedDrivesIt->second);
968  resetDrives.erase(mergedDrivesIt);
969  }
970  }
971 
972  // Keep note of this drive so we can point the user at the right location
973  // in case something goes wrong.
974  resetDrives[unionLeader].push_back(
975  {{dstField, dstBase}, {srcField, srcBase}, loc});
976 }
977 
978 //===----------------------------------------------------------------------===//
979 // Reset Inference
980 //===----------------------------------------------------------------------===//
981 
982 LogicalResult InferResetsPass::inferAndUpdateResets() {
983  LLVM_DEBUG({
984  llvm::dbgs() << "\n";
985  debugHeader("Infer reset types") << "\n\n";
986  });
987  for (auto it = resetClasses.begin(), end = resetClasses.end(); it != end;
988  ++it) {
989  if (!it->isLeader())
990  continue;
991  ResetNetwork net = llvm::make_range(resetClasses.member_begin(it),
992  resetClasses.member_end());
993 
994  // Infer whether this should be a sync or async reset.
995  auto kind = inferReset(net);
996  if (failed(kind))
997  return failure();
998 
999  // Update the types in the IR to match the inferred kind.
1000  if (failed(updateReset(net, *kind)))
1001  return failure();
1002  }
1003  return success();
1004 }
1005 
1006 FailureOr<ResetKind> InferResetsPass::inferReset(ResetNetwork net) {
1007  LLVM_DEBUG(llvm::dbgs() << "Inferring reset network with "
1008  << std::distance(net.begin(), net.end())
1009  << " nodes\n");
1010 
1011  // Go through the nodes and track the involved types.
1012  unsigned asyncDrives = 0;
1013  unsigned syncDrives = 0;
1014  unsigned invalidDrives = 0;
1015  for (ResetSignal signal : net) {
1016  // Keep track of whether this signal contributes a vote for async or sync.
1017  if (type_isa<AsyncResetType>(signal.type))
1018  ++asyncDrives;
1019  else if (type_isa<UIntType>(signal.type))
1020  ++syncDrives;
1021  else if (isUselessVec(signal.field) ||
1022  isa_and_nonnull<InvalidValueOp>(
1023  signal.field.getValue().getDefiningOp()))
1024  ++invalidDrives;
1025  }
1026  LLVM_DEBUG(llvm::dbgs() << "- Found " << asyncDrives << " async, "
1027  << syncDrives << " sync, " << invalidDrives
1028  << " invalid drives\n");
1029 
1030  // Handle the case where we have no votes for either kind.
1031  if (asyncDrives == 0 && syncDrives == 0 && invalidDrives == 0) {
1032  ResetSignal root = guessRoot(net);
1033  auto diag = mlir::emitError(root.field.getValue().getLoc())
1034  << "reset network never driven with concrete type";
1035  for (ResetSignal signal : net)
1036  diag.attachNote(signal.field.getLoc()) << "here: ";
1037  return failure();
1038  }
1039 
1040  // Handle the case where we have votes for both kinds.
1041  if (asyncDrives > 0 && syncDrives > 0) {
1042  ResetSignal root = guessRoot(net);
1043  bool majorityAsync = asyncDrives >= syncDrives;
1044  auto diag = mlir::emitError(root.field.getValue().getLoc())
1045  << "reset network";
1046  SmallString<32> fieldName;
1047  if (getFieldName(root.field, fieldName))
1048  diag << " \"" << fieldName << "\"";
1049  diag << " simultaneously connected to async and sync resets";
1050  diag.attachNote(root.field.getValue().getLoc())
1051  << "majority of connections to this reset are "
1052  << (majorityAsync ? "async" : "sync");
1053  for (auto &drive : getResetDrives(net)) {
1054  if ((type_isa<AsyncResetType>(drive.dst.type) && !majorityAsync) ||
1055  (type_isa<AsyncResetType>(drive.src.type) && !majorityAsync) ||
1056  (type_isa<UIntType>(drive.dst.type) && majorityAsync) ||
1057  (type_isa<UIntType>(drive.src.type) && majorityAsync))
1058  diag.attachNote(drive.loc)
1059  << (type_isa<AsyncResetType>(drive.src.type) ? "async" : "sync")
1060  << " drive here:";
1061  }
1062  return failure();
1063  }
1064 
1065  // At this point we know that the type of the reset is unambiguous. If there
1066  // are any votes for async, we make the reset async. Otherwise we make it
1067  // sync.
1068  auto kind = (asyncDrives ? ResetKind::Async : ResetKind::Sync);
1069  LLVM_DEBUG(llvm::dbgs() << "- Inferred as " << kind << "\n");
1070  return kind;
1071 }
1072 
1073 //===----------------------------------------------------------------------===//
1074 // Reset Updating
1075 //===----------------------------------------------------------------------===//
1076 
1077 LogicalResult InferResetsPass::updateReset(ResetNetwork net, ResetKind kind) {
1078  LLVM_DEBUG(llvm::dbgs() << "Updating reset network with "
1079  << std::distance(net.begin(), net.end())
1080  << " nodes to " << kind << "\n");
1081 
1082  // Determine the final type the reset should have.
1083  FIRRTLBaseType resetType;
1084  if (kind == ResetKind::Async)
1085  resetType = AsyncResetType::get(&getContext());
1086  else
1087  resetType = UIntType::get(&getContext(), 1);
1088 
1089  // Update all those values in the network that cannot be inferred from
1090  // operands. If we change the type of a module port (i.e. BlockArgument), add
1091  // the module to a module worklist since we need to update its function type.
1092  SmallSetVector<Operation *, 16> worklist;
1093  SmallDenseSet<Operation *> moduleWorklist;
1094  SmallDenseSet<std::pair<Operation *, Operation *>> extmoduleWorklist;
1095  for (auto signal : net) {
1096  Value value = signal.field.getValue();
1097  if (!isa<BlockArgument>(value) &&
1098  !isa_and_nonnull<WireOp, RegOp, RegResetOp, InstanceOp, InvalidValueOp,
1099  ConstCastOp, RefCastOp, UninferredResetCastOp>(
1100  value.getDefiningOp()))
1101  continue;
1102  if (updateReset(signal.field, resetType)) {
1103  for (auto user : value.getUsers())
1104  worklist.insert(user);
1105  if (auto blockArg = dyn_cast<BlockArgument>(value))
1106  moduleWorklist.insert(blockArg.getOwner()->getParentOp());
1107  else if (auto instOp = value.getDefiningOp<InstanceOp>()) {
1108  if (auto extmodule =
1109  instOp.getReferencedModule<FExtModuleOp>(*instanceGraph))
1110  extmoduleWorklist.insert({extmodule, instOp});
1111  } else if (auto uncast = value.getDefiningOp<UninferredResetCastOp>()) {
1112  uncast.replaceAllUsesWith(uncast.getInput());
1113  uncast.erase();
1114  }
1115  }
1116  }
1117 
1118  // Process the worklist of operations that have their type changed, pushing
1119  // types down the SSA dataflow graph. This is important because we change the
1120  // reset types in aggregates, and then need all the subindex, subfield, and
1121  // subaccess operations to be updated as appropriate.
1122  while (!worklist.empty()) {
1123  auto *wop = worklist.pop_back_val();
1124  SmallVector<Type, 2> types;
1125  if (auto op = dyn_cast<InferTypeOpInterface>(wop)) {
1126  // Determine the new result types.
1127  SmallVector<Type, 2> types;
1128  if (failed(op.inferReturnTypes(op->getContext(), op->getLoc(),
1129  op->getOperands(), op->getAttrDictionary(),
1130  op->getPropertiesStorage(),
1131  op->getRegions(), types)))
1132  return failure();
1133 
1134  // Update the results and add the changed ones to the
1135  // worklist.
1136  for (auto it : llvm::zip(op->getResults(), types)) {
1137  auto newType = std::get<1>(it);
1138  if (std::get<0>(it).getType() == newType)
1139  continue;
1140  std::get<0>(it).setType(newType);
1141  for (auto *user : std::get<0>(it).getUsers())
1142  worklist.insert(user);
1143  }
1144  LLVM_DEBUG(llvm::dbgs() << "- Inferred " << *op << "\n");
1145  } else if (auto uop = dyn_cast<UninferredResetCastOp>(wop)) {
1146  for (auto *user : uop.getResult().getUsers())
1147  worklist.insert(user);
1148  uop.replaceAllUsesWith(uop.getInput());
1149  LLVM_DEBUG(llvm::dbgs() << "- Inferred " << uop << "\n");
1150  uop.erase();
1151  }
1152  }
1153 
1154  // Update module types based on the type of the block arguments.
1155  for (auto *op : moduleWorklist) {
1156  auto module = dyn_cast<FModuleOp>(op);
1157  if (!module)
1158  continue;
1159 
1160  SmallVector<Attribute> argTypes;
1161  argTypes.reserve(module.getNumPorts());
1162  for (auto arg : module.getArguments())
1163  argTypes.push_back(TypeAttr::get(arg.getType()));
1164 
1165  module->setAttr(FModuleLike::getPortTypesAttrName(),
1166  ArrayAttr::get(op->getContext(), argTypes));
1167  LLVM_DEBUG(llvm::dbgs()
1168  << "- Updated type of module '" << module.getName() << "'\n");
1169  }
1170 
1171  // Update extmodule types based on their instantiation.
1172  for (auto pair : extmoduleWorklist) {
1173  auto module = cast<FExtModuleOp>(pair.first);
1174  auto instOp = cast<InstanceOp>(pair.second);
1175 
1176  SmallVector<Attribute> types;
1177  for (auto type : instOp.getResultTypes())
1178  types.push_back(TypeAttr::get(type));
1179 
1180  module->setAttr(FModuleLike::getPortTypesAttrName(),
1181  ArrayAttr::get(module->getContext(), types));
1182  LLVM_DEBUG(llvm::dbgs()
1183  << "- Updated type of extmodule '" << module.getName() << "'\n");
1184  }
1185 
1186  return success();
1187 }
1188 
1189 /// Update the type of a single field within a type.
1190 static FIRRTLBaseType updateType(FIRRTLBaseType oldType, unsigned fieldID,
1191  FIRRTLBaseType fieldType) {
1192  // If this is a ground type, simply replace it, preserving constness.
1193  if (oldType.isGround()) {
1194  assert(fieldID == 0);
1195  return fieldType.getConstType(oldType.isConst());
1196  }
1197 
1198  // If this is a bundle type, update the corresponding field.
1199  if (auto bundleType = type_dyn_cast<BundleType>(oldType)) {
1200  unsigned index = getIndexForFieldID(bundleType, fieldID);
1201  SmallVector<BundleType::BundleElement> fields(bundleType.begin(),
1202  bundleType.end());
1203  fields[index].type = updateType(
1204  fields[index].type, fieldID - getFieldID(bundleType, index), fieldType);
1205  return BundleType::get(oldType.getContext(), fields, bundleType.isConst());
1206  }
1207 
1208  // If this is a vector type, update the element type.
1209  if (auto vectorType = type_dyn_cast<FVectorType>(oldType)) {
1210  auto newType = updateType(vectorType.getElementType(),
1211  fieldID - getFieldID(vectorType), fieldType);
1212  return FVectorType::get(newType, vectorType.getNumElements(),
1213  vectorType.isConst());
1214  }
1215 
1216  llvm_unreachable("unknown aggregate type");
1217  return oldType;
1218 }
1219 
1220 /// Update the reset type of a specific field.
1221 bool InferResetsPass::updateReset(FieldRef field, FIRRTLBaseType resetType) {
1222  // Compute the updated type.
1223  auto oldType = type_cast<FIRRTLType>(field.getValue().getType());
1224  FIRRTLType newType = mapBaseType(oldType, [&](auto base) {
1225  return updateType(base, field.getFieldID(), resetType);
1226  });
1227 
1228  // Update the type if necessary.
1229  if (oldType == newType)
1230  return false;
1231  LLVM_DEBUG(llvm::dbgs() << "- Updating '" << field << "' from " << oldType
1232  << " to " << newType << "\n");
1233  field.getValue().setType(newType);
1234  return true;
1235 }
1236 
1237 //===----------------------------------------------------------------------===//
1238 // Reset Annotations
1239 //===----------------------------------------------------------------------===//
1240 
1241 LogicalResult InferResetsPass::collectAnnos(CircuitOp circuit) {
1242  LLVM_DEBUG({
1243  llvm::dbgs() << "\n";
1244  debugHeader("Gather async reset annotations") << "\n\n";
1245  });
1246  SmallVector<std::pair<FModuleOp, std::optional<Value>>> results;
1247  for (auto module : circuit.getOps<FModuleOp>())
1248  results.push_back({module, {}});
1249  // Collect annotations parallelly.
1250  if (failed(mlir::failableParallelForEach(
1251  circuit.getContext(), results, [&](auto &moduleAndResult) {
1252  auto result = collectAnnos(moduleAndResult.first);
1253  if (failed(result))
1254  return failure();
1255  moduleAndResult.second = *result;
1256  return success();
1257  })))
1258  return failure();
1259 
1260  for (auto [module, reset] : results)
1261  if (reset.has_value())
1262  annotatedResets.insert({module, *reset});
1263  return success();
1264 }
1265 
1267 InferResetsPass::collectAnnos(FModuleOp module) {
1268  bool anyFailed = false;
1269  SmallSetVector<std::pair<Annotation, Location>, 4> conflictingAnnos;
1270 
1271  // Consume a possible "ignore" annotation on the module itself, which
1272  // explicitly assigns it no reset domain.
1273  bool ignore = false;
1274  AnnotationSet moduleAnnos(module);
1275  if (!moduleAnnos.empty()) {
1276  moduleAnnos.removeAnnotations([&](Annotation anno) {
1278  ignore = true;
1279  conflictingAnnos.insert({anno, module.getLoc()});
1280  return true;
1281  }
1282  if (anno.isClass(fullAsyncResetAnnoClass)) {
1283  anyFailed = true;
1284  module.emitError("'FullAsyncResetAnnotation' cannot target module; "
1285  "must target port or wire/node instead");
1286  return true;
1287  }
1288  return false;
1289  });
1290  moduleAnnos.applyToOperation(module);
1291  }
1292  if (anyFailed)
1293  return failure();
1294 
1295  // Consume any reset annotations on module ports.
1296  Value reset;
1297  AnnotationSet::removePortAnnotations(module, [&](unsigned argNum,
1298  Annotation anno) {
1299  Value arg = module.getArgument(argNum);
1300  if (anno.isClass(fullAsyncResetAnnoClass)) {
1301  if (!isa<AsyncResetType>(arg.getType())) {
1302  mlir::emitError(arg.getLoc(), "'IgnoreFullAsyncResetAnnotation' must "
1303  "target async reset, but targets ")
1304  << arg.getType();
1305  anyFailed = true;
1306  return true;
1307  }
1308  reset = arg;
1309  conflictingAnnos.insert({anno, reset.getLoc()});
1310 
1311  return false;
1312  }
1314  anyFailed = true;
1315  mlir::emitError(arg.getLoc(),
1316  "'IgnoreFullAsyncResetAnnotation' cannot target port; "
1317  "must target module instead");
1318  return true;
1319  }
1320  return false;
1321  });
1322  if (anyFailed)
1323  return failure();
1324 
1325  // Consume any reset annotations on wires in the module body.
1326  module.walk([&](Operation *op) {
1327  AnnotationSet::removeAnnotations(op, [&](Annotation anno) {
1328  // Reset annotations must target wire/node ops.
1329  if (!isa<WireOp, NodeOp>(op)) {
1332  anyFailed = true;
1333  op->emitError(
1334  "reset annotations must target module, port, or wire/node");
1335  return true;
1336  }
1337  return false;
1338  }
1339 
1340  // At this point we know that we have a WireOp/NodeOp. Process the reset
1341  // annotations.
1342  auto resultType = op->getResult(0).getType();
1343  if (anno.isClass(fullAsyncResetAnnoClass)) {
1344  if (!isa<AsyncResetType>(resultType)) {
1345  mlir::emitError(op->getLoc(), "'IgnoreFullAsyncResetAnnotation' must "
1346  "target async reset, but targets ")
1347  << resultType;
1348  anyFailed = true;
1349  return true;
1350  }
1351  reset = op->getResult(0);
1352  conflictingAnnos.insert({anno, reset.getLoc()});
1353  return false;
1354  }
1356  anyFailed = true;
1357  op->emitError(
1358  "'IgnoreFullAsyncResetAnnotation' cannot target wire/node; must "
1359  "target module instead");
1360  return true;
1361  }
1362  return false;
1363  });
1364  });
1365  if (anyFailed)
1366  return failure();
1367 
1368  // If we have found no annotations, there is nothing to do. We just leave
1369  // this module unannotated, which will cause it to inherit a reset domain
1370  // from its instantiation sites.
1371  if (!ignore && !reset) {
1372  LLVM_DEBUG(llvm::dbgs()
1373  << "No reset annotation for " << module.getName() << "\n");
1374  return std::optional<Value>();
1375  }
1376 
1377  // If we have found multiple annotations, emit an error and abort.
1378  if (conflictingAnnos.size() > 1) {
1379  auto diag = module.emitError("multiple reset annotations on module '")
1380  << module.getName() << "'";
1381  for (auto &annoAndLoc : conflictingAnnos)
1382  diag.attachNote(annoAndLoc.second)
1383  << "conflicting " << annoAndLoc.first.getClassAttr() << ":";
1384  return failure();
1385  }
1386 
1387  // Dump some information in debug builds.
1388  LLVM_DEBUG({
1389  llvm::dbgs() << "Annotated reset for " << module.getName() << ": ";
1390  if (ignore)
1391  llvm::dbgs() << "no domain\n";
1392  else if (auto arg = dyn_cast<BlockArgument>(reset))
1393  llvm::dbgs() << "port " << module.getPortName(arg.getArgNumber()) << "\n";
1394  else
1395  llvm::dbgs() << "wire "
1396  << reset.getDefiningOp()->getAttrOfType<StringAttr>("name")
1397  << "\n";
1398  });
1399 
1400  // Store the annotated reset for this module.
1401  assert(ignore || reset);
1402  return std::optional<Value>(reset);
1403 }
1404 
1405 //===----------------------------------------------------------------------===//
1406 // Domain Construction
1407 //===----------------------------------------------------------------------===//
1408 
1409 /// Gather the reset domains present in a circuit. This traverses the instance
1410 /// hierarchy of the design, making instances either live in a new reset
1411 /// domain if so annotated, or inherit their parent's domain. This can go
1412 /// wrong in some cases, mainly when a module is instantiated multiple times
1413 /// within different reset domains.
1414 LogicalResult InferResetsPass::buildDomains(CircuitOp circuit) {
1415  LLVM_DEBUG({
1416  llvm::dbgs() << "\n";
1417  debugHeader("Build async reset domains") << "\n\n";
1418  });
1419 
1420  // Gather the domains.
1421  auto &instGraph = getAnalysis<InstanceGraph>();
1422  auto module = dyn_cast<FModuleOp>(*instGraph.getTopLevelNode()->getModule());
1423  if (!module) {
1424  LLVM_DEBUG(llvm::dbgs()
1425  << "Skipping circuit because main module is no `firrtl.module`");
1426  return success();
1427  }
1428  buildDomains(module, InstancePath{}, Value{}, instGraph);
1429 
1430  // Report any domain conflicts among the modules.
1431  bool anyFailed = false;
1432  for (auto &it : domains) {
1433  auto module = cast<FModuleOp>(it.first);
1434  auto &domainConflicts = it.second;
1435  if (domainConflicts.size() <= 1)
1436  continue;
1437 
1438  anyFailed = true;
1439  SmallDenseSet<Value> printedDomainResets;
1440  auto diag = module.emitError("module '")
1441  << module.getName()
1442  << "' instantiated in different reset domains";
1443  for (auto &it : domainConflicts) {
1444  ResetDomain &domain = it.first;
1445  const auto &path = it.second;
1446  auto inst = path.leaf();
1447  auto loc = path.empty() ? module.getLoc() : inst.getLoc();
1448  auto &note = diag.attachNote(loc);
1449 
1450  // Describe the instance itself.
1451  if (path.empty())
1452  note << "root instance";
1453  else {
1454  note << "instance '";
1455  llvm::interleave(
1456  path,
1457  [&](InstanceOpInterface inst) { note << inst.getInstanceName(); },
1458  [&]() { note << "/"; });
1459  note << "'";
1460  }
1461 
1462  // Describe the reset domain the instance is in.
1463  note << " is in";
1464  if (domain.reset) {
1465  auto nameAndModule = getResetNameAndModule(domain.reset);
1466  note << " reset domain rooted at '" << nameAndModule.first.getValue()
1467  << "' of module '" << nameAndModule.second.getName() << "'";
1468 
1469  // Show where the domain reset is declared (once per reset).
1470  if (printedDomainResets.insert(domain.reset).second) {
1471  diag.attachNote(domain.reset.getLoc())
1472  << "reset domain '" << nameAndModule.first.getValue()
1473  << "' of module '" << nameAndModule.second.getName()
1474  << "' declared here:";
1475  }
1476  } else
1477  note << " no reset domain";
1478  }
1479  }
1480  return failure(anyFailed);
1481 }
1482 
1483 void InferResetsPass::buildDomains(FModuleOp module,
1484  const InstancePath &instPath,
1485  Value parentReset, InstanceGraph &instGraph,
1486  unsigned indent) {
1487  LLVM_DEBUG({
1488  llvm::dbgs().indent(indent * 2) << "Visiting ";
1489  if (instPath.empty())
1490  llvm::dbgs() << "$root";
1491  else
1492  llvm::dbgs() << instPath.leaf().getInstanceName();
1493  llvm::dbgs() << " (" << module.getName() << ")\n";
1494  });
1495 
1496  // Assemble the domain for this module.
1497  ResetDomain domain(parentReset);
1498  auto it = annotatedResets.find(module);
1499  if (it != annotatedResets.end()) {
1500  domain.isTop = true;
1501  domain.reset = it->second;
1502  }
1503 
1504  // Associate the domain with this module. If the module already has an
1505  // associated domain, it must be identical. Otherwise we'll have to report
1506  // the conflicting domains to the user.
1507  auto &entries = domains[module];
1508  if (llvm::all_of(entries,
1509  [&](const auto &entry) { return entry.first != domain; }))
1510  entries.push_back({domain, instPath});
1511 
1512  // Traverse the child instances.
1513  for (auto *record : *instGraph[module]) {
1514  auto submodule = dyn_cast<FModuleOp>(*record->getTarget()->getModule());
1515  if (!submodule)
1516  continue;
1517  auto childPath =
1518  instancePathCache->appendInstance(instPath, record->getInstance());
1519  buildDomains(submodule, childPath, domain.reset, instGraph, indent + 1);
1520  }
1521 }
1522 
1523 /// Determine how the reset for each module shall be implemented.
1524 void InferResetsPass::determineImpl() {
1525  LLVM_DEBUG({
1526  llvm::dbgs() << "\n";
1527  debugHeader("Determine implementation") << "\n\n";
1528  });
1529  for (auto &it : domains) {
1530  auto module = cast<FModuleOp>(it.first);
1531  auto &domain = it.second.back().first;
1532  determineImpl(module, domain);
1533  }
1534 }
1535 
1536 /// Determine how the reset for a module shall be implemented. This function
1537 /// fills in the `existingValue`, `existingPort`, and `newPortName` fields of
1538 /// the given reset domain.
1539 ///
1540 /// Generally it does the following:
1541 /// - If the domain has explicitly no reset ("ignore"), leaves everything
1542 /// empty.
1543 /// - If the domain is the place where the reset is defined ("top"), fills in
1544 /// the existing port/wire/node as reset.
1545 /// - If the module already has a port with the reset's name:
1546 /// - If the type is `asyncreset`, reuses that port.
1547 /// - Otherwise appends a `_N` suffix with increasing N to create a
1548 /// yet-unused
1549 /// port name, and marks that as to be created.
1550 /// - Otherwise indicates that a port with the reset's name should be created.
1551 ///
1552 void InferResetsPass::determineImpl(FModuleOp module, ResetDomain &domain) {
1553  if (!domain.reset)
1554  return; // nothing to do if the module needs no reset
1555  LLVM_DEBUG(llvm::dbgs() << "Planning reset for " << module.getName() << "\n");
1556 
1557  // If this is the root of a reset domain, we don't need to add any ports
1558  // and can just simply reuse the existing values.
1559  if (domain.isTop) {
1560  LLVM_DEBUG(llvm::dbgs() << "- Rooting at local value "
1561  << getResetName(domain.reset) << "\n");
1562  domain.existingValue = domain.reset;
1563  if (auto blockArg = dyn_cast<BlockArgument>(domain.reset))
1564  domain.existingPort = blockArg.getArgNumber();
1565  return;
1566  }
1567 
1568  // Otherwise, check if a port with this name and type already exists and
1569  // reuse that where possible.
1570  auto neededName = getResetName(domain.reset);
1571  auto neededType = domain.reset.getType();
1572  LLVM_DEBUG(llvm::dbgs() << "- Looking for existing port " << neededName
1573  << "\n");
1574  auto portNames = module.getPortNames();
1575  auto ports = llvm::zip(portNames, module.getArguments());
1576  auto portIt = llvm::find_if(
1577  ports, [&](auto port) { return std::get<0>(port) == neededName; });
1578  if (portIt != ports.end() && std::get<1>(*portIt).getType() == neededType) {
1579  LLVM_DEBUG(llvm::dbgs()
1580  << "- Reusing existing port " << neededName << "\n");
1581  domain.existingValue = std::get<1>(*portIt);
1582  domain.existingPort = std::distance(ports.begin(), portIt);
1583  return;
1584  }
1585 
1586  // If we have found a port but the types don't match, pick a new name for
1587  // the reset port.
1588  //
1589  // CAVEAT: The Scala FIRRTL compiler just throws an error in this case. This
1590  // seems unnecessary though, since the compiler can just insert a new reset
1591  // signal as needed.
1592  if (portIt != ports.end()) {
1593  LLVM_DEBUG(llvm::dbgs()
1594  << "- Existing " << neededName << " has incompatible type "
1595  << std::get<1>(*portIt).getType() << "\n");
1596  StringAttr newName;
1597  unsigned suffix = 0;
1598  do {
1599  newName =
1600  StringAttr::get(&getContext(), Twine(neededName.getValue()) +
1601  Twine("_") + Twine(suffix++));
1602  } while (llvm::is_contained(portNames, newName));
1603  LLVM_DEBUG(llvm::dbgs()
1604  << "- Creating uniquified port " << newName << "\n");
1605  domain.newPortName = newName;
1606  return;
1607  }
1608 
1609  // At this point we know that there is no such port, and we can safely
1610  // create one as needed.
1611  LLVM_DEBUG(llvm::dbgs() << "- Creating new port " << neededName << "\n");
1612  domain.newPortName = neededName;
1613 }
1614 
1615 //===----------------------------------------------------------------------===//
1616 // Async Reset Implementation
1617 //===----------------------------------------------------------------------===//
1618 
1619 /// Implement the async resets gathered in the pass' `domains` map.
1620 LogicalResult InferResetsPass::implementAsyncReset() {
1621  LLVM_DEBUG({
1622  llvm::dbgs() << "\n";
1623  debugHeader("Implement async resets") << "\n\n";
1624  });
1625  for (auto &it : domains)
1626  if (failed(implementAsyncReset(cast<FModuleOp>(it.first),
1627  it.second.back().first)))
1628  return failure();
1629  return success();
1630 }
1631 
1632 /// Implement the async resets for a specific module.
1633 ///
1634 /// This will add ports to the module as appropriate, update the register ops
1635 /// in the module, and update any instantiated submodules with their
1636 /// corresponding reset implementation details.
1637 LogicalResult InferResetsPass::implementAsyncReset(FModuleOp module,
1638  ResetDomain &domain) {
1639  LLVM_DEBUG(llvm::dbgs() << "Implementing async reset for " << module.getName()
1640  << "\n");
1641 
1642  // Nothing to do if the module was marked explicitly with no reset domain.
1643  if (!domain.reset) {
1644  LLVM_DEBUG(llvm::dbgs()
1645  << "- Skipping because module explicitly has no domain\n");
1646  return success();
1647  }
1648 
1649  // If needed, add a reset port to the module.
1650  Value actualReset = domain.existingValue;
1651  if (domain.newPortName) {
1652  PortInfo portInfo{domain.newPortName,
1653  AsyncResetType::get(&getContext()),
1654  Direction::In,
1655  {},
1656  domain.reset.getLoc()};
1657  module.insertPorts({{0, portInfo}});
1658  actualReset = module.getArgument(0);
1659  LLVM_DEBUG(llvm::dbgs()
1660  << "- Inserted port " << domain.newPortName << "\n");
1661  }
1662  assert(actualReset);
1663  LLVM_DEBUG({
1664  llvm::dbgs() << "- Using ";
1665  if (auto blockArg = dyn_cast<BlockArgument>(actualReset))
1666  llvm::dbgs() << "port #" << blockArg.getArgNumber() << " ";
1667  else
1668  llvm::dbgs() << "wire/node ";
1669  llvm::dbgs() << getResetName(actualReset) << "\n";
1670  });
1671 
1672  // Gather a list of operations in the module that need to be updated with
1673  // the new reset.
1674  SmallVector<Operation *> opsToUpdate;
1675  module.walk([&](Operation *op) {
1676  if (isa<InstanceOp, RegOp, RegResetOp>(op))
1677  opsToUpdate.push_back(op);
1678  });
1679 
1680  // If the reset is a local wire or node, move it upwards such that it
1681  // dominates all the operations that it will need to attach to. In the case
1682  // of a node this might not be easily possible, so we just spill into a wire
1683  // in that case.
1684  if (!isa<BlockArgument>(actualReset)) {
1685  mlir::DominanceInfo dom(module);
1686  // The first op in `opsToUpdate` is the top-most op in the module, since
1687  // the ops and blocks are traversed in a depth-first, top-to-bottom order
1688  // in `walk`. So we can simply check if the local reset declaration is
1689  // before the first op to find out if we need to move anything.
1690  auto *resetOp = actualReset.getDefiningOp();
1691  if (!opsToUpdate.empty() && !dom.dominates(resetOp, opsToUpdate[0])) {
1692  LLVM_DEBUG(llvm::dbgs()
1693  << "- Reset doesn't dominate all uses, needs to be moved\n");
1694 
1695  // If the node can't be moved because its input doesn't dominate the
1696  // target location, convert it to a wire.
1697  auto nodeOp = dyn_cast<NodeOp>(resetOp);
1698  if (nodeOp && !dom.dominates(nodeOp.getInput(), opsToUpdate[0])) {
1699  LLVM_DEBUG(llvm::dbgs()
1700  << "- Promoting node to wire for move: " << nodeOp << "\n");
1701  ImplicitLocOpBuilder builder(nodeOp.getLoc(), nodeOp);
1702  auto wireOp = builder.create<WireOp>(
1703  nodeOp.getResult().getType(), nodeOp.getNameAttr(),
1704  nodeOp.getNameKindAttr(), nodeOp.getAnnotationsAttr(),
1705  nodeOp.getInnerSymAttr(), nodeOp.getForceableAttr());
1706  builder.create<StrictConnectOp>(wireOp.getResult(), nodeOp.getInput());
1707  nodeOp->replaceAllUsesWith(wireOp);
1708  nodeOp.erase();
1709  resetOp = wireOp;
1710  actualReset = wireOp.getResult();
1711  domain.existingValue = wireOp.getResult();
1712  }
1713 
1714  // Determine the block into which the reset declaration needs to be
1715  // moved.
1716  Block *targetBlock = dom.findNearestCommonDominator(
1717  resetOp->getBlock(), opsToUpdate[0]->getBlock());
1718  LLVM_DEBUG({
1719  if (targetBlock != resetOp->getBlock())
1720  llvm::dbgs() << "- Needs to be moved to different block\n";
1721  });
1722 
1723  // At this point we have to figure out in front of which operation in
1724  // the target block the reset declaration has to be moved. The reset
1725  // declaration and the first op it needs to dominate may be buried
1726  // inside blocks of other operations (e.g. `WhenOp`), so we have to look
1727  // through their parent operations until we find the one that lies
1728  // within the target block.
1729  auto getParentInBlock = [](Operation *op, Block *block) {
1730  while (op && op->getBlock() != block)
1731  op = op->getParentOp();
1732  return op;
1733  };
1734  auto *resetOpInTarget = getParentInBlock(resetOp, targetBlock);
1735  auto *firstOpInTarget = getParentInBlock(opsToUpdate[0], targetBlock);
1736 
1737  // Move the operation upwards. Since there are situations where the
1738  // reset declaration does not dominate the first use, but the `WhenOp`
1739  // it is nested within actually *does* come before that use, we have to
1740  // consider moving the reset declaration in front of its parent op.
1741  if (resetOpInTarget->isBeforeInBlock(firstOpInTarget))
1742  resetOp->moveBefore(resetOpInTarget);
1743  else
1744  resetOp->moveBefore(firstOpInTarget);
1745  }
1746  }
1747 
1748  // Update the operations.
1749  for (auto *op : opsToUpdate)
1750  implementAsyncReset(op, module, actualReset);
1751 
1752  return success();
1753 }
1754 
1755 /// Modify an operation in a module to implement an async reset for that
1756 /// module.
1757 void InferResetsPass::implementAsyncReset(Operation *op, FModuleOp module,
1758  Value actualReset) {
1759  ImplicitLocOpBuilder builder(op->getLoc(), op);
1760 
1761  // Handle instances.
1762  if (auto instOp = dyn_cast<InstanceOp>(op)) {
1763  // Lookup the reset domain of the instantiated module. If there is no
1764  // reset domain associated with that module, or the module is explicitly
1765  // marked as being in no domain, simply skip.
1766  auto refModule = instOp.getReferencedModule<FModuleOp>(*instanceGraph);
1767  if (!refModule)
1768  return;
1769  auto domainIt = domains.find(refModule);
1770  if (domainIt == domains.end())
1771  return;
1772  auto &domain = domainIt->second.back().first;
1773  if (!domain.reset)
1774  return;
1775  LLVM_DEBUG(llvm::dbgs()
1776  << "- Update instance '" << instOp.getName() << "'\n");
1777 
1778  // If needed, add a reset port to the instance.
1779  Value instReset;
1780  if (domain.newPortName) {
1781  LLVM_DEBUG(llvm::dbgs() << " - Adding new result as reset\n");
1782 
1783  auto newInstOp = instOp.cloneAndInsertPorts(
1784  {{/*portIndex=*/0,
1785  {domain.newPortName,
1786  type_cast<FIRRTLBaseType>(actualReset.getType()),
1787  Direction::In}}});
1788  instReset = newInstOp.getResult(0);
1789 
1790  // Update the uses over to the new instance and drop the old instance.
1791  instOp.replaceAllUsesWith(newInstOp.getResults().drop_front());
1792  instanceGraph->replaceInstance(instOp, newInstOp);
1793  instOp->erase();
1794  instOp = newInstOp;
1795  } else if (domain.existingPort.has_value()) {
1796  auto idx = *domain.existingPort;
1797  instReset = instOp.getResult(idx);
1798  LLVM_DEBUG(llvm::dbgs() << " - Using result #" << idx << " as reset\n");
1799  }
1800 
1801  // If there's no reset port on the instance to connect, we're done. This
1802  // can happen if the instantiated module has a reset domain, but that
1803  // domain is e.g. rooted at an internal wire.
1804  if (!instReset)
1805  return;
1806 
1807  // Connect the instance's reset to the actual reset.
1808  assert(instReset && actualReset);
1809  builder.setInsertionPointAfter(instOp);
1810  builder.create<StrictConnectOp>(instReset, actualReset);
1811  return;
1812  }
1813 
1814  // Handle reset-less registers.
1815  if (auto regOp = dyn_cast<RegOp>(op)) {
1816  if (AnnotationSet::removeAnnotations(regOp, excludeMemToRegAnnoClass))
1817  return;
1818 
1819  LLVM_DEBUG(llvm::dbgs() << "- Adding async reset to " << regOp << "\n");
1820  auto zero = createZeroValue(builder, regOp.getResult().getType());
1821  auto newRegOp = builder.create<RegResetOp>(
1822  regOp.getResult().getType(), regOp.getClockVal(), actualReset, zero,
1823  regOp.getNameAttr(), regOp.getNameKindAttr(), regOp.getAnnotations(),
1824  regOp.getInnerSymAttr(), regOp.getForceableAttr());
1825  regOp.getResult().replaceAllUsesWith(newRegOp.getResult());
1826  if (regOp.getForceable())
1827  regOp.getRef().replaceAllUsesWith(newRegOp.getRef());
1828  regOp->erase();
1829  return;
1830  }
1831 
1832  // Handle registers with reset.
1833  if (auto regOp = dyn_cast<RegResetOp>(op)) {
1834  // If the register already has an async reset, leave it untouched.
1835  if (type_isa<AsyncResetType>(regOp.getResetSignal().getType())) {
1836  LLVM_DEBUG(llvm::dbgs()
1837  << "- Skipping (has async reset) " << regOp << "\n");
1838  // The following performs the logic of `CheckResets` in the original
1839  // Scala source code.
1840  if (failed(regOp.verifyInvariants()))
1841  signalPassFailure();
1842  return;
1843  }
1844  LLVM_DEBUG(llvm::dbgs() << "- Updating reset of " << regOp << "\n");
1845 
1846  auto reset = regOp.getResetSignal();
1847  auto value = regOp.getResetValue();
1848 
1849  // If we arrive here, the register has a sync reset. In order to add an
1850  // async reset, we have to move the sync reset into a mux in front of the
1851  // register.
1852  insertResetMux(builder, regOp.getResult(), reset, value);
1853  builder.setInsertionPointAfterValue(regOp.getResult());
1854  auto mux = builder.create<MuxPrimOp>(reset, value, regOp.getResult());
1855  emitConnect(builder, regOp.getResult(), mux);
1856 
1857  // Replace the existing reset with the async reset.
1858  builder.setInsertionPoint(regOp);
1859  auto zero = createZeroValue(builder, regOp.getResult().getType());
1860  regOp.getResetSignalMutable().assign(actualReset);
1861  regOp.getResetValueMutable().assign(zero);
1862  }
1863 }
1864 
1865 LogicalResult InferResetsPass::verifyNoAbstractReset() {
1866  bool hasAbstractResetPorts = false;
1867  for (FModuleLike module :
1868  getOperation().getBodyBlock()->getOps<FModuleLike>()) {
1869  for (PortInfo port : module.getPorts()) {
1870  if (getBaseOfType<ResetType>(port.type)) {
1871  auto diag = emitError(port.loc)
1872  << "a port \"" << port.getName()
1873  << "\" with abstract reset type was unable to be "
1874  "inferred by InferResets (is this a top-level port?)";
1875  diag.attachNote(module->getLoc())
1876  << "the module with this uninferred reset port was defined here";
1877  hasAbstractResetPorts = true;
1878  }
1879  }
1880  }
1881 
1882  if (hasAbstractResetPorts)
1883  return failure();
1884  return success();
1885 }
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:54
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:116
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)