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