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