Loading [MathJax]/extensions/tex2jax.js
CIRCT 21.0.0git
All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Macros Pages
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 (const auto &it : resetClasses) {
1032 if (!it->isLeader())
1033 continue;
1034 ResetNetwork net = resetClasses.members(*it);
1035
1036 // Infer whether this should be a sync or async reset.
1037 auto kind = inferReset(net);
1038 if (failed(kind))
1039 return failure();
1040
1041 // Update the types in the IR to match the inferred kind.
1042 if (failed(updateReset(net, *kind)))
1043 return failure();
1044 }
1045 return success();
1046}
1047
1048FailureOr<ResetKind> InferResetsPass::inferReset(ResetNetwork net) {
1049 LLVM_DEBUG(llvm::dbgs() << "Inferring reset network with "
1050 << std::distance(net.begin(), net.end())
1051 << " nodes\n");
1052
1053 // Go through the nodes and track the involved types.
1054 unsigned asyncDrives = 0;
1055 unsigned syncDrives = 0;
1056 unsigned invalidDrives = 0;
1057 for (ResetSignal signal : net) {
1058 // Keep track of whether this signal contributes a vote for async or sync.
1059 if (type_isa<AsyncResetType>(signal.type))
1060 ++asyncDrives;
1061 else if (type_isa<UIntType>(signal.type))
1062 ++syncDrives;
1063 else if (isUselessVec(signal.field) ||
1064 isa_and_nonnull<InvalidValueOp>(
1065 signal.field.getValue().getDefiningOp()))
1066 ++invalidDrives;
1067 }
1068 LLVM_DEBUG(llvm::dbgs() << "- Found " << asyncDrives << " async, "
1069 << syncDrives << " sync, " << invalidDrives
1070 << " invalid drives\n");
1071
1072 // Handle the case where we have no votes for either kind.
1073 if (asyncDrives == 0 && syncDrives == 0 && invalidDrives == 0) {
1074 ResetSignal root = guessRoot(net);
1075 auto diag = mlir::emitError(root.field.getValue().getLoc())
1076 << "reset network never driven with concrete type";
1077 for (ResetSignal signal : net)
1078 diag.attachNote(signal.field.getLoc()) << "here: ";
1079 return failure();
1080 }
1081
1082 // Handle the case where we have votes for both kinds.
1083 if (asyncDrives > 0 && syncDrives > 0) {
1084 ResetSignal root = guessRoot(net);
1085 bool majorityAsync = asyncDrives >= syncDrives;
1086 auto diag = mlir::emitError(root.field.getValue().getLoc())
1087 << "reset network";
1088 SmallString<32> fieldName;
1089 if (getFieldName(root.field, fieldName))
1090 diag << " \"" << fieldName << "\"";
1091 diag << " simultaneously connected to async and sync resets";
1092 diag.attachNote(root.field.getValue().getLoc())
1093 << "majority of connections to this reset are "
1094 << (majorityAsync ? "async" : "sync");
1095 for (auto &drive : getResetDrives(net)) {
1096 if ((type_isa<AsyncResetType>(drive.dst.type) && !majorityAsync) ||
1097 (type_isa<AsyncResetType>(drive.src.type) && !majorityAsync) ||
1098 (type_isa<UIntType>(drive.dst.type) && majorityAsync) ||
1099 (type_isa<UIntType>(drive.src.type) && majorityAsync))
1100 diag.attachNote(drive.loc)
1101 << (type_isa<AsyncResetType>(drive.src.type) ? "async" : "sync")
1102 << " drive here:";
1103 }
1104 return failure();
1105 }
1106
1107 // At this point we know that the type of the reset is unambiguous. If there
1108 // are any votes for async, we make the reset async. Otherwise we make it
1109 // sync.
1110 auto kind = (asyncDrives ? ResetKind::Async : ResetKind::Sync);
1111 LLVM_DEBUG(llvm::dbgs() << "- Inferred as " << kind << "\n");
1112 return kind;
1113}
1114
1115//===----------------------------------------------------------------------===//
1116// Reset Updating
1117//===----------------------------------------------------------------------===//
1118
1119LogicalResult InferResetsPass::updateReset(ResetNetwork net, ResetKind kind) {
1120 LLVM_DEBUG(llvm::dbgs() << "Updating reset network with "
1121 << std::distance(net.begin(), net.end())
1122 << " nodes to " << kind << "\n");
1123
1124 // Determine the final type the reset should have.
1125 FIRRTLBaseType resetType;
1126 if (kind == ResetKind::Async)
1127 resetType = AsyncResetType::get(&getContext());
1128 else
1129 resetType = UIntType::get(&getContext(), 1);
1130
1131 // Update all those values in the network that cannot be inferred from
1132 // operands. If we change the type of a module port (i.e. BlockArgument), add
1133 // the module to a module worklist since we need to update its function type.
1134 SmallSetVector<Operation *, 16> worklist;
1135 SmallDenseSet<Operation *> moduleWorklist;
1136 SmallDenseSet<std::pair<Operation *, Operation *>> extmoduleWorklist;
1137 for (auto signal : net) {
1138 Value value = signal.field.getValue();
1139 if (!isa<BlockArgument>(value) &&
1140 !isa_and_nonnull<WireOp, RegOp, RegResetOp, InstanceOp, InvalidValueOp,
1141 ConstCastOp, RefCastOp, UninferredResetCastOp,
1142 RWProbeOp>(value.getDefiningOp()))
1143 continue;
1144 if (updateReset(signal.field, resetType)) {
1145 for (auto user : value.getUsers())
1146 worklist.insert(user);
1147 if (auto blockArg = dyn_cast<BlockArgument>(value))
1148 moduleWorklist.insert(blockArg.getOwner()->getParentOp());
1149 else if (auto instOp = value.getDefiningOp<InstanceOp>()) {
1150 if (auto extmodule =
1151 instOp.getReferencedModule<FExtModuleOp>(*instanceGraph))
1152 extmoduleWorklist.insert({extmodule, instOp});
1153 } else if (auto uncast = value.getDefiningOp<UninferredResetCastOp>()) {
1154 uncast.replaceAllUsesWith(uncast.getInput());
1155 uncast.erase();
1156 }
1157 }
1158 }
1159
1160 // Process the worklist of operations that have their type changed, pushing
1161 // types down the SSA dataflow graph. This is important because we change the
1162 // reset types in aggregates, and then need all the subindex, subfield, and
1163 // subaccess operations to be updated as appropriate.
1164 while (!worklist.empty()) {
1165 auto *wop = worklist.pop_back_val();
1166 SmallVector<Type, 2> types;
1167 if (auto op = dyn_cast<InferTypeOpInterface>(wop)) {
1168 // Determine the new result types.
1169 SmallVector<Type, 2> types;
1170 if (failed(op.inferReturnTypes(op->getContext(), op->getLoc(),
1171 op->getOperands(), op->getAttrDictionary(),
1172 op->getPropertiesStorage(),
1173 op->getRegions(), types)))
1174 return failure();
1175
1176 // Update the results and add the changed ones to the
1177 // worklist.
1178 for (auto it : llvm::zip(op->getResults(), types)) {
1179 auto newType = std::get<1>(it);
1180 if (std::get<0>(it).getType() == newType)
1181 continue;
1182 std::get<0>(it).setType(newType);
1183 for (auto *user : std::get<0>(it).getUsers())
1184 worklist.insert(user);
1185 }
1186 LLVM_DEBUG(llvm::dbgs() << "- Inferred " << *op << "\n");
1187 } else if (auto uop = dyn_cast<UninferredResetCastOp>(wop)) {
1188 for (auto *user : uop.getResult().getUsers())
1189 worklist.insert(user);
1190 uop.replaceAllUsesWith(uop.getInput());
1191 LLVM_DEBUG(llvm::dbgs() << "- Inferred " << uop << "\n");
1192 uop.erase();
1193 }
1194 }
1195
1196 // Update module types based on the type of the block arguments.
1197 for (auto *op : moduleWorklist) {
1198 auto module = dyn_cast<FModuleOp>(op);
1199 if (!module)
1200 continue;
1201
1202 SmallVector<Attribute> argTypes;
1203 argTypes.reserve(module.getNumPorts());
1204 for (auto arg : module.getArguments())
1205 argTypes.push_back(TypeAttr::get(arg.getType()));
1206
1207 module.setPortTypesAttr(ArrayAttr::get(op->getContext(), argTypes));
1208 LLVM_DEBUG(llvm::dbgs()
1209 << "- Updated type of module '" << module.getName() << "'\n");
1210 }
1211
1212 // Update extmodule types based on their instantiation.
1213 for (auto pair : extmoduleWorklist) {
1214 auto module = cast<FExtModuleOp>(pair.first);
1215 auto instOp = cast<InstanceOp>(pair.second);
1216
1217 SmallVector<Attribute> types;
1218 for (auto type : instOp.getResultTypes())
1219 types.push_back(TypeAttr::get(type));
1220
1221 module.setPortTypesAttr(ArrayAttr::get(module->getContext(), types));
1222 LLVM_DEBUG(llvm::dbgs()
1223 << "- Updated type of extmodule '" << module.getName() << "'\n");
1224 }
1225
1226 return success();
1227}
1228
1229/// Update the type of a single field within a type.
1230static FIRRTLBaseType updateType(FIRRTLBaseType oldType, unsigned fieldID,
1231 FIRRTLBaseType fieldType) {
1232 // If this is a ground type, simply replace it, preserving constness.
1233 if (oldType.isGround()) {
1234 assert(fieldID == 0);
1235 return fieldType.getConstType(oldType.isConst());
1236 }
1237
1238 // If this is a bundle type, update the corresponding field.
1239 if (auto bundleType = type_dyn_cast<BundleType>(oldType)) {
1240 unsigned index = getIndexForFieldID(bundleType, fieldID);
1241 SmallVector<BundleType::BundleElement> fields(bundleType.begin(),
1242 bundleType.end());
1243 fields[index].type = updateType(
1244 fields[index].type, fieldID - getFieldID(bundleType, index), fieldType);
1245 return BundleType::get(oldType.getContext(), fields, bundleType.isConst());
1246 }
1247
1248 // If this is a vector type, update the element type.
1249 if (auto vectorType = type_dyn_cast<FVectorType>(oldType)) {
1250 auto newType = updateType(vectorType.getElementType(),
1251 fieldID - getFieldID(vectorType), fieldType);
1252 return FVectorType::get(newType, vectorType.getNumElements(),
1253 vectorType.isConst());
1254 }
1255
1256 llvm_unreachable("unknown aggregate type");
1257 return oldType;
1258}
1259
1260/// Update the reset type of a specific field.
1261bool InferResetsPass::updateReset(FieldRef field, FIRRTLBaseType resetType) {
1262 // Compute the updated type.
1263 auto oldType = type_cast<FIRRTLType>(field.getValue().getType());
1264 FIRRTLType newType = mapBaseType(oldType, [&](auto base) {
1265 return updateType(base, field.getFieldID(), resetType);
1266 });
1267
1268 // Update the type if necessary.
1269 if (oldType == newType)
1270 return false;
1271 LLVM_DEBUG(llvm::dbgs() << "- Updating '" << field << "' from " << oldType
1272 << " to " << newType << "\n");
1273 field.getValue().setType(newType);
1274 return true;
1275}
1276
1277//===----------------------------------------------------------------------===//
1278// Reset Annotations
1279//===----------------------------------------------------------------------===//
1280
1281LogicalResult InferResetsPass::collectAnnos(CircuitOp circuit) {
1282 LLVM_DEBUG({
1283 llvm::dbgs() << "\n";
1284 debugHeader("Gather reset annotations") << "\n\n";
1285 });
1286 SmallVector<std::pair<FModuleOp, std::optional<Value>>> results;
1287 for (auto module : circuit.getOps<FModuleOp>())
1288 results.push_back({module, {}});
1289 // Collect annotations parallelly.
1290 if (failed(mlir::failableParallelForEach(
1291 circuit.getContext(), results, [&](auto &moduleAndResult) {
1292 auto result = collectAnnos(moduleAndResult.first);
1293 if (failed(result))
1294 return failure();
1295 moduleAndResult.second = *result;
1296 return success();
1297 })))
1298 return failure();
1299
1300 for (auto [module, reset] : results)
1301 if (reset.has_value())
1302 annotatedResets.insert({module, *reset});
1303 return success();
1304}
1305
1306FailureOr<std::optional<Value>>
1307InferResetsPass::collectAnnos(FModuleOp module) {
1308 bool anyFailed = false;
1309 SmallSetVector<std::pair<Annotation, Location>, 4> conflictingAnnos;
1310
1311 // Consume a possible "ignore" annotation on the module itself, which
1312 // explicitly assigns it no reset domain.
1313 bool ignore = false;
1316 ignore = true;
1317 conflictingAnnos.insert({anno, module.getLoc()});
1318 return true;
1319 }
1320 if (anno.isClass(fullResetAnnoClass)) {
1321 anyFailed = true;
1322 module.emitError("''FullResetAnnotation' cannot target module; must "
1323 "target port or wire/node instead");
1324 return true;
1325 }
1326 return false;
1327 });
1328 if (anyFailed)
1329 return failure();
1330
1331 // Consume any reset annotations on module ports.
1332 Value reset;
1333 // Helper for checking annotations and determining the reset
1334 auto checkAnnotations = [&](Annotation anno, Value arg) {
1335 if (anno.isClass(fullResetAnnoClass)) {
1336 ResetKind expectedResetKind;
1337 if (auto rt = anno.getMember<StringAttr>("resetType")) {
1338 if (rt == "sync") {
1339 expectedResetKind = ResetKind::Sync;
1340 } else if (rt == "async") {
1341 expectedResetKind = ResetKind::Async;
1342 } else {
1343 mlir::emitError(arg.getLoc(),
1344 "'FullResetAnnotation' requires resetType == 'sync' "
1345 "| 'async', but got resetType == ")
1346 << rt;
1347 anyFailed = true;
1348 return true;
1349 }
1350 } else {
1351 mlir::emitError(arg.getLoc(),
1352 "'FullResetAnnotation' requires resetType == "
1353 "'sync' | 'async', but got no resetType");
1354 anyFailed = true;
1355 return true;
1356 }
1357 // Check that the type is well-formed
1358 bool isAsync = expectedResetKind == ResetKind::Async;
1359 bool validUint = false;
1360 if (auto uintT = dyn_cast<UIntType>(arg.getType()))
1361 validUint = uintT.getWidth() == 1;
1362 if ((isAsync && !isa<AsyncResetType>(arg.getType())) ||
1363 (!isAsync && !validUint)) {
1364 auto kind = resetKindToStringRef(expectedResetKind);
1365 mlir::emitError(arg.getLoc(),
1366 "'FullResetAnnotation' with resetType == '")
1367 << kind << "' must target " << kind << " reset, but targets "
1368 << arg.getType();
1369 anyFailed = true;
1370 return true;
1371 }
1372
1373 reset = arg;
1374 conflictingAnnos.insert({anno, reset.getLoc()});
1375
1376 return false;
1377 }
1378 if (anno.isClass(excludeFromFullResetAnnoClass)) {
1379 anyFailed = true;
1380 mlir::emitError(arg.getLoc(),
1381 "'ExcludeFromFullResetAnnotation' cannot "
1382 "target port/wire/node; must target module instead");
1383 return true;
1384 }
1385 return false;
1386 };
1387
1389 [&](unsigned argNum, Annotation anno) {
1390 Value arg = module.getArgument(argNum);
1391 return checkAnnotations(anno, arg);
1392 });
1393 if (anyFailed)
1394 return failure();
1395
1396 // Consume any reset annotations on wires in the module body.
1397 module.getBody().walk([&](Operation *op) {
1398 // Reset annotations must target wire/node ops.
1399 if (!isa<WireOp, NodeOp>(op)) {
1400 if (AnnotationSet::hasAnnotation(op, fullResetAnnoClass,
1401 excludeFromFullResetAnnoClass)) {
1402 anyFailed = true;
1403 op->emitError(
1404 "reset annotations must target module, port, or wire/node");
1405 }
1406 return;
1407 }
1408
1409 // At this point we know that we have a WireOp/NodeOp. Process the reset
1410 // annotations.
1412 auto arg = op->getResult(0);
1413 return checkAnnotations(anno, arg);
1414 });
1415 });
1416 if (anyFailed)
1417 return failure();
1418
1419 // If we have found no annotations, there is nothing to do. We just leave
1420 // this module unannotated, which will cause it to inherit a reset domain
1421 // from its instantiation sites.
1422 if (!ignore && !reset) {
1423 LLVM_DEBUG(llvm::dbgs()
1424 << "No reset annotation for " << module.getName() << "\n");
1425 return std::optional<Value>();
1426 }
1427
1428 // If we have found multiple annotations, emit an error and abort.
1429 if (conflictingAnnos.size() > 1) {
1430 auto diag = module.emitError("multiple reset annotations on module '")
1431 << module.getName() << "'";
1432 for (auto &annoAndLoc : conflictingAnnos)
1433 diag.attachNote(annoAndLoc.second)
1434 << "conflicting " << annoAndLoc.first.getClassAttr() << ":";
1435 return failure();
1436 }
1437
1438 // Dump some information in debug builds.
1439 LLVM_DEBUG({
1440 llvm::dbgs() << "Annotated reset for " << module.getName() << ": ";
1441 if (ignore)
1442 llvm::dbgs() << "no domain\n";
1443 else if (auto arg = dyn_cast<BlockArgument>(reset))
1444 llvm::dbgs() << "port " << module.getPortName(arg.getArgNumber()) << "\n";
1445 else
1446 llvm::dbgs() << "wire "
1447 << reset.getDefiningOp()->getAttrOfType<StringAttr>("name")
1448 << "\n";
1449 });
1450
1451 // Store the annotated reset for this module.
1452 assert(ignore || reset);
1453 return std::optional<Value>(reset);
1454}
1455
1456//===----------------------------------------------------------------------===//
1457// Domain Construction
1458//===----------------------------------------------------------------------===//
1459
1460/// Gather the reset domains present in a circuit. This traverses the instance
1461/// hierarchy of the design, making instances either live in a new reset
1462/// domain if so annotated, or inherit their parent's domain. This can go
1463/// wrong in some cases, mainly when a module is instantiated multiple times
1464/// within different reset domains.
1465LogicalResult InferResetsPass::buildDomains(CircuitOp circuit) {
1466 LLVM_DEBUG({
1467 llvm::dbgs() << "\n";
1468 debugHeader("Build full reset domains") << "\n\n";
1469 });
1470
1471 // Gather the domains.
1472 auto &instGraph = getAnalysis<InstanceGraph>();
1473 auto module = dyn_cast<FModuleOp>(*instGraph.getTopLevelNode()->getModule());
1474 if (!module) {
1475 LLVM_DEBUG(llvm::dbgs()
1476 << "Skipping circuit because main module is no `firrtl.module`");
1477 return success();
1478 }
1479 buildDomains(module, InstancePath{}, Value{}, instGraph);
1480
1481 // Report any domain conflicts among the modules.
1482 bool anyFailed = false;
1483 for (auto &it : domains) {
1484 auto module = cast<FModuleOp>(it.first);
1485 auto &domainConflicts = it.second;
1486 if (domainConflicts.size() <= 1)
1487 continue;
1488
1489 anyFailed = true;
1490 SmallDenseSet<Value> printedDomainResets;
1491 auto diag = module.emitError("module '")
1492 << module.getName()
1493 << "' instantiated in different reset domains";
1494 for (auto &it : domainConflicts) {
1495 ResetDomain &domain = it.first;
1496 const auto &path = it.second;
1497 auto inst = path.leaf();
1498 auto loc = path.empty() ? module.getLoc() : inst.getLoc();
1499 auto &note = diag.attachNote(loc);
1500
1501 // Describe the instance itself.
1502 if (path.empty())
1503 note << "root instance";
1504 else {
1505 note << "instance '";
1506 llvm::interleave(
1507 path,
1508 [&](InstanceOpInterface inst) { note << inst.getInstanceName(); },
1509 [&]() { note << "/"; });
1510 note << "'";
1511 }
1512
1513 // Describe the reset domain the instance is in.
1514 note << " is in";
1515 if (domain.rootReset) {
1516 auto nameAndModule = getResetNameAndModule(domain.rootReset);
1517 note << " reset domain rooted at '" << nameAndModule.first.getValue()
1518 << "' of module '" << nameAndModule.second.getName() << "'";
1519
1520 // Show where the domain reset is declared (once per reset).
1521 if (printedDomainResets.insert(domain.rootReset).second) {
1522 diag.attachNote(domain.rootReset.getLoc())
1523 << "reset domain '" << nameAndModule.first.getValue()
1524 << "' of module '" << nameAndModule.second.getName()
1525 << "' declared here:";
1526 }
1527 } else
1528 note << " no reset domain";
1529 }
1530 }
1531 return failure(anyFailed);
1532}
1533
1534void InferResetsPass::buildDomains(FModuleOp module,
1535 const InstancePath &instPath,
1536 Value parentReset, InstanceGraph &instGraph,
1537 unsigned indent) {
1538 LLVM_DEBUG({
1539 llvm::dbgs().indent(indent * 2) << "Visiting ";
1540 if (instPath.empty())
1541 llvm::dbgs() << "$root";
1542 else
1543 llvm::dbgs() << instPath.leaf().getInstanceName();
1544 llvm::dbgs() << " (" << module.getName() << ")\n";
1545 });
1546
1547 // Assemble the domain for this module.
1548 ResetDomain domain;
1549 auto it = annotatedResets.find(module);
1550 if (it != annotatedResets.end()) {
1551 // If there is an actual reset, use it for our domain. Otherwise, our
1552 // module is explicitly marked to have no domain.
1553 if (auto localReset = it->second)
1554 domain = ResetDomain(localReset);
1555 domain.isTop = true;
1556 } else if (parentReset) {
1557 // Otherwise, we default to using the reset domain of our parent.
1558 domain = ResetDomain(parentReset);
1559 }
1560
1561 // Associate the domain with this module. If the module already has an
1562 // associated domain, it must be identical. Otherwise we'll have to report
1563 // the conflicting domains to the user.
1564 auto &entries = domains[module];
1565 if (llvm::all_of(entries,
1566 [&](const auto &entry) { return entry.first != domain; }))
1567 entries.push_back({domain, instPath});
1568
1569 // Traverse the child instances.
1570 for (auto *record : *instGraph[module]) {
1571 auto submodule = dyn_cast<FModuleOp>(*record->getTarget()->getModule());
1572 if (!submodule)
1573 continue;
1574 auto childPath =
1575 instancePathCache->appendInstance(instPath, record->getInstance());
1576 buildDomains(submodule, childPath, domain.rootReset, instGraph, indent + 1);
1577 }
1578}
1579
1580/// Determine how the reset for each module shall be implemented.
1581LogicalResult InferResetsPass::determineImpl() {
1582 auto anyFailed = false;
1583 LLVM_DEBUG({
1584 llvm::dbgs() << "\n";
1585 debugHeader("Determine implementation") << "\n\n";
1586 });
1587 for (auto &it : domains) {
1588 auto module = cast<FModuleOp>(it.first);
1589 auto &domain = it.second.back().first;
1590 if (failed(determineImpl(module, domain)))
1591 anyFailed = true;
1592 }
1593 return failure(anyFailed);
1594}
1595
1596/// Determine how the reset for a module shall be implemented. This function
1597/// fills in the `localReset` and `existingPort` fields of the given reset
1598/// domain.
1599///
1600/// Generally it does the following:
1601/// - If the domain has explicitly no reset ("ignore"), leaves everything
1602/// empty.
1603/// - If the domain is the place where the reset is defined ("top"), fills in
1604/// the existing port/wire/node as reset.
1605/// - If the module already has a port with the reset's name:
1606/// - If the port has the same name and type as the reset domain, reuses that
1607/// port.
1608/// - Otherwise errors out.
1609/// - Otherwise indicates that a port with the reset's name should be created.
1610///
1611LogicalResult InferResetsPass::determineImpl(FModuleOp module,
1612 ResetDomain &domain) {
1613 // Nothing to do if the module needs no reset.
1614 if (!domain)
1615 return success();
1616 LLVM_DEBUG(llvm::dbgs() << "Planning reset for " << module.getName() << "\n");
1617
1618 // If this is the root of a reset domain, we don't need to add any ports
1619 // and can just simply reuse the existing values.
1620 if (domain.isTop) {
1621 LLVM_DEBUG(llvm::dbgs()
1622 << "- Rooting at local value " << domain.resetName << "\n");
1623 domain.localReset = domain.rootReset;
1624 if (auto blockArg = dyn_cast<BlockArgument>(domain.rootReset))
1625 domain.existingPort = blockArg.getArgNumber();
1626 return success();
1627 }
1628
1629 // Otherwise, check if a port with this name and type already exists and
1630 // reuse that where possible.
1631 auto neededName = domain.resetName;
1632 auto neededType = domain.resetType;
1633 LLVM_DEBUG(llvm::dbgs() << "- Looking for existing port " << neededName
1634 << "\n");
1635 auto portNames = module.getPortNames();
1636 auto *portIt = llvm::find(portNames, neededName);
1637
1638 // If this port does not yet exist, record that we need to create it.
1639 if (portIt == portNames.end()) {
1640 LLVM_DEBUG(llvm::dbgs() << "- Creating new port " << neededName << "\n");
1641 domain.resetName = neededName;
1642 return success();
1643 }
1644
1645 LLVM_DEBUG(llvm::dbgs() << "- Reusing existing port " << neededName << "\n");
1646
1647 // If this port has the wrong type, then error out.
1648 auto portNo = std::distance(portNames.begin(), portIt);
1649 auto portType = module.getPortType(portNo);
1650 if (portType != neededType) {
1651 auto diag = emitError(module.getPortLocation(portNo), "module '")
1652 << module.getName() << "' is in reset domain requiring port '"
1653 << domain.resetName.getValue() << "' to have type "
1654 << domain.resetType << ", but has type " << portType;
1655 diag.attachNote(domain.rootReset.getLoc()) << "reset domain rooted here";
1656 return failure();
1657 }
1658
1659 // We have a pre-existing port which we should use.
1660 domain.existingPort = portNo;
1661 domain.localReset = module.getArgument(portNo);
1662 return success();
1663}
1664
1665//===----------------------------------------------------------------------===//
1666// Full Reset Implementation
1667//===----------------------------------------------------------------------===//
1668
1669/// Implement the annotated resets gathered in the pass' `domains` map.
1670LogicalResult InferResetsPass::implementFullReset() {
1671 LLVM_DEBUG({
1672 llvm::dbgs() << "\n";
1673 debugHeader("Implement full resets") << "\n\n";
1674 });
1675 for (auto &it : domains)
1676 if (failed(implementFullReset(cast<FModuleOp>(it.first),
1677 it.second.back().first)))
1678 return failure();
1679 return success();
1680}
1681
1682/// Implement the async resets for a specific module.
1683///
1684/// This will add ports to the module as appropriate, update the register ops
1685/// in the module, and update any instantiated submodules with their
1686/// corresponding reset implementation details.
1687LogicalResult InferResetsPass::implementFullReset(FModuleOp module,
1688 ResetDomain &domain) {
1689 LLVM_DEBUG(llvm::dbgs() << "Implementing full reset for " << module.getName()
1690 << "\n");
1691
1692 // Nothing to do if the module was marked explicitly with no reset domain.
1693 if (!domain) {
1694 LLVM_DEBUG(llvm::dbgs()
1695 << "- Skipping because module explicitly has no domain\n");
1696 return success();
1697 }
1698
1699 // Add an annotation indicating that this module belongs to a reset domain.
1700 auto *context = module.getContext();
1701 AnnotationSet annotations(module);
1702 annotations.addAnnotations(DictionaryAttr::get(
1703 context, NamedAttribute(StringAttr::get(context, "class"),
1704 StringAttr::get(context, fullResetAnnoClass))));
1705 annotations.applyToOperation(module);
1706
1707 // If needed, add a reset port to the module.
1708 auto actualReset = domain.localReset;
1709 if (!domain.localReset) {
1710 PortInfo portInfo{domain.resetName,
1711 domain.resetType,
1712 Direction::In,
1713 {},
1714 domain.rootReset.getLoc()};
1715 module.insertPorts({{0, portInfo}});
1716 actualReset = module.getArgument(0);
1717 LLVM_DEBUG(llvm::dbgs() << "- Inserted port " << domain.resetName << "\n");
1718 }
1719
1720 LLVM_DEBUG({
1721 llvm::dbgs() << "- Using ";
1722 if (auto blockArg = dyn_cast<BlockArgument>(actualReset))
1723 llvm::dbgs() << "port #" << blockArg.getArgNumber() << " ";
1724 else
1725 llvm::dbgs() << "wire/node ";
1726 llvm::dbgs() << getResetName(actualReset) << "\n";
1727 });
1728
1729 // Gather a list of operations in the module that need to be updated with
1730 // the new reset.
1731 SmallVector<Operation *> opsToUpdate;
1732 module.walk([&](Operation *op) {
1733 if (isa<InstanceOp, RegOp, RegResetOp>(op))
1734 opsToUpdate.push_back(op);
1735 });
1736
1737 // If the reset is a local wire or node, move it upwards such that it
1738 // dominates all the operations that it will need to attach to. In the case
1739 // of a node this might not be easily possible, so we just spill into a wire
1740 // in that case.
1741 if (!isa<BlockArgument>(actualReset)) {
1742 mlir::DominanceInfo dom(module);
1743 // The first op in `opsToUpdate` is the top-most op in the module, since
1744 // the ops and blocks are traversed in a depth-first, top-to-bottom order
1745 // in `walk`. So we can simply check if the local reset declaration is
1746 // before the first op to find out if we need to move anything.
1747 auto *resetOp = actualReset.getDefiningOp();
1748 if (!opsToUpdate.empty() && !dom.dominates(resetOp, opsToUpdate[0])) {
1749 LLVM_DEBUG(llvm::dbgs()
1750 << "- Reset doesn't dominate all uses, needs to be moved\n");
1751
1752 // If the node can't be moved because its input doesn't dominate the
1753 // target location, convert it to a wire.
1754 auto nodeOp = dyn_cast<NodeOp>(resetOp);
1755 if (nodeOp && !dom.dominates(nodeOp.getInput(), opsToUpdate[0])) {
1756 LLVM_DEBUG(llvm::dbgs()
1757 << "- Promoting node to wire for move: " << nodeOp << "\n");
1758 auto builder = ImplicitLocOpBuilder::atBlockBegin(nodeOp.getLoc(),
1759 nodeOp->getBlock());
1760 auto wireOp = builder.create<WireOp>(
1761 nodeOp.getResult().getType(), nodeOp.getNameAttr(),
1762 nodeOp.getNameKindAttr(), nodeOp.getAnnotationsAttr(),
1763 nodeOp.getInnerSymAttr(), nodeOp.getForceableAttr());
1764 // Don't delete the node, since it might be in use in worklists.
1765 nodeOp->replaceAllUsesWith(wireOp);
1766 nodeOp->removeAttr(nodeOp.getInnerSymAttrName());
1767 nodeOp.setName("");
1768 // Leave forcable alone, since we cannot remove a result. It will be
1769 // cleaned up in canonicalization since it is dead. As will this node.
1770 nodeOp.setNameKind(NameKindEnum::DroppableName);
1771 nodeOp.setAnnotationsAttr(ArrayAttr::get(builder.getContext(), {}));
1772 builder.setInsertionPointAfter(nodeOp);
1773 emitConnect(builder, wireOp.getResult(), nodeOp.getResult());
1774 resetOp = wireOp;
1775 actualReset = wireOp.getResult();
1776 domain.localReset = wireOp.getResult();
1777 }
1778
1779 // Determine the block into which the reset declaration needs to be
1780 // moved.
1781 Block *targetBlock = dom.findNearestCommonDominator(
1782 resetOp->getBlock(), opsToUpdate[0]->getBlock());
1783 LLVM_DEBUG({
1784 if (targetBlock != resetOp->getBlock())
1785 llvm::dbgs() << "- Needs to be moved to different block\n";
1786 });
1787
1788 // At this point we have to figure out in front of which operation in
1789 // the target block the reset declaration has to be moved. The reset
1790 // declaration and the first op it needs to dominate may be buried
1791 // inside blocks of other operations (e.g. `WhenOp`), so we have to look
1792 // through their parent operations until we find the one that lies
1793 // within the target block.
1794 auto getParentInBlock = [](Operation *op, Block *block) {
1795 while (op && op->getBlock() != block)
1796 op = op->getParentOp();
1797 return op;
1798 };
1799 auto *resetOpInTarget = getParentInBlock(resetOp, targetBlock);
1800 auto *firstOpInTarget = getParentInBlock(opsToUpdate[0], targetBlock);
1801
1802 // Move the operation upwards. Since there are situations where the
1803 // reset declaration does not dominate the first use, but the `WhenOp`
1804 // it is nested within actually *does* come before that use, we have to
1805 // consider moving the reset declaration in front of its parent op.
1806 if (resetOpInTarget->isBeforeInBlock(firstOpInTarget))
1807 resetOp->moveBefore(resetOpInTarget);
1808 else
1809 resetOp->moveBefore(firstOpInTarget);
1810 }
1811 }
1812
1813 // Update the operations.
1814 for (auto *op : opsToUpdate)
1815 implementFullReset(op, module, actualReset);
1816
1817 return success();
1818}
1819
1820/// Modify an operation in a module to implement an full reset for that
1821/// module.
1822void InferResetsPass::implementFullReset(Operation *op, FModuleOp module,
1823 Value actualReset) {
1824 ImplicitLocOpBuilder builder(op->getLoc(), op);
1825
1826 // Handle instances.
1827 if (auto instOp = dyn_cast<InstanceOp>(op)) {
1828 // Lookup the reset domain of the instantiated module. If there is no
1829 // reset domain associated with that module, or the module is explicitly
1830 // marked as being in no domain, simply skip.
1831 auto refModule = instOp.getReferencedModule<FModuleOp>(*instanceGraph);
1832 if (!refModule)
1833 return;
1834 auto domainIt = domains.find(refModule);
1835 if (domainIt == domains.end())
1836 return;
1837 auto &domain = domainIt->second.back().first;
1838 if (!domain)
1839 return;
1840 LLVM_DEBUG(llvm::dbgs()
1841 << "- Update instance '" << instOp.getName() << "'\n");
1842
1843 // If needed, add a reset port to the instance.
1844 Value instReset;
1845 if (!domain.localReset) {
1846 LLVM_DEBUG(llvm::dbgs() << " - Adding new result as reset\n");
1847
1848 auto newInstOp = instOp.cloneAndInsertPorts(
1849 {{/*portIndex=*/0,
1850 {domain.resetName, type_cast<FIRRTLBaseType>(actualReset.getType()),
1851 Direction::In}}});
1852 instReset = newInstOp.getResult(0);
1853
1854 // Update the uses over to the new instance and drop the old instance.
1855 instOp.replaceAllUsesWith(newInstOp.getResults().drop_front());
1856 instanceGraph->replaceInstance(instOp, newInstOp);
1857 instOp->erase();
1858 instOp = newInstOp;
1859 } else if (domain.existingPort.has_value()) {
1860 auto idx = *domain.existingPort;
1861 instReset = instOp.getResult(idx);
1862 LLVM_DEBUG(llvm::dbgs() << " - Using result #" << idx << " as reset\n");
1863 }
1864
1865 // If there's no reset port on the instance to connect, we're done. This
1866 // can happen if the instantiated module has a reset domain, but that
1867 // domain is e.g. rooted at an internal wire.
1868 if (!instReset)
1869 return;
1870
1871 // Connect the instance's reset to the actual reset.
1872 assert(instReset && actualReset);
1873 builder.setInsertionPointAfter(instOp);
1874 emitConnect(builder, instReset, actualReset);
1875 return;
1876 }
1877
1878 // Handle reset-less registers.
1879 if (auto regOp = dyn_cast<RegOp>(op)) {
1881 return;
1882
1883 LLVM_DEBUG(llvm::dbgs() << "- Adding full reset to " << regOp << "\n");
1884 auto zero = createZeroValue(builder, regOp.getResult().getType());
1885 auto newRegOp = builder.create<RegResetOp>(
1886 regOp.getResult().getType(), regOp.getClockVal(), actualReset, zero,
1887 regOp.getNameAttr(), regOp.getNameKindAttr(), regOp.getAnnotations(),
1888 regOp.getInnerSymAttr(), regOp.getForceableAttr());
1889 regOp.getResult().replaceAllUsesWith(newRegOp.getResult());
1890 if (regOp.getForceable())
1891 regOp.getRef().replaceAllUsesWith(newRegOp.getRef());
1892 regOp->erase();
1893 return;
1894 }
1895
1896 // Handle registers with reset.
1897 if (auto regOp = dyn_cast<RegResetOp>(op)) {
1898 // If the register already has an async reset or if the type of the added
1899 // reset is sync, leave it alone.
1900 if (type_isa<AsyncResetType>(regOp.getResetSignal().getType()) ||
1901 type_isa<UIntType>(actualReset.getType())) {
1902 LLVM_DEBUG(llvm::dbgs() << "- Skipping (has reset) " << regOp << "\n");
1903 // The following performs the logic of `CheckResets` in the original
1904 // Scala source code.
1905 if (failed(regOp.verifyInvariants()))
1906 signalPassFailure();
1907 return;
1908 }
1909 LLVM_DEBUG(llvm::dbgs() << "- Updating reset of " << regOp << "\n");
1910
1911 auto reset = regOp.getResetSignal();
1912 auto value = regOp.getResetValue();
1913
1914 // If we arrive here, the register has a sync reset and the added reset is
1915 // async. In order to add an async reset, we have to move the sync reset
1916 // into a mux in front of the register.
1917 insertResetMux(builder, regOp.getResult(), reset, value);
1918 builder.setInsertionPointAfterValue(regOp.getResult());
1919 auto mux = builder.create<MuxPrimOp>(reset, value, regOp.getResult());
1920 emitConnect(builder, regOp.getResult(), mux);
1921
1922 // Replace the existing reset with the async reset.
1923 builder.setInsertionPoint(regOp);
1924 auto zero = createZeroValue(builder, regOp.getResult().getType());
1925 regOp.getResetSignalMutable().assign(actualReset);
1926 regOp.getResetValueMutable().assign(zero);
1927 }
1928}
1929
1930LogicalResult InferResetsPass::verifyNoAbstractReset() {
1931 bool hasAbstractResetPorts = false;
1932 for (FModuleLike module :
1933 getOperation().getBodyBlock()->getOps<FModuleLike>()) {
1934 for (PortInfo port : module.getPorts()) {
1935 if (getBaseOfType<ResetType>(port.type)) {
1936 auto diag = emitError(port.loc)
1937 << "a port \"" << port.getName()
1938 << "\" with abstract reset type was unable to be "
1939 "inferred by InferResets (is this a top-level port?)";
1940 diag.attachNote(module->getLoc())
1941 << "the module with this uninferred reset port was defined here";
1942 hasAbstractResetPorts = true;
1943 }
1944 }
1945 }
1946
1947 if (hasAbstractResetPorts)
1948 return failure();
1949 return success();
1950}
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)