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"
32#define DEBUG_TYPE "infer-resets"
36#define GEN_PASS_DEF_INFERRESETS
37#include "circt/Dialect/FIRRTL/Passes.h.inc"
41using circt::igraph::InstanceOpInterface;
44using llvm::BumpPtrAllocator;
46using llvm::SmallDenseSet;
47using llvm::SmallSetVector;
49using mlir::InferTypeOpInterface;
52using namespace firrtl;
61 if (
auto arg = dyn_cast<BlockArgument>(reset)) {
62 auto module = cast<FModuleOp>(arg.getParentRegion()->getParentOp());
63 return {
module.getPortNameAttr(arg.getArgNumber()), module};
65 auto *op = reset.getDefiningOp();
66 return {op->getAttrOfType<StringAttr>(
"name"),
67 op->getParentOfType<FModuleOp>()};
96 std::optional<unsigned> existingPort;
99 ResetDomain() =
default;
102 ResetDomain(Value rootReset)
103 : rootReset(rootReset), resetName(
getResetName(rootReset)),
104 resetType(rootReset.getType()) {}
107 explicit operator bool()
const {
return static_cast<bool>(rootReset); }
111inline bool operator==(
const ResetDomain &a,
const ResetDomain &b) {
112 return (a.isTop == b.isTop && a.resetName == b.resetName &&
113 a.resetType == b.resetType);
115inline bool operator!=(
const ResetDomain &a,
const ResetDomain &b) {
124 auto it = cache.find(type);
125 if (it != cache.end())
127 auto nullBit = [&]() {
129 builder, UIntType::get(builder.getContext(), 1,
true),
134 .
Case<ClockType>([&](
auto type) {
135 return AsClockPrimOp::create(builder, nullBit());
137 .Case<AsyncResetType>([&](
auto type) {
138 return AsAsyncResetPrimOp::create(builder, nullBit());
140 .Case<SIntType, UIntType>([&](
auto type) {
141 return ConstantOp::create(
142 builder, type, APInt::getZero(type.getWidth().value_or(1)));
144 .Case<FEnumType>([&](
auto type) -> Value {
147 if (type.getNumElements() != 0 &&
148 type.getElement(0).value.getValue().isZero()) {
149 const auto &element = type.getElement(0);
151 return FEnumCreateOp::create(builder, type, element.name, value);
153 auto value = ConstantOp::create(builder,
154 UIntType::get(builder.getContext(),
157 APInt::getZero(type.getBitWidth()));
158 return BitCastOp::create(builder, type, value);
160 .Case<BundleType>([&](
auto type) {
161 auto wireOp = WireOp::create(builder, type);
162 for (
unsigned i = 0, e = type.getNumElements(); i < e; ++i) {
163 auto fieldType = type.getElementTypePreservingConst(i);
166 SubfieldOp::create(builder, fieldType, wireOp.getResult(), i);
169 return wireOp.getResult();
171 .Case<FVectorType>([&](
auto type) {
172 auto wireOp = WireOp::create(builder, type);
174 builder, type.getElementTypePreservingConst(), cache);
175 for (
unsigned i = 0, e = type.getNumElements(); i < e; ++i) {
176 auto acc = SubindexOp::create(builder, zero.getType(),
177 wireOp.getResult(), i);
180 return wireOp.getResult();
182 .Case<ResetType, AnalogType>(
183 [&](
auto type) {
return InvalidValueOp::create(builder, type); })
185 llvm_unreachable(
"switch handles all types");
188 cache.insert({type, value});
205 Value reset, Value resetValue) {
209 bool resetValueUsed =
false;
211 for (
auto &use : target.getUses()) {
212 Operation *useOp = use.getOwner();
213 builder.setInsertionPoint(useOp);
214 TypeSwitch<Operation *>(useOp)
217 .Case<ConnectOp, MatchingConnectOp>([&](
auto op) {
218 if (op.getDest() != target)
220 LLVM_DEBUG(llvm::dbgs() <<
" - Insert mux into " << op <<
"\n");
222 MuxPrimOp::create(builder, reset, resetValue, op.getSrc());
223 op.getSrcMutable().assign(muxOp);
224 resetValueUsed =
true;
227 .Case<SubfieldOp>([&](
auto op) {
229 SubfieldOp::create(builder, resetValue, op.getFieldIndexAttr());
231 resetValueUsed =
true;
233 resetSubValue.erase();
236 .Case<SubindexOp>([&](
auto op) {
238 SubindexOp::create(builder, resetValue, op.getIndexAttr());
240 resetValueUsed =
true;
242 resetSubValue.erase();
245 .Case<SubaccessOp>([&](
auto op) {
246 if (op.getInput() != target)
249 SubaccessOp::create(builder, resetValue, op.getIndex());
251 resetValueUsed =
true;
253 resetSubValue.erase();
256 return resetValueUsed;
271 bool operator<(
const ResetSignal &other)
const {
return field < other.field; }
272 bool operator==(
const ResetSignal &other)
const {
273 return field == other.field;
275 bool operator!=(
const ResetSignal &other)
const {
return !(*
this == other); }
295using ResetDrives = SmallVector<ResetDrive, 1>;
298using ResetNetwork = llvm::iterator_range<
299 llvm::EquivalenceClasses<ResetSignal>::member_iterator>;
302enum class ResetKind { Async, Sync };
304static StringRef resetKindToStringRef(
const ResetKind &kind) {
306 case ResetKind::Async:
308 case ResetKind::Sync:
311 llvm_unreachable(
"unhandled reset kind");
327 static bool isEqual(
const ResetSignal &lhs,
const ResetSignal &rhs) {
336 case ResetKind::Async:
337 return os <<
"async";
338 case ResetKind::Sync:
448struct InferResetsPass
449 :
public circt::firrtl::impl::InferResetsBase<InferResetsPass> {
450 void runOnOperation()
override;
451 void runOnOperationInner();
454 using InferResetsBase::InferResetsBase;
455 InferResetsPass(
const InferResetsPass &other) : InferResetsBase(other) {}
460 void traceResets(CircuitOp circuit);
461 void traceResets(InstanceOp inst);
462 void traceResets(Value dst, Value src, Location loc);
463 void traceResets(Value value);
464 void traceResets(Type dstType, Value dst,
unsigned dstID, Type srcType,
465 Value src,
unsigned srcID, Location loc);
467 LogicalResult inferAndUpdateResets();
468 FailureOr<ResetKind> inferReset(ResetNetwork net);
469 LogicalResult updateReset(ResetNetwork net, ResetKind kind);
475 LogicalResult collectAnnos(CircuitOp circuit);
481 FailureOr<std::optional<Value>> collectAnnos(FModuleOp module);
483 LogicalResult buildDomains(CircuitOp circuit);
484 void buildDomains(FModuleOp module,
const InstancePath &instPath,
486 unsigned indent = 0);
488 LogicalResult determineImpl();
489 LogicalResult determineImpl(FModuleOp module, ResetDomain &domain);
491 LogicalResult implementFullReset();
492 LogicalResult implementFullReset(FModuleOp module, ResetDomain &domain);
493 void implementFullReset(Operation *op, FModuleOp module, Value actualReset);
495 LogicalResult verifyNoAbstractReset();
501 ResetNetwork getResetNetwork(ResetSignal signal) {
502 return llvm::make_range(resetClasses.findLeader(signal),
503 resetClasses.member_end());
507 ResetDrives &getResetDrives(ResetNetwork net) {
508 return resetDrives[*net.begin()];
513 ResetSignal guessRoot(ResetNetwork net);
514 ResetSignal guessRoot(ResetSignal signal) {
515 return guessRoot(getResetNetwork(signal));
522 llvm::EquivalenceClasses<ResetSignal> resetClasses;
525 DenseMap<ResetSignal, ResetDrives> resetDrives;
530 DenseMap<Operation *, Value> annotatedResets;
534 MapVector<FModuleOp, SmallVector<std::pair<ResetDomain, InstancePath>, 1>>
541 std::unique_ptr<InstancePathCache> instancePathCache;
545void InferResetsPass::runOnOperation() {
546 runOnOperationInner();
547 resetClasses = llvm::EquivalenceClasses<ResetSignal>();
549 annotatedResets.clear();
551 instancePathCache.reset(
nullptr);
552 markAnalysesPreserved<InstanceGraph>();
555void InferResetsPass::runOnOperationInner() {
556 instanceGraph = &getAnalysis<InstanceGraph>();
557 instancePathCache = std::make_unique<InstancePathCache>(*instanceGraph);
560 traceResets(getOperation());
563 if (failed(inferAndUpdateResets()))
564 return signalPassFailure();
567 if (failed(collectAnnos(getOperation())))
568 return signalPassFailure();
571 if (failed(buildDomains(getOperation())))
572 return signalPassFailure();
575 if (failed(determineImpl()))
576 return signalPassFailure();
579 if (failed(implementFullReset()))
580 return signalPassFailure();
583 if (failed(verifyNoAbstractReset()))
584 return signalPassFailure();
587ResetSignal InferResetsPass::guessRoot(ResetNetwork net) {
588 ResetDrives &drives = getResetDrives(net);
589 ResetSignal bestSignal = *net.begin();
590 unsigned bestNumDrives = -1;
592 for (
auto signal : net) {
594 if (isa_and_nonnull<InvalidValueOp>(
595 signal.field.getValue().getDefiningOp()))
600 unsigned numDrives = 0;
601 for (
auto &drive : drives)
602 if (drive.dst == signal)
608 if (numDrives < bestNumDrives) {
609 bestNumDrives = numDrives;
628 .
Case<BundleType>([](
auto type) {
630 for (
auto e : type.getElements())
635 [](
auto type) {
return getMaxFieldID(type.getElementType()) + 1; })
636 .Default([](
auto) {
return 0; });
640 assert(index < type.getNumElements());
642 for (
unsigned i = 0; i < index; ++i)
650 assert(type.getNumElements() &&
"Bundle must have >0 fields");
652 for (
const auto &e : llvm::enumerate(type.getElements())) {
654 if (fieldID < numSubfields)
656 fieldID -= numSubfields;
658 assert(
false &&
"field id outside bundle");
664 if (oldType.isGround()) {
670 if (
auto bundleType = type_dyn_cast<BundleType>(oldType)) {
678 if (
auto vectorType = type_dyn_cast<FVectorType>(oldType)) {
679 if (vectorType.getNumElements() == 0)
696 if (
auto arg = dyn_cast<BlockArgument>(value)) {
697 auto module = cast<FModuleOp>(arg.getOwner()->getParentOp());
698 string +=
module.getPortName(arg.getArgNumber());
702 auto *op = value.getDefiningOp();
703 return TypeSwitch<Operation *, bool>(op)
704 .Case<InstanceOp, MemOp>([&](
auto op) {
705 string += op.getName();
708 op.getPortName(cast<OpResult>(value).getResultNumber()).getValue();
711 .Case<WireOp, NodeOp, RegOp, RegResetOp>([&](
auto op) {
712 string += op.getName();
715 .Default([](
auto) {
return false; });
719 SmallString<64> name;
724 auto type = value.getType();
727 if (
auto bundleType = type_dyn_cast<BundleType>(type)) {
730 auto &element = bundleType.getElements()[index];
733 string += element.name.getValue();
736 localID = localID -
getFieldID(bundleType, index);
737 }
else if (
auto vecType = type_dyn_cast<FVectorType>(type)) {
740 type = vecType.getElementType();
747 llvm_unreachable(
"unsupported type");
759 return TypeSwitch<Type, bool>(type)
761 return type.getRecursiveTypeProperties().hasUninferredReset;
763 .Default([](
auto) {
return false; });
770void InferResetsPass::traceResets(CircuitOp circuit) {
772 llvm::dbgs() <<
"\n";
773 debugHeader(
"Tracing uninferred resets") <<
"\n\n";
776 SmallVector<std::pair<FModuleOp, SmallVector<Operation *>>> moduleToOps;
778 for (
auto module : circuit.getOps<FModuleOp>())
779 moduleToOps.push_back({module, {}});
782 getAnalysis<hw::InnerSymbolTableCollection>()};
784 mlir::parallelForEach(circuit.getContext(), moduleToOps, [](
auto &e) {
785 e.first.walk([&](Operation *op) {
789 op->getResultTypes(),
790 [](mlir::Type type) { return typeContainsReset(type); }) ||
791 llvm::any_of(op->getOperandTypes(), typeContainsReset))
792 e.second.push_back(op);
796 for (
auto &[_, ops] : moduleToOps)
797 for (auto *op : ops) {
798 TypeSwitch<Operation *>(op)
799 .Case<FConnectLike>([&](
auto op) {
800 traceResets(op.getDest(), op.getSrc(), op.getLoc());
802 .Case<InstanceOp>([&](
auto op) { traceResets(op); })
803 .Case<RefSendOp>([&](
auto op) {
805 traceResets(op.getType().getType(), op.getResult(), 0,
806 op.getBase().getType().getPassiveType(), op.getBase(),
809 .Case<RefResolveOp>([&](
auto op) {
811 traceResets(op.getType(), op.getResult(), 0,
812 op.getRef().getType().getType(), op.getRef(), 0,
815 .Case<Forceable>([&](Forceable op) {
816 if (
auto node = dyn_cast<NodeOp>(op.getOperation()))
817 traceResets(node.getResult(), node.getInput(), node.getLoc());
819 if (op.isForceable())
820 traceResets(op.getDataType(), op.getData(), 0, op.getDataType(),
821 op.getDataRef(), 0, op.getLoc());
823 .Case<RWProbeOp>([&](RWProbeOp op) {
824 auto ist = irn.lookup(op.getTarget());
827 auto baseType = op.getType().getType();
828 traceResets(baseType, op.getResult(), 0, baseType.getPassiveType(),
829 ref.getValue(), ref.getFieldID(), op.getLoc());
831 .Case<UninferredResetCastOp, ConstCastOp, RefCastOp>([&](
auto op) {
832 traceResets(op.getResult(), op.getInput(), op.getLoc());
834 .Case<InvalidValueOp>([&](
auto op) {
843 auto type = op.getType();
846 LLVM_DEBUG(llvm::dbgs() <<
"Uniquify " << op <<
"\n");
847 ImplicitLocOpBuilder builder(op->getLoc(), op);
849 llvm::make_early_inc_range(
llvm::drop_begin(op->getUses()))) {
855 auto newOp = InvalidValueOp::create(builder, type);
860 .Case<SubfieldOp>([&](
auto op) {
863 BundleType bundleType = op.getInput().getType();
864 auto index = op.getFieldIndex();
865 traceResets(op.getType(), op.getResult(), 0,
866 bundleType.getElements()[index].type, op.getInput(),
870 .Case<SubindexOp, SubaccessOp>([&](
auto op) {
883 FVectorType vectorType = op.getInput().getType();
884 traceResets(op.getType(), op.getResult(), 0,
885 vectorType.getElementType(), op.getInput(),
889 .Case<RefSubOp>([&](RefSubOp op) {
891 auto aggType = op.getInput().getType().getType();
892 uint64_t fieldID = TypeSwitch<FIRRTLBaseType, uint64_t>(aggType)
893 .Case<FVectorType>([](
auto type) {
896 .Case<BundleType>([&](
auto type) {
899 traceResets(op.getType(), op.getResult(), 0,
900 op.getResult().getType(), op.getInput(), fieldID,
908void InferResetsPass::traceResets(InstanceOp inst) {
910 auto module = inst.getReferencedModule<FModuleOp>(*instanceGraph);
913 LLVM_DEBUG(llvm::dbgs() <<
"Visiting instance " << inst.getName() <<
"\n");
916 for (
const auto &it :
llvm::enumerate(inst.getResults())) {
917 auto dir =
module.getPortDirection(it.index());
918 Value dstPort =
module.getArgument(it.index());
919 Value srcPort = it.value();
920 if (dir == Direction::Out)
921 std::swap(dstPort, srcPort);
922 traceResets(dstPort, srcPort, it.value().getLoc());
928void InferResetsPass::traceResets(Value dst, Value src, Location loc) {
930 traceResets(dst.getType(), dst, 0, src.getType(), src, 0, loc);
935void InferResetsPass::traceResets(Type dstType, Value dst,
unsigned dstID,
936 Type srcType, Value src,
unsigned srcID,
938 if (
auto dstBundle = type_dyn_cast<BundleType>(dstType)) {
939 auto srcBundle = type_cast<BundleType>(srcType);
940 for (
unsigned dstIdx = 0, e = dstBundle.getNumElements(); dstIdx < e;
942 auto dstField = dstBundle.getElements()[dstIdx].name;
943 auto srcIdx = srcBundle.getElementIndex(dstField);
946 auto &dstElt = dstBundle.getElements()[dstIdx];
947 auto &srcElt = srcBundle.getElements()[*srcIdx];
949 traceResets(srcElt.type, src, srcID +
getFieldID(srcBundle, *srcIdx),
950 dstElt.type, dst, dstID +
getFieldID(dstBundle, dstIdx),
953 traceResets(dstElt.type, dst, dstID +
getFieldID(dstBundle, dstIdx),
954 srcElt.type, src, srcID +
getFieldID(srcBundle, *srcIdx),
961 if (
auto dstVector = type_dyn_cast<FVectorType>(dstType)) {
962 auto srcVector = type_cast<FVectorType>(srcType);
963 auto srcElType = srcVector.getElementType();
964 auto dstElType = dstVector.getElementType();
977 traceResets(dstElType, dst, dstID +
getFieldID(dstVector), srcElType, src,
983 if (
auto dstRef = type_dyn_cast<RefType>(dstType)) {
984 auto srcRef = type_cast<RefType>(srcType);
985 return traceResets(dstRef.getType(), dst, dstID, srcRef.getType(), src,
990 auto dstBase = type_dyn_cast<FIRRTLBaseType>(dstType);
991 auto srcBase = type_dyn_cast<FIRRTLBaseType>(srcType);
992 if (!dstBase || !srcBase)
994 if (!type_isa<ResetType>(dstBase) && !type_isa<ResetType>(srcBase))
999 LLVM_DEBUG(llvm::dbgs() <<
"Visiting driver '" << dstField <<
"' = '"
1000 << srcField <<
"' (" << dstType <<
" = " << srcType
1006 ResetSignal dstLeader =
1007 *resetClasses.findLeader(resetClasses.insert({dstField, dstBase}));
1008 ResetSignal srcLeader =
1009 *resetClasses.findLeader(resetClasses.insert({srcField, srcBase}));
1012 ResetSignal unionLeader = *resetClasses.unionSets(dstLeader, srcLeader);
1013 assert(unionLeader == dstLeader || unionLeader == srcLeader);
1018 if (dstLeader != srcLeader) {
1019 auto &unionDrives = resetDrives[unionLeader];
1020 auto mergedDrivesIt =
1021 resetDrives.find(unionLeader == dstLeader ? srcLeader : dstLeader);
1022 if (mergedDrivesIt != resetDrives.end()) {
1023 unionDrives.append(mergedDrivesIt->second);
1024 resetDrives.erase(mergedDrivesIt);
1030 resetDrives[unionLeader].push_back(
1031 {{dstField, dstBase}, {srcField, srcBase}, loc});
1038LogicalResult InferResetsPass::inferAndUpdateResets() {
1040 llvm::dbgs() <<
"\n";
1043 for (
const auto &it : resetClasses) {
1044 if (!it->isLeader())
1046 ResetNetwork net = resetClasses.members(*it);
1049 auto kind = inferReset(net);
1054 if (failed(updateReset(net, *kind)))
1060FailureOr<ResetKind> InferResetsPass::inferReset(ResetNetwork net) {
1061 LLVM_DEBUG(llvm::dbgs() <<
"Inferring reset network with "
1062 << std::distance(net.begin(), net.end())
1066 unsigned asyncDrives = 0;
1067 unsigned syncDrives = 0;
1068 unsigned invalidDrives = 0;
1069 for (ResetSignal signal : net) {
1071 if (type_isa<AsyncResetType>(signal.type))
1073 else if (type_isa<UIntType>(signal.type))
1076 isa_and_nonnull<InvalidValueOp>(
1077 signal.field.getValue().getDefiningOp()))
1080 LLVM_DEBUG(llvm::dbgs() <<
"- Found " << asyncDrives <<
" async, "
1081 << syncDrives <<
" sync, " << invalidDrives
1082 <<
" invalid drives\n");
1085 if (asyncDrives == 0 && syncDrives == 0 && invalidDrives == 0) {
1086 ResetSignal root = guessRoot(net);
1087 auto diag = mlir::emitError(root.field.getValue().getLoc())
1088 <<
"reset network never driven with concrete type";
1089 for (ResetSignal signal : net)
1090 diag.attachNote(signal.field.
getLoc()) <<
"here: ";
1095 if (asyncDrives > 0 && syncDrives > 0) {
1096 ResetSignal root = guessRoot(net);
1097 bool majorityAsync = asyncDrives >= syncDrives;
1098 auto diag = mlir::emitError(root.field.getValue().getLoc())
1100 SmallString<32> fieldName;
1102 diag <<
" \"" << fieldName <<
"\"";
1103 diag <<
" simultaneously connected to async and sync resets";
1104 diag.attachNote(root.field.getValue().getLoc())
1105 <<
"majority of connections to this reset are "
1106 << (majorityAsync ?
"async" :
"sync");
1107 for (
auto &drive : getResetDrives(net)) {
1108 if ((type_isa<AsyncResetType>(drive.dst.type) && !majorityAsync) ||
1109 (type_isa<AsyncResetType>(drive.src.type) && !majorityAsync) ||
1110 (type_isa<UIntType>(drive.dst.type) && majorityAsync) ||
1111 (type_isa<UIntType>(drive.src.type) && majorityAsync))
1112 diag.attachNote(drive.loc)
1113 << (type_isa<AsyncResetType>(drive.src.type) ?
"async" :
"sync")
1122 auto kind = (asyncDrives ? ResetKind::Async : ResetKind::Sync);
1123 LLVM_DEBUG(llvm::dbgs() <<
"- Inferred as " << kind <<
"\n");
1131LogicalResult InferResetsPass::updateReset(ResetNetwork net, ResetKind kind) {
1132 LLVM_DEBUG(llvm::dbgs() <<
"Updating reset network with "
1133 << std::distance(net.begin(), net.end())
1134 <<
" nodes to " << kind <<
"\n");
1138 if (kind == ResetKind::Async)
1139 resetType = AsyncResetType::get(&getContext());
1141 resetType = UIntType::get(&getContext(), 1);
1146 SmallSetVector<Operation *, 16> worklist;
1147 SmallDenseSet<Operation *> moduleWorklist;
1148 SmallDenseSet<std::pair<Operation *, Operation *>> extmoduleWorklist;
1149 for (
auto signal : net) {
1150 Value value = signal.field.getValue();
1151 if (!isa<BlockArgument>(value) &&
1152 !isa_and_nonnull<WireOp, RegOp, RegResetOp, InstanceOp, InvalidValueOp,
1153 ConstCastOp, RefCastOp, UninferredResetCastOp,
1154 RWProbeOp>(value.getDefiningOp()))
1156 if (updateReset(signal.field, resetType)) {
1157 for (
auto user : value.getUsers())
1158 worklist.insert(user);
1159 if (
auto blockArg = dyn_cast<BlockArgument>(value))
1160 moduleWorklist.insert(blockArg.getOwner()->getParentOp());
1161 else if (
auto instOp = value.getDefiningOp<InstanceOp>()) {
1162 if (
auto extmodule =
1163 instOp.getReferencedModule<FExtModuleOp>(*instanceGraph))
1164 extmoduleWorklist.insert({extmodule, instOp});
1165 }
else if (
auto uncast = value.getDefiningOp<UninferredResetCastOp>()) {
1166 uncast.replaceAllUsesWith(uncast.getInput());
1176 while (!worklist.empty()) {
1177 auto *wop = worklist.pop_back_val();
1178 SmallVector<Type, 2> types;
1179 if (
auto op = dyn_cast<InferTypeOpInterface>(wop)) {
1181 SmallVector<Type, 2> types;
1182 if (failed(op.inferReturnTypes(op->getContext(), op->getLoc(),
1183 op->getOperands(), op->getAttrDictionary(),
1184 op->getPropertiesStorage(),
1185 op->getRegions(), types)))
1190 for (
auto it :
llvm::zip(op->getResults(), types)) {
1191 auto newType = std::get<1>(it);
1192 if (std::get<0>(it).getType() == newType)
1194 std::get<0>(it).setType(newType);
1195 for (
auto *user : std::
get<0>(it).getUsers())
1196 worklist.insert(user);
1198 LLVM_DEBUG(llvm::dbgs() <<
"- Inferred " << *op <<
"\n");
1199 }
else if (
auto uop = dyn_cast<UninferredResetCastOp>(wop)) {
1200 for (
auto *user : uop.getResult().getUsers())
1201 worklist.insert(user);
1202 uop.replaceAllUsesWith(uop.getInput());
1203 LLVM_DEBUG(llvm::dbgs() <<
"- Inferred " << uop <<
"\n");
1209 for (
auto *op : moduleWorklist) {
1210 auto module = dyn_cast<FModuleOp>(op);
1214 SmallVector<Attribute> argTypes;
1215 argTypes.reserve(module.getNumPorts());
1216 for (
auto arg : module.getArguments())
1217 argTypes.push_back(TypeAttr::
get(arg.getType()));
1219 module.setPortTypesAttr(ArrayAttr::get(op->getContext(), argTypes));
1220 LLVM_DEBUG(llvm::dbgs()
1221 <<
"- Updated type of module '" << module.getName() <<
"'\n");
1225 for (
auto pair : extmoduleWorklist) {
1226 auto module = cast<FExtModuleOp>(pair.first);
1227 auto instOp = cast<InstanceOp>(pair.second);
1229 SmallVector<Attribute> types;
1230 for (
auto type : instOp.getResultTypes())
1231 types.push_back(TypeAttr::
get(type));
1233 module.setPortTypesAttr(ArrayAttr::get(module->getContext(), types));
1234 LLVM_DEBUG(llvm::dbgs()
1235 <<
"- Updated type of extmodule '" << module.getName() <<
"'\n");
1245 if (oldType.isGround()) {
1251 if (
auto bundleType = type_dyn_cast<BundleType>(oldType)) {
1253 SmallVector<BundleType::BundleElement> fields(bundleType.begin(),
1256 fields[index].type, fieldID -
getFieldID(bundleType, index), fieldType);
1257 return BundleType::get(oldType.getContext(), fields, bundleType.
isConst());
1261 if (
auto vectorType = type_dyn_cast<FVectorType>(oldType)) {
1262 auto newType =
updateType(vectorType.getElementType(),
1263 fieldID -
getFieldID(vectorType), fieldType);
1264 return FVectorType::get(newType, vectorType.getNumElements(),
1265 vectorType.isConst());
1268 llvm_unreachable(
"unknown aggregate type");
1275 auto oldType = type_cast<FIRRTLType>(field.
getValue().getType());
1281 if (oldType == newType)
1283 LLVM_DEBUG(llvm::dbgs() <<
"- Updating '" << field <<
"' from " << oldType
1284 <<
" to " << newType <<
"\n");
1293LogicalResult InferResetsPass::collectAnnos(CircuitOp circuit) {
1295 llvm::dbgs() <<
"\n";
1296 debugHeader(
"Gather reset annotations") <<
"\n\n";
1298 SmallVector<std::pair<FModuleOp, std::optional<Value>>> results;
1299 for (
auto module : circuit.getOps<FModuleOp>())
1300 results.push_back({module, {}});
1302 if (failed(mlir::failableParallelForEach(
1303 circuit.getContext(), results, [&](
auto &moduleAndResult) {
1304 auto result = collectAnnos(moduleAndResult.first);
1307 moduleAndResult.second = *result;
1312 for (
auto [module, reset] : results)
1313 if (reset.has_value())
1314 annotatedResets.insert({module, *reset});
1318FailureOr<std::optional<Value>>
1319InferResetsPass::collectAnnos(FModuleOp module) {
1320 bool anyFailed =
false;
1321 SmallSetVector<std::pair<Annotation, Location>, 4> conflictingAnnos;
1325 bool ignore =
false;
1329 conflictingAnnos.insert({anno, module.getLoc()});
1334 module.emitError("''FullResetAnnotation' cannot target module; must
"
1335 "target port or wire/node instead
");
1343 // Consume any reset annotations on module ports.
1345 // Helper for checking annotations and determining the reset
1346 auto checkAnnotations = [&](Annotation anno, Value arg) {
1347 if (anno.isClass(fullResetAnnoClass)) {
1348 ResetKind expectedResetKind;
1349 if (auto rt = anno.getMember<StringAttr>("resetType
")) {
1351 expectedResetKind = ResetKind::Sync;
1352 } else if (rt == "async
") {
1353 expectedResetKind = ResetKind::Async;
1355 mlir::emitError(arg.getLoc(),
1356 "'FullResetAnnotation' requires resetType ==
'sync' "
1357 "|
'async', but got resetType ==
")
1363 mlir::emitError(arg.getLoc(),
1364 "'FullResetAnnotation' requires resetType ==
"
1365 "'sync' |
'async', but got no resetType
");
1369 // Check that the type is well-formed
1370 bool isAsync = expectedResetKind == ResetKind::Async;
1371 bool validUint = false;
1372 if (auto uintT = dyn_cast<UIntType>(arg.getType()))
1373 validUint = uintT.getWidth() == 1;
1374 if ((isAsync && !isa<AsyncResetType>(arg.getType())) ||
1375 (!isAsync && !validUint)) {
1376 auto kind = resetKindToStringRef(expectedResetKind);
1377 mlir::emitError(arg.getLoc(),
1378 "'FullResetAnnotation' with resetType ==
'")
1379 << kind << "' must target
" << kind << " reset, but targets
"
1386 conflictingAnnos.insert({anno, reset.getLoc()});
1390 if (anno.isClass(excludeFromFullResetAnnoClass)) {
1392 mlir::emitError(arg.getLoc(),
1393 "'ExcludeFromFullResetAnnotation' cannot
"
1394 "target port/wire/node; must target
module instead");
1402 Value arg =
module.getArgument(argNum);
1403 return checkAnnotations(anno, arg);
1409 module.getBody().walk([&](Operation *op) {
1411 if (!isa<WireOp, NodeOp>(op)) {
1412 if (AnnotationSet::hasAnnotation(op, fullResetAnnoClass,
1413 excludeFromFullResetAnnoClass)) {
1416 "reset annotations must target module, port, or wire/node");
1424 auto arg = op->getResult(0);
1425 return checkAnnotations(anno, arg);
1434 if (!ignore && !reset) {
1435 LLVM_DEBUG(llvm::dbgs()
1436 <<
"No reset annotation for " << module.getName() <<
"\n");
1437 return std::optional<Value>();
1441 if (conflictingAnnos.size() > 1) {
1442 auto diag =
module.emitError("multiple reset annotations on module '")
1443 << module.getName() << "'";
1444 for (
auto &annoAndLoc : conflictingAnnos)
1445 diag.attachNote(annoAndLoc.second)
1446 <<
"conflicting " << annoAndLoc.first.getClassAttr() <<
":";
1452 llvm::dbgs() <<
"Annotated reset for " <<
module.getName() << ": ";
1454 llvm::dbgs() <<
"no domain\n";
1455 else if (
auto arg = dyn_cast<BlockArgument>(reset))
1456 llvm::dbgs() <<
"port " <<
module.getPortName(arg.getArgNumber()) << "\n";
1458 llvm::dbgs() <<
"wire "
1459 << reset.getDefiningOp()->getAttrOfType<StringAttr>(
"name")
1465 return std::optional<Value>(reset);
1477LogicalResult InferResetsPass::buildDomains(CircuitOp circuit) {
1479 llvm::dbgs() <<
"\n";
1480 debugHeader(
"Build full reset domains") <<
"\n\n";
1484 auto &instGraph = getAnalysis<InstanceGraph>();
1485 auto module = dyn_cast<FModuleOp>(*instGraph.getTopLevelNode()->getModule());
1487 LLVM_DEBUG(llvm::dbgs()
1488 <<
"Skipping circuit because main module is no `firrtl.module`");
1491 buildDomains(module,
InstancePath{}, Value{}, instGraph);
1494 bool anyFailed =
false;
1495 for (
auto &it : domains) {
1496 auto module = cast<FModuleOp>(it.first);
1497 auto &domainConflicts = it.second;
1498 if (domainConflicts.size() <= 1)
1502 SmallDenseSet<Value> printedDomainResets;
1503 auto diag =
module.emitError("module '")
1505 << "' instantiated in different reset domains";
1506 for (
auto &it : domainConflicts) {
1507 ResetDomain &domain = it.first;
1508 const auto &path = it.second;
1509 auto inst = path.leaf();
1510 auto loc = path.empty() ?
module.getLoc() : inst.getLoc();
1511 auto ¬e = diag.attachNote(loc);
1515 note <<
"root instance";
1517 note <<
"instance '";
1520 [&](InstanceOpInterface inst) { note << inst.getInstanceName(); },
1521 [&]() { note <<
"/"; });
1527 if (domain.rootReset) {
1529 note <<
" reset domain rooted at '" << nameAndModule.first.getValue()
1530 <<
"' of module '" << nameAndModule.second.getName() <<
"'";
1533 if (printedDomainResets.insert(domain.rootReset).second) {
1534 diag.attachNote(domain.rootReset.getLoc())
1535 <<
"reset domain '" << nameAndModule.first.getValue()
1536 <<
"' of module '" << nameAndModule.second.getName()
1537 <<
"' declared here:";
1540 note <<
" no reset domain";
1543 return failure(anyFailed);
1546void InferResetsPass::buildDomains(FModuleOp module,
1551 llvm::dbgs().indent(indent * 2) <<
"Visiting ";
1552 if (instPath.
empty())
1553 llvm::dbgs() <<
"$root";
1555 llvm::dbgs() << instPath.
leaf().getInstanceName();
1556 llvm::dbgs() <<
" (" <<
module.getName() << ")\n";
1561 auto it = annotatedResets.find(module);
1562 if (it != annotatedResets.end()) {
1565 if (
auto localReset = it->second)
1566 domain = ResetDomain(localReset);
1567 domain.isTop =
true;
1568 }
else if (parentReset) {
1570 domain = ResetDomain(parentReset);
1576 auto &entries = domains[module];
1577 if (llvm::all_of(entries,
1578 [&](
const auto &entry) {
return entry.first != domain; }))
1579 entries.push_back({domain, instPath});
1582 for (
auto *record : *instGraph[module]) {
1583 auto submodule = dyn_cast<FModuleOp>(*record->getTarget()->getModule());
1587 instancePathCache->appendInstance(instPath, record->getInstance());
1588 buildDomains(submodule, childPath, domain.rootReset, instGraph, indent + 1);
1593LogicalResult InferResetsPass::determineImpl() {
1594 auto anyFailed =
false;
1596 llvm::dbgs() <<
"\n";
1597 debugHeader(
"Determine implementation") <<
"\n\n";
1599 for (
auto &it : domains) {
1600 auto module = cast<FModuleOp>(it.first);
1601 auto &domain = it.second.back().first;
1602 if (failed(determineImpl(module, domain)))
1605 return failure(anyFailed);
1623LogicalResult InferResetsPass::determineImpl(FModuleOp module,
1624 ResetDomain &domain) {
1628 LLVM_DEBUG(llvm::dbgs() <<
"Planning reset for " << module.getName() <<
"\n");
1633 LLVM_DEBUG(llvm::dbgs()
1634 <<
"- Rooting at local value " << domain.resetName <<
"\n");
1635 domain.localReset = domain.rootReset;
1636 if (
auto blockArg = dyn_cast<BlockArgument>(domain.rootReset))
1637 domain.existingPort = blockArg.getArgNumber();
1643 auto neededName = domain.resetName;
1644 auto neededType = domain.resetType;
1645 LLVM_DEBUG(llvm::dbgs() <<
"- Looking for existing port " << neededName
1647 auto portNames =
module.getPortNames();
1648 auto *portIt = llvm::find(portNames, neededName);
1651 if (portIt == portNames.end()) {
1652 LLVM_DEBUG(llvm::dbgs() <<
"- Creating new port " << neededName <<
"\n");
1653 domain.resetName = neededName;
1657 LLVM_DEBUG(llvm::dbgs() <<
"- Reusing existing port " << neededName <<
"\n");
1660 auto portNo = std::distance(portNames.begin(), portIt);
1661 auto portType =
module.getPortType(portNo);
1662 if (portType != neededType) {
1663 auto diag = emitError(module.getPortLocation(portNo),
"module '")
1664 <<
module.getName() << "' is in reset domain requiring port '"
1665 << domain.resetName.getValue() << "' to have type "
1666 << domain.resetType << ", but has type " << portType;
1667 diag.attachNote(domain.rootReset.getLoc()) <<
"reset domain rooted here";
1672 domain.existingPort = portNo;
1673 domain.localReset =
module.getArgument(portNo);
1682LogicalResult InferResetsPass::implementFullReset() {
1684 llvm::dbgs() <<
"\n";
1687 for (
auto &it : domains)
1688 if (failed(implementFullReset(cast<FModuleOp>(it.first),
1689 it.second.back().first)))
1699LogicalResult InferResetsPass::implementFullReset(FModuleOp module,
1700 ResetDomain &domain) {
1701 LLVM_DEBUG(llvm::dbgs() <<
"Implementing full reset for " << module.getName()
1706 LLVM_DEBUG(llvm::dbgs()
1707 <<
"- Skipping because module explicitly has no domain\n");
1712 auto *context =
module.getContext();
1714 annotations.addAnnotations(DictionaryAttr::get(
1715 context, NamedAttribute(StringAttr::get(context,
"class"),
1717 annotations.applyToOperation(module);
1720 auto actualReset = domain.localReset;
1721 if (!domain.localReset) {
1722 PortInfo portInfo{domain.resetName,
1726 domain.rootReset.getLoc()};
1727 module.insertPorts({{0, portInfo}});
1728 actualReset =
module.getArgument(0);
1729 LLVM_DEBUG(llvm::dbgs() <<
"- Inserted port " << domain.resetName <<
"\n");
1733 llvm::dbgs() <<
"- Using ";
1734 if (
auto blockArg = dyn_cast<BlockArgument>(actualReset))
1735 llvm::dbgs() <<
"port #" << blockArg.getArgNumber() <<
" ";
1737 llvm::dbgs() <<
"wire/node ";
1743 SmallVector<Operation *> opsToUpdate;
1744 module.walk([&](Operation *op) {
1745 if (isa<InstanceOp, RegOp, RegResetOp>(op))
1746 opsToUpdate.push_back(op);
1753 if (!isa<BlockArgument>(actualReset)) {
1754 mlir::DominanceInfo dom(module);
1759 auto *resetOp = actualReset.getDefiningOp();
1760 if (!opsToUpdate.empty() && !dom.dominates(resetOp, opsToUpdate[0])) {
1761 LLVM_DEBUG(llvm::dbgs()
1762 <<
"- Reset doesn't dominate all uses, needs to be moved\n");
1766 auto nodeOp = dyn_cast<NodeOp>(resetOp);
1767 if (nodeOp && !dom.dominates(nodeOp.getInput(), opsToUpdate[0])) {
1768 LLVM_DEBUG(llvm::dbgs()
1769 <<
"- Promoting node to wire for move: " << nodeOp <<
"\n");
1770 auto builder = ImplicitLocOpBuilder::atBlockBegin(nodeOp.getLoc(),
1771 nodeOp->getBlock());
1772 auto wireOp = WireOp::create(
1773 builder, nodeOp.getResult().getType(), nodeOp.getNameAttr(),
1774 nodeOp.getNameKindAttr(), nodeOp.getAnnotationsAttr(),
1775 nodeOp.getInnerSymAttr(), nodeOp.getForceableAttr());
1777 nodeOp->replaceAllUsesWith(wireOp);
1778 nodeOp->removeAttr(nodeOp.getInnerSymAttrName());
1782 nodeOp.setNameKind(NameKindEnum::DroppableName);
1783 nodeOp.setAnnotationsAttr(ArrayAttr::get(builder.getContext(), {}));
1784 builder.setInsertionPointAfter(nodeOp);
1785 emitConnect(builder, wireOp.getResult(), nodeOp.getResult());
1787 actualReset = wireOp.getResult();
1788 domain.localReset = wireOp.getResult();
1793 Block *targetBlock = dom.findNearestCommonDominator(
1794 resetOp->getBlock(), opsToUpdate[0]->getBlock());
1796 if (targetBlock != resetOp->getBlock())
1797 llvm::dbgs() <<
"- Needs to be moved to different block\n";
1806 auto getParentInBlock = [](Operation *op,
Block *block) {
1807 while (op && op->getBlock() != block)
1808 op = op->getParentOp();
1811 auto *resetOpInTarget = getParentInBlock(resetOp, targetBlock);
1812 auto *firstOpInTarget = getParentInBlock(opsToUpdate[0], targetBlock);
1818 if (resetOpInTarget->isBeforeInBlock(firstOpInTarget))
1819 resetOp->moveBefore(resetOpInTarget);
1821 resetOp->moveBefore(firstOpInTarget);
1826 for (
auto *op : opsToUpdate)
1827 implementFullReset(op, module, actualReset);
1834void InferResetsPass::implementFullReset(Operation *op, FModuleOp module,
1835 Value actualReset) {
1836 ImplicitLocOpBuilder builder(op->getLoc(), op);
1839 if (
auto instOp = dyn_cast<InstanceOp>(op)) {
1843 auto refModule = instOp.getReferencedModule<FModuleOp>(*instanceGraph);
1846 auto domainIt = domains.find(refModule);
1847 if (domainIt == domains.end())
1849 auto &domain = domainIt->second.back().first;
1852 LLVM_DEBUG(llvm::dbgs()
1853 <<
"- Update instance '" << instOp.getName() <<
"'\n");
1857 if (!domain.localReset) {
1858 LLVM_DEBUG(llvm::dbgs() <<
" - Adding new result as reset\n");
1860 auto newInstOp = instOp.cloneAndInsertPorts(
1862 {domain.resetName, type_cast<FIRRTLBaseType>(actualReset.getType()),
1864 instReset = newInstOp.getResult(0);
1867 instOp.replaceAllUsesWith(newInstOp.getResults().drop_front());
1868 instanceGraph->replaceInstance(instOp, newInstOp);
1871 }
else if (domain.existingPort.has_value()) {
1872 auto idx = *domain.existingPort;
1873 instReset = instOp.getResult(idx);
1874 LLVM_DEBUG(llvm::dbgs() <<
" - Using result #" << idx <<
" as reset\n");
1884 assert(instReset && actualReset);
1885 builder.setInsertionPointAfter(instOp);
1891 if (
auto regOp = dyn_cast<RegOp>(op)) {
1892 LLVM_DEBUG(llvm::dbgs() <<
"- Adding full reset to " << regOp <<
"\n");
1894 auto newRegOp = RegResetOp::create(
1895 builder, regOp.getResult().getType(), regOp.getClockVal(), actualReset,
1896 zero, regOp.getNameAttr(), regOp.getNameKindAttr(),
1897 regOp.getAnnotations(), regOp.getInnerSymAttr(),
1898 regOp.getForceableAttr());
1899 regOp.getResult().replaceAllUsesWith(newRegOp.getResult());
1900 if (regOp.getForceable())
1901 regOp.getRef().replaceAllUsesWith(newRegOp.getRef());
1907 if (
auto regOp = dyn_cast<RegResetOp>(op)) {
1910 if (type_isa<AsyncResetType>(regOp.getResetSignal().getType()) ||
1911 type_isa<UIntType>(actualReset.getType())) {
1912 LLVM_DEBUG(llvm::dbgs() <<
"- Skipping (has reset) " << regOp <<
"\n");
1915 if (failed(regOp.verifyInvariants()))
1916 signalPassFailure();
1919 LLVM_DEBUG(llvm::dbgs() <<
"- Updating reset of " << regOp <<
"\n");
1921 auto reset = regOp.getResetSignal();
1922 auto value = regOp.getResetValue();
1928 builder.setInsertionPointAfterValue(regOp.getResult());
1929 auto mux = MuxPrimOp::create(builder, reset, value, regOp.getResult());
1933 builder.setInsertionPoint(regOp);
1935 regOp.getResetSignalMutable().assign(actualReset);
1936 regOp.getResetValueMutable().assign(zero);
1940LogicalResult InferResetsPass::verifyNoAbstractReset() {
1941 bool hasAbstractResetPorts =
false;
1942 for (FModuleLike module :
1943 getOperation().
getBodyBlock()->getOps<FModuleLike>()) {
1944 for (
PortInfo port : module.getPorts()) {
1945 if (getBaseOfType<ResetType>(port.type)) {
1946 auto diag = emitError(port.loc)
1947 <<
"a port \"" << port.getName()
1948 <<
"\" with abstract reset type was unable to be "
1949 "inferred by InferResets (is this a top-level port?)";
1950 diag.attachNote(module->getLoc())
1951 <<
"the module with this uninferred reset port was defined here";
1952 hasAbstractResetPorts =
true;
1957 if (hasAbstractResetPorts)
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 Location getLoc(DefSlot slot)
static Block * getBodyBlock(FModuleLike mod)
static InstancePath empty
This class represents a reference to a specific field or element of an aggregate value.
unsigned getFieldID() const
Get the field ID of this FieldRef, which is a unique identifier mapped to a specific field in a bundl...
Value getValue() const
Get the Value which created this location.
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.
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.
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.
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.
static bool operator==(const ModulePort &a, const ModulePort &b)
static llvm::hash_code hash_value(const ModulePort &port)
bool operator<(const DictEntry &entry, const DictEntry &other)
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.
bool operator!=(uint64_t a, const FVInt &b)
This holds the name and type that describes the module's ports.
This class represents the namespace in which InnerRef's can be resolved.
A data structure that caches and provides paths to module instances in the IR.
static ResetSignal getEmptyKey()
static ResetSignal getTombstoneKey()
static bool isEqual(const ResetSignal &lhs, const ResetSignal &rhs)
static unsigned getHashValue(const ResetSignal &x)