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