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<InstanceChoiceOp>(
133 .Case<SubindexOp>([&](SubindexOp sub) {
136 sub.getInput().getType().base().getFieldID(sub.getIndex()),
139 .Case<SubfieldOp>([&](SubfieldOp sub) {
142 sub.getInput().getType().base().getFieldID(sub.getFieldIndex()),
145 .Case<SubaccessOp>([&](SubaccessOp sub) {
146 auto vecType = sub.getInput().getType().base();
147 auto input = sub.getInput();
148 auto result = sub.getResult();
151 for (
size_t index = 0; index < vecType.getNumElements(); ++index)
155 .Case<RefSubOp>([&](RefSubOp sub) {
156 size_t fieldID = TypeSwitch<FIRRTLBaseType, size_t>(
157 sub.getInput().getType().getType())
158 .Case<FVectorType, BundleType>([&](
auto type) {
159 return type.getFieldID(sub.getIndex());
164 .Case<BundleCreateOp, VectorCreateOp>([&](
auto op) {
165 auto type = op.getType();
166 auto res = op.getResult();
167 auto getFieldId = [&](
unsigned index) {
169 TypeSwitch<FIRRTLBaseType, size_t>(type)
170 .Case<FVectorType, BundleType>(
171 [&](
auto type) {
return type.getFieldID(index); });
174 for (
auto [index, v] :
llvm::enumerate(op.getOperands()))
177 .Case<FConnectLike>([&](FConnectLike connect) {
180 .Default([&](Operation *op) {
183 for (
auto res : op->getResults())
184 for (auto src : op->getOperands())
193 auto iter = valToFieldRefs.find(v);
194 if (iter == valToFieldRefs.end())
195 return getOrAddNode({v, 0});
196 return getOrAddNode(*iter->second.begin());
201 auto iter = nodes.find(f);
202 if (iter != nodes.end())
205 auto id = drivenBy.size();
209 drivenBy.push_back({f, {}});
217 auto srcNode = getOrAddNode(src);
218 auto dstNode = getOrAddNode(dst);
219 drivenBy[dstNode].second.push_back(srcNode);
225 if (
auto *def = srcVal.getDefiningOp())
226 if (def->hasTrait<OpTrait::ConstantLike>())
231 auto dstIt = valToFieldRefs.find(dstVal);
232 auto srcIt = valToFieldRefs.find(srcVal);
237 if (dstIt == valToFieldRefs.end())
238 if (
auto *def = dstVal.getDefiningOp())
239 if (isa<RegOp, RegResetOp, SubaccessOp, SubfieldOp, SubindexOp,
247 if (!(srcValType && dstValType))
248 return addDrivenBy({dstVal, 0}, {srcVal, 0});
254 if (dstValType && !dstValType.isGround())
259 if (srcValType == dstValType) {
266 }
else if (srcValType && !srcValType.isGround())
276 addDrivenBy(dst, src);
285 if (dstIt == valToFieldRefs.end() && srcIt == valToFieldRefs.end())
286 return addDef({dstVal, 0}, {srcVal, 0});
289 if (dstIt == valToFieldRefs.end() && srcIt != valToFieldRefs.end()) {
290 llvm::for_each(srcIt->getSecond(), [&](
const FieldRef &srcField) {
291 addDef(FieldRef(dstVal, 0), srcField);
297 if (dstIt != valToFieldRefs.end() && srcIt == valToFieldRefs.end()) {
298 llvm::for_each(dstIt->getSecond(), [&](
const FieldRef &dstField) {
299 addDef(dstField, FieldRef(srcVal, 0));
305 llvm::for_each(srcIt->second, [&](
const FieldRef &srcField) {
306 llvm::for_each(dstIt->second, [&](const FieldRef &dstField) {
307 addDef(dstField, srcField);
314 recordDataflow(dstProbe, srcVal);
315 auto dstNode = getOrAddNode(dstProbe);
317 auto leader = rwProbeClasses.findLeader(dstNode);
318 if (leader == rwProbeClasses.member_end())
320 auto iter = rwProbeRefersTo.find(*leader);
325 if (iter == rwProbeRefersTo.end())
328 assert(iter != rwProbeRefersTo.end());
329 if (iter->second != dstNode)
330 drivenBy[iter->second].second.push_back(getOrAddNode(srcVal));
336 auto modulePaths = modulePortPaths.find(refMod);
337 if (modulePaths == modulePortPaths.end())
352 for (
auto &path : modulePaths->second) {
353 auto modSinkPortField = path.first;
355 cast<BlockArgument>(modSinkPortField.getValue()).getArgNumber();
356 FieldRef sinkPort(instResults[sinkArgNum], modSinkPortField.getFieldID());
357 auto sinkNode = getOrAddNode(sinkPort);
358 bool sinkPortIsForceable =
false;
359 if (
auto refResultType =
360 type_dyn_cast<RefType>(instResults[sinkArgNum].getType()))
361 sinkPortIsForceable = refResultType.getForceable();
363 DenseSet<unsigned> setOfEquivalentRWProbes;
364 unsigned minArgNum = sinkArgNum;
365 unsigned basePortNode = sinkNode;
366 for (
auto &modSrcPortField : path.second) {
368 cast<BlockArgument>(modSrcPortField.getValue()).getArgNumber();
370 if (modSrcPortField == modSinkPortField)
373 FieldRef srcPort(instResults[srcArgNum], modSrcPortField.getFieldID());
374 bool srcPortIsForceable =
false;
375 if (
auto refResultType =
376 type_dyn_cast<RefType>(instResults[srcArgNum].getType()))
377 srcPortIsForceable = refResultType.getForceable();
380 if (sinkPortIsForceable && srcPortIsForceable) {
381 auto srcNode = getOrAddNode(srcPort);
383 if (rwProbeClasses.findLeader(srcNode) !=
384 rwProbeClasses.member_end() &&
385 rwProbeClasses.findLeader(sinkNode) ==
386 rwProbeClasses.findLeader(srcNode))
389 auto drivenBysToSrcPort = modulePaths->second.find(modSrcPortField);
390 if (drivenBysToSrcPort != modulePaths->second.end())
391 if (llvm::find(drivenBysToSrcPort->second, modSinkPortField) !=
392 drivenBysToSrcPort->second.end()) {
398 setOfEquivalentRWProbes.insert(srcNode);
399 if (minArgNum > srcArgNum) {
402 minArgNum = srcArgNum;
403 basePortNode = srcNode;
408 addDrivenBy(sinkPort, srcPort);
410 if (setOfEquivalentRWProbes.empty())
414 for (
auto probe : setOfEquivalentRWProbes)
415 rwProbeClasses.unionSets(probe, sinkNode);
420 auto leader = rwProbeClasses.getLeaderValue(sinkNode);
421 rwProbeRefersTo[leader] = basePortNode;
423 setOfEquivalentRWProbes.insert(sinkNode);
425 for (
auto probe : setOfEquivalentRWProbes)
426 if (probe != basePortNode)
427 drivenBy[probe].second.push_back(basePortNode);
435 auto refMod = inst.getReferencedModule<FModuleOp>(instanceGraph);
447 for (
auto moduleName : inst.getReferencedModuleNamesAttr()) {
448 auto moduleNameStr = cast<StringAttr>(moduleName);
449 auto *node = instanceGraph.lookup(moduleNameStr);
454 if (
auto refMod = dyn_cast<FModuleOp>(*node->getModule()))
464 auto it = valToFieldRefs.find(base);
465 if (it != valToFieldRefs.end()) {
468 SmallVector<FieldRef> entry;
469 for (
auto &sub : it->second)
470 entry.emplace_back(sub.getValue(), sub.getFieldID() + fieldID);
472 valToFieldRefs[result].append(entry.begin(), entry.end());
476 if (
auto *def = base.getDefiningOp()) {
477 if (isa<RegOp, RegResetOp, SubfieldOp, SubindexOp, SubaccessOp>(def))
480 valToFieldRefs[result].emplace_back(base, fieldID);
486 auto numNodes = graph.size();
487 SmallVector<bool> onStack(numNodes,
false);
488 SmallVector<unsigned> dfsStack;
490 auto hasCycle = [&](
unsigned rootNode, DenseSet<unsigned> &visited,
491 bool recordPortPaths =
false) {
492 if (visited.contains(rootNode))
494 dfsStack.push_back(rootNode);
496 while (!dfsStack.empty()) {
497 auto currentNode = dfsStack.back();
499 if (!visited.contains(currentNode)) {
500 visited.insert(currentNode);
501 onStack[currentNode] =
true;
502 LLVM_DEBUG(llvm::dbgs()
504 << drivenBy[currentNode].first.getValue().getType()
505 << drivenBy[currentNode].first.getValue() <<
","
506 << drivenBy[currentNode].first.getFieldID() <<
"\n"
507 << getName(drivenBy[currentNode].first));
509 FieldRef currentF = drivenBy[currentNode].first;
510 if (recordPortPaths && currentNode != rootNode) {
511 if (isa<mlir::BlockArgument>(currentF.
getValue()))
512 portPaths[drivenBy[rootNode].first].insert(currentF);
515 addToPortPathsIfRWProbe(currentNode,
516 portPaths[drivenBy[rootNode].first]);
519 onStack[currentNode] =
false;
523 for (
auto neighbor : graph[currentNode].second) {
524 if (!visited.contains(neighbor)) {
525 dfsStack.push_back(neighbor);
526 }
else if (onStack[neighbor]) {
528 SmallVector<FieldRef, 16> path;
529 auto loopNode = neighbor;
532 SmallVector<unsigned>::iterator it =
533 llvm::find_if(drivenBy[loopNode].second,
534 [&](
unsigned node) {
return onStack[node]; });
535 if (it == drivenBy[loopNode].second.end())
538 path.push_back(drivenBy[loopNode].first);
540 }
while (loopNode != neighbor);
542 reportLoopFound(path, drivenBy[neighbor].first.getLoc());
550 DenseSet<unsigned> visited;
551 for (
unsigned node = 0; node < graph.size(); ++node) {
553 if (
auto arg = dyn_cast<BlockArgument>(drivenBy[node].first.getValue()))
554 if (module.getPortDirection(arg.getArgNumber()) == Direction::Out) {
561 if (hasCycle(node, visited,
isPort).failed())
568 auto errorDiag = mlir::emitError(
569 module.getLoc(),
"detected combinational cycle in a FIRRTL module");
571 std::string firstName;
573 firstName = getName(v);
574 return !firstName.empty();
576 if (it == path.end()) {
577 errorDiag.append(
", but unable to find names for any involved values.");
578 errorDiag.attachNote(loc) <<
"cycle detected here";
584 auto n = getName(*findStartIt);
585 if (!n.empty() && n < firstName) {
590 errorDiag.append(
", sample path: ");
592 bool lastWasDots =
false;
593 errorDiag <<
module.getName() << ".{" << getName(*it);
594 for (
auto v : llvm::concat<FieldRef>(
595 llvm::make_range(std::next(it), path.end()),
596 llvm::make_range(path.begin(), std::next(it)))) {
597 auto name = getName(v);
599 errorDiag <<
" <- " << name;
603 errorDiag <<
" <- ...";
612 llvm::dbgs() <<
"\n Connectivity Graph ==>";
613 for (
const auto &[index, i] : llvm::enumerate(drivenBy)) {
614 llvm::dbgs() <<
"\n ===>dst:" << getName(i.first)
615 <<
"::" << i.first.getValue();
616 for (
auto s : i.second)
617 llvm::dbgs() <<
"<---" << getName(drivenBy[s].first)
618 <<
"::" << drivenBy[s].first.getValue();
621 llvm::dbgs() <<
"\n Value to FieldRef :";
622 for (
const auto &fields : valToFieldRefs) {
623 llvm::dbgs() <<
"\n Val:" << fields.first;
624 for (
auto f : fields.second)
625 llvm::dbgs() <<
", FieldRef:" << getName(f) <<
"::" << f.getFieldID();
627 llvm::dbgs() <<
"\n Port paths:";
628 for (
const auto &p : portPaths) {
629 llvm::dbgs() <<
"\n Output :" << getName(p.first);
630 for (
auto i : p.second)
631 llvm::dbgs() <<
"\n Input :" << getName(i);
633 llvm::dbgs() <<
"\n rwprobes:";
634 for (
auto node : rwProbeRefersTo) {
635 llvm::dbgs() <<
"\n node:" << getName(drivenBy[node.first].first)
636 <<
"=> probe:" << getName(drivenBy[node.second].first);
643 llvm::interleave(rwProbeClasses.members(*i), llvm::dbgs(),
"\n");
644 llvm::dbgs() <<
"\n dataflow at leader::" << i->getData() <<
"\n =>"
645 << rwProbeRefersTo[i->getData()];
646 llvm::dbgs() <<
"\n Done\n";
652 auto refNode = getOrAddNode(ref);
653 auto dataNode = getOrAddNode(
data);
654 rwProbeRefersTo[rwProbeClasses.getOrInsertLeaderValue(refNode)] = dataNode;
660 auto p1Node = getOrAddNode(probe1);
661 auto p2Node = getOrAddNode(probe2);
662 rwProbeClasses.unionSets(p1Node, p2Node);
666 DenseSet<FieldRef> &inputPortPaths) {
668 auto baseFieldRef = drivenBy[srcNode].first;
669 if (
auto defOp = dyn_cast_or_null<Forceable>(baseFieldRef.getDefiningOp()))
670 if (defOp.isForceable() && !defOp.getDataRef().use_empty()) {
673 rwProbeClasses.getLeaderValue(getOrAddNode(defOp.getDataRef()));
676 for (
auto probe : rwProbeClasses.members(rwProbeNode)) {
677 auto probeVal = drivenBy[probe].first;
680 if (isa<BlockArgument>(probeVal.getValue())) {
681 inputPortPaths.insert(probeVal);
726 auto &instanceGraph = getAnalysis<InstanceGraph>();
727 DenseMap<FModuleLike, DrivenBysMapType> modulePortPaths;
732 for (
auto *igNode : llvm::post_order<InstanceGraph *>(&instanceGraph)) {
733 if (
auto module = dyn_cast<FModuleOp>(*igNode->getModule())) {
735 modulePortPaths[module]);
737 return signalPassFailure();
741 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
static LogicalResult processInstancePorts(const DomainInfo &info, TermAllocator &allocator, DomainTable &table, T op)
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)
void processInstancePorts(FModuleOp refMod, ValueRange instResults)
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.
void handleInstanceChoiceOp(InstanceChoiceOp inst)
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.