25#include "mlir/IR/Dominance.h"
26#include "mlir/IR/ImplicitLocOpBuilder.h"
27#include "mlir/IR/Threading.h"
28#include "mlir/Pass/Pass.h"
29#include "llvm/ADT/EquivalenceClasses.h"
30#include "llvm/ADT/SetVector.h"
31#include "llvm/ADT/TypeSwitch.h"
32#include "llvm/Support/Debug.h"
33#include "llvm/Support/LogicalResult.h"
35#define DEBUG_TYPE "infer-resets"
39#define GEN_PASS_DEF_INFERRESETS
40#include "circt/Dialect/FIRRTL/Passes.h.inc"
44using circt::igraph::InstanceOpInterface;
47using llvm::BumpPtrAllocator;
48using llvm::SmallDenseSet;
51using mlir::InferTypeOpInterface;
65 ResetSignal(FieldRef field, FIRRTLBaseType type) : field(field), type(type) {}
66 bool operator<(
const ResetSignal &other)
const {
return field < other.field; }
67 bool operator==(
const ResetSignal &other)
const {
68 return field == other.field;
70 bool operator!=(
const ResetSignal &other)
const {
return !(*
this == other); }
90using ResetDrives = SmallVector<ResetDrive, 1>;
93using ResetNetwork = llvm::iterator_range<
94 llvm::EquivalenceClasses<ResetSignal>::member_iterator>;
97enum class ResetKind { Async, Sync };
107 static bool isEqual(
const ResetSignal &lhs,
const ResetSignal &rhs) {
116 case ResetKind::Async:
117 return os <<
"async";
118 case ResetKind::Sync:
228struct InferResetsPass
229 :
public circt::firrtl::impl::InferResetsBase<InferResetsPass> {
230 void runOnOperation()
override;
231 void runOnOperationInner();
234 using InferResetsBase::InferResetsBase;
235 InferResetsPass(
const InferResetsPass &other) : InferResetsBase(other) {}
240 void traceResets(CircuitOp circuit);
241 void traceResets(FInstanceLike inst);
242 void traceResets(Value dst, Value src, Location loc);
243 void traceResets(Value value);
244 void traceResets(Type dstType, Value dst,
unsigned dstID, Type srcType,
245 Value src,
unsigned srcID, Location loc);
247 LogicalResult inferAndUpdateResets();
248 FailureOr<ResetKind> inferReset(ResetNetwork net);
249 LogicalResult updateReset(ResetNetwork net, ResetKind kind);
250 bool updateReset(FieldRef field, FIRRTLBaseType resetType);
252 LogicalResult verifyNoAbstractReset();
258 ResetNetwork getResetNetwork(ResetSignal signal) {
259 return llvm::make_range(resetClasses.findLeader(signal),
260 resetClasses.member_end());
264 ResetDrives &getResetDrives(ResetNetwork net) {
265 return resetDrives[*net.begin()];
270 ResetSignal guessRoot(ResetNetwork net);
271 ResetSignal guessRoot(ResetSignal signal) {
272 return guessRoot(getResetNetwork(signal));
279 llvm::EquivalenceClasses<ResetSignal> resetClasses;
282 DenseMap<ResetSignal, ResetDrives> resetDrives;
289void InferResetsPass::runOnOperation() {
290 runOnOperationInner();
291 resetClasses = llvm::EquivalenceClasses<ResetSignal>();
293 markAnalysesPreserved<InstanceGraph>();
296void InferResetsPass::runOnOperationInner() {
297 instanceGraph = &getAnalysis<InstanceGraph>();
300 traceResets(getOperation());
303 if (failed(inferAndUpdateResets()))
304 return signalPassFailure();
307 if (failed(verifyNoAbstractReset()))
308 return signalPassFailure();
311ResetSignal InferResetsPass::guessRoot(ResetNetwork net) {
312 ResetDrives &drives = getResetDrives(net);
313 ResetSignal bestSignal = *net.begin();
314 unsigned bestNumDrives = -1;
316 for (
auto signal : net) {
318 if (isa_and_nonnull<InvalidValueOp>(
319 signal.field.getValue().getDefiningOp()))
324 unsigned numDrives = 0;
325 for (
auto &drive : drives)
326 if (drive.dst == signal)
332 if (numDrives < bestNumDrives) {
333 bestNumDrives = numDrives;
352 .
Case<BundleType>([](
auto type) {
354 for (
auto e : type.getElements())
359 [](
auto type) {
return getMaxFieldID(type.getElementType()) + 1; })
360 .Default([](
auto) {
return 0; });
364 assert(index < type.getNumElements());
366 for (
unsigned i = 0; i < index; ++i)
374 assert(type.getNumElements() &&
"Bundle must have >0 fields");
376 for (
const auto &e : llvm::enumerate(type.getElements())) {
378 if (fieldID < numSubfields)
380 fieldID -= numSubfields;
382 assert(
false &&
"field id outside bundle");
388 if (oldType.isGround()) {
403 if (vectorType.getNumElements() == 0)
420 if (
auto arg = dyn_cast<BlockArgument>(value)) {
421 auto module = cast<FModuleOp>(arg.getOwner()->getParentOp());
422 string +=
module.getPortName(arg.getArgNumber());
426 auto *op = value.getDefiningOp();
427 return TypeSwitch<Operation *, bool>(op)
428 .Case<InstanceOp, InstanceChoiceOp, MemOp>([&](
auto op) {
429 string += op.getName();
431 string += op.getPortName(cast<OpResult>(value).getResultNumber());
434 .Case<WireOp, NodeOp, RegOp, RegResetOp>([&](
auto op) {
435 string += op.getName();
438 .Default([](
auto) {
return false; });
442 SmallString<64> name;
447 auto type = value.getType();
453 auto &element = bundleType.getElements()[index];
456 string += element.name.getValue();
459 localID = localID -
getFieldID(bundleType, index);
463 type = vecType.getElementType();
470 llvm_unreachable(
"unsupported type");
482 return TypeSwitch<Type, bool>(type)
484 return type.getRecursiveTypeProperties().hasUninferredReset;
486 .Default([](
auto) {
return false; });
493void InferResetsPass::traceResets(CircuitOp circuit) {
495 llvm::dbgs() <<
"\n";
496 debugHeader(
"Tracing uninferred resets") <<
"\n\n";
499 SmallVector<std::pair<FModuleOp, SmallVector<Operation *>>> moduleToOps;
501 for (
auto module : circuit.getOps<FModuleOp>())
502 moduleToOps.push_back({module, {}});
504 hw::InnerRefNamespace irn{getAnalysis<SymbolTable>(),
505 getAnalysis<hw::InnerSymbolTableCollection>()};
507 mlir::parallelForEach(circuit.getContext(), moduleToOps, [](
auto &e) {
508 e.first.walk([&](Operation *op) {
512 op->getResultTypes(),
513 [](mlir::Type type) { return typeContainsReset(type); }) ||
514 llvm::any_of(op->getOperandTypes(), typeContainsReset))
515 e.second.push_back(op);
519 for (
auto &[_, ops] : moduleToOps)
520 for (auto *op : ops) {
521 TypeSwitch<Operation *>(op)
522 .Case<FConnectLike>([&](
auto op) {
523 traceResets(op.getDest(), op.getSrc(), op.getLoc());
525 .Case<FInstanceLike>([&](
auto op) { traceResets(op); })
526 .Case<RefSendOp>([&](
auto op) {
528 traceResets(op.getType().getType(), op.getResult(), 0,
529 op.getBase().getType().getPassiveType(), op.getBase(),
532 .Case<RefResolveOp>([&](
auto op) {
534 traceResets(op.getType(), op.getResult(), 0,
535 op.getRef().getType().getType(), op.getRef(), 0,
538 .Case<Forceable>([&](Forceable op) {
539 if (
auto node = dyn_cast<NodeOp>(op.getOperation()))
540 traceResets(node.getResult(), node.getInput(), node.getLoc());
542 if (op.isForceable())
543 traceResets(op.getDataType(), op.getData(), 0, op.getDataType(),
544 op.getDataRef(), 0, op.getLoc());
546 .Case<RWProbeOp>([&](RWProbeOp op) {
547 auto ist = irn.lookup(op.getTarget());
550 auto baseType = op.getType().getType();
551 traceResets(baseType, op.getResult(), 0, baseType.getPassiveType(),
552 ref.getValue(), ref.getFieldID(), op.getLoc());
554 .Case<UninferredResetCastOp, ConstCastOp, RefCastOp,
555 UnsafeDomainCastOp>([&](
auto op) {
556 traceResets(op.getResult(), op.getInput(), op.getLoc());
558 .Case<InvalidValueOp>([&](
auto op) {
567 auto type = op.getType();
570 LLVM_DEBUG(llvm::dbgs() <<
"Uniquify " << op <<
"\n");
571 ImplicitLocOpBuilder builder(op->getLoc(), op);
573 llvm::make_early_inc_range(llvm::drop_begin(op->getUses()))) {
579 auto newOp = InvalidValueOp::create(builder, type);
584 .Case<SubfieldOp>([&](
auto op) {
587 BundleType bundleType = op.getInput().getType();
588 auto index = op.getFieldIndex();
589 traceResets(op.getType(), op.getResult(), 0,
590 bundleType.getElements()[index].type, op.getInput(),
594 .Case<SubindexOp, SubaccessOp>([&](
auto op) {
607 FVectorType vectorType = op.getInput().getType();
608 traceResets(op.getType(), op.getResult(), 0,
609 vectorType.getElementType(), op.getInput(),
613 .Case<RefSubOp>([&](RefSubOp op) {
615 auto aggType = op.getInput().getType().getType();
616 uint64_t fieldID = TypeSwitch<FIRRTLBaseType, uint64_t>(aggType)
617 .Case<FVectorType>([](
auto type) {
620 .Case<BundleType>([&](
auto type) {
623 traceResets(op.getType(), op.getResult(), 0,
624 op.getResult().getType(), op.getInput(), fieldID,
632void InferResetsPass::traceResets(FInstanceLike inst) {
633 LLVM_DEBUG(llvm::dbgs() <<
"Visiting instance " << inst.getInstanceName()
635 auto moduleNames = inst.getReferencedModuleNamesAttr();
636 for (
auto moduleName : moduleNames.getAsRange<StringAttr>()) {
637 auto *node = instanceGraph->
lookup(moduleName);
638 auto module = dyn_cast<FModuleOp>(*node->getModule());
643 for (
const auto &it : llvm::enumerate(inst->getResults())) {
644 Value dstPort =
module.getArgument(it.index());
645 Value srcPort = it.value();
646 if (module.getPortDirection(it.index()) == Direction::Out)
647 std::swap(dstPort, srcPort);
648 traceResets(dstPort, srcPort, it.value().getLoc());
655void InferResetsPass::traceResets(Value dst, Value src, Location loc) {
657 traceResets(dst.getType(), dst, 0, src.getType(), src, 0, loc);
662void InferResetsPass::traceResets(Type dstType, Value dst,
unsigned dstID,
663 Type srcType, Value src,
unsigned srcID,
667 for (
unsigned dstIdx = 0, e = dstBundle.getNumElements(); dstIdx < e;
669 auto dstField = dstBundle.getElements()[dstIdx].name;
670 auto srcIdx = srcBundle.getElementIndex(dstField);
673 auto &dstElt = dstBundle.getElements()[dstIdx];
674 auto &srcElt = srcBundle.getElements()[*srcIdx];
676 traceResets(srcElt.type, src, srcID +
getFieldID(srcBundle, *srcIdx),
677 dstElt.type, dst, dstID +
getFieldID(dstBundle, dstIdx),
680 traceResets(dstElt.type, dst, dstID +
getFieldID(dstBundle, dstIdx),
681 srcElt.type, src, srcID +
getFieldID(srcBundle, *srcIdx),
690 auto srcElType = srcVector.getElementType();
691 auto dstElType = dstVector.getElementType();
704 traceResets(dstElType, dst, dstID +
getFieldID(dstVector), srcElType, src,
712 return traceResets(dstRef.getType(), dst, dstID, srcRef.getType(), src,
719 if (!dstBase || !srcBase)
724 FieldRef dstField(dst, dstID);
725 FieldRef srcField(src, srcID);
726 LLVM_DEBUG(llvm::dbgs() <<
"Visiting driver '" << dstField <<
"' = '"
727 << srcField <<
"' (" << dstType <<
" = " << srcType
733 ResetSignal dstLeader =
734 *resetClasses.findLeader(resetClasses.insert({dstField, dstBase}));
735 ResetSignal srcLeader =
736 *resetClasses.findLeader(resetClasses.insert({srcField, srcBase}));
739 ResetSignal unionLeader = *resetClasses.unionSets(dstLeader, srcLeader);
740 assert(unionLeader == dstLeader || unionLeader == srcLeader);
745 if (dstLeader != srcLeader) {
746 auto &unionDrives = resetDrives[unionLeader];
747 auto mergedDrivesIt =
748 resetDrives.find(unionLeader == dstLeader ? srcLeader : dstLeader);
749 if (mergedDrivesIt != resetDrives.end()) {
750 unionDrives.append(mergedDrivesIt->second);
751 resetDrives.erase(mergedDrivesIt);
757 resetDrives[unionLeader].push_back(
758 {{dstField, dstBase}, {srcField, srcBase}, loc});
765LogicalResult InferResetsPass::inferAndUpdateResets() {
767 llvm::dbgs() <<
"\n";
770 for (
const auto &it : resetClasses) {
773 ResetNetwork net = resetClasses.members(*it);
776 auto kind = inferReset(net);
781 if (failed(updateReset(net, *kind)))
787FailureOr<ResetKind> InferResetsPass::inferReset(ResetNetwork net) {
788 LLVM_DEBUG(llvm::dbgs() <<
"Inferring reset network with "
789 << std::distance(net.begin(), net.end())
793 unsigned asyncDrives = 0;
794 unsigned syncDrives = 0;
795 unsigned invalidDrives = 0;
796 for (ResetSignal signal : net) {
803 isa_and_nonnull<InvalidValueOp>(
804 signal.field.getValue().getDefiningOp()))
807 LLVM_DEBUG(llvm::dbgs() <<
"- Found " << asyncDrives <<
" async, "
808 << syncDrives <<
" sync, " << invalidDrives
809 <<
" invalid drives\n");
812 if (asyncDrives == 0 && syncDrives == 0 && invalidDrives == 0) {
813 ResetSignal root = guessRoot(net);
814 auto diag = mlir::emitError(root.field.
getValue().getLoc())
815 <<
"reset network never driven with concrete type";
816 for (ResetSignal signal : net)
817 diag.attachNote(signal.field.getLoc()) <<
"here: ";
822 if (asyncDrives > 0 && syncDrives > 0) {
823 ResetSignal root = guessRoot(net);
824 bool majorityAsync = asyncDrives >= syncDrives;
825 auto diag = mlir::emitError(root.field.
getValue().getLoc())
827 SmallString<32> fieldName;
829 diag <<
" \"" << fieldName <<
"\"";
830 diag <<
" simultaneously connected to async and sync resets";
831 diag.attachNote(root.field.
getValue().getLoc())
832 <<
"majority of connections to this reset are "
833 << (majorityAsync ?
"async" :
"sync");
834 for (
auto &drive : getResetDrives(net)) {
839 diag.attachNote(drive.loc)
849 auto kind = (asyncDrives ? ResetKind::Async : ResetKind::Sync);
850 LLVM_DEBUG(llvm::dbgs() <<
"- Inferred as " << kind <<
"\n");
858LogicalResult InferResetsPass::updateReset(ResetNetwork net, ResetKind kind) {
859 LLVM_DEBUG(llvm::dbgs() <<
"Updating reset network with "
860 << std::distance(net.begin(), net.end())
861 <<
" nodes to " << kind <<
"\n");
864 FIRRTLBaseType resetType;
865 if (kind == ResetKind::Async)
866 resetType = AsyncResetType::get(&getContext());
868 resetType = UIntType::get(&getContext(), 1);
873 SmallSetVector<Operation *, 16> worklist;
874 SmallDenseSet<Operation *> moduleWorklist;
875 SmallDenseSet<std::pair<Operation *, Operation *>> extmoduleWorklist;
876 for (
auto signal : net) {
877 Value value = signal.field.getValue();
878 if (!isa<BlockArgument>(value) &&
879 !isa_and_nonnull<WireOp, RegOp, RegResetOp, FInstanceLike,
880 InvalidValueOp, ConstCastOp, RefCastOp,
881 UninferredResetCastOp, RWProbeOp, AsResetPrimOp>(
882 value.getDefiningOp()))
884 if (updateReset(signal.field, resetType)) {
885 for (
auto *user : value.getUsers())
886 worklist.insert(user);
887 if (
auto blockArg = dyn_cast<BlockArgument>(value)) {
888 moduleWorklist.insert(blockArg.getOwner()->getParentOp());
892 TypeSwitch<Operation *>(value.getDefiningOp())
893 .Case<FInstanceLike>([&](FInstanceLike op) {
894 for (
auto moduleName : op.getReferencedModuleNamesAttr()) {
895 auto *node = instanceGraph->
lookup(cast<StringAttr>(moduleName));
896 if (
auto refModule = dyn_cast<FExtModuleOp>(*node->getModule()))
897 extmoduleWorklist.insert({refModule, op.getOperation()});
900 .Case<UninferredResetCastOp>([&](
auto op) {
901 op.replaceAllUsesWith(op.getInput());
904 .Case<AsResetPrimOp>([&](
auto op) {
907 Value result = op.getInput();
909 ImplicitLocOpBuilder builder(op.getLoc(), op);
910 result = AsAsyncResetPrimOp::create(builder, op.getInput());
912 op.replaceAllUsesWith(result);
922 while (!worklist.empty()) {
923 auto *wop = worklist.pop_back_val();
924 SmallVector<Type, 2> types;
925 if (
auto op = dyn_cast<InferTypeOpInterface>(wop)) {
927 SmallVector<Type, 2> types;
928 if (failed(op.inferReturnTypes(op->getContext(), op->getLoc(),
929 op->getOperands(), op->getAttrDictionary(),
930 op->getPropertiesStorage(),
931 op->getRegions(), types)))
936 for (
auto it : llvm::zip(op->getResults(), types)) {
937 auto newType = std::get<1>(it);
938 if (std::get<0>(it).getType() == newType)
940 std::get<0>(it).setType(newType);
941 for (
auto *user : std::get<0>(it).getUsers())
942 worklist.insert(user);
944 LLVM_DEBUG(llvm::dbgs() <<
"- Inferred " << *op <<
"\n");
945 }
else if (
auto uop = dyn_cast<UninferredResetCastOp>(wop)) {
946 for (
auto *user : uop.getResult().getUsers())
947 worklist.insert(user);
948 uop.replaceAllUsesWith(uop.getInput());
949 LLVM_DEBUG(llvm::dbgs() <<
"- Inferred " << uop <<
"\n");
955 for (
auto *op : moduleWorklist) {
956 auto module = dyn_cast<FModuleOp>(op);
960 SmallVector<Attribute> argTypes;
961 argTypes.reserve(module.getNumPorts());
962 for (
auto arg : module.getArguments())
963 argTypes.push_back(TypeAttr::get(arg.getType()));
965 module.setPortTypesAttr(ArrayAttr::get(op->getContext(), argTypes));
966 LLVM_DEBUG(llvm::dbgs()
967 <<
"- Updated type of module '" << module.getName() <<
"'\n");
971 for (
auto [mod, instOp] : extmoduleWorklist) {
972 auto module = cast<FExtModuleOp>(mod);
974 SmallVector<Attribute> types;
975 for (
auto type : instOp->getResultTypes())
976 types.push_back(TypeAttr::get(type));
978 module.setPortTypesAttr(ArrayAttr::get(module->getContext(), types));
979 LLVM_DEBUG(llvm::dbgs()
980 <<
"- Updated type of extmodule '" << module.getName() <<
"'\n");
990 if (oldType.isGround()) {
998 SmallVector<BundleType::BundleElement> fields(bundleType.begin(),
1001 fields[index].type, fieldID -
getFieldID(bundleType, index), fieldType);
1002 return BundleType::get(oldType.getContext(), fields, bundleType.isConst());
1007 auto newType =
updateType(vectorType.getElementType(),
1008 fieldID -
getFieldID(vectorType), fieldType);
1009 return FVectorType::get(newType, vectorType.getNumElements(),
1010 vectorType.isConst());
1013 llvm_unreachable(
"unknown aggregate type");
1018bool InferResetsPass::updateReset(FieldRef field, FIRRTLBaseType resetType) {
1021 FIRRTLType newType =
mapBaseType(oldType, [&](
auto base) {
1026 if (oldType == newType)
1028 LLVM_DEBUG(llvm::dbgs() <<
"- Updating '" << field <<
"' from " << oldType
1029 <<
" to " << newType <<
"\n");
1034LogicalResult InferResetsPass::verifyNoAbstractReset() {
1035 bool hasAbstractResetPorts =
false;
1036 for (FModuleLike module :
1037 getOperation().
getBodyBlock()->getOps<FModuleLike>()) {
1038 for (PortInfo port : module.getPorts()) {
1040 auto diag = emitError(port.loc)
1041 <<
"a port \"" << port.getName()
1042 <<
"\" with abstract reset type was unable to be "
1043 "inferred by InferResets (is this a top-level port?)";
1044 diag.attachNote(module->getLoc())
1045 <<
"the module with this uninferred reset port was defined here";
1046 hasAbstractResetPorts =
true;
1051 if (hasAbstractResetPorts)
assert(baseType &&"element must be base type")
static void getDeclName(Value value, SmallString< 64 > &string, bool nameSafe)
Get the string name of a value which is a direct child of a declaration op.
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 bool typeContainsReset(Type type)
Check whether a type contains a ResetType.
static unsigned getMaxFieldID(FIRRTLBaseType type)
false return failure()
Parse an optional keyword or string and set instance into 'result'.
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.
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.
InstanceGraphNode * lookup(ModuleOpInterface op)
Look up an InstanceGraphNode for a module.
An instance path composed of a series of instances.
BaseTy type_cast(Type type)
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.
T getBaseOfType(Type type)
Get base type if isa<> the requested type, else null.
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.
BaseTy type_dyn_cast(Type type)
std::map< std::string, std::set< std::string > > InstanceGraph
Iterates over the handshake::FuncOp's in the program to build an instance graph.
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(const llvm::Twine &str, unsigned width=80)
Write a "header"-like string to the debug stream with a certain width.
bool operator!=(uint64_t a, const FVInt &b)
A data structure that caches and provides paths to module instances in the IR.
static bool isEqual(const ResetSignal &lhs, const ResetSignal &rhs)
static unsigned getHashValue(const ResetSignal &x)