CIRCT  19.0.0git
CheckCombLoops.cpp
Go to the documentation of this file.
1 //===- CheckCombLoops.cpp - FIRRTL check combinational cycles ------------===//
2 //
3 // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4 // See https://llvm.org/LICENSE.txt for license information.
5 // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6 //
7 //===----------------------------------------------------------------------===//
8 //
9 // This file implements the FIRRTL combinational cycles detection pass.
10 // Terminology:
11 // In the context of the circt that the MLIR represents, a Value is called the
12 // driver of another Value, if the driver actively sets the the other Value. The
13 // driver Value is responsible for determining the logic level of the driven
14 // Value.
15 // This pass is a dataflow analysis that interprets the operations of the
16 // circt to build a connectivity graph, which represents the driver
17 // relationships. Each node in this connectivity graph is a FieldRef, and an
18 // edge exists from a source node to a desination node if the source drives the
19 // destination..
20 // Algorithm to construct the connectivity graph.
21 // 1. Traverse each module in the Instance Graph bottom up.
22 // 2. Preprocess step: Construct the circt connectivity directed graph.
23 // Perform a dataflow analysis, on the domain of FieldRefs. Interpret each
24 // operation, to record the values that can potentially drive another value.
25 // Each node in the graph is a fieldRef. Each edge represents a dataflow from
26 // the source to the sink.
27 // 3. Perform a DFS traversal on the graph, to detect combinational loops and
28 // paths between ports.
29 // 4. Inline the combinational paths discovered in each module to its instance
30 // and continue the analysis through the instance graph.
31 //===----------------------------------------------------------------------===//
32 
39 #include "mlir/Pass/Pass.h"
40 #include "llvm/ADT/DenseMap.h"
41 #include "llvm/ADT/EquivalenceClasses.h"
42 #include "llvm/ADT/PostOrderIterator.h"
43 
44 #define DEBUG_TYPE "check-comb-loops"
45 
46 namespace circt {
47 namespace firrtl {
48 #define GEN_PASS_DEF_CHECKCOMBLOOPS
49 #include "circt/Dialect/FIRRTL/Passes.h.inc"
50 } // namespace firrtl
51 } // namespace circt
52 
53 using namespace circt;
54 using namespace firrtl;
55 
56 using DrivenBysMapType = DenseMap<FieldRef, DenseSet<FieldRef>>;
57 
59 
60  /// Adjacency list representation.
61  /// Each entry is a pair, the first element is the FieldRef corresponding to
62  /// the graph vertex. The second element is the list of vertices, that have an
63  /// edge to this vertex.
64  /// The directed edges represent a connectivity relation, of a source that
65  /// drives the sink.
67  SmallVector<std::pair<FieldRef, SmallVector<unsigned>>, 64>;
68 
69 public:
71  FModuleOp module, InstanceGraph &instanceGraph,
72  const DenseMap<FModuleLike, DrivenBysMapType> &otherModulePortPaths,
73  DrivenBysMapType &thisModulePortPaths)
74  : module(module), instanceGraph(instanceGraph),
75  modulePortPaths(otherModulePortPaths), portPaths(thisModulePortPaths) {}
76 
77  LogicalResult processModule() {
78  LLVM_DEBUG(llvm::dbgs() << "\n processing module :" << module.getName());
79  constructConnectivityGraph(module);
80  return dfsTraverse(drivenBy);
81  }
82 
83  void constructConnectivityGraph(FModuleOp module) {
84  LLVM_DEBUG(llvm::dbgs() << "\n Module :" << module.getName());
85 
86  // ALl the module output ports must be added as the initial nodes.
87  for (auto port : module.getArguments()) {
88  if (module.getPortDirection(port.getArgNumber()) != Direction::Out)
89  continue;
90  walkGroundTypes(cast<FIRRTLType>(port.getType()),
91  [&](uint64_t index, FIRRTLBaseType t, auto isFlip) {
92  getOrAddNode(FieldRef(port, index));
93  });
94  }
95 
96  walk(module, [&](Operation *op) {
97  llvm::TypeSwitch<Operation *>(op)
98  .Case<hw::CombDataFlow>([&](hw::CombDataFlow df) {
99  // computeDataFlow returns a pair of FieldRefs, first element is the
100  // destination and the second is the source.
101  for (auto [dest, source] : df.computeDataFlow())
102  addDrivenBy(dest, source);
103 
104  })
105  .Case<Forceable>([&](Forceable forceableOp) {
106  // Any declaration that can be forced.
107  if (auto node = dyn_cast<NodeOp>(op))
108  recordDataflow(node.getData(), node.getInput());
109  if (!forceableOp.isForceable() ||
110  forceableOp.getDataRef().use_empty())
111  return;
112  auto data = forceableOp.getData();
113  auto ref = forceableOp.getDataRef();
114  // Record dataflow from data to the probe.
115  recordDataflow(ref, data);
116  recordProbe(data, ref);
117  })
118  .Case<RefSendOp>([&](RefSendOp send) {
119  recordDataflow(send.getResult(), send.getBase());
120  })
121  .Case<RefDefineOp>([&](RefDefineOp def) {
122  // Dataflow from src to dst.
123  recordDataflow(def.getDest(), def.getSrc());
124  if (!def.getDest().getType().getForceable())
125  return;
126  // Dataflow from dst to src, for RWProbe.
127  probesReferToSameData(def.getSrc(), def.getDest());
128  })
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(
134  sub.getInput(),
135  sub.getInput().getType().base().getFieldID(sub.getIndex()),
136  sub.getResult());
137  })
138  .Case<SubfieldOp>([&](SubfieldOp sub) {
139  recordValueRefersToFieldRef(
140  sub.getInput(),
141  sub.getInput().getType().base().getFieldID(sub.getFieldIndex()),
142  sub.getResult());
143  })
144  .Case<SubaccessOp>([&](SubaccessOp sub) {
145  auto vecType = sub.getInput().getType().base();
146  auto input = sub.getInput();
147  auto result = sub.getResult();
148  // Flatten the subaccess. The result can refer to any of the
149  // elements.
150  for (size_t index = 0; index < vecType.getNumElements(); ++index)
151  recordValueRefersToFieldRef(input, vecType.getFieldID(index),
152  result);
153  })
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());
159  });
160  recordValueRefersToFieldRef(sub.getInput(), fieldID,
161  sub.getResult());
162  })
163  .Case<BundleCreateOp, VectorCreateOp>([&](auto op) {
164  auto type = op.getType();
165  auto res = op.getResult();
166  auto getFieldId = [&](unsigned index) {
167  size_t fieldID =
168  TypeSwitch<FIRRTLBaseType, size_t>(type)
169  .Case<FVectorType, BundleType>(
170  [&](auto type) { return type.getFieldID(index); });
171  return fieldID;
172  };
173  for (auto [index, v] : llvm::enumerate(op.getOperands()))
174  recordValueRefersToFieldRef(res, getFieldId(index), v);
175  })
176  .Case<FConnectLike>([&](FConnectLike connect) {
177  recordDataflow(connect.getDest(), connect.getSrc());
178  })
179  .Default([&](Operation *op) {
180  // All other expressions are assumed to be combinational, so record
181  // the dataflow between all inputs to outputs.
182  for (auto res : op->getResults())
183  for (auto src : op->getOperands())
184  recordDataflow(res, src);
185  });
186  });
187  }
188 
189  static std::string getName(FieldRef v) { return getFieldName(v).first; };
190 
191  unsigned getOrAddNode(Value v) {
192  auto iter = valToFieldRefs.find(v);
193  if (iter == valToFieldRefs.end())
194  return getOrAddNode({v, 0});
195  return getOrAddNode(*iter->second.begin());
196  }
197 
198  // Get the node id if it exists, else add it to the graph.
199  unsigned getOrAddNode(FieldRef f) {
200  auto iter = nodes.find(f);
201  if (iter != nodes.end())
202  return iter->second;
203  // Add the fieldRef to the graph.
204  auto id = drivenBy.size();
205  // The node id can be used to index into the graph. The entry is a pair,
206  // first element is the corresponding FieldRef, and the second entry is a
207  // list of adjacent nodes.
208  drivenBy.push_back({f, {}});
209  nodes[f] = id;
210  return id;
211  }
212 
213  // Construct the connectivity graph, by adding `dst` and `src` as new nodes,
214  // if not already existing. Then add an edge from `src` to `dst`.
215  void addDrivenBy(FieldRef dst, FieldRef src) {
216  auto srcNode = getOrAddNode(src);
217  auto dstNode = getOrAddNode(dst);
218  drivenBy[dstNode].second.push_back(srcNode);
219  }
220 
221  // Add `dstVal` as being driven by `srcVal`.
222  void recordDataflow(Value dstVal, Value srcVal) {
223  // Ignore connectivity from constants.
224  if (auto *def = srcVal.getDefiningOp())
225  if (def->hasTrait<OpTrait::ConstantLike>())
226  return;
227  // Check if srcVal/dstVal is a fieldRef to an aggregate. Then, there may
228  // exist other values, that refer to the same fieldRef. Add a connectivity
229  // from all such "aliasing" values.
230  auto dstIt = valToFieldRefs.find(dstVal);
231  auto srcIt = valToFieldRefs.find(srcVal);
232 
233  // Block the dataflow through registers.
234  // dstVal refers to a register, If dstVal is not recorded as the fieldref of
235  // an aggregate, and its either a register, or result of a sub op.
236  if (dstIt == valToFieldRefs.end())
237  if (auto *def = dstVal.getDefiningOp())
238  if (isa<RegOp, RegResetOp, SubaccessOp, SubfieldOp, SubindexOp,
239  RefSubOp>(def))
240  return;
241 
242  auto dstValType = getBaseType(dstVal.getType());
243  auto srcValType = getBaseType(srcVal.getType());
244 
245  // Handle Properties.
246  if (!(srcValType && dstValType))
247  return addDrivenBy({dstVal, 0}, {srcVal, 0});
248 
249  auto addDef = [&](FieldRef dst, FieldRef src) {
250  // If the dstVal and srcVal are aggregate types, then record the dataflow
251  // between each individual ground type. This is equivalent to flattening
252  // the type to ensure all the contained FieldRefs are also recorded.
253  if (dstValType && !dstValType.isGround())
254  walkGroundTypes(dstValType, [&](uint64_t dstIndex, FIRRTLBaseType t,
255  bool dstIsFlip) {
256  // Handle the case when the dst and src are not of the same type.
257  // For each dst ground type, and for each src ground type.
258  if (srcValType == dstValType) {
259  // This is the only case when the flip is valid. Flip is relevant
260  // only for connect ops, and src and dst of a connect op must be
261  // type equivalent!
262  if (dstIsFlip)
263  std::swap(dst, src);
264  addDrivenBy(dst.getSubField(dstIndex), src.getSubField(dstIndex));
265  } else if (srcValType && !srcValType.isGround())
266  walkGroundTypes(srcValType, [&](uint64_t srcIndex, FIRRTLBaseType t,
267  auto) {
268  addDrivenBy(dst.getSubField(dstIndex), src.getSubField(srcIndex));
269  });
270  // Else, the src is ground type.
271  else
272  addDrivenBy(dst.getSubField(dstIndex), src);
273  });
274 
275  addDrivenBy(dst, src);
276  };
277 
278  // Both the dstVal and srcVal, can refer to multiple FieldRefs, ensure that
279  // we capture the dataflow between each pair. Occurs when val result of
280  // subaccess op.
281 
282  // Case 1: None of src and dst are fields of an aggregate. But they can be
283  // aggregate values.
284  if (dstIt == valToFieldRefs.end() && srcIt == valToFieldRefs.end())
285  return addDef({dstVal, 0}, {srcVal, 0});
286  // Case 2: Only src is the field of an aggregate. Get all the fields that
287  // the src refers to.
288  if (dstIt == valToFieldRefs.end() && srcIt != valToFieldRefs.end()) {
289  llvm::for_each(srcIt->getSecond(), [&](const FieldRef &srcField) {
290  addDef(FieldRef(dstVal, 0), srcField);
291  });
292  return;
293  }
294  // Case 3: Only dst is the field of an aggregate. Get all the fields that
295  // the dst refers to.
296  if (dstIt != valToFieldRefs.end() && srcIt == valToFieldRefs.end()) {
297  llvm::for_each(dstIt->getSecond(), [&](const FieldRef &dstField) {
298  addDef(dstField, FieldRef(srcVal, 0));
299  });
300  return;
301  }
302  // Case 4: Both src and dst are the fields of an aggregate. Get all the
303  // fields that they refers to.
304  llvm::for_each(srcIt->second, [&](const FieldRef &srcField) {
305  llvm::for_each(dstIt->second, [&](const FieldRef &dstField) {
306  addDef(dstField, srcField);
307  });
308  });
309  }
310 
311  // Record srcVal as driving the original data value that the probe refers to.
312  void handleRefForce(Value dstProbe, Value srcVal) {
313  recordDataflow(dstProbe, srcVal);
314  auto dstNode = getOrAddNode(dstProbe);
315  // Now add srcVal as driving the data that dstProbe refers to.
316  auto leader = rwProbeClasses.findLeader(dstNode);
317  if (leader == rwProbeClasses.member_end())
318  return;
319  auto iter = rwProbeRefersTo.find(*leader);
320 
321  // This should be found, but for now may not be due to needing
322  // RWProbeOp support. May cause missed loops involving force for now.
323  // https://github.com/llvm/circt/issues/6820
324  if (iter == rwProbeRefersTo.end())
325  return;
326 
327  assert(iter != rwProbeRefersTo.end());
328  if (iter->second != dstNode)
329  drivenBy[iter->second].second.push_back(getOrAddNode(srcVal));
330  }
331 
332  // Check the referenced module paths and add input ports as the drivers for
333  // the corresponding output port. The granularity of the connectivity
334  // relations is per field.
335  void handleInstanceOp(InstanceOp inst) {
336  auto refMod = inst.getReferencedModule<FModuleOp>(instanceGraph);
337  // TODO: External modules not handled !!
338  if (!refMod)
339  return;
340  auto modulePaths = modulePortPaths.find(refMod);
341  if (modulePaths == modulePortPaths.end())
342  return;
343  // Note: Handling RWProbes.
344  // 1. For RWProbes, output ports can be source of dataflow.
345  // 2. All the RWProbes that refer to the same base value form a strongly
346  // connected component. Each has a dataflow from the other, including
347  // itself.
348  // Steps to add the instance ports to the connectivity graph:
349  // 1. Find the set of RWProbes that refer to the same base value.
350  // 2. Add them to the same rwProbeClasses.
351  // 3. Choose the first RWProbe port from this set as a representative base
352  // value. And add it as the source driver for every other RWProbe
353  // port in the set.
354  // 4. This will ensure we can detect cycles involving different RWProbes to
355  // the same base value.
356  for (auto &path : modulePaths->second) {
357  auto modSinkPortField = path.first;
358  auto sinkArgNum =
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();
367 
368  DenseSet<unsigned> setOfEquivalentRWProbes;
369  unsigned minArgNum = sinkArgNum;
370  unsigned basePortNode = sinkNode;
371  for (auto &modSrcPortField : path.second) {
372  auto srcArgNum =
373  cast<BlockArgument>(modSrcPortField.getValue()).getArgNumber();
374  // Self loop will exist for RWProbes, ignore them.
375  if (modSrcPortField == modSinkPortField)
376  continue;
377 
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();
384  // RWProbes can potentially refer to the same base value. Such ports
385  // have a path from each other, a false loop, detect such cases.
386  if (sinkPortIsForceable && srcPortIsForceable) {
387  auto srcNode = getOrAddNode(srcPort);
388  // If a path is already recorded, continue.
389  if (rwProbeClasses.findLeader(srcNode) !=
390  rwProbeClasses.member_end() &&
391  rwProbeClasses.findLeader(sinkNode) ==
392  rwProbeClasses.findLeader(srcNode))
393  continue;
394  // Check if sinkPort is a driver of sourcePort.
395  auto drivenBysToSrcPort = modulePaths->second.find(modSrcPortField);
396  if (drivenBysToSrcPort != modulePaths->second.end())
397  if (llvm::find(drivenBysToSrcPort->second, modSinkPortField) !=
398  drivenBysToSrcPort->second.end()) {
399  // This case can occur when there are multiple RWProbes on the
400  // port, which refer to the same base value. So, each of such
401  // probes are drivers of each other. Hence the false
402  // loops. Instead of recording this in the drivenByGraph,
403  // record it separately with the rwProbeClasses.
404  setOfEquivalentRWProbes.insert(srcNode);
405  if (minArgNum > srcArgNum) {
406  // Make one of the RWProbe port the base node. Use the first
407  // port for deterministic error messages.
408  minArgNum = srcArgNum;
409  basePortNode = srcNode;
410  }
411  continue;
412  }
413  }
414  addDrivenBy(sinkPort, srcPort);
415  }
416  if (setOfEquivalentRWProbes.empty())
417  continue;
418 
419  // Add all the rwprobes to the same class.
420  for (auto probe : setOfEquivalentRWProbes)
421  rwProbeClasses.unionSets(probe, sinkNode);
422 
423  // Make the first port as the base value.
424  // Note: this is a port and the actual reference base exists in another
425  // module.
426  auto leader = rwProbeClasses.getLeaderValue(sinkNode);
427  rwProbeRefersTo[leader] = basePortNode;
428 
429  setOfEquivalentRWProbes.insert(sinkNode);
430  // Add the base RWProbe port as a driver to all other RWProbe ports.
431  for (auto probe : setOfEquivalentRWProbes)
432  if (probe != basePortNode)
433  drivenBy[probe].second.push_back(basePortNode);
434  }
435  }
436 
437  // Record the FieldRef, corresponding to the result of the sub op
438  // `result = base[index]`
439  void recordValueRefersToFieldRef(Value base, unsigned fieldID, Value result) {
440 
441  // Check if base is itself field of an aggregate.
442  auto it = valToFieldRefs.find(base);
443  if (it != valToFieldRefs.end()) {
444  // Rebase it to the original aggregate.
445  // Because of subaccess op, each value can refer to multiple FieldRefs.
446  SmallVector<FieldRef> entry;
447  for (auto &sub : it->second)
448  entry.emplace_back(sub.getValue(), sub.getFieldID() + fieldID);
449  // Update the map at the end, to avoid invaliding the iterator.
450  valToFieldRefs[result].append(entry.begin(), entry.end());
451  return;
452  }
453  // Break cycles from registers.
454  if (auto *def = base.getDefiningOp()) {
455  if (isa<RegOp, RegResetOp, SubfieldOp, SubindexOp, SubaccessOp>(def))
456  return;
457  }
458  valToFieldRefs[result].emplace_back(base, fieldID);
459  }
460 
461  // Perform an iterative DFS traversal of the given graph. Record paths between
462  // the ports and detect and report any cycles in the graph.
463  LogicalResult dfsTraverse(const DrivenByGraphType &graph) {
464  auto numNodes = graph.size();
465  SmallVector<bool> onStack(numNodes, false);
466  SmallVector<unsigned> dfsStack;
467 
468  auto hasCycle = [&](unsigned rootNode, DenseSet<unsigned> &visited,
469  bool recordPortPaths = false) {
470  if (visited.contains(rootNode))
471  return success();
472  dfsStack.push_back(rootNode);
473 
474  while (!dfsStack.empty()) {
475  auto currentNode = dfsStack.back();
476 
477  if (!visited.contains(currentNode)) {
478  visited.insert(currentNode);
479  onStack[currentNode] = true;
480  LLVM_DEBUG(llvm::dbgs()
481  << "\n visiting :"
482  << drivenBy[currentNode].first.getValue().getType()
483  << drivenBy[currentNode].first.getValue() << ","
484  << drivenBy[currentNode].first.getFieldID() << "\n"
485  << getName(drivenBy[currentNode].first));
486 
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);
491  // Even if the current node is not a port, there can be RWProbes of
492  // the current node at the port.
493  addToPortPathsIfRWProbe(currentNode,
494  portPaths[drivenBy[rootNode].first]);
495  }
496  } else {
497  onStack[currentNode] = false;
498  dfsStack.pop_back();
499  }
500 
501  for (auto neighbor : graph[currentNode].second) {
502  if (!visited.contains(neighbor)) {
503  dfsStack.push_back(neighbor);
504  } else if (onStack[neighbor]) {
505  // Cycle found !!
506  SmallVector<FieldRef, 16> path;
507  auto loopNode = neighbor;
508  // Construct the cyclic path.
509  do {
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())
514  break;
515 
516  path.push_back(drivenBy[loopNode].first);
517  loopNode = *it;
518  } while (loopNode != neighbor);
519 
520  reportLoopFound(path, drivenBy[neighbor].first.getLoc());
521  return failure();
522  }
523  }
524  }
525  return success();
526  };
527 
528  DenseSet<unsigned> visited;
529  for (unsigned node = 0; node < graph.size(); ++node) {
530  bool isPort = false;
531  if (auto arg = dyn_cast<BlockArgument>(drivenBy[node].first.getValue()))
532  if (module.getPortDirection(arg.getArgNumber()) == Direction::Out) {
533  // For output ports, reset the visited. Required to revisit the entire
534  // graph, to discover all the paths that exist from any input port.
535  visited.clear();
536  isPort = true;
537  }
538 
539  if (hasCycle(node, visited, isPort).failed())
540  return failure();
541  }
542  return success();
543  }
544 
545  void reportLoopFound(SmallVectorImpl<FieldRef> &path, Location loc) {
546  auto errorDiag = mlir::emitError(
547  module.getLoc(), "detected combinational cycle in a FIRRTL module");
548  // Find a value we can name
549  std::string firstName;
550  FieldRef *it = llvm::find_if(path, [&](FieldRef v) {
551  firstName = getName(v);
552  return !firstName.empty();
553  });
554  if (it == path.end()) {
555  errorDiag.append(", but unable to find names for any involved values.");
556  errorDiag.attachNote(loc) << "cycle detected here";
557  return;
558  }
559  // Begin the path from the "smallest string".
560  for (circt::FieldRef *findStartIt = it; findStartIt != path.end();
561  ++findStartIt) {
562  auto n = getName(*findStartIt);
563  if (!n.empty() && n < firstName) {
564  firstName = n;
565  it = findStartIt;
566  }
567  }
568  errorDiag.append(", sample path: ");
569 
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)))) {
575  auto name = getName(v);
576  if (!name.empty()) {
577  errorDiag << " <- " << name;
578  lastWasDots = false;
579  } else {
580  if (!lastWasDots)
581  errorDiag << " <- ...";
582  lastWasDots = true;
583  }
584  }
585  errorDiag << "}";
586  }
587 
588  void dumpMap() {
589  LLVM_DEBUG({
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();
597  }
598 
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();
604  }
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);
610  }
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);
615  }
616  for (auto i = rwProbeClasses.begin(), e = rwProbeClasses.end(); i != e;
617  ++i) { // Iterate over all of the equivalence sets.
618  if (!i->isLeader())
619  continue; // Ignore non-leader sets.
620  // Print members in this set.
621  llvm::interleave(llvm::make_range(rwProbeClasses.member_begin(i),
622  rwProbeClasses.member_end()),
623  llvm::dbgs(), "\n");
624  llvm::dbgs() << "\n dataflow at leader::" << i->getData() << "\n =>"
625  << rwProbeRefersTo[i->getData()];
626  llvm::dbgs() << "\n Done\n"; // Finish set.
627  }
628  });
629  }
630 
631  void recordProbe(Value data, Value ref) {
632  auto refNode = getOrAddNode(ref);
633  auto dataNode = getOrAddNode(data);
634  rwProbeRefersTo[rwProbeClasses.getOrInsertLeaderValue(refNode)] = dataNode;
635  }
636 
637  // Add both the probes to the same equivalence class, to record that they
638  // refer to the same data value.
639  void probesReferToSameData(Value probe1, Value probe2) {
640  auto p1Node = getOrAddNode(probe1);
641  auto p2Node = getOrAddNode(probe2);
642  rwProbeClasses.unionSets(p1Node, p2Node);
643  }
644 
645  void addToPortPathsIfRWProbe(unsigned srcNode,
646  DenseSet<FieldRef> &inputPortPaths) {
647  // Check if there exists any RWProbe for the srcNode.
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()) {
651  // Assumption, the probe must exist in the equivalence classes.
652  auto rwProbeNode =
653  rwProbeClasses.getLeaderValue(getOrAddNode(defOp.getDataRef()));
654  // For all the probes, that are in the same eqv class, i.e., refer to
655  // the same value.
656  for (auto probe :
657  llvm::make_range(rwProbeClasses.member_begin(
658  rwProbeClasses.findValue(rwProbeNode)),
659  rwProbeClasses.member_end())) {
660  auto probeVal = drivenBy[probe].first;
661  // If the probe is a port, then record the path from the probe to the
662  // input port.
663  if (isa<BlockArgument>(probeVal.getValue())) {
664  inputPortPaths.insert(probeVal);
665  }
666  }
667  }
668  }
669 
670 private:
671  FModuleOp module;
673 
674  /// Map of values to the set of all FieldRefs (same base) that this may be
675  /// directly derived from through indexing operations.
676  DenseMap<Value, SmallVector<FieldRef>> valToFieldRefs;
677  /// Comb paths that exist between module ports. This is maintained across
678  /// modules.
679  const DenseMap<FModuleLike, DrivenBysMapType> &modulePortPaths;
680  /// The comb paths between the ports of this module. This is the final
681  /// output of this intra-procedural analysis, that is used to construct the
682  /// inter-procedural dataflow.
684 
685  /// This is an adjacency list representation of the connectivity graph. This
686  /// can be indexed by the graph node id, and each entry is the list of graph
687  /// nodes that has an edge to it. Each graph node represents a FieldRef and
688  /// each edge represents a source that directly drives the sink node.
690  /// Map of FieldRef to its corresponding graph node.
691  DenseMap<FieldRef, size_t> nodes;
692 
693  /// The base value that the RWProbe refers to. Used to add an edge to the base
694  /// value, when the probe is forced.
695  DenseMap<unsigned, unsigned> rwProbeRefersTo;
696 
697  /// An eqv class of all the RWProbes that refer to the same base value.
698  llvm::EquivalenceClasses<unsigned> rwProbeClasses;
699 };
700 
701 /// This pass constructs a local graph for each module to detect
702 /// combinational cycles. To capture the cross-module combinational cycles,
703 /// this pass inlines the combinational paths between IOs of its
704 /// subinstances into a subgraph and encodes them in `modulePortPaths`.
706  : public circt::firrtl::impl::CheckCombLoopsBase<CheckCombLoopsPass> {
707 public:
708  void runOnOperation() override {
709  auto &instanceGraph = getAnalysis<InstanceGraph>();
710  DenseMap<FModuleLike, DrivenBysMapType> modulePortPaths;
711 
712  // Traverse modules in a post order to make sure the combinational paths
713  // between IOs of a module have been detected and recorded in
714  // `modulePortPaths` before we handle its parent modules.
715  for (auto *igNode : llvm::post_order<InstanceGraph *>(&instanceGraph)) {
716  if (auto module = dyn_cast<FModuleOp>(*igNode->getModule())) {
717  DiscoverLoops rdf(module, instanceGraph, modulePortPaths,
718  modulePortPaths[module]);
719  if (rdf.processModule().failed()) {
720  return signalPassFailure();
721  }
722  }
723  }
724  markAllAnalysesPreserved();
725  }
726 };
727 
728 std::unique_ptr<mlir::Pass> circt::firrtl::createCheckCombLoopsPass() {
729  return std::make_unique<CheckCombLoopsPass>();
730 }
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.
Definition: CalyxOps.cpp:134
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.
Definition: FieldRef.h:28
FieldRef getSubField(unsigned subFieldID) const
Get a reference to a subfield.
Definition: FieldRef.h:62
Value getValue() const
Get the Value which created this location.
Definition: FieldRef.h:37
This graph tracks modules and where they are instantiated.
def connect(destination, source)
Definition: support.py:37
std::unique_ptr< mlir::Pass > createCheckCombLoopsPass()
FIRRTLBaseType getBaseType(Type type)
If it is a base type, return it as is.
Definition: FIRRTLUtils.h:222
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.
Definition: DebugAnalysis.h:21