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);
111inline bool operator==(
const ResetDomain &a,
const ResetDomain &b) {
…}
115inline bool operator!=(
const ResetDomain &a,
const ResetDomain &b) {
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 builder.create<AsClockPrimOp>(nullBit());
137 .Case<AsyncResetType>([&](
auto type) {
138 return builder.create<AsAsyncResetPrimOp>(nullBit());
140 .Case<SIntType, UIntType>([&](
auto type) {
141 return builder.create<ConstantOp>(
142 type, APInt::getZero(type.getWidth().value_or(1)));
144 .Case<BundleType>([&](
auto type) {
145 auto wireOp = builder.create<WireOp>(type);
146 for (
unsigned i = 0, e = type.getNumElements(); i < e; ++i) {
147 auto fieldType = type.getElementTypePreservingConst(i);
150 builder.create<SubfieldOp>(fieldType, wireOp.getResult(), i);
153 return wireOp.getResult();
155 .Case<FVectorType>([&](
auto type) {
156 auto wireOp = builder.create<WireOp>(type);
158 builder, type.getElementTypePreservingConst(), cache);
159 for (
unsigned i = 0, e = type.getNumElements(); i < e; ++i) {
160 auto acc = builder.create<SubindexOp>(zero.getType(),
161 wireOp.getResult(), i);
164 return wireOp.getResult();
166 .Case<ResetType, AnalogType>(
167 [&](
auto type) {
return builder.create<InvalidValueOp>(type); })
169 llvm_unreachable(
"switch handles all types");
172 cache.insert({type, value});
189 Value reset, Value resetValue) {
193 bool resetValueUsed =
false;
195 for (
auto &use : target.getUses()) {
196 Operation *useOp = use.getOwner();
197 builder.setInsertionPoint(useOp);
198 TypeSwitch<Operation *>(useOp)
201 .Case<ConnectOp, MatchingConnectOp>([&](
auto op) {
202 if (op.getDest() != target)
204 LLVM_DEBUG(llvm::dbgs() <<
" - Insert mux into " << op <<
"\n");
206 builder.create<MuxPrimOp>(reset, resetValue, op.getSrc());
207 op.getSrcMutable().assign(muxOp);
208 resetValueUsed =
true;
211 .Case<SubfieldOp>([&](
auto op) {
213 builder.create<SubfieldOp>(resetValue, op.getFieldIndexAttr());
215 resetValueUsed =
true;
217 resetSubValue.erase();
220 .Case<SubindexOp>([&](
auto op) {
222 builder.create<SubindexOp>(resetValue, op.getIndexAttr());
224 resetValueUsed =
true;
226 resetSubValue.erase();
229 .Case<SubaccessOp>([&](
auto op) {
230 if (op.getInput() != target)
233 builder.create<SubaccessOp>(resetValue, op.getIndex());
235 resetValueUsed =
true;
237 resetSubValue.erase();
240 return resetValueUsed;
255 bool operator<(
const ResetSignal &other)
const {
return field < other.field; }
256 bool operator==(
const ResetSignal &other)
const {
257 return field == other.field;
259 bool operator!=(
const ResetSignal &other)
const {
return !(*
this == other); }
279using ResetDrives = SmallVector<ResetDrive, 1>;
282using ResetNetwork = llvm::iterator_range<
283 llvm::EquivalenceClasses<ResetSignal>::member_iterator>;
286enum class ResetKind { Async, Sync };
288static StringRef resetKindToStringRef(
const ResetKind &kind) {
290 case ResetKind::Async:
292 case ResetKind::Sync:
295 llvm_unreachable(
"unhandled reset kind");
311 static bool isEqual(
const ResetSignal &lhs,
const ResetSignal &rhs) {
311 static bool isEqual(
const ResetSignal &lhs,
const ResetSignal &rhs) {
…}
320 case ResetKind::Async:
321 return os <<
"async";
322 case ResetKind::Sync:
432struct InferResetsPass
433 :
public circt::firrtl::impl::InferResetsBase<InferResetsPass> {
434 void runOnOperation()
override;
435 void runOnOperationInner();
438 using InferResetsBase::InferResetsBase;
439 InferResetsPass(
const InferResetsPass &other) : InferResetsBase(other) {}
444 void traceResets(CircuitOp circuit);
445 void traceResets(InstanceOp inst);
446 void traceResets(Value dst, Value src, Location loc);
447 void traceResets(Value value);
448 void traceResets(Type dstType, Value dst,
unsigned dstID, Type srcType,
449 Value src,
unsigned srcID, Location loc);
451 LogicalResult inferAndUpdateResets();
452 FailureOr<ResetKind> inferReset(ResetNetwork net);
453 LogicalResult updateReset(ResetNetwork net, ResetKind kind);
459 LogicalResult collectAnnos(CircuitOp circuit);
465 FailureOr<std::optional<Value>> collectAnnos(FModuleOp module);
467 LogicalResult buildDomains(CircuitOp circuit);
468 void buildDomains(FModuleOp module,
const InstancePath &instPath,
470 unsigned indent = 0);
472 LogicalResult determineImpl();
473 LogicalResult determineImpl(FModuleOp module, ResetDomain &domain);
475 LogicalResult implementFullReset();
476 LogicalResult implementFullReset(FModuleOp module, ResetDomain &domain);
477 void implementFullReset(Operation *op, FModuleOp module, Value actualReset);
479 LogicalResult verifyNoAbstractReset();
485 ResetNetwork getResetNetwork(ResetSignal signal) {
486 return llvm::make_range(resetClasses.findLeader(signal),
487 resetClasses.member_end());
491 ResetDrives &getResetDrives(ResetNetwork net) {
492 return resetDrives[*net.begin()];
497 ResetSignal guessRoot(ResetNetwork net);
498 ResetSignal guessRoot(ResetSignal signal) {
499 return guessRoot(getResetNetwork(signal));
506 llvm::EquivalenceClasses<ResetSignal> resetClasses;
509 DenseMap<ResetSignal, ResetDrives> resetDrives;
514 DenseMap<Operation *, Value> annotatedResets;
518 MapVector<FModuleOp, SmallVector<std::pair<ResetDomain, InstancePath>, 1>>
525 std::unique_ptr<InstancePathCache> instancePathCache;
529void InferResetsPass::runOnOperation() {
530 runOnOperationInner();
531 resetClasses = llvm::EquivalenceClasses<ResetSignal>();
533 annotatedResets.clear();
535 instancePathCache.reset(
nullptr);
536 markAnalysesPreserved<InstanceGraph>();
539void InferResetsPass::runOnOperationInner() {
540 instanceGraph = &getAnalysis<InstanceGraph>();
541 instancePathCache = std::make_unique<InstancePathCache>(*instanceGraph);
544 traceResets(getOperation());
547 if (failed(inferAndUpdateResets()))
548 return signalPassFailure();
551 if (failed(collectAnnos(getOperation())))
552 return signalPassFailure();
555 if (failed(buildDomains(getOperation())))
556 return signalPassFailure();
559 if (failed(determineImpl()))
560 return signalPassFailure();
563 if (failed(implementFullReset()))
564 return signalPassFailure();
567 if (failed(verifyNoAbstractReset()))
568 return signalPassFailure();
572 return std::make_unique<InferResetsPass>();
575ResetSignal InferResetsPass::guessRoot(ResetNetwork net) {
576 ResetDrives &drives = getResetDrives(net);
577 ResetSignal bestSignal = *net.begin();
578 unsigned bestNumDrives = -1;
580 for (
auto signal : net) {
582 if (isa_and_nonnull<InvalidValueOp>(
583 signal.field.getValue().getDefiningOp()))
588 unsigned numDrives = 0;
589 for (
auto &drive : drives)
590 if (drive.dst == signal)
596 if (numDrives < bestNumDrives) {
597 bestNumDrives = numDrives;
616 .
Case<BundleType>([](
auto type) {
618 for (
auto e : type.getElements())
623 [](
auto type) {
return getMaxFieldID(type.getElementType()) + 1; })
624 .Default([](
auto) {
return 0; });
628 assert(index < type.getNumElements());
630 for (
unsigned i = 0; i < index; ++i)
638 assert(type.getNumElements() &&
"Bundle must have >0 fields");
640 for (
const auto &e : llvm::enumerate(type.getElements())) {
642 if (fieldID < numSubfields)
644 fieldID -= numSubfields;
646 assert(
false &&
"field id outside bundle");
652 if (oldType.isGround()) {
658 if (
auto bundleType = type_dyn_cast<BundleType>(oldType)) {
666 if (
auto vectorType = type_dyn_cast<FVectorType>(oldType)) {
667 if (vectorType.getNumElements() == 0)
684 if (
auto arg = dyn_cast<BlockArgument>(value)) {
685 auto module = cast<FModuleOp>(arg.getOwner()->getParentOp());
686 string +=
module.getPortName(arg.getArgNumber());
690 auto *op = value.getDefiningOp();
691 return TypeSwitch<Operation *, bool>(op)
692 .Case<InstanceOp, MemOp>([&](
auto op) {
693 string += op.getName();
696 op.getPortName(cast<OpResult>(value).getResultNumber()).getValue();
699 .Case<WireOp, NodeOp, RegOp, RegResetOp>([&](
auto op) {
700 string += op.getName();
703 .Default([](
auto) {
return false; });
707 SmallString<64> name;
712 auto type = value.getType();
715 if (
auto bundleType = type_dyn_cast<BundleType>(type)) {
718 auto &element = bundleType.getElements()[index];
721 string += element.name.getValue();
724 localID = localID -
getFieldID(bundleType, index);
725 }
else if (
auto vecType = type_dyn_cast<FVectorType>(type)) {
728 type = vecType.getElementType();
735 llvm_unreachable(
"unsupported type");
747 return TypeSwitch<Type, bool>(type)
749 return type.getRecursiveTypeProperties().hasUninferredReset;
751 .Default([](
auto) {
return false; });
758void InferResetsPass::traceResets(CircuitOp circuit) {
760 llvm::dbgs() <<
"\n";
761 debugHeader(
"Tracing uninferred resets") <<
"\n\n";
764 SmallVector<std::pair<FModuleOp, SmallVector<Operation *>>> moduleToOps;
766 for (
auto module : circuit.getOps<FModuleOp>())
767 moduleToOps.push_back({module, {}});
770 getAnalysis<hw::InnerSymbolTableCollection>()};
772 mlir::parallelForEach(circuit.getContext(), moduleToOps, [](
auto &e) {
773 e.first.walk([&](Operation *op) {
777 op->getResultTypes(),
778 [](mlir::Type type) { return typeContainsReset(type); }) ||
779 llvm::any_of(op->getOperandTypes(), typeContainsReset))
780 e.second.push_back(op);
784 for (
auto &[_, ops] : moduleToOps)
785 for (auto *op : ops) {
786 TypeSwitch<Operation *>(op)
787 .Case<FConnectLike>([&](
auto op) {
788 traceResets(op.getDest(), op.getSrc(), op.getLoc());
790 .Case<InstanceOp>([&](
auto op) { traceResets(op); })
791 .Case<RefSendOp>([&](
auto op) {
793 traceResets(op.getType().getType(), op.getResult(), 0,
794 op.getBase().getType().getPassiveType(), op.getBase(),
797 .Case<RefResolveOp>([&](
auto op) {
799 traceResets(op.getType(), op.getResult(), 0,
800 op.getRef().getType().getType(), op.getRef(), 0,
803 .Case<Forceable>([&](Forceable op) {
804 if (
auto node = dyn_cast<NodeOp>(op.getOperation()))
805 traceResets(node.getResult(), node.getInput(), node.getLoc());
807 if (op.isForceable())
808 traceResets(op.getDataType(), op.getData(), 0, op.getDataType(),
809 op.getDataRef(), 0, op.getLoc());
811 .Case<RWProbeOp>([&](RWProbeOp op) {
812 auto ist = irn.lookup(op.getTarget());
815 auto baseType = op.getType().getType();
816 traceResets(baseType, op.getResult(), 0, baseType.getPassiveType(),
817 ref.getValue(), ref.getFieldID(), op.getLoc());
819 .Case<UninferredResetCastOp, ConstCastOp, RefCastOp>([&](
auto op) {
820 traceResets(op.getResult(), op.getInput(), op.getLoc());
822 .Case<InvalidValueOp>([&](
auto op) {
831 auto type = op.getType();
834 LLVM_DEBUG(llvm::dbgs() <<
"Uniquify " << op <<
"\n");
835 ImplicitLocOpBuilder builder(op->getLoc(), op);
837 llvm::make_early_inc_range(
llvm::drop_begin(op->getUses()))) {
843 auto newOp = builder.create<InvalidValueOp>(type);
848 .Case<SubfieldOp>([&](
auto op) {
851 BundleType bundleType = op.getInput().getType();
852 auto index = op.getFieldIndex();
853 traceResets(op.getType(), op.getResult(), 0,
854 bundleType.getElements()[index].type, op.getInput(),
858 .Case<SubindexOp, SubaccessOp>([&](
auto op) {
871 FVectorType vectorType = op.getInput().getType();
872 traceResets(op.getType(), op.getResult(), 0,
873 vectorType.getElementType(), op.getInput(),
877 .Case<RefSubOp>([&](RefSubOp op) {
879 auto aggType = op.getInput().getType().getType();
880 uint64_t fieldID = TypeSwitch<FIRRTLBaseType, uint64_t>(aggType)
881 .Case<FVectorType>([](
auto type) {
884 .Case<BundleType>([&](
auto type) {
887 traceResets(op.getType(), op.getResult(), 0,
888 op.getResult().getType(), op.getInput(), fieldID,
896void InferResetsPass::traceResets(InstanceOp inst) {
898 auto module = inst.getReferencedModule<FModuleOp>(*instanceGraph);
901 LLVM_DEBUG(llvm::dbgs() <<
"Visiting instance " << inst.getName() <<
"\n");
904 for (
const auto &it :
llvm::enumerate(inst.getResults())) {
905 auto dir =
module.getPortDirection(it.index());
906 Value dstPort =
module.getArgument(it.index());
907 Value srcPort = it.value();
908 if (dir == Direction::Out)
909 std::swap(dstPort, srcPort);
910 traceResets(dstPort, srcPort, it.value().getLoc());
916void InferResetsPass::traceResets(Value dst, Value src, Location loc) {
918 traceResets(dst.getType(), dst, 0, src.getType(), src, 0, loc);
923void InferResetsPass::traceResets(Type dstType, Value dst,
unsigned dstID,
924 Type srcType, Value src,
unsigned srcID,
926 if (
auto dstBundle = type_dyn_cast<BundleType>(dstType)) {
927 auto srcBundle = type_cast<BundleType>(srcType);
928 for (
unsigned dstIdx = 0, e = dstBundle.getNumElements(); dstIdx < e;
930 auto dstField = dstBundle.getElements()[dstIdx].name;
931 auto srcIdx = srcBundle.getElementIndex(dstField);
934 auto &dstElt = dstBundle.getElements()[dstIdx];
935 auto &srcElt = srcBundle.getElements()[*srcIdx];
937 traceResets(srcElt.type, src, srcID +
getFieldID(srcBundle, *srcIdx),
938 dstElt.type, dst, dstID +
getFieldID(dstBundle, dstIdx),
941 traceResets(dstElt.type, dst, dstID +
getFieldID(dstBundle, dstIdx),
942 srcElt.type, src, srcID +
getFieldID(srcBundle, *srcIdx),
949 if (
auto dstVector = type_dyn_cast<FVectorType>(dstType)) {
950 auto srcVector = type_cast<FVectorType>(srcType);
951 auto srcElType = srcVector.getElementType();
952 auto dstElType = dstVector.getElementType();
965 traceResets(dstElType, dst, dstID +
getFieldID(dstVector), srcElType, src,
971 if (
auto dstRef = type_dyn_cast<RefType>(dstType)) {
972 auto srcRef = type_cast<RefType>(srcType);
973 return traceResets(dstRef.getType(), dst, dstID, srcRef.getType(), src,
978 auto dstBase = type_dyn_cast<FIRRTLBaseType>(dstType);
979 auto srcBase = type_dyn_cast<FIRRTLBaseType>(srcType);
980 if (!dstBase || !srcBase)
982 if (!type_isa<ResetType>(dstBase) && !type_isa<ResetType>(srcBase))
987 LLVM_DEBUG(llvm::dbgs() <<
"Visiting driver '" << dstField <<
"' = '"
988 << srcField <<
"' (" << dstType <<
" = " << srcType
994 ResetSignal dstLeader =
995 *resetClasses.findLeader(resetClasses.insert({dstField, dstBase}));
996 ResetSignal srcLeader =
997 *resetClasses.findLeader(resetClasses.insert({srcField, srcBase}));
1000 ResetSignal unionLeader = *resetClasses.unionSets(dstLeader, srcLeader);
1001 assert(unionLeader == dstLeader || unionLeader == srcLeader);
1006 if (dstLeader != srcLeader) {
1007 auto &unionDrives = resetDrives[unionLeader];
1008 auto mergedDrivesIt =
1009 resetDrives.find(unionLeader == dstLeader ? srcLeader : dstLeader);
1010 if (mergedDrivesIt != resetDrives.end()) {
1011 unionDrives.append(mergedDrivesIt->second);
1012 resetDrives.erase(mergedDrivesIt);
1018 resetDrives[unionLeader].push_back(
1019 {{dstField, dstBase}, {srcField, srcBase}, loc});
1026LogicalResult InferResetsPass::inferAndUpdateResets() {
1028 llvm::dbgs() <<
"\n";
1031 for (
const auto &it : resetClasses) {
1032 if (!it->isLeader())
1034 ResetNetwork net = resetClasses.members(*it);
1037 auto kind = inferReset(net);
1042 if (failed(updateReset(net, *kind)))
1048FailureOr<ResetKind> InferResetsPass::inferReset(ResetNetwork net) {
1049 LLVM_DEBUG(llvm::dbgs() <<
"Inferring reset network with "
1050 << std::distance(net.begin(), net.end())
1054 unsigned asyncDrives = 0;
1055 unsigned syncDrives = 0;
1056 unsigned invalidDrives = 0;
1057 for (ResetSignal signal : net) {
1059 if (type_isa<AsyncResetType>(signal.type))
1061 else if (type_isa<UIntType>(signal.type))
1064 isa_and_nonnull<InvalidValueOp>(
1065 signal.field.getValue().getDefiningOp()))
1068 LLVM_DEBUG(llvm::dbgs() <<
"- Found " << asyncDrives <<
" async, "
1069 << syncDrives <<
" sync, " << invalidDrives
1070 <<
" invalid drives\n");
1073 if (asyncDrives == 0 && syncDrives == 0 && invalidDrives == 0) {
1074 ResetSignal root = guessRoot(net);
1075 auto diag = mlir::emitError(root.field.getValue().getLoc())
1076 <<
"reset network never driven with concrete type";
1077 for (ResetSignal signal : net)
1078 diag.attachNote(signal.field.
getLoc()) <<
"here: ";
1083 if (asyncDrives > 0 && syncDrives > 0) {
1084 ResetSignal root = guessRoot(net);
1085 bool majorityAsync = asyncDrives >= syncDrives;
1086 auto diag = mlir::emitError(root.field.getValue().getLoc())
1088 SmallString<32> fieldName;
1090 diag <<
" \"" << fieldName <<
"\"";
1091 diag <<
" simultaneously connected to async and sync resets";
1092 diag.attachNote(root.field.getValue().getLoc())
1093 <<
"majority of connections to this reset are "
1094 << (majorityAsync ?
"async" :
"sync");
1095 for (
auto &drive : getResetDrives(net)) {
1096 if ((type_isa<AsyncResetType>(drive.dst.type) && !majorityAsync) ||
1097 (type_isa<AsyncResetType>(drive.src.type) && !majorityAsync) ||
1098 (type_isa<UIntType>(drive.dst.type) && majorityAsync) ||
1099 (type_isa<UIntType>(drive.src.type) && majorityAsync))
1100 diag.attachNote(drive.loc)
1101 << (type_isa<AsyncResetType>(drive.src.type) ?
"async" :
"sync")
1110 auto kind = (asyncDrives ? ResetKind::Async : ResetKind::Sync);
1111 LLVM_DEBUG(llvm::dbgs() <<
"- Inferred as " << kind <<
"\n");
1119LogicalResult InferResetsPass::updateReset(ResetNetwork net, ResetKind kind) {
1120 LLVM_DEBUG(llvm::dbgs() <<
"Updating reset network with "
1121 << std::distance(net.begin(), net.end())
1122 <<
" nodes to " << kind <<
"\n");
1126 if (kind == ResetKind::Async)
1127 resetType = AsyncResetType::get(&getContext());
1129 resetType = UIntType::get(&getContext(), 1);
1134 SmallSetVector<Operation *, 16> worklist;
1135 SmallDenseSet<Operation *> moduleWorklist;
1136 SmallDenseSet<std::pair<Operation *, Operation *>> extmoduleWorklist;
1137 for (
auto signal : net) {
1138 Value value = signal.field.getValue();
1139 if (!isa<BlockArgument>(value) &&
1140 !isa_and_nonnull<WireOp, RegOp, RegResetOp, InstanceOp, InvalidValueOp,
1141 ConstCastOp, RefCastOp, UninferredResetCastOp,
1142 RWProbeOp>(value.getDefiningOp()))
1144 if (updateReset(signal.field, resetType)) {
1145 for (
auto user : value.getUsers())
1146 worklist.insert(user);
1147 if (
auto blockArg = dyn_cast<BlockArgument>(value))
1148 moduleWorklist.insert(blockArg.getOwner()->getParentOp());
1149 else if (
auto instOp = value.getDefiningOp<InstanceOp>()) {
1150 if (
auto extmodule =
1151 instOp.getReferencedModule<FExtModuleOp>(*instanceGraph))
1152 extmoduleWorklist.insert({extmodule, instOp});
1153 }
else if (
auto uncast = value.getDefiningOp<UninferredResetCastOp>()) {
1154 uncast.replaceAllUsesWith(uncast.getInput());
1164 while (!worklist.empty()) {
1165 auto *wop = worklist.pop_back_val();
1166 SmallVector<Type, 2> types;
1167 if (
auto op = dyn_cast<InferTypeOpInterface>(wop)) {
1169 SmallVector<Type, 2> types;
1170 if (failed(op.inferReturnTypes(op->getContext(), op->getLoc(),
1171 op->getOperands(), op->getAttrDictionary(),
1172 op->getPropertiesStorage(),
1173 op->getRegions(), types)))
1178 for (
auto it :
llvm::zip(op->getResults(), types)) {
1179 auto newType = std::get<1>(it);
1180 if (std::get<0>(it).getType() == newType)
1182 std::get<0>(it).setType(newType);
1183 for (
auto *user : std::
get<0>(it).getUsers())
1184 worklist.insert(user);
1186 LLVM_DEBUG(llvm::dbgs() <<
"- Inferred " << *op <<
"\n");
1187 }
else if (
auto uop = dyn_cast<UninferredResetCastOp>(wop)) {
1188 for (
auto *user : uop.getResult().getUsers())
1189 worklist.insert(user);
1190 uop.replaceAllUsesWith(uop.getInput());
1191 LLVM_DEBUG(llvm::dbgs() <<
"- Inferred " << uop <<
"\n");
1197 for (
auto *op : moduleWorklist) {
1198 auto module = dyn_cast<FModuleOp>(op);
1202 SmallVector<Attribute> argTypes;
1203 argTypes.reserve(module.getNumPorts());
1204 for (
auto arg : module.getArguments())
1205 argTypes.push_back(TypeAttr::
get(arg.getType()));
1207 module.setPortTypesAttr(ArrayAttr::get(op->getContext(), argTypes));
1208 LLVM_DEBUG(llvm::dbgs()
1209 <<
"- Updated type of module '" << module.getName() <<
"'\n");
1213 for (
auto pair : extmoduleWorklist) {
1214 auto module = cast<FExtModuleOp>(pair.first);
1215 auto instOp = cast<InstanceOp>(pair.second);
1217 SmallVector<Attribute> types;
1218 for (
auto type : instOp.getResultTypes())
1219 types.push_back(TypeAttr::
get(type));
1221 module.setPortTypesAttr(ArrayAttr::get(module->getContext(), types));
1222 LLVM_DEBUG(llvm::dbgs()
1223 <<
"- Updated type of extmodule '" << module.getName() <<
"'\n");
1233 if (oldType.isGround()) {
1239 if (
auto bundleType = type_dyn_cast<BundleType>(oldType)) {
1241 SmallVector<BundleType::BundleElement> fields(bundleType.begin(),
1244 fields[index].type, fieldID -
getFieldID(bundleType, index), fieldType);
1245 return BundleType::get(oldType.getContext(), fields, bundleType.
isConst());
1249 if (
auto vectorType = type_dyn_cast<FVectorType>(oldType)) {
1250 auto newType =
updateType(vectorType.getElementType(),
1251 fieldID -
getFieldID(vectorType), fieldType);
1252 return FVectorType::get(newType, vectorType.getNumElements(),
1253 vectorType.isConst());
1256 llvm_unreachable(
"unknown aggregate type");
1263 auto oldType = type_cast<FIRRTLType>(field.
getValue().getType());
1269 if (oldType == newType)
1271 LLVM_DEBUG(llvm::dbgs() <<
"- Updating '" << field <<
"' from " << oldType
1272 <<
" to " << newType <<
"\n");
1281LogicalResult InferResetsPass::collectAnnos(CircuitOp circuit) {
1283 llvm::dbgs() <<
"\n";
1284 debugHeader(
"Gather reset annotations") <<
"\n\n";
1286 SmallVector<std::pair<FModuleOp, std::optional<Value>>> results;
1287 for (
auto module : circuit.getOps<FModuleOp>())
1288 results.push_back({module, {}});
1290 if (failed(mlir::failableParallelForEach(
1291 circuit.getContext(), results, [&](
auto &moduleAndResult) {
1292 auto result = collectAnnos(moduleAndResult.first);
1295 moduleAndResult.second = *result;
1300 for (
auto [module, reset] : results)
1301 if (reset.has_value())
1302 annotatedResets.insert({module, *reset});
1306FailureOr<std::optional<Value>>
1307InferResetsPass::collectAnnos(FModuleOp module) {
1308 bool anyFailed =
false;
1309 SmallSetVector<std::pair<Annotation, Location>, 4> conflictingAnnos;
1313 bool ignore =
false;
1317 conflictingAnnos.insert({anno, module.getLoc()});
1322 module.emitError("''FullResetAnnotation' cannot target module; must
"
1323 "target port or wire/node instead
");
1331 // Consume any reset annotations on module ports.
1333 // Helper for checking annotations and determining the reset
1334 auto checkAnnotations = [&](Annotation anno, Value arg) {
1335 if (anno.isClass(fullResetAnnoClass)) {
1336 ResetKind expectedResetKind;
1337 if (auto rt = anno.getMember<StringAttr>("resetType
")) {
1339 expectedResetKind = ResetKind::Sync;
1340 } else if (rt == "async
") {
1341 expectedResetKind = ResetKind::Async;
1343 mlir::emitError(arg.getLoc(),
1344 "'FullResetAnnotation' requires resetType ==
'sync' "
1345 "|
'async', but got resetType ==
")
1351 mlir::emitError(arg.getLoc(),
1352 "'FullResetAnnotation' requires resetType ==
"
1353 "'sync' |
'async', but got no resetType
");
1357 // Check that the type is well-formed
1358 bool isAsync = expectedResetKind == ResetKind::Async;
1359 bool validUint = false;
1360 if (auto uintT = dyn_cast<UIntType>(arg.getType()))
1361 validUint = uintT.getWidth() == 1;
1362 if ((isAsync && !isa<AsyncResetType>(arg.getType())) ||
1363 (!isAsync && !validUint)) {
1364 auto kind = resetKindToStringRef(expectedResetKind);
1365 mlir::emitError(arg.getLoc(),
1366 "'FullResetAnnotation' with resetType ==
'")
1367 << kind << "' must target
" << kind << " reset, but targets
"
1374 conflictingAnnos.insert({anno, reset.getLoc()});
1378 if (anno.isClass(excludeFromFullResetAnnoClass)) {
1380 mlir::emitError(arg.getLoc(),
1381 "'ExcludeFromFullResetAnnotation' cannot
"
1382 "target port/wire/node; must target
module instead");
1390 Value arg =
module.getArgument(argNum);
1391 return checkAnnotations(anno, arg);
1397 module.getBody().walk([&](Operation *op) {
1399 if (!isa<WireOp, NodeOp>(op)) {
1400 if (AnnotationSet::hasAnnotation(op, fullResetAnnoClass,
1401 excludeFromFullResetAnnoClass)) {
1404 "reset annotations must target module, port, or wire/node");
1412 auto arg = op->getResult(0);
1413 return checkAnnotations(anno, arg);
1422 if (!ignore && !reset) {
1423 LLVM_DEBUG(llvm::dbgs()
1424 <<
"No reset annotation for " << module.getName() <<
"\n");
1425 return std::optional<Value>();
1429 if (conflictingAnnos.size() > 1) {
1430 auto diag =
module.emitError("multiple reset annotations on module '")
1431 << module.getName() << "'";
1432 for (
auto &annoAndLoc : conflictingAnnos)
1433 diag.attachNote(annoAndLoc.second)
1434 <<
"conflicting " << annoAndLoc.first.getClassAttr() <<
":";
1440 llvm::dbgs() <<
"Annotated reset for " <<
module.getName() << ": ";
1442 llvm::dbgs() <<
"no domain\n";
1443 else if (
auto arg = dyn_cast<BlockArgument>(reset))
1444 llvm::dbgs() <<
"port " <<
module.getPortName(arg.getArgNumber()) << "\n";
1446 llvm::dbgs() <<
"wire "
1447 << reset.getDefiningOp()->getAttrOfType<StringAttr>(
"name")
1453 return std::optional<Value>(reset);
1465LogicalResult InferResetsPass::buildDomains(CircuitOp circuit) {
1467 llvm::dbgs() <<
"\n";
1468 debugHeader(
"Build full reset domains") <<
"\n\n";
1472 auto &instGraph = getAnalysis<InstanceGraph>();
1473 auto module = dyn_cast<FModuleOp>(*instGraph.getTopLevelNode()->getModule());
1475 LLVM_DEBUG(llvm::dbgs()
1476 <<
"Skipping circuit because main module is no `firrtl.module`");
1479 buildDomains(module,
InstancePath{}, Value{}, instGraph);
1482 bool anyFailed =
false;
1483 for (
auto &it : domains) {
1484 auto module = cast<FModuleOp>(it.first);
1485 auto &domainConflicts = it.second;
1486 if (domainConflicts.size() <= 1)
1490 SmallDenseSet<Value> printedDomainResets;
1491 auto diag =
module.emitError("module '")
1493 << "' instantiated in different reset domains";
1494 for (
auto &it : domainConflicts) {
1495 ResetDomain &domain = it.first;
1496 const auto &path = it.second;
1497 auto inst = path.leaf();
1498 auto loc = path.empty() ?
module.getLoc() : inst.getLoc();
1499 auto ¬e = diag.attachNote(loc);
1503 note <<
"root instance";
1505 note <<
"instance '";
1508 [&](InstanceOpInterface inst) { note << inst.getInstanceName(); },
1509 [&]() { note <<
"/"; });
1515 if (domain.rootReset) {
1517 note <<
" reset domain rooted at '" << nameAndModule.first.getValue()
1518 <<
"' of module '" << nameAndModule.second.getName() <<
"'";
1521 if (printedDomainResets.insert(domain.rootReset).second) {
1522 diag.attachNote(domain.rootReset.getLoc())
1523 <<
"reset domain '" << nameAndModule.first.getValue()
1524 <<
"' of module '" << nameAndModule.second.getName()
1525 <<
"' declared here:";
1528 note <<
" no reset domain";
1531 return failure(anyFailed);
1534void InferResetsPass::buildDomains(FModuleOp module,
1539 llvm::dbgs().indent(indent * 2) <<
"Visiting ";
1540 if (instPath.
empty())
1541 llvm::dbgs() <<
"$root";
1543 llvm::dbgs() << instPath.
leaf().getInstanceName();
1544 llvm::dbgs() <<
" (" <<
module.getName() << ")\n";
1549 auto it = annotatedResets.find(module);
1550 if (it != annotatedResets.end()) {
1553 if (
auto localReset = it->second)
1554 domain = ResetDomain(localReset);
1555 domain.isTop =
true;
1556 }
else if (parentReset) {
1558 domain = ResetDomain(parentReset);
1564 auto &entries = domains[module];
1565 if (llvm::all_of(entries,
1566 [&](
const auto &entry) {
return entry.first != domain; }))
1567 entries.push_back({domain, instPath});
1570 for (
auto *record : *instGraph[module]) {
1571 auto submodule = dyn_cast<FModuleOp>(*record->getTarget()->getModule());
1575 instancePathCache->appendInstance(instPath, record->getInstance());
1576 buildDomains(submodule, childPath, domain.rootReset, instGraph, indent + 1);
1581LogicalResult InferResetsPass::determineImpl() {
1582 auto anyFailed =
false;
1584 llvm::dbgs() <<
"\n";
1585 debugHeader(
"Determine implementation") <<
"\n\n";
1587 for (
auto &it : domains) {
1588 auto module = cast<FModuleOp>(it.first);
1589 auto &domain = it.second.back().first;
1590 if (failed(determineImpl(module, domain)))
1593 return failure(anyFailed);
1611LogicalResult InferResetsPass::determineImpl(FModuleOp module,
1612 ResetDomain &domain) {
1616 LLVM_DEBUG(llvm::dbgs() <<
"Planning reset for " << module.getName() <<
"\n");
1621 LLVM_DEBUG(llvm::dbgs()
1622 <<
"- Rooting at local value " << domain.resetName <<
"\n");
1623 domain.localReset = domain.rootReset;
1624 if (
auto blockArg = dyn_cast<BlockArgument>(domain.rootReset))
1625 domain.existingPort = blockArg.getArgNumber();
1631 auto neededName = domain.resetName;
1632 auto neededType = domain.resetType;
1633 LLVM_DEBUG(llvm::dbgs() <<
"- Looking for existing port " << neededName
1635 auto portNames =
module.getPortNames();
1636 auto *portIt = llvm::find(portNames, neededName);
1639 if (portIt == portNames.end()) {
1640 LLVM_DEBUG(llvm::dbgs() <<
"- Creating new port " << neededName <<
"\n");
1641 domain.resetName = neededName;
1645 LLVM_DEBUG(llvm::dbgs() <<
"- Reusing existing port " << neededName <<
"\n");
1648 auto portNo = std::distance(portNames.begin(), portIt);
1649 auto portType =
module.getPortType(portNo);
1650 if (portType != neededType) {
1651 auto diag = emitError(module.getPortLocation(portNo),
"module '")
1652 <<
module.getName() << "' is in reset domain requiring port '"
1653 << domain.resetName.getValue() << "' to have type "
1654 << domain.resetType << ", but has type " << portType;
1655 diag.attachNote(domain.rootReset.getLoc()) <<
"reset domain rooted here";
1660 domain.existingPort = portNo;
1661 domain.localReset =
module.getArgument(portNo);
1670LogicalResult InferResetsPass::implementFullReset() {
1672 llvm::dbgs() <<
"\n";
1675 for (
auto &it : domains)
1676 if (failed(implementFullReset(cast<FModuleOp>(it.first),
1677 it.second.back().first)))
1687LogicalResult InferResetsPass::implementFullReset(FModuleOp module,
1688 ResetDomain &domain) {
1689 LLVM_DEBUG(llvm::dbgs() <<
"Implementing full reset for " << module.getName()
1694 LLVM_DEBUG(llvm::dbgs()
1695 <<
"- Skipping because module explicitly has no domain\n");
1700 auto *context =
module.getContext();
1702 annotations.addAnnotations(DictionaryAttr::get(
1703 context, NamedAttribute(StringAttr::get(context,
"class"),
1705 annotations.applyToOperation(module);
1708 auto actualReset = domain.localReset;
1709 if (!domain.localReset) {
1710 PortInfo portInfo{domain.resetName,
1714 domain.rootReset.getLoc()};
1715 module.insertPorts({{0, portInfo}});
1716 actualReset =
module.getArgument(0);
1717 LLVM_DEBUG(llvm::dbgs() <<
"- Inserted port " << domain.resetName <<
"\n");
1721 llvm::dbgs() <<
"- Using ";
1722 if (
auto blockArg = dyn_cast<BlockArgument>(actualReset))
1723 llvm::dbgs() <<
"port #" << blockArg.getArgNumber() <<
" ";
1725 llvm::dbgs() <<
"wire/node ";
1731 SmallVector<Operation *> opsToUpdate;
1732 module.walk([&](Operation *op) {
1733 if (isa<InstanceOp, RegOp, RegResetOp>(op))
1734 opsToUpdate.push_back(op);
1741 if (!isa<BlockArgument>(actualReset)) {
1742 mlir::DominanceInfo dom(module);
1747 auto *resetOp = actualReset.getDefiningOp();
1748 if (!opsToUpdate.empty() && !dom.dominates(resetOp, opsToUpdate[0])) {
1749 LLVM_DEBUG(llvm::dbgs()
1750 <<
"- Reset doesn't dominate all uses, needs to be moved\n");
1754 auto nodeOp = dyn_cast<NodeOp>(resetOp);
1755 if (nodeOp && !dom.dominates(nodeOp.getInput(), opsToUpdate[0])) {
1756 LLVM_DEBUG(llvm::dbgs()
1757 <<
"- Promoting node to wire for move: " << nodeOp <<
"\n");
1758 auto builder = ImplicitLocOpBuilder::atBlockBegin(nodeOp.getLoc(),
1759 nodeOp->getBlock());
1760 auto wireOp = builder.create<WireOp>(
1761 nodeOp.getResult().getType(), nodeOp.getNameAttr(),
1762 nodeOp.getNameKindAttr(), nodeOp.getAnnotationsAttr(),
1763 nodeOp.getInnerSymAttr(), nodeOp.getForceableAttr());
1765 nodeOp->replaceAllUsesWith(wireOp);
1766 nodeOp->removeAttr(nodeOp.getInnerSymAttrName());
1770 nodeOp.setNameKind(NameKindEnum::DroppableName);
1771 nodeOp.setAnnotationsAttr(ArrayAttr::get(builder.getContext(), {}));
1772 builder.setInsertionPointAfter(nodeOp);
1773 emitConnect(builder, wireOp.getResult(), nodeOp.getResult());
1775 actualReset = wireOp.getResult();
1776 domain.localReset = wireOp.getResult();
1781 Block *targetBlock = dom.findNearestCommonDominator(
1782 resetOp->getBlock(), opsToUpdate[0]->getBlock());
1784 if (targetBlock != resetOp->getBlock())
1785 llvm::dbgs() <<
"- Needs to be moved to different block\n";
1794 auto getParentInBlock = [](Operation *op,
Block *block) {
1795 while (op && op->getBlock() != block)
1796 op = op->getParentOp();
1799 auto *resetOpInTarget = getParentInBlock(resetOp, targetBlock);
1800 auto *firstOpInTarget = getParentInBlock(opsToUpdate[0], targetBlock);
1806 if (resetOpInTarget->isBeforeInBlock(firstOpInTarget))
1807 resetOp->moveBefore(resetOpInTarget);
1809 resetOp->moveBefore(firstOpInTarget);
1814 for (
auto *op : opsToUpdate)
1815 implementFullReset(op, module, actualReset);
1822void InferResetsPass::implementFullReset(Operation *op, FModuleOp module,
1823 Value actualReset) {
1824 ImplicitLocOpBuilder builder(op->getLoc(), op);
1827 if (
auto instOp = dyn_cast<InstanceOp>(op)) {
1831 auto refModule = instOp.getReferencedModule<FModuleOp>(*instanceGraph);
1834 auto domainIt = domains.find(refModule);
1835 if (domainIt == domains.end())
1837 auto &domain = domainIt->second.back().first;
1840 LLVM_DEBUG(llvm::dbgs()
1841 <<
"- Update instance '" << instOp.getName() <<
"'\n");
1845 if (!domain.localReset) {
1846 LLVM_DEBUG(llvm::dbgs() <<
" - Adding new result as reset\n");
1848 auto newInstOp = instOp.cloneAndInsertPorts(
1850 {domain.resetName, type_cast<FIRRTLBaseType>(actualReset.getType()),
1852 instReset = newInstOp.getResult(0);
1855 instOp.replaceAllUsesWith(newInstOp.getResults().drop_front());
1856 instanceGraph->replaceInstance(instOp, newInstOp);
1859 }
else if (domain.existingPort.has_value()) {
1860 auto idx = *domain.existingPort;
1861 instReset = instOp.getResult(idx);
1862 LLVM_DEBUG(llvm::dbgs() <<
" - Using result #" << idx <<
" as reset\n");
1872 assert(instReset && actualReset);
1873 builder.setInsertionPointAfter(instOp);
1879 if (
auto regOp = dyn_cast<RegOp>(op)) {
1883 LLVM_DEBUG(llvm::dbgs() <<
"- Adding full reset to " << regOp <<
"\n");
1885 auto newRegOp = builder.create<RegResetOp>(
1886 regOp.getResult().getType(), regOp.getClockVal(), actualReset, zero,
1887 regOp.getNameAttr(), regOp.getNameKindAttr(), regOp.getAnnotations(),
1888 regOp.getInnerSymAttr(), regOp.getForceableAttr());
1889 regOp.getResult().replaceAllUsesWith(newRegOp.getResult());
1890 if (regOp.getForceable())
1891 regOp.getRef().replaceAllUsesWith(newRegOp.getRef());
1897 if (
auto regOp = dyn_cast<RegResetOp>(op)) {
1900 if (type_isa<AsyncResetType>(regOp.getResetSignal().getType()) ||
1901 type_isa<UIntType>(actualReset.getType())) {
1902 LLVM_DEBUG(llvm::dbgs() <<
"- Skipping (has reset) " << regOp <<
"\n");
1905 if (failed(regOp.verifyInvariants()))
1906 signalPassFailure();
1909 LLVM_DEBUG(llvm::dbgs() <<
"- Updating reset of " << regOp <<
"\n");
1911 auto reset = regOp.getResetSignal();
1912 auto value = regOp.getResetValue();
1918 builder.setInsertionPointAfterValue(regOp.getResult());
1919 auto mux = builder.create<MuxPrimOp>(reset, value, regOp.getResult());
1923 builder.setInsertionPoint(regOp);
1925 regOp.getResetSignalMutable().assign(actualReset);
1926 regOp.getResetValueMutable().assign(zero);
1930LogicalResult InferResetsPass::verifyNoAbstractReset() {
1931 bool hasAbstractResetPorts =
false;
1932 for (FModuleLike module :
1933 getOperation().
getBodyBlock()->getOps<FModuleLike>()) {
1934 for (
PortInfo port : module.getPorts()) {
1935 if (getBaseOfType<ResetType>(port.type)) {
1936 auto diag = emitError(port.loc)
1937 <<
"a port \"" << port.getName()
1938 <<
"\" with abstract reset type was unable to be "
1939 "inferred by InferResets (is this a top-level port?)";
1940 diag.attachNote(module->getLoc())
1941 <<
"the module with this uninferred reset port was defined here";
1942 hasAbstractResetPorts =
true;
1947 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 InstancePath empty
static Location getLoc(DefSlot slot)
static Block * getBodyBlock(FModuleLike mod)
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.
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.
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)
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 absolute 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)