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