39 #include "mlir/Pass/Pass.h"
40 #include "llvm/ADT/DenseMap.h"
41 #include "llvm/ADT/EquivalenceClasses.h"
42 #include "llvm/ADT/PostOrderIterator.h"
44 #define DEBUG_TYPE "check-comb-loops"
48 #define GEN_PASS_DEF_CHECKCOMBLOOPS
49 #include "circt/Dialect/FIRRTL/Passes.h.inc"
53 using namespace circt;
54 using namespace firrtl;
67 SmallVector<std::pair<FieldRef, SmallVector<unsigned>>, 64>;
72 const DenseMap<FModuleLike, DrivenBysMapType> &otherModulePortPaths,
74 : module(module), instanceGraph(instanceGraph),
75 modulePortPaths(otherModulePortPaths), portPaths(thisModulePortPaths) {}
78 LLVM_DEBUG(llvm::dbgs() <<
"\n processing module :" << module.getName());
79 constructConnectivityGraph(module);
80 return dfsTraverse(drivenBy);
84 LLVM_DEBUG(llvm::dbgs() <<
"\n Module :" << module.getName());
87 for (
auto port : module.getArguments()) {
88 if (module.getPortDirection(port.getArgNumber()) !=
Direction::Out)
92 getOrAddNode(FieldRef(port, index));
96 walk(module, [&](Operation *op) {
97 llvm::TypeSwitch<Operation *>(op)
98 .Case<hw::CombDataFlow>([&](hw::CombDataFlow df) {
101 for (
auto [dest, source] : df.computeDataFlow())
102 addDrivenBy(dest, source);
105 .Case<Forceable>([&](Forceable forceableOp) {
107 if (
auto node = dyn_cast<NodeOp>(op))
108 recordDataflow(node.getData(), node.getInput());
109 if (!forceableOp.isForceable() ||
110 forceableOp.getDataRef().use_empty())
112 auto data = forceableOp.getData();
113 auto ref = forceableOp.getDataRef();
115 recordDataflow(ref,
data);
116 recordProbe(
data, ref);
118 .Case<RefSendOp>([&](RefSendOp send) {
119 recordDataflow(send.getResult(), send.getBase());
121 .Case<RefDefineOp>([&](RefDefineOp def) {
123 recordDataflow(def.getDest(), def.getSrc());
124 if (!def.getDest().getType().getForceable())
127 probesReferToSameData(def.getSrc(), def.getDest());
129 .Case<RefForceOp, RefForceInitialOp>(
130 [&](
auto ref) { handleRefForce(ref.getDest(), ref.getSrc()); })
131 .Case<InstanceOp>([&](
auto inst) { handleInstanceOp(inst); })
132 .Case<SubindexOp>([&](SubindexOp sub) {
133 recordValueRefersToFieldRef(
135 sub.getInput().getType().base().getFieldID(sub.getIndex()),
138 .Case<SubfieldOp>([&](SubfieldOp sub) {
139 recordValueRefersToFieldRef(
141 sub.getInput().getType().base().getFieldID(sub.getFieldIndex()),
144 .Case<SubaccessOp>([&](SubaccessOp sub) {
145 auto vecType = sub.getInput().getType().base();
146 auto input = sub.getInput();
147 auto result = sub.getResult();
150 for (
size_t index = 0; index < vecType.getNumElements(); ++index)
151 recordValueRefersToFieldRef(input, vecType.getFieldID(index),
154 .Case<RefSubOp>([&](RefSubOp sub) {
155 size_t fieldID = TypeSwitch<FIRRTLBaseType, size_t>(
156 sub.getInput().getType().getType())
157 .Case<FVectorType, BundleType>([&](
auto type) {
158 return type.getFieldID(sub.getIndex());
160 recordValueRefersToFieldRef(sub.getInput(), fieldID,
163 .Case<BundleCreateOp, VectorCreateOp>([&](
auto op) {
164 auto type = op.getType();
165 auto res = op.getResult();
166 auto getFieldId = [&](
unsigned index) {
168 TypeSwitch<FIRRTLBaseType, size_t>(type)
169 .Case<FVectorType, BundleType>(
170 [&](
auto type) {
return type.getFieldID(index); });
173 for (
auto [index, v] : llvm::enumerate(op.getOperands()))
174 recordValueRefersToFieldRef(res, getFieldId(index), v);
176 .Case<FConnectLike>([&](FConnectLike
connect) {
179 .Default([&](Operation *op) {
182 for (
auto res : op->getResults())
183 for (
auto src : op->getOperands())
184 recordDataflow(res, src);
192 auto iter = valToFieldRefs.find(v);
193 if (iter == valToFieldRefs.end())
194 return getOrAddNode({v, 0});
195 return getOrAddNode(*iter->second.begin());
200 auto iter = nodes.find(f);
201 if (iter != nodes.end())
204 auto id = drivenBy.size();
208 drivenBy.push_back({f, {}});
216 auto srcNode = getOrAddNode(src);
217 auto dstNode = getOrAddNode(dst);
218 drivenBy[dstNode].second.push_back(srcNode);
224 if (
auto *def = srcVal.getDefiningOp())
225 if (def->hasTrait<OpTrait::ConstantLike>())
230 auto dstIt = valToFieldRefs.find(dstVal);
231 auto srcIt = valToFieldRefs.find(srcVal);
236 if (dstIt == valToFieldRefs.end())
237 if (
auto *def = dstVal.getDefiningOp())
238 if (isa<RegOp, RegResetOp, SubaccessOp, SubfieldOp, SubindexOp,
246 if (!(srcValType && dstValType))
247 return addDrivenBy({dstVal, 0}, {srcVal, 0});
253 if (dstValType && !dstValType.isGround())
258 if (srcValType == dstValType) {
265 }
else if (srcValType && !srcValType.isGround())
275 addDrivenBy(dst, src);
284 if (dstIt == valToFieldRefs.end() && srcIt == valToFieldRefs.end())
285 return addDef({dstVal, 0}, {srcVal, 0});
288 if (dstIt == valToFieldRefs.end() && srcIt != valToFieldRefs.end()) {
289 llvm::for_each(srcIt->getSecond(), [&](
const FieldRef &srcField) {
290 addDef(FieldRef(dstVal, 0), srcField);
296 if (dstIt != valToFieldRefs.end() && srcIt == valToFieldRefs.end()) {
297 llvm::for_each(dstIt->getSecond(), [&](
const FieldRef &dstField) {
298 addDef(dstField, FieldRef(srcVal, 0));
304 llvm::for_each(srcIt->second, [&](
const FieldRef &srcField) {
305 llvm::for_each(dstIt->second, [&](const FieldRef &dstField) {
306 addDef(dstField, srcField);
313 recordDataflow(dstProbe, srcVal);
314 auto dstNode = getOrAddNode(dstProbe);
316 auto leader = rwProbeClasses.findLeader(dstNode);
317 if (leader == rwProbeClasses.member_end())
319 auto iter = rwProbeRefersTo.find(*leader);
324 if (iter == rwProbeRefersTo.end())
327 assert(iter != rwProbeRefersTo.end());
328 if (iter->second != dstNode)
329 drivenBy[iter->second].second.push_back(getOrAddNode(srcVal));
336 auto refMod = inst.getReferencedModule<FModuleOp>(instanceGraph);
340 auto modulePaths = modulePortPaths.find(refMod);
341 if (modulePaths == modulePortPaths.end())
356 for (
auto &path : modulePaths->second) {
357 auto modSinkPortField = path.first;
359 cast<BlockArgument>(modSinkPortField.getValue()).getArgNumber();
360 FieldRef sinkPort(inst.getResult(sinkArgNum),
361 modSinkPortField.getFieldID());
362 auto sinkNode = getOrAddNode(sinkPort);
363 bool sinkPortIsForceable =
false;
364 if (
auto refResultType =
365 type_dyn_cast<RefType>(inst.getResult(sinkArgNum).getType()))
366 sinkPortIsForceable = refResultType.getForceable();
368 DenseSet<unsigned> setOfEquivalentRWProbes;
369 unsigned minArgNum = sinkArgNum;
370 unsigned basePortNode = sinkNode;
371 for (
auto &modSrcPortField : path.second) {
373 cast<BlockArgument>(modSrcPortField.getValue()).getArgNumber();
375 if (modSrcPortField == modSinkPortField)
378 FieldRef srcPort(inst.getResult(srcArgNum),
379 modSrcPortField.getFieldID());
380 bool srcPortIsForceable =
false;
381 if (
auto refResultType =
382 type_dyn_cast<RefType>(inst.getResult(srcArgNum).getType()))
383 srcPortIsForceable = refResultType.getForceable();
386 if (sinkPortIsForceable && srcPortIsForceable) {
387 auto srcNode = getOrAddNode(srcPort);
389 if (rwProbeClasses.findLeader(srcNode) !=
390 rwProbeClasses.member_end() &&
391 rwProbeClasses.findLeader(sinkNode) ==
392 rwProbeClasses.findLeader(srcNode))
395 auto drivenBysToSrcPort = modulePaths->second.find(modSrcPortField);
396 if (drivenBysToSrcPort != modulePaths->second.end())
397 if (llvm::find(drivenBysToSrcPort->second, modSinkPortField) !=
398 drivenBysToSrcPort->second.end()) {
404 setOfEquivalentRWProbes.insert(srcNode);
405 if (minArgNum > srcArgNum) {
408 minArgNum = srcArgNum;
409 basePortNode = srcNode;
414 addDrivenBy(sinkPort, srcPort);
416 if (setOfEquivalentRWProbes.empty())
420 for (
auto probe : setOfEquivalentRWProbes)
421 rwProbeClasses.unionSets(probe, sinkNode);
426 auto leader = rwProbeClasses.getLeaderValue(sinkNode);
427 rwProbeRefersTo[leader] = basePortNode;
429 setOfEquivalentRWProbes.insert(sinkNode);
431 for (
auto probe : setOfEquivalentRWProbes)
432 if (probe != basePortNode)
433 drivenBy[probe].second.push_back(basePortNode);
442 auto it = valToFieldRefs.find(base);
443 if (it != valToFieldRefs.end()) {
446 SmallVector<FieldRef> entry;
447 for (
auto &sub : it->second)
448 entry.emplace_back(sub.getValue(), sub.getFieldID() + fieldID);
450 valToFieldRefs[result].append(entry.begin(), entry.end());
454 if (
auto *def = base.getDefiningOp()) {
455 if (isa<RegOp, RegResetOp, SubfieldOp, SubindexOp, SubaccessOp>(def))
458 valToFieldRefs[result].emplace_back(base, fieldID);
464 auto numNodes = graph.size();
465 SmallVector<bool> onStack(numNodes,
false);
466 SmallVector<unsigned> dfsStack;
468 auto hasCycle = [&](
unsigned rootNode, DenseSet<unsigned> &visited,
469 bool recordPortPaths =
false) {
470 if (visited.contains(rootNode))
472 dfsStack.push_back(rootNode);
474 while (!dfsStack.empty()) {
475 auto currentNode = dfsStack.back();
477 if (!visited.contains(currentNode)) {
478 visited.insert(currentNode);
479 onStack[currentNode] =
true;
480 LLVM_DEBUG(llvm::dbgs()
482 << drivenBy[currentNode].first.getValue().getType()
483 << drivenBy[currentNode].first.getValue() <<
","
484 << drivenBy[currentNode].first.getFieldID() <<
"\n"
485 <<
getName(drivenBy[currentNode].first));
487 FieldRef currentF = drivenBy[currentNode].first;
488 if (recordPortPaths && currentNode != rootNode) {
489 if (isa<mlir::BlockArgument>(currentF.
getValue()))
490 portPaths[drivenBy[rootNode].first].insert(currentF);
493 addToPortPathsIfRWProbe(currentNode,
494 portPaths[drivenBy[rootNode].first]);
497 onStack[currentNode] =
false;
501 for (
auto neighbor : graph[currentNode].second) {
502 if (!visited.contains(neighbor)) {
503 dfsStack.push_back(neighbor);
504 }
else if (onStack[neighbor]) {
506 SmallVector<FieldRef, 16> path;
507 auto loopNode = neighbor;
510 SmallVector<unsigned>::iterator it =
511 llvm::find_if(drivenBy[loopNode].second,
512 [&](
unsigned node) {
return onStack[node]; });
513 if (it == drivenBy[loopNode].second.end())
516 path.push_back(drivenBy[loopNode].first);
518 }
while (loopNode != neighbor);
520 reportLoopFound(path, drivenBy[neighbor].first.getLoc());
528 DenseSet<unsigned> visited;
529 for (
unsigned node = 0; node < graph.size(); ++node) {
531 if (
auto arg = dyn_cast<BlockArgument>(drivenBy[node].first.getValue()))
532 if (module.getPortDirection(arg.getArgNumber()) ==
Direction::Out) {
539 if (hasCycle(node, visited,
isPort).failed())
546 auto errorDiag = mlir::emitError(
547 module.getLoc(),
"detected combinational cycle in a FIRRTL module");
549 std::string firstName;
552 return !firstName.empty();
554 if (it == path.end()) {
555 errorDiag.append(
", but unable to find names for any involved values.");
556 errorDiag.attachNote(loc) <<
"cycle detected here";
562 auto n =
getName(*findStartIt);
563 if (!n.empty() && n < firstName) {
568 errorDiag.append(
", sample path: ");
570 bool lastWasDots =
false;
571 errorDiag << module.getName() <<
".{" <<
getName(*it);
572 for (
auto v : llvm::concat<FieldRef>(
573 llvm::make_range(std::next(it), path.end()),
574 llvm::make_range(path.begin(), std::next(it)))) {
577 errorDiag <<
" <- " << name;
581 errorDiag <<
" <- ...";
590 llvm::dbgs() <<
"\n Connectivity Graph ==>";
591 for (
const auto &[index, i] : llvm::enumerate(drivenBy)) {
592 llvm::dbgs() <<
"\n ===>dst:" <<
getName(i.first)
593 <<
"::" << i.first.getValue();
594 for (
auto s : i.second)
595 llvm::dbgs() <<
"<---" <<
getName(drivenBy[s].first)
596 <<
"::" << drivenBy[s].first.getValue();
599 llvm::dbgs() <<
"\n Value to FieldRef :";
600 for (
const auto &fields : valToFieldRefs) {
601 llvm::dbgs() <<
"\n Val:" << fields.first;
602 for (
auto f : fields.second)
603 llvm::dbgs() <<
", FieldRef:" <<
getName(f) <<
"::" << f.getFieldID();
605 llvm::dbgs() <<
"\n Port paths:";
606 for (
const auto &p : portPaths) {
607 llvm::dbgs() <<
"\n Output :" <<
getName(p.first);
608 for (
auto i : p.second)
609 llvm::dbgs() <<
"\n Input :" <<
getName(i);
611 llvm::dbgs() <<
"\n rwprobes:";
612 for (
auto node : rwProbeRefersTo) {
613 llvm::dbgs() <<
"\n node:" <<
getName(drivenBy[node.first].first)
614 <<
"=> probe:" <<
getName(drivenBy[node.second].first);
616 for (
auto i = rwProbeClasses.begin(), e = rwProbeClasses.end(); i != e;
621 llvm::interleave(llvm::make_range(rwProbeClasses.member_begin(i),
622 rwProbeClasses.member_end()),
624 llvm::dbgs() <<
"\n dataflow at leader::" << i->getData() <<
"\n =>"
625 << rwProbeRefersTo[i->getData()];
626 llvm::dbgs() <<
"\n Done\n";
632 auto refNode = getOrAddNode(ref);
633 auto dataNode = getOrAddNode(
data);
634 rwProbeRefersTo[rwProbeClasses.getOrInsertLeaderValue(refNode)] = dataNode;
640 auto p1Node = getOrAddNode(probe1);
641 auto p2Node = getOrAddNode(probe2);
642 rwProbeClasses.unionSets(p1Node, p2Node);
646 DenseSet<FieldRef> &inputPortPaths) {
648 auto baseFieldRef = drivenBy[srcNode].first;
649 if (
auto defOp = dyn_cast_or_null<Forceable>(baseFieldRef.getDefiningOp()))
650 if (defOp.isForceable() && !defOp.getDataRef().use_empty()) {
653 rwProbeClasses.getLeaderValue(getOrAddNode(defOp.getDataRef()));
657 llvm::make_range(rwProbeClasses.member_begin(
658 rwProbeClasses.findValue(rwProbeNode)),
659 rwProbeClasses.member_end())) {
660 auto probeVal = drivenBy[probe].first;
663 if (isa<BlockArgument>(probeVal.getValue())) {
664 inputPortPaths.insert(probeVal);
709 auto &instanceGraph = getAnalysis<InstanceGraph>();
710 DenseMap<FModuleLike, DrivenBysMapType> modulePortPaths;
715 for (
auto *igNode : llvm::post_order<InstanceGraph *>(&instanceGraph)) {
716 if (
auto module = dyn_cast<FModuleOp>(*igNode->getModule())) {
718 modulePortPaths[module]);
720 return signalPassFailure();
724 markAllAnalysesPreserved();
729 return std::make_unique<CheckCombLoopsPass>();
assert(baseType &&"element must be base type")
static bool isPort(Value value)
Returns whether this value is either (1) a port on a ComponentOp or (2) a port on a cell interface.
DenseMap< FieldRef, DenseSet< FieldRef > > DrivenBysMapType
This pass constructs a local graph for each module to detect combinational cycles.
void runOnOperation() override
LogicalResult processModule()
unsigned getOrAddNode(Value v)
DenseMap< FieldRef, size_t > nodes
Map of FieldRef to its corresponding graph node.
SmallVector< std::pair< FieldRef, SmallVector< unsigned > >, 64 > DrivenByGraphType
Adjacency list representation.
DenseMap< Value, SmallVector< FieldRef > > valToFieldRefs
Map of values to the set of all FieldRefs (same base) that this may be directly derived from through ...
void handleRefForce(Value dstProbe, Value srcVal)
const DenseMap< FModuleLike, DrivenBysMapType > & modulePortPaths
Comb paths that exist between module ports.
static std::string getName(FieldRef v)
void constructConnectivityGraph(FModuleOp module)
DrivenBysMapType & portPaths
The comb paths between the ports of this module.
void recordValueRefersToFieldRef(Value base, unsigned fieldID, Value result)
void addDrivenBy(FieldRef dst, FieldRef src)
unsigned getOrAddNode(FieldRef f)
void recordProbe(Value data, Value ref)
InstanceGraph & instanceGraph
DrivenByGraphType drivenBy
This is an adjacency list representation of the connectivity graph.
void recordDataflow(Value dstVal, Value srcVal)
void handleInstanceOp(InstanceOp inst)
void probesReferToSameData(Value probe1, Value probe2)
DenseMap< unsigned, unsigned > rwProbeRefersTo
The base value that the RWProbe refers to.
llvm::EquivalenceClasses< unsigned > rwProbeClasses
An eqv class of all the RWProbes that refer to the same base value.
void reportLoopFound(SmallVectorImpl< FieldRef > &path, Location loc)
LogicalResult dfsTraverse(const DrivenByGraphType &graph)
void addToPortPathsIfRWProbe(unsigned srcNode, DenseSet< FieldRef > &inputPortPaths)
DiscoverLoops(FModuleOp module, InstanceGraph &instanceGraph, const DenseMap< FModuleLike, DrivenBysMapType > &otherModulePortPaths, DrivenBysMapType &thisModulePortPaths)
This class represents a reference to a specific field or element of an aggregate value.
FieldRef getSubField(unsigned subFieldID) const
Get a reference to a subfield.
Value getValue() const
Get the Value which created this location.
This graph tracks modules and where they are instantiated.
def connect(destination, source)
std::unique_ptr< mlir::Pass > createCheckCombLoopsPass()
FIRRTLBaseType getBaseType(Type type)
If it is a base type, return it as is.
void walkGroundTypes(FIRRTLType firrtlType, llvm::function_ref< void(uint64_t, FIRRTLBaseType, bool)> fn)
Walk leaf ground types in the firrtlType and apply the function fn.
std::pair< std::string, bool > getFieldName(const FieldRef &fieldRef, bool nameSafe=false)
Get a string identifier representing the FieldRef.
StringAttr getName(ArrayAttr names, size_t idx)
Return the name at the specified index of the ArrayAttr or null if it cannot be determined.
The InstanceGraph op interface, see InstanceGraphInterface.td for more details.