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. Only record non-null reset domains;
1584 // the `domains[module]` entry is created regardless, so modules in no-domain
1585 // contexts will have an empty entries list. If the module already has an
1586 // entry for this domain, don't add a duplicate.
1587 auto &entries = domains[module];
1588 if (domain.rootReset)
1589 if (llvm::all_of(entries,
1590 [&](const auto &entry) { return entry.first != domain; }))
1591 entries.push_back({domain, instPath});
1592
1593 // Traverse the child instances.
1594 for (auto *record : *instGraph[module]) {
1595 auto submodule = dyn_cast<FModuleOp>(*record->getTarget()->getModule());
1596 if (!submodule)
1597 continue;
1598 auto childPath =
1599 instancePathCache->appendInstance(instPath, record->getInstance());
1600 buildDomains(submodule, childPath, domain.rootReset, instGraph, indent + 1);
1601 }
1602}
1603
1604/// Determine how the reset for each module shall be implemented.
1605LogicalResult InferResetsPass::determineImpl() {
1606 auto anyFailed = false;
1607 LLVM_DEBUG({
1608 llvm::dbgs() << "\n";
1609 debugHeader("Determine implementation") << "\n\n";
1610 });
1611 for (auto &it : domains) {
1612 auto module = cast<FModuleOp>(it.first);
1613 auto &entries = it.second;
1614 // Skip modules with no reset domain (empty entries).
1615 if (entries.empty())
1616 continue;
1617 auto &domain = entries.back().first;
1618 if (failed(determineImpl(module, domain)))
1619 anyFailed = true;
1620 }
1621 return failure(anyFailed);
1622}
1623
1624/// Determine how the reset for a module shall be implemented. This function
1625/// fills in the `localReset` and `existingPort` fields of the given reset
1626/// domain.
1627///
1628/// Generally it does the following:
1629/// - If the domain has explicitly no reset ("ignore"), leaves everything
1630/// empty.
1631/// - If the domain is the place where the reset is defined ("top"), fills in
1632/// the existing port/wire/node as reset.
1633/// - If the module already has a port with the reset's name:
1634/// - If the port has the same name and type as the reset domain, reuses that
1635/// port.
1636/// - Otherwise errors out.
1637/// - Otherwise indicates that a port with the reset's name should be created.
1638///
1639LogicalResult InferResetsPass::determineImpl(FModuleOp module,
1640 ResetDomain &domain) {
1641 // Nothing to do if the module needs no reset.
1642 if (!domain)
1643 return success();
1644 LLVM_DEBUG(llvm::dbgs() << "Planning reset for " << module.getName() << "\n");
1645
1646 // If this is the root of a reset domain, we don't need to add any ports
1647 // and can just simply reuse the existing values.
1648 if (domain.isTop) {
1649 LLVM_DEBUG(llvm::dbgs()
1650 << "- Rooting at local value " << domain.resetName << "\n");
1651 domain.localReset = domain.rootReset;
1652 if (auto blockArg = dyn_cast<BlockArgument>(domain.rootReset))
1653 domain.existingPort = blockArg.getArgNumber();
1654 return success();
1655 }
1656
1657 // Otherwise, check if a port with this name and type already exists and
1658 // reuse that where possible.
1659 auto neededName = domain.resetName;
1660 auto neededType = domain.resetType;
1661 LLVM_DEBUG(llvm::dbgs() << "- Looking for existing port " << neededName
1662 << "\n");
1663 auto portNames = module.getPortNames();
1664 auto *portIt = llvm::find(portNames, neededName);
1665
1666 // If this port does not yet exist, record that we need to create it.
1667 if (portIt == portNames.end()) {
1668 LLVM_DEBUG(llvm::dbgs() << "- Creating new port " << neededName << "\n");
1669 domain.resetName = neededName;
1670 return success();
1671 }
1672
1673 LLVM_DEBUG(llvm::dbgs() << "- Reusing existing port " << neededName << "\n");
1674
1675 // If this port has the wrong type, then error out.
1676 auto portNo = std::distance(portNames.begin(), portIt);
1677 auto portType = module.getPortType(portNo);
1678 if (portType != neededType) {
1679 auto diag = emitError(module.getPortLocation(portNo), "module '")
1680 << module.getName() << "' is in reset domain requiring port '"
1681 << domain.resetName.getValue() << "' to have type "
1682 << domain.resetType << ", but has type " << portType;
1683 diag.attachNote(domain.rootReset.getLoc()) << "reset domain rooted here";
1684 return failure();
1685 }
1686
1687 // We have a pre-existing port which we should use.
1688 domain.existingPort = portNo;
1689 domain.localReset = module.getArgument(portNo);
1690 return success();
1691}
1692
1693//===----------------------------------------------------------------------===//
1694// Full Reset Implementation
1695//===----------------------------------------------------------------------===//
1696
1697/// Implement the annotated resets gathered in the pass' `domains` map.
1698LogicalResult InferResetsPass::implementFullReset() {
1699 LLVM_DEBUG({
1700 llvm::dbgs() << "\n";
1701 debugHeader("Implement full resets") << "\n\n";
1702 });
1703 for (auto &it : domains) {
1704 auto module = cast<FModuleOp>(it.first);
1705 auto &entries = it.second;
1706 // For modules with a real domain, use that domain. For no-domain modules,
1707 // use a default empty domain but still process for tie-off.
1708 ResetDomain domain;
1709 if (!entries.empty())
1710 domain = entries.back().first;
1711 if (failed(implementFullReset(module, domain)))
1712 return failure();
1713 }
1714 return success();
1715}
1716
1717/// Implement the async resets for a specific module.
1718///
1719/// This will add ports to the module as appropriate, update the register ops
1720/// in the module, and update any instantiated submodules with their
1721/// corresponding reset implementation details.
1722LogicalResult InferResetsPass::implementFullReset(FModuleOp module,
1723 ResetDomain &domain) {
1724 // For modules in no-domain contexts, we skip local transformations (adding
1725 // reset ports, converting registers) but still process instances to tie off
1726 // reset ports of children that have a real reset domain.
1727 if (!domain) {
1728 SmallVector<InstanceOp> instances;
1729 module.walk([&](InstanceOp instOp) { instances.push_back(instOp); });
1730 LLVM_DEBUG({
1731 if (!instances.empty())
1732 llvm::dbgs() << "Tie off instances in " << module.getName() << "\n";
1733 });
1734 for (auto instOp : instances)
1735 implementFullReset(instOp, module, Value());
1736 return success();
1737 }
1738
1739 LLVM_DEBUG(llvm::dbgs() << "Implementing full reset for " << module.getName()
1740 << "\n");
1741
1742 // Add an annotation indicating that this module belongs to a reset domain.
1743 auto *context = module.getContext();
1744 AnnotationSet annotations(module);
1745 annotations.addAnnotations(DictionaryAttr::get(
1746 context, NamedAttribute(StringAttr::get(context, "class"),
1747 StringAttr::get(context, fullResetAnnoClass))));
1748 annotations.applyToOperation(module);
1749
1750 // If needed, add a reset port to the module.
1751 auto actualReset = domain.localReset;
1752 if (!domain.localReset) {
1753 PortInfo portInfo{domain.resetName,
1754 domain.resetType,
1755 Direction::In,
1756 {},
1757 domain.rootReset.getLoc()};
1758 module.insertPorts({{0, portInfo}});
1759 actualReset = module.getArgument(0);
1760 LLVM_DEBUG(llvm::dbgs() << "- Inserted port " << domain.resetName << "\n");
1761 }
1762
1763 LLVM_DEBUG({
1764 llvm::dbgs() << "- Using ";
1765 if (auto blockArg = dyn_cast<BlockArgument>(actualReset))
1766 llvm::dbgs() << "port #" << blockArg.getArgNumber() << " ";
1767 else
1768 llvm::dbgs() << "wire/node ";
1769 llvm::dbgs() << getResetName(actualReset) << "\n";
1770 });
1771
1772 // Gather a list of operations in the module that need to be updated with
1773 // the new reset.
1774 SmallVector<Operation *> opsToUpdate;
1775 module.walk([&](Operation *op) {
1776 if (isa<InstanceOp, RegOp, RegResetOp>(op))
1777 opsToUpdate.push_back(op);
1778 });
1779
1780 // If the reset is a local wire or node, move it upwards such that it
1781 // dominates all the operations that it will need to attach to. In the case
1782 // of a node this might not be easily possible, so we just spill into a wire
1783 // in that case.
1784 if (!isa<BlockArgument>(actualReset)) {
1785 mlir::DominanceInfo dom(module);
1786 // The first op in `opsToUpdate` is the top-most op in the module, since
1787 // the ops and blocks are traversed in a depth-first, top-to-bottom order
1788 // in `walk`. So we can simply check if the local reset declaration is
1789 // before the first op to find out if we need to move anything.
1790 auto *resetOp = actualReset.getDefiningOp();
1791 if (!opsToUpdate.empty() && !dom.dominates(resetOp, opsToUpdate[0])) {
1792 LLVM_DEBUG(llvm::dbgs()
1793 << "- Reset doesn't dominate all uses, needs to be moved\n");
1794
1795 // If the node can't be moved because its input doesn't dominate the
1796 // target location, convert it to a wire.
1797 auto nodeOp = dyn_cast<NodeOp>(resetOp);
1798 if (nodeOp && !dom.dominates(nodeOp.getInput(), opsToUpdate[0])) {
1799 LLVM_DEBUG(llvm::dbgs()
1800 << "- Promoting node to wire for move: " << nodeOp << "\n");
1801 auto builder = ImplicitLocOpBuilder::atBlockBegin(nodeOp.getLoc(),
1802 nodeOp->getBlock());
1803 auto wireOp = WireOp::create(
1804 builder, nodeOp.getResult().getType(), nodeOp.getNameAttr(),
1805 nodeOp.getNameKindAttr(), nodeOp.getAnnotationsAttr(),
1806 nodeOp.getInnerSymAttr(), nodeOp.getForceableAttr());
1807 // Don't delete the node, since it might be in use in worklists.
1808 nodeOp->replaceAllUsesWith(wireOp);
1809 nodeOp->removeAttr(nodeOp.getInnerSymAttrName());
1810 nodeOp.setName("");
1811 // Leave forcable alone, since we cannot remove a result. It will be
1812 // cleaned up in canonicalization since it is dead. As will this node.
1813 nodeOp.setNameKind(NameKindEnum::DroppableName);
1814 nodeOp.setAnnotationsAttr(ArrayAttr::get(builder.getContext(), {}));
1815 builder.setInsertionPointAfter(nodeOp);
1816 emitConnect(builder, wireOp.getResult(), nodeOp.getResult());
1817 resetOp = wireOp;
1818 actualReset = wireOp.getResult();
1819 domain.localReset = wireOp.getResult();
1820 }
1821
1822 // Determine the block into which the reset declaration needs to be
1823 // moved.
1824 Block *targetBlock = dom.findNearestCommonDominator(
1825 resetOp->getBlock(), opsToUpdate[0]->getBlock());
1826 LLVM_DEBUG({
1827 if (targetBlock != resetOp->getBlock())
1828 llvm::dbgs() << "- Needs to be moved to different block\n";
1829 });
1830
1831 // At this point we have to figure out in front of which operation in
1832 // the target block the reset declaration has to be moved. The reset
1833 // declaration and the first op it needs to dominate may be buried
1834 // inside blocks of other operations (e.g. `WhenOp`), so we have to look
1835 // through their parent operations until we find the one that lies
1836 // within the target block.
1837 auto getParentInBlock = [](Operation *op, Block *block) {
1838 while (op && op->getBlock() != block)
1839 op = op->getParentOp();
1840 return op;
1841 };
1842 auto *resetOpInTarget = getParentInBlock(resetOp, targetBlock);
1843 auto *firstOpInTarget = getParentInBlock(opsToUpdate[0], targetBlock);
1844
1845 // Move the operation upwards. Since there are situations where the
1846 // reset declaration does not dominate the first use, but the `WhenOp`
1847 // it is nested within actually *does* come before that use, we have to
1848 // consider moving the reset declaration in front of its parent op.
1849 if (resetOpInTarget->isBeforeInBlock(firstOpInTarget))
1850 resetOp->moveBefore(resetOpInTarget);
1851 else
1852 resetOp->moveBefore(firstOpInTarget);
1853 }
1854 }
1855
1856 // Update the operations.
1857 for (auto *op : opsToUpdate)
1858 implementFullReset(op, module, actualReset);
1859
1860 return success();
1861}
1862
1863/// Modify an operation in a module to implement an full reset for that
1864/// module. If actualReset is null and op is an `InstanceOp`, creates a tie-off
1865/// constant for added reset ports. If the op is not an instance, aborts.
1866void InferResetsPass::implementFullReset(Operation *op, FModuleOp module,
1867 Value actualReset) {
1868 ImplicitLocOpBuilder builder(op->getLoc(), op);
1869
1870 // Handle instances.
1871 if (auto instOp = dyn_cast<InstanceOp>(op)) {
1872 // Lookup the reset domain of the instantiated module. If there is no
1873 // reset domain associated with that module, as indicated by an empty list
1874 // of domains, simply skip it.
1875 auto refModule = instOp.getReferencedModule<FModuleOp>(*instanceGraph);
1876 if (!refModule)
1877 return;
1878 auto domainIt = domains.find(refModule);
1879 if (domainIt == domains.end() || domainIt->second.empty())
1880 return;
1881 auto &domain = domainIt->second.back().first;
1882 assert(domain && "null domains should not be listed");
1883 LLVM_DEBUG(llvm::dbgs()
1884 << (actualReset ? "- Update instance '" : "- Tie-off instance '")
1885 << instOp.getName() << "'\n");
1886
1887 // If needed, add a reset port to the instance.
1888 Value instReset;
1889 if (!domain.localReset) {
1890 LLVM_DEBUG(llvm::dbgs() << " - Adding new result as reset\n");
1891 auto newInstOp = instOp.cloneWithInsertedPortsAndReplaceUses(
1892 {{/*portIndex=*/0,
1893 {domain.resetName, domain.resetType, Direction::In}}});
1894 instReset = newInstOp.getResult(0);
1895 instanceGraph->replaceInstance(instOp, newInstOp);
1896 instOp->erase();
1897 instOp = newInstOp;
1898 } else if (domain.existingPort.has_value()) {
1899 auto idx = *domain.existingPort;
1900 instReset = instOp.getResult(idx);
1901 LLVM_DEBUG(llvm::dbgs() << " - Using result #" << idx << " as reset\n");
1902 }
1903
1904 // If there's no reset port on the instance to connect, we're done. This
1905 // can happen if the instantiated module has a reset domain, but that
1906 // domain is e.g. rooted at an internal wire.
1907 if (!instReset)
1908 return;
1909
1910 builder.setInsertionPointAfter(instOp);
1911
1912 // If the module that contains the instance is not in a reset domain, as
1913 // indicated by actualReset being null, create a tie-off constant which
1914 // effectively turns the no-reset registers that had full resets added back
1915 // into no-reset registers.
1916 if (!actualReset) {
1917 LLVM_DEBUG(llvm::dbgs() << " - Tying off reset to constant 0\n");
1918 if (type_isa<AsyncResetType>(domain.resetType))
1919 actualReset =
1920 SpecialConstantOp::create(builder, domain.resetType, false);
1921 else
1922 actualReset = ConstantOp::create(
1923 builder, UIntType::get(builder.getContext(), 1), APInt(1, 0));
1924 }
1925
1926 // Connect the instance's reset to the actual reset or tie-off.
1927 assert(instReset && actualReset);
1928 emitConnect(builder, instReset, actualReset);
1929 return;
1930 }
1931
1932 // All other ops require an actual reset. We only ever call this function with
1933 // null actualReset to create tie-offs on instance ops.
1934 assert(actualReset);
1935
1936 // Handle reset-less registers.
1937 if (auto regOp = dyn_cast<RegOp>(op)) {
1938 LLVM_DEBUG(llvm::dbgs() << "- Adding full reset to " << regOp << "\n");
1939 auto zero = createZeroValue(builder, regOp.getResult().getType());
1940 auto newRegOp = RegResetOp::create(
1941 builder, regOp.getResult().getType(), regOp.getClockVal(), actualReset,
1942 zero, regOp.getNameAttr(), regOp.getNameKindAttr(),
1943 regOp.getAnnotations(), regOp.getInnerSymAttr(),
1944 regOp.getForceableAttr());
1945 regOp.getResult().replaceAllUsesWith(newRegOp.getResult());
1946 if (regOp.getForceable())
1947 regOp.getRef().replaceAllUsesWith(newRegOp.getRef());
1948 regOp->erase();
1949 return;
1950 }
1951
1952 // Handle registers with reset.
1953 if (auto regOp = dyn_cast<RegResetOp>(op)) {
1954 // If the register already has an async reset or if the type of the added
1955 // reset is sync, leave it alone.
1956 if (type_isa<AsyncResetType>(regOp.getResetSignal().getType()) ||
1957 type_isa<UIntType>(actualReset.getType())) {
1958 LLVM_DEBUG(llvm::dbgs() << "- Skipping (has reset) " << regOp << "\n");
1959 // The following performs the logic of `CheckResets` in the original
1960 // Scala source code.
1961 if (failed(regOp.verifyInvariants()))
1962 signalPassFailure();
1963 return;
1964 }
1965 LLVM_DEBUG(llvm::dbgs() << "- Updating reset of " << regOp << "\n");
1966
1967 auto reset = regOp.getResetSignal();
1968 auto value = regOp.getResetValue();
1969
1970 // If we arrive here, the register has a sync reset and the added reset is
1971 // async. In order to add an async reset, we have to move the sync reset
1972 // into a mux in front of the register.
1973 insertResetMux(builder, regOp.getResult(), reset, value);
1974 builder.setInsertionPointAfterValue(regOp.getResult());
1975 auto mux = MuxPrimOp::create(builder, reset, value, regOp.getResult());
1976 emitConnect(builder, regOp.getResult(), mux);
1977
1978 // Replace the existing reset with the async reset.
1979 builder.setInsertionPoint(regOp);
1980 auto zero = createZeroValue(builder, regOp.getResult().getType());
1981 regOp.getResetSignalMutable().assign(actualReset);
1982 regOp.getResetValueMutable().assign(zero);
1983 }
1984}
1985
1986LogicalResult InferResetsPass::verifyNoAbstractReset() {
1987 bool hasAbstractResetPorts = false;
1988 for (FModuleLike module :
1989 getOperation().getBodyBlock()->getOps<FModuleLike>()) {
1990 for (PortInfo port : module.getPorts()) {
1991 if (getBaseOfType<ResetType>(port.type)) {
1992 auto diag = emitError(port.loc)
1993 << "a port \"" << port.getName()
1994 << "\" with abstract reset type was unable to be "
1995 "inferred by InferResets (is this a top-level port?)";
1996 diag.attachNote(module->getLoc())
1997 << "the module with this uninferred reset port was defined here";
1998 hasAbstractResetPorts = true;
1999 }
2000 }
2001 }
2002
2003 if (hasAbstractResetPorts)
2004 return failure();
2005 return success();
2006}
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.
StringAttr getName(ArrayAttr names, size_t idx)
Return the name at the specified index of the ArrayAttr or null if it cannot be determined.
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)