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