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