CIRCT 22.0.0git
Loading...
Searching...
No Matches
InferResets.cpp
Go to the documentation of this file.
1//===- InferResets.cpp - Infer resets and add full reset --------*- C++ -*-===//
2//
3// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4// See https://llvm.org/LICENSE.txt for license information.
5// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6//
7//===----------------------------------------------------------------------===//
8//
9// This file defines the InferResets pass.
10//
11//===----------------------------------------------------------------------===//
12
19#include "circt/Support/Debug.h"
23#include "mlir/IR/Dominance.h"
24#include "mlir/IR/ImplicitLocOpBuilder.h"
25#include "mlir/IR/Threading.h"
26#include "mlir/Pass/Pass.h"
27#include "llvm/ADT/EquivalenceClasses.h"
28#include "llvm/ADT/SetVector.h"
29#include "llvm/ADT/TypeSwitch.h"
30#include "llvm/Support/Debug.h"
31
32#define DEBUG_TYPE "infer-resets"
33
34namespace circt {
35namespace firrtl {
36#define GEN_PASS_DEF_INFERRESETS
37#include "circt/Dialect/FIRRTL/Passes.h.inc"
38} // namespace firrtl
39} // namespace circt
40
41using circt::igraph::InstanceOpInterface;
44using llvm::BumpPtrAllocator;
45using llvm::MapVector;
46using llvm::SmallDenseSet;
47using llvm::SmallSetVector;
48using mlir::FailureOr;
49using mlir::InferTypeOpInterface;
50
51using namespace circt;
52using namespace firrtl;
53
54//===----------------------------------------------------------------------===//
55// Utilities
56//===----------------------------------------------------------------------===//
57
58/// Return the name and parent module of a reset. The reset value must either be
59/// a module port or a wire/node operation.
60static std::pair<StringAttr, FModuleOp> getResetNameAndModule(Value reset) {
61 if (auto arg = dyn_cast<BlockArgument>(reset)) {
62 auto module = cast<FModuleOp>(arg.getParentRegion()->getParentOp());
63 return {module.getPortNameAttr(arg.getArgNumber()), module};
64 }
65 auto *op = reset.getDefiningOp();
66 return {op->getAttrOfType<StringAttr>("name"),
67 op->getParentOfType<FModuleOp>()};
68}
69
70/// Return the name of a reset. The reset value must either be a module port or
71/// a wire/node operation.
72static StringAttr getResetName(Value reset) {
73 return getResetNameAndModule(reset).first;
74}
75
76namespace {
77/// A reset domain.
78struct ResetDomain {
79 /// Whether this is the root of the reset domain.
80 bool isTop = false;
81
82 /// The reset signal for this domain. A null value indicates that this domain
83 /// explicitly has no reset.
84 Value rootReset;
85
86 /// The name of this reset signal.
87 StringAttr resetName;
88 /// The type of this reset signal.
89 Type resetType;
90
91 /// Implementation details for this domain. This will be the module local
92 /// signal for this domain.
93 Value localReset;
94 /// If this module already has a port with the matching name, this holds the
95 /// index of the port.
96 std::optional<unsigned> existingPort;
97
98 /// Create a reset domain without any reset.
99 ResetDomain() = default;
100
101 /// Create a reset domain associated with the root reset.
102 ResetDomain(Value rootReset)
103 : rootReset(rootReset), resetName(getResetName(rootReset)),
104 resetType(rootReset.getType()) {}
105
106 /// Returns true if this is in a reset domain, false if this is not a domain.
107 explicit operator bool() const { return static_cast<bool>(rootReset); }
108};
109} // namespace
110
111inline bool operator==(const ResetDomain &a, const ResetDomain &b) {
112 return (a.isTop == b.isTop && a.resetName == b.resetName &&
113 a.resetType == b.resetType);
114}
115inline bool operator!=(const ResetDomain &a, const ResetDomain &b) {
116 return !(a == b);
117}
118
119/// Construct a zero value of the given type using the given builder.
120static Value createZeroValue(ImplicitLocOpBuilder &builder, FIRRTLBaseType type,
122 // The zero value's type is a const version of `type`.
123 type = type.getConstType(true);
124 auto it = cache.find(type);
125 if (it != cache.end())
126 return it->second;
127 auto nullBit = [&]() {
128 return createZeroValue(
129 builder, UIntType::get(builder.getContext(), 1, /*isConst=*/true),
130 cache);
131 };
132 auto value =
134 .Case<ClockType>([&](auto type) {
135 return AsClockPrimOp::create(builder, nullBit());
136 })
137 .Case<AsyncResetType>([&](auto type) {
138 return AsAsyncResetPrimOp::create(builder, nullBit());
139 })
140 .Case<SIntType, UIntType>([&](auto type) {
141 return ConstantOp::create(
142 builder, 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 FEnumCreateOp::create(builder, type, element.name, value);
152 }
153 auto value = ConstantOp::create(builder,
154 UIntType::get(builder.getContext(),
155 type.getBitWidth(),
156 /*isConst=*/true),
157 APInt::getZero(type.getBitWidth()));
158 return BitCastOp::create(builder, type, value);
159 })
160 .Case<BundleType>([&](auto type) {
161 auto wireOp = WireOp::create(builder, type);
162 for (unsigned i = 0, e = type.getNumElements(); i < e; ++i) {
163 auto fieldType = type.getElementTypePreservingConst(i);
164 auto zero = createZeroValue(builder, fieldType, cache);
165 auto acc =
166 SubfieldOp::create(builder, fieldType, wireOp.getResult(), i);
167 emitConnect(builder, acc, zero);
168 }
169 return wireOp.getResult();
170 })
171 .Case<FVectorType>([&](auto type) {
172 auto wireOp = WireOp::create(builder, type);
173 auto zero = createZeroValue(
174 builder, type.getElementTypePreservingConst(), cache);
175 for (unsigned i = 0, e = type.getNumElements(); i < e; ++i) {
176 auto acc = SubindexOp::create(builder, zero.getType(),
177 wireOp.getResult(), i);
178 emitConnect(builder, acc, zero);
179 }
180 return wireOp.getResult();
181 })
182 .Case<ResetType, AnalogType>(
183 [&](auto type) { return InvalidValueOp::create(builder, type); })
184 .Default([](auto) {
185 llvm_unreachable("switch handles all types");
186 return Value{};
187 });
188 cache.insert({type, value});
189 return value;
190}
191
192/// Construct a null value of the given type using the given builder.
193static Value createZeroValue(ImplicitLocOpBuilder &builder,
194 FIRRTLBaseType type) {
196 return createZeroValue(builder, type, cache);
197}
198
199/// Helper function that inserts reset multiplexer into all `ConnectOp`s
200/// with the given target. Looks through `SubfieldOp`, `SubindexOp`,
201/// and `SubaccessOp`, and inserts multiplexers into connects to
202/// these subaccesses as well. Modifies the insertion location of the builder.
203/// Returns true if the `resetValue` was used in any way, false otherwise.
204static bool insertResetMux(ImplicitLocOpBuilder &builder, Value target,
205 Value reset, Value resetValue) {
206 // Indicates whether the `resetValue` was assigned to in some way. We use this
207 // to erase unused subfield/subindex/subaccess ops on the reset value if they
208 // end up unused.
209 bool resetValueUsed = false;
210
211 for (auto &use : target.getUses()) {
212 Operation *useOp = use.getOwner();
213 builder.setInsertionPoint(useOp);
214 TypeSwitch<Operation *>(useOp)
215 // Insert a mux on the value connected to the target:
216 // connect(dst, src) -> connect(dst, mux(reset, resetValue, src))
217 .Case<ConnectOp, MatchingConnectOp>([&](auto op) {
218 if (op.getDest() != target)
219 return;
220 LLVM_DEBUG(llvm::dbgs() << " - Insert mux into " << op << "\n");
221 auto muxOp =
222 MuxPrimOp::create(builder, reset, resetValue, op.getSrc());
223 op.getSrcMutable().assign(muxOp);
224 resetValueUsed = true;
225 })
226 // Look through subfields.
227 .Case<SubfieldOp>([&](auto op) {
228 auto resetSubValue =
229 SubfieldOp::create(builder, resetValue, op.getFieldIndexAttr());
230 if (insertResetMux(builder, op, reset, resetSubValue))
231 resetValueUsed = true;
232 else
233 resetSubValue.erase();
234 })
235 // Look through subindices.
236 .Case<SubindexOp>([&](auto op) {
237 auto resetSubValue =
238 SubindexOp::create(builder, resetValue, op.getIndexAttr());
239 if (insertResetMux(builder, op, reset, resetSubValue))
240 resetValueUsed = true;
241 else
242 resetSubValue.erase();
243 })
244 // Look through subaccesses.
245 .Case<SubaccessOp>([&](auto op) {
246 if (op.getInput() != target)
247 return;
248 auto resetSubValue =
249 SubaccessOp::create(builder, resetValue, op.getIndex());
250 if (insertResetMux(builder, op, reset, resetSubValue))
251 resetValueUsed = true;
252 else
253 resetSubValue.erase();
254 });
255 }
256 return resetValueUsed;
257}
258
259//===----------------------------------------------------------------------===//
260// Reset Network
261//===----------------------------------------------------------------------===//
262
263namespace {
264
265/// A reset signal.
266///
267/// This essentially combines the exact `FieldRef` of the signal in question
268/// with a type to be used for error reporting and inferring the reset kind.
269struct ResetSignal {
270 ResetSignal(FieldRef field, FIRRTLBaseType type) : field(field), type(type) {}
271 bool operator<(const ResetSignal &other) const { return field < other.field; }
272 bool operator==(const ResetSignal &other) const {
273 return field == other.field;
274 }
275 bool operator!=(const ResetSignal &other) const { return !(*this == other); }
276
277 FieldRef field;
278 FIRRTLBaseType type;
279};
280
281/// A connection made to or from a reset network.
282///
283/// These drives are tracked for each reset network, and are used for error
284/// reporting to the user.
285struct ResetDrive {
286 /// What's being driven.
287 ResetSignal dst;
288 /// What's driving.
289 ResetSignal src;
290 /// The location to use for diagnostics.
291 Location loc;
292};
293
294/// A list of connections to a reset network.
295using ResetDrives = SmallVector<ResetDrive, 1>;
296
297/// All signals connected together into a reset network.
298using ResetNetwork = llvm::iterator_range<
299 llvm::EquivalenceClasses<ResetSignal>::member_iterator>;
300
301/// Whether a reset is sync or async.
302enum class ResetKind { Async, Sync };
303
304static StringRef resetKindToStringRef(const ResetKind &kind) {
305 switch (kind) {
306 case ResetKind::Async:
307 return "async";
308 case ResetKind::Sync:
309 return "sync";
310 }
311 llvm_unreachable("unhandled reset kind");
312}
313} // namespace
314
315namespace llvm {
316template <>
317struct DenseMapInfo<ResetSignal> {
318 static inline ResetSignal getEmptyKey() {
319 return ResetSignal{DenseMapInfo<FieldRef>::getEmptyKey(), {}};
320 }
321 static inline ResetSignal getTombstoneKey() {
322 return ResetSignal{DenseMapInfo<FieldRef>::getTombstoneKey(), {}};
323 }
324 static unsigned getHashValue(const ResetSignal &x) {
325 return circt::hash_value(x.field);
326 }
327 static bool isEqual(const ResetSignal &lhs, const ResetSignal &rhs) {
328 return lhs == rhs;
329 }
330};
331} // namespace llvm
332
333template <typename T>
334static T &operator<<(T &os, const ResetKind &kind) {
335 switch (kind) {
336 case ResetKind::Async:
337 return os << "async";
338 case ResetKind::Sync:
339 return os << "sync";
340 }
341 return os;
342}
343
344//===----------------------------------------------------------------------===//
345// Pass Infrastructure
346//===----------------------------------------------------------------------===//
347
348namespace {
349/// Infer concrete reset types and insert full reset.
350///
351/// This pass replaces `reset` types in the IR with a concrete `asyncreset` or
352/// `uint<1>` depending on how the reset is used, and adds resets to registers
353/// in modules marked with the corresponding `FullResetAnnotation`.
354///
355/// On a high level, the first stage of the pass that deals with reset inference
356/// operates as follows:
357///
358/// 1. Build a global graph of the resets in the design by tracing reset signals
359/// through instances. This uses the `ResetNetwork` utilities and boils down
360/// to finding groups of values in the IR that are part of the same reset
361/// network (i.e., somehow attached together through ports, wires, instances,
362/// and connects). We use LLVM's `EquivalenceClasses` data structure to do
363/// this efficiently.
364///
365/// 2. Infer the type of each reset network found in step 1 by looking at the
366/// type of values connected to the network. This results in the network
367/// being declared a sync (`uint<1>`) or async (`asyncreset`) network. If the
368/// reset is never driven by a concrete type, an error is emitted.
369///
370/// 3. Walk the IR and update the type of wires and ports with the reset types
371/// found in step 2. This will replace all `reset` types in the IR with
372/// a concrete type.
373///
374/// The second stage that deals with the addition of full resets operates as
375/// follows:
376///
377/// 4. Visit every module in the design and determine if it has an explicit
378/// reset annotated. Ports of and wires in the module can have a
379/// `FullResetAnnotation`, which marks that port or wire as the reset for
380/// the module. A module may also carry a `ExcludeFromFullResetAnnotation`,
381/// which marks it as being explicitly not in a reset domain. These
382/// annotations are sparse; it is very much possible that just the top-level
383/// module in the design has a full reset annotation. A module can only
384/// ever carry one of these annotations, which puts it into one of three
385/// categories from a full reset inference perspective:
386///
387/// a. unambiguously marks a port or wire as the module's full reset
388/// b. explicitly marks it as not to have any full resets added
389/// c. inherit reset
390///
391/// 5. For every module in the design, determine the full full reset domain it
392/// is in. Note that this very narrowly deals with the inference of a
393/// "default" full reset, which basically goes through the IR and attaches
394/// all non-reset registers to a default full reset signal. If a module
395/// carries one of the annotations mentioned in (4), the annotated port or
396/// wire is used as its reset domain. Otherwise, it inherits the reset domain
397/// from parent modules. This conceptually involves looking at all the places
398/// where a module is instantiated, and recursively determining the reset
399/// domain at the instantiation site. A module can only ever be in one reset
400/// domain. In case it is inferred to lie in multiple ones, e.g., if it is
401/// instantiated in different reset domains, an error is emitted. If
402/// successful, every module is associated with a reset signal, either one of
403/// its local ports or wires, or a port or wire within one of its parent
404/// modules.
405///
406/// 6. For every module in the design, determine how full resets shall be
407/// implemented. This step handles the following distinct cases:
408///
409/// a. Skip a module because it is marked as having no reset domain.
410/// b. Use a port or wire in the module itself as reset. This is possible
411/// if the module is at the "top" of its reset domain, which means that
412/// it itself carried a reset annotation, and the reset value is either
413/// a port or wire of the module itself.
414/// c. Route a parent module's reset through a module port and use that
415/// port as the reset. This happens if the module is *not* at the "top"
416/// of its reset domain, but rather refers to a value in a parent module
417/// as its reset.
418///
419/// As a result, a module's reset domain is annotated with the existing local
420/// value to reuse (port or wire), the index of an existing port to reuse,
421/// and the name of an additional port to insert into its port list.
422///
423/// 7. For every module in the design, full resets are implemented. This
424/// determines the local value to use as the reset signal and updates the
425/// `reg` and `regreset` operations in the design. If the register already
426/// has an async reset, or if the type of the full reset is sync, the
427/// register's reset is left unchanged. If it has a sync reset and the full
428/// reset is async, the sync reset is moved into a `mux` operation on all
429/// `connect`s to the register (which the Scala code base called the
430/// `RemoveResets` pass). Finally the register is replaced with a `regreset`
431/// operation, with the reset signal determined earlier, and a "zero" value
432/// constructed for the register's type.
433///
434/// Determining the local reset value is trivial if step 6 found a module to
435/// be of case a or b. Case c is the non-trivial one, because it requires
436/// modifying the port list of the module. This is done by first determining
437/// the name of the reset signal in the parent module, which is either the
438/// name of the port or wire declaration. We then look for an existing
439/// port of the same type in the port list and reuse that as reset. If no
440/// port with that name was found, or the existing port is of the wrong type,
441/// a new port is inserted into the port list.
442///
443/// TODO: This logic is *very* brittle and error-prone. It may make sense to
444/// just add an additional port for the inferred reset in any case, with an
445/// optimization to use an existing port if all of the module's
446/// instantiations have that port connected to the desired signal already.
447///
448struct InferResetsPass
449 : public circt::firrtl::impl::InferResetsBase<InferResetsPass> {
450 void runOnOperation() override;
451 void runOnOperationInner();
452
453 // Copy creates a new empty pass (because ResetMap has no copy constructor).
454 using InferResetsBase::InferResetsBase;
455 InferResetsPass(const InferResetsPass &other) : InferResetsBase(other) {}
456
457 //===--------------------------------------------------------------------===//
458 // Reset type inference
459
460 void traceResets(CircuitOp circuit);
461 void traceResets(InstanceOp inst);
462 void traceResets(Value dst, Value src, Location loc);
463 void traceResets(Value value);
464 void traceResets(Type dstType, Value dst, unsigned dstID, Type srcType,
465 Value src, unsigned srcID, Location loc);
466
467 LogicalResult inferAndUpdateResets();
468 FailureOr<ResetKind> inferReset(ResetNetwork net);
469 LogicalResult updateReset(ResetNetwork net, ResetKind kind);
470 bool updateReset(FieldRef field, FIRRTLBaseType resetType);
471
472 //===--------------------------------------------------------------------===//
473 // Full reset implementation
474
475 LogicalResult collectAnnos(CircuitOp circuit);
476 // Collect reset annotations in the module and return a reset signal.
477 // Return `failure()` if there was an error in the annotation processing.
478 // Return `std::nullopt` if there was no reset annotation.
479 // Return `nullptr` if there was `ignore` annotation.
480 // Return a non-null Value if the reset was actually provided.
481 FailureOr<std::optional<Value>> collectAnnos(FModuleOp module);
482
483 LogicalResult buildDomains(CircuitOp circuit);
484 void buildDomains(FModuleOp module, const InstancePath &instPath,
485 Value parentReset, InstanceGraph &instGraph,
486 unsigned indent = 0);
487
488 LogicalResult determineImpl();
489 LogicalResult determineImpl(FModuleOp module, ResetDomain &domain);
490
491 LogicalResult implementFullReset();
492 LogicalResult implementFullReset(FModuleOp module, ResetDomain &domain);
493 void implementFullReset(Operation *op, FModuleOp module, Value actualReset);
494
495 LogicalResult verifyNoAbstractReset();
496
497 //===--------------------------------------------------------------------===//
498 // Utilities
499
500 /// Get the reset network a signal belongs to.
501 ResetNetwork getResetNetwork(ResetSignal signal) {
502 return llvm::make_range(resetClasses.findLeader(signal),
503 resetClasses.member_end());
504 }
505
506 /// Get the drives of a reset network.
507 ResetDrives &getResetDrives(ResetNetwork net) {
508 return resetDrives[*net.begin()];
509 }
510
511 /// Guess the root node of a reset network, such that we have something for
512 /// the user to make sense of.
513 ResetSignal guessRoot(ResetNetwork net);
514 ResetSignal guessRoot(ResetSignal signal) {
515 return guessRoot(getResetNetwork(signal));
516 }
517
518 //===--------------------------------------------------------------------===//
519 // Analysis data
520
521 /// A map of all traced reset networks in the circuit.
522 llvm::EquivalenceClasses<ResetSignal> resetClasses;
523
524 /// A map of all connects to and from a reset.
525 DenseMap<ResetSignal, ResetDrives> resetDrives;
526
527 /// The annotated reset for a module. A null value indicates that the module
528 /// is explicitly annotated with `ignore`. Otherwise the port/wire/node
529 /// annotated as reset within the module is stored.
530 DenseMap<Operation *, Value> annotatedResets;
531
532 /// The reset domain for a module. In case of conflicting domain membership,
533 /// the vector for a module contains multiple elements.
534 MapVector<FModuleOp, SmallVector<std::pair<ResetDomain, InstancePath>, 1>>
535 domains;
536
537 /// Cache of modules symbols
538 InstanceGraph *instanceGraph;
539
540 /// Cache of instance paths.
541 std::unique_ptr<InstancePathCache> instancePathCache;
542};
543} // namespace
544
545void InferResetsPass::runOnOperation() {
546 runOnOperationInner();
547 resetClasses = llvm::EquivalenceClasses<ResetSignal>();
548 resetDrives.clear();
549 annotatedResets.clear();
550 domains.clear();
551 instancePathCache.reset(nullptr);
552 markAnalysesPreserved<InstanceGraph>();
553}
554
555void InferResetsPass::runOnOperationInner() {
556 instanceGraph = &getAnalysis<InstanceGraph>();
557 instancePathCache = std::make_unique<InstancePathCache>(*instanceGraph);
558
559 // Trace the uninferred reset networks throughout the design.
560 traceResets(getOperation());
561
562 // Infer the type of the traced resets and update the IR.
563 if (failed(inferAndUpdateResets()))
564 return signalPassFailure();
565
566 // Gather the reset annotations throughout the modules.
567 if (failed(collectAnnos(getOperation())))
568 return signalPassFailure();
569
570 // Build the reset domains in the design.
571 if (failed(buildDomains(getOperation())))
572 return signalPassFailure();
573
574 // Determine how each reset shall be implemented.
575 if (failed(determineImpl()))
576 return signalPassFailure();
577
578 // Implement the full resets.
579 if (failed(implementFullReset()))
580 return signalPassFailure();
581
582 // Require that no Abstract Resets exist on ports in the design.
583 if (failed(verifyNoAbstractReset()))
584 return signalPassFailure();
585}
586
587ResetSignal InferResetsPass::guessRoot(ResetNetwork net) {
588 ResetDrives &drives = getResetDrives(net);
589 ResetSignal bestSignal = *net.begin();
590 unsigned bestNumDrives = -1;
591
592 for (auto signal : net) {
593 // Don't consider `invalidvalue` for reporting as a root.
594 if (isa_and_nonnull<InvalidValueOp>(
595 signal.field.getValue().getDefiningOp()))
596 continue;
597
598 // Count the number of times this particular signal in the reset network is
599 // assigned to.
600 unsigned numDrives = 0;
601 for (auto &drive : drives)
602 if (drive.dst == signal)
603 ++numDrives;
604
605 // Keep track of the signal with the lowest number of assigns. These tend to
606 // be the signals further up the reset tree. This will usually resolve to
607 // the root of the reset tree far up in the design hierarchy.
608 if (numDrives < bestNumDrives) {
609 bestNumDrives = numDrives;
610 bestSignal = signal;
611 }
612 }
613 return bestSignal;
614}
615
616//===----------------------------------------------------------------------===//
617// Custom Field IDs
618//===----------------------------------------------------------------------===//
619
620// The following functions implement custom field IDs specifically for the use
621// in reset inference. They look much more like tracking fields on types than
622// individual values. For example, vectors don't carry separate IDs for each of
623// their elements. Instead they have one set of IDs for the entire vector, since
624// the element type is uniform across all elements.
625
626static unsigned getMaxFieldID(FIRRTLBaseType type) {
628 .Case<BundleType>([](auto type) {
629 unsigned id = 0;
630 for (auto e : type.getElements())
631 id += getMaxFieldID(e.type) + 1;
632 return id;
633 })
634 .Case<FVectorType>(
635 [](auto type) { return getMaxFieldID(type.getElementType()) + 1; })
636 .Default([](auto) { return 0; });
637}
638
639static unsigned getFieldID(BundleType type, unsigned index) {
640 assert(index < type.getNumElements());
641 unsigned id = 1;
642 for (unsigned i = 0; i < index; ++i)
643 id += getMaxFieldID(type.getElementType(i)) + 1;
644 return id;
645}
646
647static unsigned getFieldID(FVectorType type) { return 1; }
648
649static unsigned getIndexForFieldID(BundleType type, unsigned fieldID) {
650 assert(type.getNumElements() && "Bundle must have >0 fields");
651 --fieldID;
652 for (const auto &e : llvm::enumerate(type.getElements())) {
653 auto numSubfields = getMaxFieldID(e.value().type) + 1;
654 if (fieldID < numSubfields)
655 return e.index();
656 fieldID -= numSubfields;
657 }
658 assert(false && "field id outside bundle");
659 return 0;
660}
661
662// If a field is pointing to a child of a zero-length vector, it is useless.
663static bool isUselessVec(FIRRTLBaseType oldType, unsigned fieldID) {
664 if (oldType.isGround()) {
665 assert(fieldID == 0);
666 return false;
667 }
668
669 // If this is a bundle type, recurse.
670 if (auto bundleType = type_dyn_cast<BundleType>(oldType)) {
671 unsigned index = getIndexForFieldID(bundleType, fieldID);
672 return isUselessVec(bundleType.getElementType(index),
673 fieldID - getFieldID(bundleType, index));
674 }
675
676 // If this is a vector type, check if it is zero length. Anything in a
677 // zero-length vector is useless.
678 if (auto vectorType = type_dyn_cast<FVectorType>(oldType)) {
679 if (vectorType.getNumElements() == 0)
680 return true;
681 return isUselessVec(vectorType.getElementType(),
682 fieldID - getFieldID(vectorType));
683 }
684
685 return false;
686}
687
688// If a field is pointing to a child of a zero-length vector, it is useless.
689static bool isUselessVec(FieldRef field) {
690 return isUselessVec(
691 getBaseType(type_cast<FIRRTLType>(field.getValue().getType())),
692 field.getFieldID());
693}
694
695static bool getDeclName(Value value, SmallString<32> &string) {
696 if (auto arg = dyn_cast<BlockArgument>(value)) {
697 auto module = cast<FModuleOp>(arg.getOwner()->getParentOp());
698 string += module.getPortName(arg.getArgNumber());
699 return true;
700 }
701
702 auto *op = value.getDefiningOp();
703 return TypeSwitch<Operation *, bool>(op)
704 .Case<InstanceOp, MemOp>([&](auto op) {
705 string += op.getName();
706 string += ".";
707 string += op.getPortName(cast<OpResult>(value).getResultNumber());
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 = InvalidValueOp::create(builder, 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 = WireOp::create(
1772 builder, 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 auto newInstOp = instOp.cloneWithInsertedPortsAndReplaceUses(
1859 {{/*portIndex=*/0,
1860 {domain.resetName, type_cast<FIRRTLBaseType>(actualReset.getType()),
1861 Direction::In}}});
1862 instReset = newInstOp.getResult(0);
1863 instanceGraph->replaceInstance(instOp, newInstOp);
1864 instOp->erase();
1865 instOp = newInstOp;
1866 } else if (domain.existingPort.has_value()) {
1867 auto idx = *domain.existingPort;
1868 instReset = instOp.getResult(idx);
1869 LLVM_DEBUG(llvm::dbgs() << " - Using result #" << idx << " as reset\n");
1870 }
1871
1872 // If there's no reset port on the instance to connect, we're done. This
1873 // can happen if the instantiated module has a reset domain, but that
1874 // domain is e.g. rooted at an internal wire.
1875 if (!instReset)
1876 return;
1877
1878 // Connect the instance's reset to the actual reset.
1879 assert(instReset && actualReset);
1880 builder.setInsertionPointAfter(instOp);
1881 emitConnect(builder, instReset, actualReset);
1882 return;
1883 }
1884
1885 // Handle reset-less registers.
1886 if (auto regOp = dyn_cast<RegOp>(op)) {
1887 LLVM_DEBUG(llvm::dbgs() << "- Adding full reset to " << regOp << "\n");
1888 auto zero = createZeroValue(builder, regOp.getResult().getType());
1889 auto newRegOp = RegResetOp::create(
1890 builder, regOp.getResult().getType(), regOp.getClockVal(), actualReset,
1891 zero, regOp.getNameAttr(), regOp.getNameKindAttr(),
1892 regOp.getAnnotations(), regOp.getInnerSymAttr(),
1893 regOp.getForceableAttr());
1894 regOp.getResult().replaceAllUsesWith(newRegOp.getResult());
1895 if (regOp.getForceable())
1896 regOp.getRef().replaceAllUsesWith(newRegOp.getRef());
1897 regOp->erase();
1898 return;
1899 }
1900
1901 // Handle registers with reset.
1902 if (auto regOp = dyn_cast<RegResetOp>(op)) {
1903 // If the register already has an async reset or if the type of the added
1904 // reset is sync, leave it alone.
1905 if (type_isa<AsyncResetType>(regOp.getResetSignal().getType()) ||
1906 type_isa<UIntType>(actualReset.getType())) {
1907 LLVM_DEBUG(llvm::dbgs() << "- Skipping (has reset) " << regOp << "\n");
1908 // The following performs the logic of `CheckResets` in the original
1909 // Scala source code.
1910 if (failed(regOp.verifyInvariants()))
1911 signalPassFailure();
1912 return;
1913 }
1914 LLVM_DEBUG(llvm::dbgs() << "- Updating reset of " << regOp << "\n");
1915
1916 auto reset = regOp.getResetSignal();
1917 auto value = regOp.getResetValue();
1918
1919 // If we arrive here, the register has a sync reset and the added reset is
1920 // async. In order to add an async reset, we have to move the sync reset
1921 // into a mux in front of the register.
1922 insertResetMux(builder, regOp.getResult(), reset, value);
1923 builder.setInsertionPointAfterValue(regOp.getResult());
1924 auto mux = MuxPrimOp::create(builder, reset, value, regOp.getResult());
1925 emitConnect(builder, regOp.getResult(), mux);
1926
1927 // Replace the existing reset with the async reset.
1928 builder.setInsertionPoint(regOp);
1929 auto zero = createZeroValue(builder, regOp.getResult().getType());
1930 regOp.getResetSignalMutable().assign(actualReset);
1931 regOp.getResetValueMutable().assign(zero);
1932 }
1933}
1934
1935LogicalResult InferResetsPass::verifyNoAbstractReset() {
1936 bool hasAbstractResetPorts = false;
1937 for (FModuleLike module :
1938 getOperation().getBodyBlock()->getOps<FModuleLike>()) {
1939 for (PortInfo port : module.getPorts()) {
1940 if (getBaseOfType<ResetType>(port.type)) {
1941 auto diag = emitError(port.loc)
1942 << "a port \"" << port.getName()
1943 << "\" with abstract reset type was unable to be "
1944 "inferred by InferResets (is this a top-level port?)";
1945 diag.attachNote(module->getLoc())
1946 << "the module with this uninferred reset port was defined here";
1947 hasAbstractResetPorts = true;
1948 }
1949 }
1950 }
1951
1952 if (hasAbstractResetPorts)
1953 return failure();
1954 return success();
1955}
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:216
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(const llvm::Twine &str, unsigned width=80)
Write a "header"-like string to the debug stream with a certain width.
Definition Debug.cpp:17
bool operator!=(uint64_t a, const FVInt &b)
Definition FVInt.h:685
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)