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"
54using namespace firrtl;
67 SmallVector<std::pair<FieldRef, SmallVector<unsigned>>, 64>;
72 const DenseMap<FModuleLike, DrivenBysMapType> &otherModulePortPaths,
78 LLVM_DEBUG(llvm::dbgs() <<
"\n processing module :" << module.getName());
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())
104 .Case<Forceable>([&](Forceable forceableOp) {
106 if (
auto node = dyn_cast<NodeOp>(op))
108 if (!forceableOp.isForceable() ||
109 forceableOp.getDataRef().use_empty())
111 auto data = forceableOp.getData();
112 auto ref = forceableOp.getDataRef();
117 .Case<RefSendOp>([&](RefSendOp send) {
120 .Case<RefDefineOp>([&](RefDefineOp def) {
123 if (!def.getDest().getType().getForceable())
128 .Case<RefForceOp, RefForceInitialOp>(
131 .Case<SubindexOp>([&](SubindexOp sub) {
134 sub.getInput().getType().base().getFieldID(sub.getIndex()),
137 .Case<SubfieldOp>([&](SubfieldOp sub) {
140 sub.getInput().getType().base().getFieldID(sub.getFieldIndex()),
143 .Case<SubaccessOp>([&](SubaccessOp sub) {
144 auto vecType = sub.getInput().getType().base();
145 auto input = sub.getInput();
146 auto result = sub.getResult();
149 for (
size_t index = 0; index < vecType.getNumElements(); ++index)
153 .Case<RefSubOp>([&](RefSubOp sub) {
154 size_t fieldID = TypeSwitch<FIRRTLBaseType, size_t>(
155 sub.getInput().getType().getType())
156 .Case<FVectorType, BundleType>([&](
auto type) {
157 return type.getFieldID(sub.getIndex());
162 .Case<BundleCreateOp, VectorCreateOp>([&](
auto op) {
163 auto type = op.getType();
164 auto res = op.getResult();
165 auto getFieldId = [&](
unsigned index) {
167 TypeSwitch<FIRRTLBaseType, size_t>(type)
168 .Case<FVectorType, BundleType>(
169 [&](
auto type) {
return type.getFieldID(index); });
172 for (
auto [index, v] :
llvm::enumerate(op.getOperands()))
175 .Case<FConnectLike>([&](FConnectLike connect) {
178 .Default([&](Operation *op) {
181 for (
auto res : op->getResults())
182 for (auto src : op->getOperands())
191 auto iter = valToFieldRefs.find(v);
192 if (iter == valToFieldRefs.end())
193 return getOrAddNode({v, 0});
194 return getOrAddNode(*iter->second.begin());
199 auto iter = nodes.find(f);
200 if (iter != nodes.end())
203 auto id = drivenBy.size();
207 drivenBy.push_back({f, {}});
215 auto srcNode = getOrAddNode(src);
216 auto dstNode = getOrAddNode(dst);
217 drivenBy[dstNode].second.push_back(srcNode);
223 if (
auto *def = srcVal.getDefiningOp())
224 if (def->hasTrait<OpTrait::ConstantLike>())
229 auto dstIt = valToFieldRefs.find(dstVal);
230 auto srcIt = valToFieldRefs.find(srcVal);
235 if (dstIt == valToFieldRefs.end())
236 if (
auto *def = dstVal.getDefiningOp())
237 if (isa<RegOp, RegResetOp, SubaccessOp, SubfieldOp, SubindexOp,
245 if (!(srcValType && dstValType))
246 return addDrivenBy({dstVal, 0}, {srcVal, 0});
252 if (dstValType && !dstValType.isGround())
257 if (srcValType == dstValType) {
264 }
else if (srcValType && !srcValType.isGround())
274 addDrivenBy(dst, src);
283 if (dstIt == valToFieldRefs.end() && srcIt == valToFieldRefs.end())
284 return addDef({dstVal, 0}, {srcVal, 0});
287 if (dstIt == valToFieldRefs.end() && srcIt != valToFieldRefs.end()) {
288 llvm::for_each(srcIt->getSecond(), [&](
const FieldRef &srcField) {
289 addDef(FieldRef(dstVal, 0), srcField);
295 if (dstIt != valToFieldRefs.end() && srcIt == valToFieldRefs.end()) {
296 llvm::for_each(dstIt->getSecond(), [&](
const FieldRef &dstField) {
297 addDef(dstField, FieldRef(srcVal, 0));
303 llvm::for_each(srcIt->second, [&](
const FieldRef &srcField) {
304 llvm::for_each(dstIt->second, [&](const FieldRef &dstField) {
305 addDef(dstField, srcField);
312 recordDataflow(dstProbe, srcVal);
313 auto dstNode = getOrAddNode(dstProbe);
315 auto leader = rwProbeClasses.findLeader(dstNode);
316 if (leader == rwProbeClasses.member_end())
318 auto iter = rwProbeRefersTo.find(*leader);
323 if (iter == rwProbeRefersTo.end())
326 assert(iter != rwProbeRefersTo.end());
327 if (iter->second != dstNode)
328 drivenBy[iter->second].second.push_back(getOrAddNode(srcVal));
335 auto refMod = inst.getReferencedModule<FModuleOp>(instanceGraph);
339 auto modulePaths = modulePortPaths.find(refMod);
340 if (modulePaths == modulePortPaths.end())
355 for (
auto &path : modulePaths->second) {
356 auto modSinkPortField = path.first;
358 cast<BlockArgument>(modSinkPortField.getValue()).getArgNumber();
359 FieldRef sinkPort(inst.getResult(sinkArgNum),
360 modSinkPortField.getFieldID());
361 auto sinkNode = getOrAddNode(sinkPort);
362 bool sinkPortIsForceable =
false;
363 if (
auto refResultType =
364 type_dyn_cast<RefType>(inst.getResult(sinkArgNum).getType()))
365 sinkPortIsForceable = refResultType.getForceable();
367 DenseSet<unsigned> setOfEquivalentRWProbes;
368 unsigned minArgNum = sinkArgNum;
369 unsigned basePortNode = sinkNode;
370 for (
auto &modSrcPortField : path.second) {
372 cast<BlockArgument>(modSrcPortField.getValue()).getArgNumber();
374 if (modSrcPortField == modSinkPortField)
377 FieldRef srcPort(inst.getResult(srcArgNum),
378 modSrcPortField.getFieldID());
379 bool srcPortIsForceable =
false;
380 if (
auto refResultType =
381 type_dyn_cast<RefType>(inst.getResult(srcArgNum).getType()))
382 srcPortIsForceable = refResultType.getForceable();
385 if (sinkPortIsForceable && srcPortIsForceable) {
386 auto srcNode = getOrAddNode(srcPort);
388 if (rwProbeClasses.findLeader(srcNode) !=
389 rwProbeClasses.member_end() &&
390 rwProbeClasses.findLeader(sinkNode) ==
391 rwProbeClasses.findLeader(srcNode))
394 auto drivenBysToSrcPort = modulePaths->second.find(modSrcPortField);
395 if (drivenBysToSrcPort != modulePaths->second.end())
396 if (llvm::find(drivenBysToSrcPort->second, modSinkPortField) !=
397 drivenBysToSrcPort->second.end()) {
403 setOfEquivalentRWProbes.insert(srcNode);
404 if (minArgNum > srcArgNum) {
407 minArgNum = srcArgNum;
408 basePortNode = srcNode;
413 addDrivenBy(sinkPort, srcPort);
415 if (setOfEquivalentRWProbes.empty())
419 for (
auto probe : setOfEquivalentRWProbes)
420 rwProbeClasses.unionSets(probe, sinkNode);
425 auto leader = rwProbeClasses.getLeaderValue(sinkNode);
426 rwProbeRefersTo[leader] = basePortNode;
428 setOfEquivalentRWProbes.insert(sinkNode);
430 for (
auto probe : setOfEquivalentRWProbes)
431 if (probe != basePortNode)
432 drivenBy[probe].second.push_back(basePortNode);
441 auto it = valToFieldRefs.find(base);
442 if (it != valToFieldRefs.end()) {
445 SmallVector<FieldRef> entry;
446 for (
auto &sub : it->second)
447 entry.emplace_back(sub.getValue(), sub.getFieldID() + fieldID);
449 valToFieldRefs[result].append(entry.begin(), entry.end());
453 if (
auto *def = base.getDefiningOp()) {
454 if (isa<RegOp, RegResetOp, SubfieldOp, SubindexOp, SubaccessOp>(def))
457 valToFieldRefs[result].emplace_back(base, fieldID);
463 auto numNodes = graph.size();
464 SmallVector<bool> onStack(numNodes,
false);
465 SmallVector<unsigned> dfsStack;
467 auto hasCycle = [&](
unsigned rootNode, DenseSet<unsigned> &visited,
468 bool recordPortPaths =
false) {
469 if (visited.contains(rootNode))
471 dfsStack.push_back(rootNode);
473 while (!dfsStack.empty()) {
474 auto currentNode = dfsStack.back();
476 if (!visited.contains(currentNode)) {
477 visited.insert(currentNode);
478 onStack[currentNode] =
true;
479 LLVM_DEBUG(llvm::dbgs()
481 << drivenBy[currentNode].first.getValue().getType()
482 << drivenBy[currentNode].first.getValue() <<
","
483 << drivenBy[currentNode].first.getFieldID() <<
"\n"
484 << getName(drivenBy[currentNode].first));
486 FieldRef currentF = drivenBy[currentNode].first;
487 if (recordPortPaths && currentNode != rootNode) {
488 if (isa<mlir::BlockArgument>(currentF.
getValue()))
489 portPaths[drivenBy[rootNode].first].insert(currentF);
492 addToPortPathsIfRWProbe(currentNode,
493 portPaths[drivenBy[rootNode].first]);
496 onStack[currentNode] =
false;
500 for (
auto neighbor : graph[currentNode].second) {
501 if (!visited.contains(neighbor)) {
502 dfsStack.push_back(neighbor);
503 }
else if (onStack[neighbor]) {
505 SmallVector<FieldRef, 16> path;
506 auto loopNode = neighbor;
509 SmallVector<unsigned>::iterator it =
510 llvm::find_if(drivenBy[loopNode].second,
511 [&](
unsigned node) {
return onStack[node]; });
512 if (it == drivenBy[loopNode].second.end())
515 path.push_back(drivenBy[loopNode].first);
517 }
while (loopNode != neighbor);
519 reportLoopFound(path, drivenBy[neighbor].first.getLoc());
527 DenseSet<unsigned> visited;
528 for (
unsigned node = 0; node < graph.size(); ++node) {
530 if (
auto arg = dyn_cast<BlockArgument>(drivenBy[node].first.getValue()))
531 if (module.getPortDirection(arg.getArgNumber()) == Direction::Out) {
538 if (hasCycle(node, visited,
isPort).failed())
545 auto errorDiag = mlir::emitError(
546 module.getLoc(),
"detected combinational cycle in a FIRRTL module");
548 std::string firstName;
550 firstName = getName(v);
551 return !firstName.empty();
553 if (it == path.end()) {
554 errorDiag.append(
", but unable to find names for any involved values.");
555 errorDiag.attachNote(loc) <<
"cycle detected here";
561 auto n = getName(*findStartIt);
562 if (!n.empty() && n < firstName) {
567 errorDiag.append(
", sample path: ");
569 bool lastWasDots =
false;
570 errorDiag <<
module.getName() << ".{" << getName(*it);
571 for (
auto v : llvm::concat<FieldRef>(
572 llvm::make_range(std::next(it), path.end()),
573 llvm::make_range(path.begin(), std::next(it)))) {
574 auto name = getName(v);
576 errorDiag <<
" <- " << name;
580 errorDiag <<
" <- ...";
589 llvm::dbgs() <<
"\n Connectivity Graph ==>";
590 for (
const auto &[index, i] : llvm::enumerate(drivenBy)) {
591 llvm::dbgs() <<
"\n ===>dst:" << getName(i.first)
592 <<
"::" << i.first.getValue();
593 for (
auto s : i.second)
594 llvm::dbgs() <<
"<---" << getName(drivenBy[s].first)
595 <<
"::" << drivenBy[s].first.getValue();
598 llvm::dbgs() <<
"\n Value to FieldRef :";
599 for (
const auto &fields : valToFieldRefs) {
600 llvm::dbgs() <<
"\n Val:" << fields.first;
601 for (
auto f : fields.second)
602 llvm::dbgs() <<
", FieldRef:" << getName(f) <<
"::" << f.getFieldID();
604 llvm::dbgs() <<
"\n Port paths:";
605 for (
const auto &p : portPaths) {
606 llvm::dbgs() <<
"\n Output :" << getName(p.first);
607 for (
auto i : p.second)
608 llvm::dbgs() <<
"\n Input :" << getName(i);
610 llvm::dbgs() <<
"\n rwprobes:";
611 for (
auto node : rwProbeRefersTo) {
612 llvm::dbgs() <<
"\n node:" << getName(drivenBy[node.first].first)
613 <<
"=> probe:" << getName(drivenBy[node.second].first);
620 llvm::interleave(rwProbeClasses.members(*i), llvm::dbgs(),
"\n");
621 llvm::dbgs() <<
"\n dataflow at leader::" << i->getData() <<
"\n =>"
622 << rwProbeRefersTo[i->getData()];
623 llvm::dbgs() <<
"\n Done\n";
629 auto refNode = getOrAddNode(ref);
630 auto dataNode = getOrAddNode(
data);
631 rwProbeRefersTo[rwProbeClasses.getOrInsertLeaderValue(refNode)] = dataNode;
637 auto p1Node = getOrAddNode(probe1);
638 auto p2Node = getOrAddNode(probe2);
639 rwProbeClasses.unionSets(p1Node, p2Node);
643 DenseSet<FieldRef> &inputPortPaths) {
645 auto baseFieldRef = drivenBy[srcNode].first;
646 if (
auto defOp = dyn_cast_or_null<Forceable>(baseFieldRef.getDefiningOp()))
647 if (defOp.isForceable() && !defOp.getDataRef().use_empty()) {
650 rwProbeClasses.getLeaderValue(getOrAddNode(defOp.getDataRef()));
653 for (
auto probe : rwProbeClasses.members(rwProbeNode)) {
654 auto probeVal = drivenBy[probe].first;
657 if (isa<BlockArgument>(probeVal.getValue())) {
658 inputPortPaths.insert(probeVal);
703 auto &instanceGraph = getAnalysis<InstanceGraph>();
704 DenseMap<FModuleLike, DrivenBysMapType> modulePortPaths;
709 for (
auto *igNode : llvm::post_order<InstanceGraph *>(&instanceGraph)) {
710 if (
auto module = dyn_cast<FModuleOp>(*igNode->getModule())) {
712 modulePortPaths[module]);
714 return signalPassFailure();
718 markAllAnalysesPreserved();
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)
FModuleOp 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.
connect(destination, source)
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.
The InstanceGraph op interface, see InstanceGraphInterface.td for more details.