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 +=
708 op.getPortName(cast<OpResult>(value).getResultNumber()).getValue();
709 return true;
710 })
711 .Case<WireOp, NodeOp, RegOp, RegResetOp>([&](auto op) {
712 string += op.getName();
713 return true;
714 })
715 .Default([](auto) { return false; });
716}
717
718static bool getFieldName(const FieldRef &fieldRef, SmallString<32> &string) {
719 SmallString<64> name;
720 auto value = fieldRef.getValue();
721 if (!getDeclName(value, string))
722 return false;
723
724 auto type = value.getType();
725 auto localID = fieldRef.getFieldID();
726 while (localID) {
727 if (auto bundleType = type_dyn_cast<BundleType>(type)) {
728 auto index = getIndexForFieldID(bundleType, localID);
729 // Add the current field string, and recurse into a subfield.
730 auto &element = bundleType.getElements()[index];
731 if (!string.empty())
732 string += ".";
733 string += element.name.getValue();
734 // Recurse in to the element type.
735 type = element.type;
736 localID = localID - getFieldID(bundleType, index);
737 } else if (auto vecType = type_dyn_cast<FVectorType>(type)) {
738 string += "[]";
739 // Recurse in to the element type.
740 type = vecType.getElementType();
741 localID = localID - getFieldID(vecType);
742 } else {
743 // If we reach here, the field ref is pointing inside some aggregate type
744 // that isn't a bundle or a vector. If the type is a ground type, then the
745 // localID should be 0 at this point, and we should have broken from the
746 // loop.
747 llvm_unreachable("unsupported type");
748 }
749 }
750 return true;
751}
752
753//===----------------------------------------------------------------------===//
754// Reset Tracing
755//===----------------------------------------------------------------------===//
756
757/// Check whether a type contains a `ResetType`.
758static bool typeContainsReset(Type type) {
759 return TypeSwitch<Type, bool>(type)
760 .Case<FIRRTLType>([](auto type) {
761 return type.getRecursiveTypeProperties().hasUninferredReset;
762 })
763 .Default([](auto) { return false; });
764}
765
766/// Iterate over a circuit and follow all signals with `ResetType`, aggregating
767/// them into reset nets. After this function returns, the `resetMap` is
768/// populated with the reset networks in the circuit, alongside information on
769/// drivers and their types that contribute to the reset.
770void InferResetsPass::traceResets(CircuitOp circuit) {
771 LLVM_DEBUG({
772 llvm::dbgs() << "\n";
773 debugHeader("Tracing uninferred resets") << "\n\n";
774 });
775
776 SmallVector<std::pair<FModuleOp, SmallVector<Operation *>>> moduleToOps;
777
778 for (auto module : circuit.getOps<FModuleOp>())
779 moduleToOps.push_back({module, {}});
780
781 hw::InnerRefNamespace irn{getAnalysis<SymbolTable>(),
782 getAnalysis<hw::InnerSymbolTableCollection>()};
783
784 mlir::parallelForEach(circuit.getContext(), moduleToOps, [](auto &e) {
785 e.first.walk([&](Operation *op) {
786 // We are only interested in operations which are related to abstract
787 // reset.
788 if (llvm::any_of(
789 op->getResultTypes(),
790 [](mlir::Type type) { return typeContainsReset(type); }) ||
791 llvm::any_of(op->getOperandTypes(), typeContainsReset))
792 e.second.push_back(op);
793 });
794 });
795
796 for (auto &[_, ops] : moduleToOps)
797 for (auto *op : ops) {
798 TypeSwitch<Operation *>(op)
799 .Case<FConnectLike>([&](auto op) {
800 traceResets(op.getDest(), op.getSrc(), op.getLoc());
801 })
802 .Case<InstanceOp>([&](auto op) { traceResets(op); })
803 .Case<RefSendOp>([&](auto op) {
804 // Trace using base types.
805 traceResets(op.getType().getType(), op.getResult(), 0,
806 op.getBase().getType().getPassiveType(), op.getBase(),
807 0, op.getLoc());
808 })
809 .Case<RefResolveOp>([&](auto op) {
810 // Trace using base types.
811 traceResets(op.getType(), op.getResult(), 0,
812 op.getRef().getType().getType(), op.getRef(), 0,
813 op.getLoc());
814 })
815 .Case<Forceable>([&](Forceable op) {
816 if (auto node = dyn_cast<NodeOp>(op.getOperation()))
817 traceResets(node.getResult(), node.getInput(), node.getLoc());
818 // Trace reset into rwprobe. Avoid invalid IR.
819 if (op.isForceable())
820 traceResets(op.getDataType(), op.getData(), 0, op.getDataType(),
821 op.getDataRef(), 0, op.getLoc());
822 })
823 .Case<RWProbeOp>([&](RWProbeOp op) {
824 auto ist = irn.lookup(op.getTarget());
825 assert(ist);
826 auto ref = getFieldRefForTarget(ist);
827 auto baseType = op.getType().getType();
828 traceResets(baseType, op.getResult(), 0, baseType.getPassiveType(),
829 ref.getValue(), ref.getFieldID(), op.getLoc());
830 })
831 .Case<UninferredResetCastOp, ConstCastOp, RefCastOp>([&](auto op) {
832 traceResets(op.getResult(), op.getInput(), op.getLoc());
833 })
834 .Case<InvalidValueOp>([&](auto op) {
835 // Uniquify `InvalidValueOp`s that are contributing to multiple
836 // reset networks. These are tricky to handle because passes
837 // like CSE will generally ensure that there is only a single
838 // `InvalidValueOp` per type. However, a `reset` invalid value
839 // may be connected to two reset networks that end up being
840 // inferred as `asyncreset` and `uint<1>`. In that case, we need
841 // a distinct `InvalidValueOp` for each reset network in order
842 // to assign it the correct type.
843 auto type = op.getType();
844 if (!typeContainsReset(type) || op->hasOneUse() || op->use_empty())
845 return;
846 LLVM_DEBUG(llvm::dbgs() << "Uniquify " << op << "\n");
847 ImplicitLocOpBuilder builder(op->getLoc(), op);
848 for (auto &use :
849 llvm::make_early_inc_range(llvm::drop_begin(op->getUses()))) {
850 // - `make_early_inc_range` since `getUses()` is invalidated
851 // upon
852 // `use.set(...)`.
853 // - `drop_begin` such that the first use can keep the
854 // original op.
855 auto newOp = InvalidValueOp::create(builder, type);
856 use.set(newOp);
857 }
858 })
859
860 .Case<SubfieldOp>([&](auto op) {
861 // Associate the input bundle's resets with the output field's
862 // resets.
863 BundleType bundleType = op.getInput().getType();
864 auto index = op.getFieldIndex();
865 traceResets(op.getType(), op.getResult(), 0,
866 bundleType.getElements()[index].type, op.getInput(),
867 getFieldID(bundleType, index), op.getLoc());
868 })
869
870 .Case<SubindexOp, SubaccessOp>([&](auto op) {
871 // Associate the input vector's resets with the output field's
872 // resets.
873 //
874 // This collapses all elements in vectors into one shared
875 // element which will ensure that reset inference provides a
876 // uniform result for all elements.
877 //
878 // CAVEAT: This may infer reset networks that are too big, since
879 // unrelated resets in the same vector end up looking as if they
880 // were connected. However for the sake of type inference, this
881 // is indistinguishable from them having to share the same type
882 // (namely the vector element type).
883 FVectorType vectorType = op.getInput().getType();
884 traceResets(op.getType(), op.getResult(), 0,
885 vectorType.getElementType(), op.getInput(),
886 getFieldID(vectorType), op.getLoc());
887 })
888
889 .Case<RefSubOp>([&](RefSubOp op) {
890 // Trace through ref.sub.
891 auto aggType = op.getInput().getType().getType();
892 uint64_t fieldID = TypeSwitch<FIRRTLBaseType, uint64_t>(aggType)
893 .Case<FVectorType>([](auto type) {
894 return getFieldID(type);
895 })
896 .Case<BundleType>([&](auto type) {
897 return getFieldID(type, op.getIndex());
898 });
899 traceResets(op.getType(), op.getResult(), 0,
900 op.getResult().getType(), op.getInput(), fieldID,
901 op.getLoc());
902 });
903 }
904}
905
906/// Trace reset signals through an instance. This essentially associates the
907/// instance's port values with the target module's port values.
908void InferResetsPass::traceResets(InstanceOp inst) {
909 // Lookup the referenced module. Nothing to do if its an extmodule.
910 auto module = inst.getReferencedModule<FModuleOp>(*instanceGraph);
911 if (!module)
912 return;
913 LLVM_DEBUG(llvm::dbgs() << "Visiting instance " << inst.getName() << "\n");
914
915 // Establish a connection between the instance ports and module ports.
916 for (const auto &it : llvm::enumerate(inst.getResults())) {
917 auto dir = module.getPortDirection(it.index());
918 Value dstPort = module.getArgument(it.index());
919 Value srcPort = it.value();
920 if (dir == Direction::Out)
921 std::swap(dstPort, srcPort);
922 traceResets(dstPort, srcPort, it.value().getLoc());
923 }
924}
925
926/// Analyze a connect of one (possibly aggregate) value to another.
927/// Each drive involving a `ResetType` is recorded.
928void InferResetsPass::traceResets(Value dst, Value src, Location loc) {
929 // Analyze the actual connection.
930 traceResets(dst.getType(), dst, 0, src.getType(), src, 0, loc);
931}
932
933/// Analyze a connect of one (possibly aggregate) value to another.
934/// Each drive involving a `ResetType` is recorded.
935void InferResetsPass::traceResets(Type dstType, Value dst, unsigned dstID,
936 Type srcType, Value src, unsigned srcID,
937 Location loc) {
938 if (auto dstBundle = type_dyn_cast<BundleType>(dstType)) {
939 auto srcBundle = type_cast<BundleType>(srcType);
940 for (unsigned dstIdx = 0, e = dstBundle.getNumElements(); dstIdx < e;
941 ++dstIdx) {
942 auto dstField = dstBundle.getElements()[dstIdx].name;
943 auto srcIdx = srcBundle.getElementIndex(dstField);
944 if (!srcIdx)
945 continue;
946 auto &dstElt = dstBundle.getElements()[dstIdx];
947 auto &srcElt = srcBundle.getElements()[*srcIdx];
948 if (dstElt.isFlip) {
949 traceResets(srcElt.type, src, srcID + getFieldID(srcBundle, *srcIdx),
950 dstElt.type, dst, dstID + getFieldID(dstBundle, dstIdx),
951 loc);
952 } else {
953 traceResets(dstElt.type, dst, dstID + getFieldID(dstBundle, dstIdx),
954 srcElt.type, src, srcID + getFieldID(srcBundle, *srcIdx),
955 loc);
956 }
957 }
958 return;
959 }
960
961 if (auto dstVector = type_dyn_cast<FVectorType>(dstType)) {
962 auto srcVector = type_cast<FVectorType>(srcType);
963 auto srcElType = srcVector.getElementType();
964 auto dstElType = dstVector.getElementType();
965 // Collapse all elements into one shared element. See comment in traceResets
966 // above for some context. Note that we are directly passing on the field ID
967 // of the vector itself as a stand-in for its element type. This is not
968 // really what `FieldRef` is designed to do, but tends to work since all the
969 // places that need to reason about the resulting weird IDs are inside this
970 // file. Normally you would pick a specific index from the vector, which
971 // would also move the field ID forward by some amount. However, we can't
972 // distinguish individual elements for the sake of type inference *and* we
973 // have to support zero-length vectors for which the only available ID is
974 // the vector itself. Therefore we always just pick the vector itself for
975 // the field ID and make sure in `updateType` that we handle vectors
976 // accordingly.
977 traceResets(dstElType, dst, dstID + getFieldID(dstVector), srcElType, src,
978 srcID + getFieldID(srcVector), loc);
979 return;
980 }
981
982 // Handle connecting ref's. Other uses trace using base type.
983 if (auto dstRef = type_dyn_cast<RefType>(dstType)) {
984 auto srcRef = type_cast<RefType>(srcType);
985 return traceResets(dstRef.getType(), dst, dstID, srcRef.getType(), src,
986 srcID, loc);
987 }
988
989 // Handle reset connections.
990 auto dstBase = type_dyn_cast<FIRRTLBaseType>(dstType);
991 auto srcBase = type_dyn_cast<FIRRTLBaseType>(srcType);
992 if (!dstBase || !srcBase)
993 return;
994 if (!type_isa<ResetType>(dstBase) && !type_isa<ResetType>(srcBase))
995 return;
996
997 FieldRef dstField(dst, dstID);
998 FieldRef srcField(src, srcID);
999 LLVM_DEBUG(llvm::dbgs() << "Visiting driver '" << dstField << "' = '"
1000 << srcField << "' (" << dstType << " = " << srcType
1001 << ")\n");
1002
1003 // Determine the leaders for the dst and src reset networks before we make
1004 // the connection. This will allow us to later detect if dst got merged
1005 // into src, or src into dst.
1006 ResetSignal dstLeader =
1007 *resetClasses.findLeader(resetClasses.insert({dstField, dstBase}));
1008 ResetSignal srcLeader =
1009 *resetClasses.findLeader(resetClasses.insert({srcField, srcBase}));
1010
1011 // Unify the two reset networks.
1012 ResetSignal unionLeader = *resetClasses.unionSets(dstLeader, srcLeader);
1013 assert(unionLeader == dstLeader || unionLeader == srcLeader);
1014
1015 // If dst got merged into src, append dst's drives to src's, or vice
1016 // versa. Also, remove dst's or src's entry in resetDrives, because they
1017 // will never come up as a leader again.
1018 if (dstLeader != srcLeader) {
1019 auto &unionDrives = resetDrives[unionLeader]; // needed before finds
1020 auto mergedDrivesIt =
1021 resetDrives.find(unionLeader == dstLeader ? srcLeader : dstLeader);
1022 if (mergedDrivesIt != resetDrives.end()) {
1023 unionDrives.append(mergedDrivesIt->second);
1024 resetDrives.erase(mergedDrivesIt);
1025 }
1026 }
1027
1028 // Keep note of this drive so we can point the user at the right location
1029 // in case something goes wrong.
1030 resetDrives[unionLeader].push_back(
1031 {{dstField, dstBase}, {srcField, srcBase}, loc});
1032}
1033
1034//===----------------------------------------------------------------------===//
1035// Reset Inference
1036//===----------------------------------------------------------------------===//
1037
1038LogicalResult InferResetsPass::inferAndUpdateResets() {
1039 LLVM_DEBUG({
1040 llvm::dbgs() << "\n";
1041 debugHeader("Infer reset types") << "\n\n";
1042 });
1043 for (const auto &it : resetClasses) {
1044 if (!it->isLeader())
1045 continue;
1046 ResetNetwork net = resetClasses.members(*it);
1047
1048 // Infer whether this should be a sync or async reset.
1049 auto kind = inferReset(net);
1050 if (failed(kind))
1051 return failure();
1052
1053 // Update the types in the IR to match the inferred kind.
1054 if (failed(updateReset(net, *kind)))
1055 return failure();
1056 }
1057 return success();
1058}
1059
1060FailureOr<ResetKind> InferResetsPass::inferReset(ResetNetwork net) {
1061 LLVM_DEBUG(llvm::dbgs() << "Inferring reset network with "
1062 << std::distance(net.begin(), net.end())
1063 << " nodes\n");
1064
1065 // Go through the nodes and track the involved types.
1066 unsigned asyncDrives = 0;
1067 unsigned syncDrives = 0;
1068 unsigned invalidDrives = 0;
1069 for (ResetSignal signal : net) {
1070 // Keep track of whether this signal contributes a vote for async or sync.
1071 if (type_isa<AsyncResetType>(signal.type))
1072 ++asyncDrives;
1073 else if (type_isa<UIntType>(signal.type))
1074 ++syncDrives;
1075 else if (isUselessVec(signal.field) ||
1076 isa_and_nonnull<InvalidValueOp>(
1077 signal.field.getValue().getDefiningOp()))
1078 ++invalidDrives;
1079 }
1080 LLVM_DEBUG(llvm::dbgs() << "- Found " << asyncDrives << " async, "
1081 << syncDrives << " sync, " << invalidDrives
1082 << " invalid drives\n");
1083
1084 // Handle the case where we have no votes for either kind.
1085 if (asyncDrives == 0 && syncDrives == 0 && invalidDrives == 0) {
1086 ResetSignal root = guessRoot(net);
1087 auto diag = mlir::emitError(root.field.getValue().getLoc())
1088 << "reset network never driven with concrete type";
1089 for (ResetSignal signal : net)
1090 diag.attachNote(signal.field.getLoc()) << "here: ";
1091 return failure();
1092 }
1093
1094 // Handle the case where we have votes for both kinds.
1095 if (asyncDrives > 0 && syncDrives > 0) {
1096 ResetSignal root = guessRoot(net);
1097 bool majorityAsync = asyncDrives >= syncDrives;
1098 auto diag = mlir::emitError(root.field.getValue().getLoc())
1099 << "reset network";
1100 SmallString<32> fieldName;
1101 if (getFieldName(root.field, fieldName))
1102 diag << " \"" << fieldName << "\"";
1103 diag << " simultaneously connected to async and sync resets";
1104 diag.attachNote(root.field.getValue().getLoc())
1105 << "majority of connections to this reset are "
1106 << (majorityAsync ? "async" : "sync");
1107 for (auto &drive : getResetDrives(net)) {
1108 if ((type_isa<AsyncResetType>(drive.dst.type) && !majorityAsync) ||
1109 (type_isa<AsyncResetType>(drive.src.type) && !majorityAsync) ||
1110 (type_isa<UIntType>(drive.dst.type) && majorityAsync) ||
1111 (type_isa<UIntType>(drive.src.type) && majorityAsync))
1112 diag.attachNote(drive.loc)
1113 << (type_isa<AsyncResetType>(drive.src.type) ? "async" : "sync")
1114 << " drive here:";
1115 }
1116 return failure();
1117 }
1118
1119 // At this point we know that the type of the reset is unambiguous. If there
1120 // are any votes for async, we make the reset async. Otherwise we make it
1121 // sync.
1122 auto kind = (asyncDrives ? ResetKind::Async : ResetKind::Sync);
1123 LLVM_DEBUG(llvm::dbgs() << "- Inferred as " << kind << "\n");
1124 return kind;
1125}
1126
1127//===----------------------------------------------------------------------===//
1128// Reset Updating
1129//===----------------------------------------------------------------------===//
1130
1131LogicalResult InferResetsPass::updateReset(ResetNetwork net, ResetKind kind) {
1132 LLVM_DEBUG(llvm::dbgs() << "Updating reset network with "
1133 << std::distance(net.begin(), net.end())
1134 << " nodes to " << kind << "\n");
1135
1136 // Determine the final type the reset should have.
1137 FIRRTLBaseType resetType;
1138 if (kind == ResetKind::Async)
1139 resetType = AsyncResetType::get(&getContext());
1140 else
1141 resetType = UIntType::get(&getContext(), 1);
1142
1143 // Update all those values in the network that cannot be inferred from
1144 // operands. If we change the type of a module port (i.e. BlockArgument), add
1145 // the module to a module worklist since we need to update its function type.
1146 SmallSetVector<Operation *, 16> worklist;
1147 SmallDenseSet<Operation *> moduleWorklist;
1148 SmallDenseSet<std::pair<Operation *, Operation *>> extmoduleWorklist;
1149 for (auto signal : net) {
1150 Value value = signal.field.getValue();
1151 if (!isa<BlockArgument>(value) &&
1152 !isa_and_nonnull<WireOp, RegOp, RegResetOp, InstanceOp, InvalidValueOp,
1153 ConstCastOp, RefCastOp, UninferredResetCastOp,
1154 RWProbeOp>(value.getDefiningOp()))
1155 continue;
1156 if (updateReset(signal.field, resetType)) {
1157 for (auto user : value.getUsers())
1158 worklist.insert(user);
1159 if (auto blockArg = dyn_cast<BlockArgument>(value))
1160 moduleWorklist.insert(blockArg.getOwner()->getParentOp());
1161 else if (auto instOp = value.getDefiningOp<InstanceOp>()) {
1162 if (auto extmodule =
1163 instOp.getReferencedModule<FExtModuleOp>(*instanceGraph))
1164 extmoduleWorklist.insert({extmodule, instOp});
1165 } else if (auto uncast = value.getDefiningOp<UninferredResetCastOp>()) {
1166 uncast.replaceAllUsesWith(uncast.getInput());
1167 uncast.erase();
1168 }
1169 }
1170 }
1171
1172 // Process the worklist of operations that have their type changed, pushing
1173 // types down the SSA dataflow graph. This is important because we change the
1174 // reset types in aggregates, and then need all the subindex, subfield, and
1175 // subaccess operations to be updated as appropriate.
1176 while (!worklist.empty()) {
1177 auto *wop = worklist.pop_back_val();
1178 SmallVector<Type, 2> types;
1179 if (auto op = dyn_cast<InferTypeOpInterface>(wop)) {
1180 // Determine the new result types.
1181 SmallVector<Type, 2> types;
1182 if (failed(op.inferReturnTypes(op->getContext(), op->getLoc(),
1183 op->getOperands(), op->getAttrDictionary(),
1184 op->getPropertiesStorage(),
1185 op->getRegions(), types)))
1186 return failure();
1187
1188 // Update the results and add the changed ones to the
1189 // worklist.
1190 for (auto it : llvm::zip(op->getResults(), types)) {
1191 auto newType = std::get<1>(it);
1192 if (std::get<0>(it).getType() == newType)
1193 continue;
1194 std::get<0>(it).setType(newType);
1195 for (auto *user : std::get<0>(it).getUsers())
1196 worklist.insert(user);
1197 }
1198 LLVM_DEBUG(llvm::dbgs() << "- Inferred " << *op << "\n");
1199 } else if (auto uop = dyn_cast<UninferredResetCastOp>(wop)) {
1200 for (auto *user : uop.getResult().getUsers())
1201 worklist.insert(user);
1202 uop.replaceAllUsesWith(uop.getInput());
1203 LLVM_DEBUG(llvm::dbgs() << "- Inferred " << uop << "\n");
1204 uop.erase();
1205 }
1206 }
1207
1208 // Update module types based on the type of the block arguments.
1209 for (auto *op : moduleWorklist) {
1210 auto module = dyn_cast<FModuleOp>(op);
1211 if (!module)
1212 continue;
1213
1214 SmallVector<Attribute> argTypes;
1215 argTypes.reserve(module.getNumPorts());
1216 for (auto arg : module.getArguments())
1217 argTypes.push_back(TypeAttr::get(arg.getType()));
1218
1219 module.setPortTypesAttr(ArrayAttr::get(op->getContext(), argTypes));
1220 LLVM_DEBUG(llvm::dbgs()
1221 << "- Updated type of module '" << module.getName() << "'\n");
1222 }
1223
1224 // Update extmodule types based on their instantiation.
1225 for (auto pair : extmoduleWorklist) {
1226 auto module = cast<FExtModuleOp>(pair.first);
1227 auto instOp = cast<InstanceOp>(pair.second);
1228
1229 SmallVector<Attribute> types;
1230 for (auto type : instOp.getResultTypes())
1231 types.push_back(TypeAttr::get(type));
1232
1233 module.setPortTypesAttr(ArrayAttr::get(module->getContext(), types));
1234 LLVM_DEBUG(llvm::dbgs()
1235 << "- Updated type of extmodule '" << module.getName() << "'\n");
1236 }
1237
1238 return success();
1239}
1240
1241/// Update the type of a single field within a type.
1242static FIRRTLBaseType updateType(FIRRTLBaseType oldType, unsigned fieldID,
1243 FIRRTLBaseType fieldType) {
1244 // If this is a ground type, simply replace it, preserving constness.
1245 if (oldType.isGround()) {
1246 assert(fieldID == 0);
1247 return fieldType.getConstType(oldType.isConst());
1248 }
1249
1250 // If this is a bundle type, update the corresponding field.
1251 if (auto bundleType = type_dyn_cast<BundleType>(oldType)) {
1252 unsigned index = getIndexForFieldID(bundleType, fieldID);
1253 SmallVector<BundleType::BundleElement> fields(bundleType.begin(),
1254 bundleType.end());
1255 fields[index].type = updateType(
1256 fields[index].type, fieldID - getFieldID(bundleType, index), fieldType);
1257 return BundleType::get(oldType.getContext(), fields, bundleType.isConst());
1258 }
1259
1260 // If this is a vector type, update the element type.
1261 if (auto vectorType = type_dyn_cast<FVectorType>(oldType)) {
1262 auto newType = updateType(vectorType.getElementType(),
1263 fieldID - getFieldID(vectorType), fieldType);
1264 return FVectorType::get(newType, vectorType.getNumElements(),
1265 vectorType.isConst());
1266 }
1267
1268 llvm_unreachable("unknown aggregate type");
1269 return oldType;
1270}
1271
1272/// Update the reset type of a specific field.
1273bool InferResetsPass::updateReset(FieldRef field, FIRRTLBaseType resetType) {
1274 // Compute the updated type.
1275 auto oldType = type_cast<FIRRTLType>(field.getValue().getType());
1276 FIRRTLType newType = mapBaseType(oldType, [&](auto base) {
1277 return updateType(base, field.getFieldID(), resetType);
1278 });
1279
1280 // Update the type if necessary.
1281 if (oldType == newType)
1282 return false;
1283 LLVM_DEBUG(llvm::dbgs() << "- Updating '" << field << "' from " << oldType
1284 << " to " << newType << "\n");
1285 field.getValue().setType(newType);
1286 return true;
1287}
1288
1289//===----------------------------------------------------------------------===//
1290// Reset Annotations
1291//===----------------------------------------------------------------------===//
1292
1293LogicalResult InferResetsPass::collectAnnos(CircuitOp circuit) {
1294 LLVM_DEBUG({
1295 llvm::dbgs() << "\n";
1296 debugHeader("Gather reset annotations") << "\n\n";
1297 });
1298 SmallVector<std::pair<FModuleOp, std::optional<Value>>> results;
1299 for (auto module : circuit.getOps<FModuleOp>())
1300 results.push_back({module, {}});
1301 // Collect annotations parallelly.
1302 if (failed(mlir::failableParallelForEach(
1303 circuit.getContext(), results, [&](auto &moduleAndResult) {
1304 auto result = collectAnnos(moduleAndResult.first);
1305 if (failed(result))
1306 return failure();
1307 moduleAndResult.second = *result;
1308 return success();
1309 })))
1310 return failure();
1311
1312 for (auto [module, reset] : results)
1313 if (reset.has_value())
1314 annotatedResets.insert({module, *reset});
1315 return success();
1316}
1317
1318FailureOr<std::optional<Value>>
1319InferResetsPass::collectAnnos(FModuleOp module) {
1320 bool anyFailed = false;
1321 SmallSetVector<std::pair<Annotation, Location>, 4> conflictingAnnos;
1322
1323 // Consume a possible "ignore" annotation on the module itself, which
1324 // explicitly assigns it no reset domain.
1325 bool ignore = false;
1328 ignore = true;
1329 conflictingAnnos.insert({anno, module.getLoc()});
1330 return true;
1331 }
1332 if (anno.isClass(fullResetAnnoClass)) {
1333 anyFailed = true;
1334 module.emitError("''FullResetAnnotation' cannot target module; must "
1335 "target port or wire/node instead");
1336 return true;
1337 }
1338 return false;
1339 });
1340 if (anyFailed)
1341 return failure();
1342
1343 // Consume any reset annotations on module ports.
1344 Value reset;
1345 // Helper for checking annotations and determining the reset
1346 auto checkAnnotations = [&](Annotation anno, Value arg) {
1347 if (anno.isClass(fullResetAnnoClass)) {
1348 ResetKind expectedResetKind;
1349 if (auto rt = anno.getMember<StringAttr>("resetType")) {
1350 if (rt == "sync") {
1351 expectedResetKind = ResetKind::Sync;
1352 } else if (rt == "async") {
1353 expectedResetKind = ResetKind::Async;
1354 } else {
1355 mlir::emitError(arg.getLoc(),
1356 "'FullResetAnnotation' requires resetType == 'sync' "
1357 "| 'async', but got resetType == ")
1358 << rt;
1359 anyFailed = true;
1360 return true;
1361 }
1362 } else {
1363 mlir::emitError(arg.getLoc(),
1364 "'FullResetAnnotation' requires resetType == "
1365 "'sync' | 'async', but got no resetType");
1366 anyFailed = true;
1367 return true;
1368 }
1369 // Check that the type is well-formed
1370 bool isAsync = expectedResetKind == ResetKind::Async;
1371 bool validUint = false;
1372 if (auto uintT = dyn_cast<UIntType>(arg.getType()))
1373 validUint = uintT.getWidth() == 1;
1374 if ((isAsync && !isa<AsyncResetType>(arg.getType())) ||
1375 (!isAsync && !validUint)) {
1376 auto kind = resetKindToStringRef(expectedResetKind);
1377 mlir::emitError(arg.getLoc(),
1378 "'FullResetAnnotation' with resetType == '")
1379 << kind << "' must target " << kind << " reset, but targets "
1380 << arg.getType();
1381 anyFailed = true;
1382 return true;
1383 }
1384
1385 reset = arg;
1386 conflictingAnnos.insert({anno, reset.getLoc()});
1387
1388 return false;
1389 }
1390 if (anno.isClass(excludeFromFullResetAnnoClass)) {
1391 anyFailed = true;
1392 mlir::emitError(arg.getLoc(),
1393 "'ExcludeFromFullResetAnnotation' cannot "
1394 "target port/wire/node; must target module instead");
1395 return true;
1396 }
1397 return false;
1398 };
1399
1401 [&](unsigned argNum, Annotation anno) {
1402 Value arg = module.getArgument(argNum);
1403 return checkAnnotations(anno, arg);
1404 });
1405 if (anyFailed)
1406 return failure();
1407
1408 // Consume any reset annotations on wires in the module body.
1409 module.getBody().walk([&](Operation *op) {
1410 // Reset annotations must target wire/node ops.
1411 if (!isa<WireOp, NodeOp>(op)) {
1412 if (AnnotationSet::hasAnnotation(op, fullResetAnnoClass,
1413 excludeFromFullResetAnnoClass)) {
1414 anyFailed = true;
1415 op->emitError(
1416 "reset annotations must target module, port, or wire/node");
1417 }
1418 return;
1419 }
1420
1421 // At this point we know that we have a WireOp/NodeOp. Process the reset
1422 // annotations.
1424 auto arg = op->getResult(0);
1425 return checkAnnotations(anno, arg);
1426 });
1427 });
1428 if (anyFailed)
1429 return failure();
1430
1431 // If we have found no annotations, there is nothing to do. We just leave
1432 // this module unannotated, which will cause it to inherit a reset domain
1433 // from its instantiation sites.
1434 if (!ignore && !reset) {
1435 LLVM_DEBUG(llvm::dbgs()
1436 << "No reset annotation for " << module.getName() << "\n");
1437 return std::optional<Value>();
1438 }
1439
1440 // If we have found multiple annotations, emit an error and abort.
1441 if (conflictingAnnos.size() > 1) {
1442 auto diag = module.emitError("multiple reset annotations on module '")
1443 << module.getName() << "'";
1444 for (auto &annoAndLoc : conflictingAnnos)
1445 diag.attachNote(annoAndLoc.second)
1446 << "conflicting " << annoAndLoc.first.getClassAttr() << ":";
1447 return failure();
1448 }
1449
1450 // Dump some information in debug builds.
1451 LLVM_DEBUG({
1452 llvm::dbgs() << "Annotated reset for " << module.getName() << ": ";
1453 if (ignore)
1454 llvm::dbgs() << "no domain\n";
1455 else if (auto arg = dyn_cast<BlockArgument>(reset))
1456 llvm::dbgs() << "port " << module.getPortName(arg.getArgNumber()) << "\n";
1457 else
1458 llvm::dbgs() << "wire "
1459 << reset.getDefiningOp()->getAttrOfType<StringAttr>("name")
1460 << "\n";
1461 });
1462
1463 // Store the annotated reset for this module.
1464 assert(ignore || reset);
1465 return std::optional<Value>(reset);
1466}
1467
1468//===----------------------------------------------------------------------===//
1469// Domain Construction
1470//===----------------------------------------------------------------------===//
1471
1472/// Gather the reset domains present in a circuit. This traverses the instance
1473/// hierarchy of the design, making instances either live in a new reset
1474/// domain if so annotated, or inherit their parent's domain. This can go
1475/// wrong in some cases, mainly when a module is instantiated multiple times
1476/// within different reset domains.
1477LogicalResult InferResetsPass::buildDomains(CircuitOp circuit) {
1478 LLVM_DEBUG({
1479 llvm::dbgs() << "\n";
1480 debugHeader("Build full reset domains") << "\n\n";
1481 });
1482
1483 // Gather the domains.
1484 auto &instGraph = getAnalysis<InstanceGraph>();
1485 auto module = dyn_cast<FModuleOp>(*instGraph.getTopLevelNode()->getModule());
1486 if (!module) {
1487 LLVM_DEBUG(llvm::dbgs()
1488 << "Skipping circuit because main module is no `firrtl.module`");
1489 return success();
1490 }
1491 buildDomains(module, InstancePath{}, Value{}, instGraph);
1492
1493 // Report any domain conflicts among the modules.
1494 bool anyFailed = false;
1495 for (auto &it : domains) {
1496 auto module = cast<FModuleOp>(it.first);
1497 auto &domainConflicts = it.second;
1498 if (domainConflicts.size() <= 1)
1499 continue;
1500
1501 anyFailed = true;
1502 SmallDenseSet<Value> printedDomainResets;
1503 auto diag = module.emitError("module '")
1504 << module.getName()
1505 << "' instantiated in different reset domains";
1506 for (auto &it : domainConflicts) {
1507 ResetDomain &domain = it.first;
1508 const auto &path = it.second;
1509 auto inst = path.leaf();
1510 auto loc = path.empty() ? module.getLoc() : inst.getLoc();
1511 auto &note = diag.attachNote(loc);
1512
1513 // Describe the instance itself.
1514 if (path.empty())
1515 note << "root instance";
1516 else {
1517 note << "instance '";
1518 llvm::interleave(
1519 path,
1520 [&](InstanceOpInterface inst) { note << inst.getInstanceName(); },
1521 [&]() { note << "/"; });
1522 note << "'";
1523 }
1524
1525 // Describe the reset domain the instance is in.
1526 note << " is in";
1527 if (domain.rootReset) {
1528 auto nameAndModule = getResetNameAndModule(domain.rootReset);
1529 note << " reset domain rooted at '" << nameAndModule.first.getValue()
1530 << "' of module '" << nameAndModule.second.getName() << "'";
1531
1532 // Show where the domain reset is declared (once per reset).
1533 if (printedDomainResets.insert(domain.rootReset).second) {
1534 diag.attachNote(domain.rootReset.getLoc())
1535 << "reset domain '" << nameAndModule.first.getValue()
1536 << "' of module '" << nameAndModule.second.getName()
1537 << "' declared here:";
1538 }
1539 } else
1540 note << " no reset domain";
1541 }
1542 }
1543 return failure(anyFailed);
1544}
1545
1546void InferResetsPass::buildDomains(FModuleOp module,
1547 const InstancePath &instPath,
1548 Value parentReset, InstanceGraph &instGraph,
1549 unsigned indent) {
1550 LLVM_DEBUG({
1551 llvm::dbgs().indent(indent * 2) << "Visiting ";
1552 if (instPath.empty())
1553 llvm::dbgs() << "$root";
1554 else
1555 llvm::dbgs() << instPath.leaf().getInstanceName();
1556 llvm::dbgs() << " (" << module.getName() << ")\n";
1557 });
1558
1559 // Assemble the domain for this module.
1560 ResetDomain domain;
1561 auto it = annotatedResets.find(module);
1562 if (it != annotatedResets.end()) {
1563 // If there is an actual reset, use it for our domain. Otherwise, our
1564 // module is explicitly marked to have no domain.
1565 if (auto localReset = it->second)
1566 domain = ResetDomain(localReset);
1567 domain.isTop = true;
1568 } else if (parentReset) {
1569 // Otherwise, we default to using the reset domain of our parent.
1570 domain = ResetDomain(parentReset);
1571 }
1572
1573 // Associate the domain with this module. If the module already has an
1574 // associated domain, it must be identical. Otherwise we'll have to report
1575 // the conflicting domains to the user.
1576 auto &entries = domains[module];
1577 if (llvm::all_of(entries,
1578 [&](const auto &entry) { return entry.first != domain; }))
1579 entries.push_back({domain, instPath});
1580
1581 // Traverse the child instances.
1582 for (auto *record : *instGraph[module]) {
1583 auto submodule = dyn_cast<FModuleOp>(*record->getTarget()->getModule());
1584 if (!submodule)
1585 continue;
1586 auto childPath =
1587 instancePathCache->appendInstance(instPath, record->getInstance());
1588 buildDomains(submodule, childPath, domain.rootReset, instGraph, indent + 1);
1589 }
1590}
1591
1592/// Determine how the reset for each module shall be implemented.
1593LogicalResult InferResetsPass::determineImpl() {
1594 auto anyFailed = false;
1595 LLVM_DEBUG({
1596 llvm::dbgs() << "\n";
1597 debugHeader("Determine implementation") << "\n\n";
1598 });
1599 for (auto &it : domains) {
1600 auto module = cast<FModuleOp>(it.first);
1601 auto &domain = it.second.back().first;
1602 if (failed(determineImpl(module, domain)))
1603 anyFailed = true;
1604 }
1605 return failure(anyFailed);
1606}
1607
1608/// Determine how the reset for a module shall be implemented. This function
1609/// fills in the `localReset` and `existingPort` fields of the given reset
1610/// domain.
1611///
1612/// Generally it does the following:
1613/// - If the domain has explicitly no reset ("ignore"), leaves everything
1614/// empty.
1615/// - If the domain is the place where the reset is defined ("top"), fills in
1616/// the existing port/wire/node as reset.
1617/// - If the module already has a port with the reset's name:
1618/// - If the port has the same name and type as the reset domain, reuses that
1619/// port.
1620/// - Otherwise errors out.
1621/// - Otherwise indicates that a port with the reset's name should be created.
1622///
1623LogicalResult InferResetsPass::determineImpl(FModuleOp module,
1624 ResetDomain &domain) {
1625 // Nothing to do if the module needs no reset.
1626 if (!domain)
1627 return success();
1628 LLVM_DEBUG(llvm::dbgs() << "Planning reset for " << module.getName() << "\n");
1629
1630 // If this is the root of a reset domain, we don't need to add any ports
1631 // and can just simply reuse the existing values.
1632 if (domain.isTop) {
1633 LLVM_DEBUG(llvm::dbgs()
1634 << "- Rooting at local value " << domain.resetName << "\n");
1635 domain.localReset = domain.rootReset;
1636 if (auto blockArg = dyn_cast<BlockArgument>(domain.rootReset))
1637 domain.existingPort = blockArg.getArgNumber();
1638 return success();
1639 }
1640
1641 // Otherwise, check if a port with this name and type already exists and
1642 // reuse that where possible.
1643 auto neededName = domain.resetName;
1644 auto neededType = domain.resetType;
1645 LLVM_DEBUG(llvm::dbgs() << "- Looking for existing port " << neededName
1646 << "\n");
1647 auto portNames = module.getPortNames();
1648 auto *portIt = llvm::find(portNames, neededName);
1649
1650 // If this port does not yet exist, record that we need to create it.
1651 if (portIt == portNames.end()) {
1652 LLVM_DEBUG(llvm::dbgs() << "- Creating new port " << neededName << "\n");
1653 domain.resetName = neededName;
1654 return success();
1655 }
1656
1657 LLVM_DEBUG(llvm::dbgs() << "- Reusing existing port " << neededName << "\n");
1658
1659 // If this port has the wrong type, then error out.
1660 auto portNo = std::distance(portNames.begin(), portIt);
1661 auto portType = module.getPortType(portNo);
1662 if (portType != neededType) {
1663 auto diag = emitError(module.getPortLocation(portNo), "module '")
1664 << module.getName() << "' is in reset domain requiring port '"
1665 << domain.resetName.getValue() << "' to have type "
1666 << domain.resetType << ", but has type " << portType;
1667 diag.attachNote(domain.rootReset.getLoc()) << "reset domain rooted here";
1668 return failure();
1669 }
1670
1671 // We have a pre-existing port which we should use.
1672 domain.existingPort = portNo;
1673 domain.localReset = module.getArgument(portNo);
1674 return success();
1675}
1676
1677//===----------------------------------------------------------------------===//
1678// Full Reset Implementation
1679//===----------------------------------------------------------------------===//
1680
1681/// Implement the annotated resets gathered in the pass' `domains` map.
1682LogicalResult InferResetsPass::implementFullReset() {
1683 LLVM_DEBUG({
1684 llvm::dbgs() << "\n";
1685 debugHeader("Implement full resets") << "\n\n";
1686 });
1687 for (auto &it : domains)
1688 if (failed(implementFullReset(cast<FModuleOp>(it.first),
1689 it.second.back().first)))
1690 return failure();
1691 return success();
1692}
1693
1694/// Implement the async resets for a specific module.
1695///
1696/// This will add ports to the module as appropriate, update the register ops
1697/// in the module, and update any instantiated submodules with their
1698/// corresponding reset implementation details.
1699LogicalResult InferResetsPass::implementFullReset(FModuleOp module,
1700 ResetDomain &domain) {
1701 LLVM_DEBUG(llvm::dbgs() << "Implementing full reset for " << module.getName()
1702 << "\n");
1703
1704 // Nothing to do if the module was marked explicitly with no reset domain.
1705 if (!domain) {
1706 LLVM_DEBUG(llvm::dbgs()
1707 << "- Skipping because module explicitly has no domain\n");
1708 return success();
1709 }
1710
1711 // Add an annotation indicating that this module belongs to a reset domain.
1712 auto *context = module.getContext();
1713 AnnotationSet annotations(module);
1714 annotations.addAnnotations(DictionaryAttr::get(
1715 context, NamedAttribute(StringAttr::get(context, "class"),
1716 StringAttr::get(context, fullResetAnnoClass))));
1717 annotations.applyToOperation(module);
1718
1719 // If needed, add a reset port to the module.
1720 auto actualReset = domain.localReset;
1721 if (!domain.localReset) {
1722 PortInfo portInfo{domain.resetName,
1723 domain.resetType,
1724 Direction::In,
1725 {},
1726 domain.rootReset.getLoc()};
1727 module.insertPorts({{0, portInfo}});
1728 actualReset = module.getArgument(0);
1729 LLVM_DEBUG(llvm::dbgs() << "- Inserted port " << domain.resetName << "\n");
1730 }
1731
1732 LLVM_DEBUG({
1733 llvm::dbgs() << "- Using ";
1734 if (auto blockArg = dyn_cast<BlockArgument>(actualReset))
1735 llvm::dbgs() << "port #" << blockArg.getArgNumber() << " ";
1736 else
1737 llvm::dbgs() << "wire/node ";
1738 llvm::dbgs() << getResetName(actualReset) << "\n";
1739 });
1740
1741 // Gather a list of operations in the module that need to be updated with
1742 // the new reset.
1743 SmallVector<Operation *> opsToUpdate;
1744 module.walk([&](Operation *op) {
1745 if (isa<InstanceOp, RegOp, RegResetOp>(op))
1746 opsToUpdate.push_back(op);
1747 });
1748
1749 // If the reset is a local wire or node, move it upwards such that it
1750 // dominates all the operations that it will need to attach to. In the case
1751 // of a node this might not be easily possible, so we just spill into a wire
1752 // in that case.
1753 if (!isa<BlockArgument>(actualReset)) {
1754 mlir::DominanceInfo dom(module);
1755 // The first op in `opsToUpdate` is the top-most op in the module, since
1756 // the ops and blocks are traversed in a depth-first, top-to-bottom order
1757 // in `walk`. So we can simply check if the local reset declaration is
1758 // before the first op to find out if we need to move anything.
1759 auto *resetOp = actualReset.getDefiningOp();
1760 if (!opsToUpdate.empty() && !dom.dominates(resetOp, opsToUpdate[0])) {
1761 LLVM_DEBUG(llvm::dbgs()
1762 << "- Reset doesn't dominate all uses, needs to be moved\n");
1763
1764 // If the node can't be moved because its input doesn't dominate the
1765 // target location, convert it to a wire.
1766 auto nodeOp = dyn_cast<NodeOp>(resetOp);
1767 if (nodeOp && !dom.dominates(nodeOp.getInput(), opsToUpdate[0])) {
1768 LLVM_DEBUG(llvm::dbgs()
1769 << "- Promoting node to wire for move: " << nodeOp << "\n");
1770 auto builder = ImplicitLocOpBuilder::atBlockBegin(nodeOp.getLoc(),
1771 nodeOp->getBlock());
1772 auto wireOp = WireOp::create(
1773 builder, nodeOp.getResult().getType(), nodeOp.getNameAttr(),
1774 nodeOp.getNameKindAttr(), nodeOp.getAnnotationsAttr(),
1775 nodeOp.getInnerSymAttr(), nodeOp.getForceableAttr());
1776 // Don't delete the node, since it might be in use in worklists.
1777 nodeOp->replaceAllUsesWith(wireOp);
1778 nodeOp->removeAttr(nodeOp.getInnerSymAttrName());
1779 nodeOp.setName("");
1780 // Leave forcable alone, since we cannot remove a result. It will be
1781 // cleaned up in canonicalization since it is dead. As will this node.
1782 nodeOp.setNameKind(NameKindEnum::DroppableName);
1783 nodeOp.setAnnotationsAttr(ArrayAttr::get(builder.getContext(), {}));
1784 builder.setInsertionPointAfter(nodeOp);
1785 emitConnect(builder, wireOp.getResult(), nodeOp.getResult());
1786 resetOp = wireOp;
1787 actualReset = wireOp.getResult();
1788 domain.localReset = wireOp.getResult();
1789 }
1790
1791 // Determine the block into which the reset declaration needs to be
1792 // moved.
1793 Block *targetBlock = dom.findNearestCommonDominator(
1794 resetOp->getBlock(), opsToUpdate[0]->getBlock());
1795 LLVM_DEBUG({
1796 if (targetBlock != resetOp->getBlock())
1797 llvm::dbgs() << "- Needs to be moved to different block\n";
1798 });
1799
1800 // At this point we have to figure out in front of which operation in
1801 // the target block the reset declaration has to be moved. The reset
1802 // declaration and the first op it needs to dominate may be buried
1803 // inside blocks of other operations (e.g. `WhenOp`), so we have to look
1804 // through their parent operations until we find the one that lies
1805 // within the target block.
1806 auto getParentInBlock = [](Operation *op, Block *block) {
1807 while (op && op->getBlock() != block)
1808 op = op->getParentOp();
1809 return op;
1810 };
1811 auto *resetOpInTarget = getParentInBlock(resetOp, targetBlock);
1812 auto *firstOpInTarget = getParentInBlock(opsToUpdate[0], targetBlock);
1813
1814 // Move the operation upwards. Since there are situations where the
1815 // reset declaration does not dominate the first use, but the `WhenOp`
1816 // it is nested within actually *does* come before that use, we have to
1817 // consider moving the reset declaration in front of its parent op.
1818 if (resetOpInTarget->isBeforeInBlock(firstOpInTarget))
1819 resetOp->moveBefore(resetOpInTarget);
1820 else
1821 resetOp->moveBefore(firstOpInTarget);
1822 }
1823 }
1824
1825 // Update the operations.
1826 for (auto *op : opsToUpdate)
1827 implementFullReset(op, module, actualReset);
1828
1829 return success();
1830}
1831
1832/// Modify an operation in a module to implement an full reset for that
1833/// module.
1834void InferResetsPass::implementFullReset(Operation *op, FModuleOp module,
1835 Value actualReset) {
1836 ImplicitLocOpBuilder builder(op->getLoc(), op);
1837
1838 // Handle instances.
1839 if (auto instOp = dyn_cast<InstanceOp>(op)) {
1840 // Lookup the reset domain of the instantiated module. If there is no
1841 // reset domain associated with that module, or the module is explicitly
1842 // marked as being in no domain, simply skip.
1843 auto refModule = instOp.getReferencedModule<FModuleOp>(*instanceGraph);
1844 if (!refModule)
1845 return;
1846 auto domainIt = domains.find(refModule);
1847 if (domainIt == domains.end())
1848 return;
1849 auto &domain = domainIt->second.back().first;
1850 if (!domain)
1851 return;
1852 LLVM_DEBUG(llvm::dbgs()
1853 << "- Update instance '" << instOp.getName() << "'\n");
1854
1855 // If needed, add a reset port to the instance.
1856 Value instReset;
1857 if (!domain.localReset) {
1858 LLVM_DEBUG(llvm::dbgs() << " - Adding new result as reset\n");
1859
1860 auto newInstOp = instOp.cloneAndInsertPorts(
1861 {{/*portIndex=*/0,
1862 {domain.resetName, type_cast<FIRRTLBaseType>(actualReset.getType()),
1863 Direction::In}}});
1864 instReset = newInstOp.getResult(0);
1865
1866 // Update the uses over to the new instance and drop the old instance.
1867 instOp.replaceAllUsesWith(newInstOp.getResults().drop_front());
1868 instanceGraph->replaceInstance(instOp, newInstOp);
1869 instOp->erase();
1870 instOp = newInstOp;
1871 } else if (domain.existingPort.has_value()) {
1872 auto idx = *domain.existingPort;
1873 instReset = instOp.getResult(idx);
1874 LLVM_DEBUG(llvm::dbgs() << " - Using result #" << idx << " as reset\n");
1875 }
1876
1877 // If there's no reset port on the instance to connect, we're done. This
1878 // can happen if the instantiated module has a reset domain, but that
1879 // domain is e.g. rooted at an internal wire.
1880 if (!instReset)
1881 return;
1882
1883 // Connect the instance's reset to the actual reset.
1884 assert(instReset && actualReset);
1885 builder.setInsertionPointAfter(instOp);
1886 emitConnect(builder, instReset, actualReset);
1887 return;
1888 }
1889
1890 // Handle reset-less registers.
1891 if (auto regOp = dyn_cast<RegOp>(op)) {
1892 LLVM_DEBUG(llvm::dbgs() << "- Adding full reset to " << regOp << "\n");
1893 auto zero = createZeroValue(builder, regOp.getResult().getType());
1894 auto newRegOp = RegResetOp::create(
1895 builder, regOp.getResult().getType(), regOp.getClockVal(), actualReset,
1896 zero, regOp.getNameAttr(), regOp.getNameKindAttr(),
1897 regOp.getAnnotations(), regOp.getInnerSymAttr(),
1898 regOp.getForceableAttr());
1899 regOp.getResult().replaceAllUsesWith(newRegOp.getResult());
1900 if (regOp.getForceable())
1901 regOp.getRef().replaceAllUsesWith(newRegOp.getRef());
1902 regOp->erase();
1903 return;
1904 }
1905
1906 // Handle registers with reset.
1907 if (auto regOp = dyn_cast<RegResetOp>(op)) {
1908 // If the register already has an async reset or if the type of the added
1909 // reset is sync, leave it alone.
1910 if (type_isa<AsyncResetType>(regOp.getResetSignal().getType()) ||
1911 type_isa<UIntType>(actualReset.getType())) {
1912 LLVM_DEBUG(llvm::dbgs() << "- Skipping (has reset) " << regOp << "\n");
1913 // The following performs the logic of `CheckResets` in the original
1914 // Scala source code.
1915 if (failed(regOp.verifyInvariants()))
1916 signalPassFailure();
1917 return;
1918 }
1919 LLVM_DEBUG(llvm::dbgs() << "- Updating reset of " << regOp << "\n");
1920
1921 auto reset = regOp.getResetSignal();
1922 auto value = regOp.getResetValue();
1923
1924 // If we arrive here, the register has a sync reset and the added reset is
1925 // async. In order to add an async reset, we have to move the sync reset
1926 // into a mux in front of the register.
1927 insertResetMux(builder, regOp.getResult(), reset, value);
1928 builder.setInsertionPointAfterValue(regOp.getResult());
1929 auto mux = MuxPrimOp::create(builder, reset, value, regOp.getResult());
1930 emitConnect(builder, regOp.getResult(), mux);
1931
1932 // Replace the existing reset with the async reset.
1933 builder.setInsertionPoint(regOp);
1934 auto zero = createZeroValue(builder, regOp.getResult().getType());
1935 regOp.getResetSignalMutable().assign(actualReset);
1936 regOp.getResetValueMutable().assign(zero);
1937 }
1938}
1939
1940LogicalResult InferResetsPass::verifyNoAbstractReset() {
1941 bool hasAbstractResetPorts = false;
1942 for (FModuleLike module :
1943 getOperation().getBodyBlock()->getOps<FModuleLike>()) {
1944 for (PortInfo port : module.getPorts()) {
1945 if (getBaseOfType<ResetType>(port.type)) {
1946 auto diag = emitError(port.loc)
1947 << "a port \"" << port.getName()
1948 << "\" with abstract reset type was unable to be "
1949 "inferred by InferResets (is this a top-level port?)";
1950 diag.attachNote(module->getLoc())
1951 << "the module with this uninferred reset port was defined here";
1952 hasAbstractResetPorts = true;
1953 }
1954 }
1955 }
1956
1957 if (hasAbstractResetPorts)
1958 return failure();
1959 return success();
1960}
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(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: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)