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