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