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