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