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