CIRCT 22.0.0git
Loading...
Searching...
No Matches
LongestPathAnalysis.cpp
Go to the documentation of this file.
1//===----------------------------------------------------------------------===//
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 analysis computes the longest combinational paths through a circuit
10// represented in the AIG (And-Inverter Graph) dialect. The key aspects are:
11//
12// - Each AIG and-inverter operation is considered to have a unit delay of 1
13// - The analysis traverses the circuit graph from inputs/registers to outputs
14// - It handles hierarchical designs by analyzing modules bottom-up
15// - Results include full path information with delays and debug points
16// - Caching is used extensively to improve performance on large designs
17//
18// The core algorithm works as follows:
19// 1. Build an instance graph of the full design hierarchy
20// 2. Analyze modules in post-order (children before parents)
21// 3. For each module:
22// - Trace paths from inputs and registers
23// - Propagate delays through logic and across module boundaries
24// - Record maximum delay and path info for each node
25// 4. Combine results across hierarchy to get full chip critical paths
26//
27// The analysis handles both closed paths (register-to-register) and open
28// paths (input-to-register, register-to-output) across the full hierarchy.
29//===----------------------------------------------------------------------===//
30
40#include "circt/Support/LLVM.h"
41#include "mlir/IR/BuiltinAttributes.h"
42#include "mlir/IR/BuiltinOps.h"
43#include "mlir/IR/Diagnostics.h"
44#include "mlir/IR/Operation.h"
45#include "mlir/IR/Threading.h"
46#include "mlir/IR/Value.h"
47#include "mlir/IR/Visitors.h"
48#include "mlir/Pass/AnalysisManager.h"
49#include "mlir/Pass/PassManager.h"
50#include "mlir/Pass/PassRegistry.h"
51#include "mlir/Support/FileUtilities.h"
52#include "mlir/Support/LLVM.h"
53#include "llvm/ADT//MapVector.h"
54#include "llvm/ADT/ArrayRef.h"
55#include "llvm/ADT/DenseMapInfoVariant.h"
56#include "llvm/ADT/EquivalenceClasses.h"
57#include "llvm/ADT/ImmutableList.h"
58#include "llvm/ADT/MapVector.h"
59#include "llvm/ADT/PostOrderIterator.h"
60#include "llvm/ADT/STLExtras.h"
61#include "llvm/ADT/SmallVector.h"
62#include "llvm/ADT/StringRef.h"
63#include "llvm/Support/Debug.h"
64#include "llvm/Support/ErrorHandling.h"
65#include "llvm/Support/JSON.h"
66#include "llvm/Support/LogicalResult.h"
67#include "llvm/Support/MathExtras.h"
68#include "llvm/Support/Mutex.h"
69#include "llvm/Support/raw_ostream.h"
70#include <condition_variable>
71#include <cstddef>
72#include <cstdint>
73#include <memory>
74#include <mutex>
75
76#define DEBUG_TYPE "aig-longest-path-analysis"
77using namespace circt;
78using namespace synth;
79
80static size_t getBitWidth(Value value) {
81 if (auto vecType = dyn_cast<seq::ClockType>(value.getType()))
82 return 1;
83 if (auto memory = dyn_cast<seq::FirMemType>(value.getType()))
84 return memory.getWidth();
85 return hw::getBitWidth(value.getType());
86}
87
88template <typename T, typename Key>
89static void
90deduplicatePathsImpl(SmallVectorImpl<T> &results, size_t startIndex,
91 llvm::function_ref<Key(const T &)> keyFn,
92 llvm::function_ref<int64_t(const T &)> delayFn) {
93 // Take only maximum for each path destination.
94 DenseMap<Key, size_t> keyToIndex;
95 for (size_t i = startIndex; i < results.size(); ++i) {
96 auto &path = results[i];
97 auto key = keyFn(path);
98 auto delay = delayFn(path);
99 auto it = keyToIndex.find(key);
100 if (it == keyToIndex.end()) {
101 // Insert a new entry.
102 size_t newIndex = keyToIndex.size() + startIndex;
103 keyToIndex[key] = newIndex;
104 results[newIndex] = std::move(results[i]);
105 continue;
106 }
107 if (delay > delayFn(results[it->second]))
108 results[it->second] = std::move(results[i]);
109 }
110
111 results.resize(keyToIndex.size() + startIndex);
112}
113
114// Filter and optimize OpenPaths based on analysis configuration.
115static void filterPaths(SmallVectorImpl<OpenPath> &results,
116 bool keepOnlyMaxDelay, bool isLocalScope) {
117 if (results.empty())
118 return;
119
120 // Fast path for local scope with max-delay filtering:
121 // Simply find and keep only the single longest delay path
122 if (keepOnlyMaxDelay && isLocalScope) {
123 OpenPath maxDelay;
124 maxDelay.delay = -1; // Initialize to invalid delay
125
126 // Find the path with maximum delay
127 for (auto &path : results) {
128 if (path.delay > maxDelay.delay)
129 maxDelay = path;
130 }
131
132 // Replace all paths with just the maximum delay path
133 results.clear();
134 if (maxDelay.delay >= 0) // Only add if we found a valid path
135 results.push_back(maxDelay);
136 return;
137 }
138
139 // Remove paths with identical start point, keeping only the longest delay
140 // path for each unique points.
141 deduplicatePathsImpl<OpenPath, Object>(
142 results, 0, [](const auto &path) { return path.startPoint; },
143 [](const auto &path) { return path.delay; });
144
145 // Global scope max-delay filtering:
146 // Keep all module input ports (BlockArguments) but only the single
147 // longest internal path to preserve boundary information
148 if (keepOnlyMaxDelay) {
149 assert(!isLocalScope);
150 size_t writeIndex = 0;
151 OpenPath maxDelay;
152 maxDelay.delay = -1; // Initialize to invalid delay
153
154 for (size_t i = 0; i < results.size(); ++i) {
155 // Preserve all module input port paths (BlockArguments) since the input
156 // port might be the critical path.
157 if (isa<BlockArgument>(results[i].getStartPoint().value)) {
158 // Keep all module input port paths, as inputs themselves may be
159 // critical
160 results[writeIndex++] = results[i];
161 } else {
162 // For internal paths, track only the maximum delay path
163 if (results[i].delay > maxDelay.delay)
164 maxDelay = results[i];
165 }
166 }
167
168 // Resize to remove processed internal paths, then add the max delay path
169 results.resize(writeIndex);
170 if (maxDelay.delay >= 0) // Only add if we found a valid internal path
171 results.push_back(maxDelay);
172 }
173}
174
175static void filterPaths(SmallVectorImpl<DataflowPath> &results,
176 size_t startIndex = 0) {
178 std::pair<DataflowPath::EndPointType, Object>>(
179 results, startIndex,
180 [](const DataflowPath &path) {
181 return std::pair(path.getEndPoint(), path.getStartPoint());
182 },
183 [](const DataflowPath &path) { return path.getDelay(); });
184}
185
186static llvm::ImmutableList<DebugPoint>
187mapList(llvm::ImmutableListFactory<DebugPoint> *debugPointFactory,
188 llvm::ImmutableList<DebugPoint> list,
189 llvm::function_ref<DebugPoint(DebugPoint)> fn) {
190 if (list.isEmpty())
191 return list;
192 auto &head = list.getHead();
193 return debugPointFactory->add(fn(head),
194 mapList(debugPointFactory, list.getTail(), fn));
195}
196
197static llvm::ImmutableList<DebugPoint>
198concatList(llvm::ImmutableListFactory<DebugPoint> *debugPointFactory,
199 llvm::ImmutableList<DebugPoint> lhs,
200 llvm::ImmutableList<DebugPoint> rhs) {
201 if (lhs.isEmpty())
202 return rhs;
203 return debugPointFactory->add(
204 lhs.getHead(), concatList(debugPointFactory, lhs.getTail(), rhs));
205}
206
207static StringAttr getNameImpl(Value value) {
208 if (auto arg = dyn_cast<BlockArgument>(value)) {
209 auto op = dyn_cast<hw::HWModuleOp>(arg.getParentBlock()->getParentOp());
210 if (!op) {
211 // TODO: Handle other operations.
212 return StringAttr::get(value.getContext(), "<unknown-argument>");
213 }
214 return op.getArgName(arg.getArgNumber());
215 }
216 return TypeSwitch<Operation *, StringAttr>(value.getDefiningOp())
217 .Case<seq::CompRegOp, seq::FirRegOp>(
218 [](auto op) { return op.getNameAttr(); })
219 .Case<hw::InstanceOp>([&](hw::InstanceOp op) {
220 SmallString<16> str;
221 str += op.getInstanceName();
222 str += ".";
223 str += cast<StringAttr>(
224 op.getResultNamesAttr()[cast<OpResult>(value).getResultNumber()]);
225 return StringAttr::get(op.getContext(), str);
226 })
227 .Case<seq::FirMemReadOp>([&](seq::FirMemReadOp op) {
228 llvm::SmallString<16> str;
229 str += op.getMemory().getDefiningOp<seq::FirMemOp>().getNameAttr();
230 str += ".read_port";
231 return StringAttr::get(value.getContext(), str);
232 })
233 .Case<seq::FirMemReadWriteOp>([&](seq::FirMemReadWriteOp op) {
234 llvm::SmallString<16> str;
235 str += op.getMemory().getDefiningOp<seq::FirMemOp>().getNameAttr();
236 str += ".rw_port";
237 return StringAttr::get(value.getContext(), str);
238 })
239 .Case<seq::FirMemOp>([&](seq::FirMemOp op) {
240 llvm::SmallString<16> str;
241 str += op.getMemory().getDefiningOp<seq::FirMemOp>().getNameAttr();
242 str += ".write_port";
243 return StringAttr::get(value.getContext(), str);
244 })
245 .Default([&](auto op) {
246 if (auto name = op->template getAttrOfType<StringAttr>("sv.namehint"))
247 return name;
248 llvm::errs() << "Unknown op: " << *op << "\n";
249 return StringAttr::get(value.getContext(), "");
250 });
251}
252
253static void printObjectImpl(llvm::raw_ostream &os, const Object &object,
254 int64_t delay = -1,
255 llvm::ImmutableList<DebugPoint> history = {},
256 StringRef comment = "") {
257 std::string pathString;
258 llvm::raw_string_ostream osPath(pathString);
259 object.instancePath.print(osPath);
260 os << "Object(" << pathString << "." << object.getName().getValue() << "["
261 << object.bitPos << "]";
262 if (delay != -1)
263 os << ", delay=" << delay;
264 if (!history.isEmpty()) {
265 os << ", history=[";
266 llvm::interleaveComma(history, os, [&](DebugPoint p) { p.print(os); });
267 os << "]";
268 }
269 if (!comment.empty())
270 os << ", comment=\"" << comment << "\"";
271 os << ")";
272}
273
274template <typename T>
275static int64_t getMaxDelayInPaths(ArrayRef<T> paths) {
276 int64_t maxDelay = 0;
277 for (auto &path : paths)
278 maxDelay = std::max(maxDelay, path.getDelay());
279 return maxDelay;
280}
281
282using namespace circt;
283using namespace aig;
284
285//===----------------------------------------------------------------------===//
286// Printing
287//===----------------------------------------------------------------------===//
288
289void OpenPath::print(llvm::raw_ostream &os) const {
290 printObjectImpl(os, startPoint, delay, history);
291}
292
293void DebugPoint::print(llvm::raw_ostream &os) const {
294 printObjectImpl(os, object, delay, {}, comment);
295}
296
297void Object::print(llvm::raw_ostream &os) const { printObjectImpl(os, *this); }
298
299StringAttr Object::getName() const { return getNameImpl(value); }
300
301void DataflowPath::printEndPoint(llvm::raw_ostream &os) {
302 if (auto *object = std::get_if<Object>(&endPoint)) {
303 object->print(os);
304 } else {
305 auto &[module, resultNumber, bitPos] =
306 *std::get_if<DataflowPath::OutputPort>(&endPoint);
307 auto outputPortName = root.getOutputName(resultNumber);
308 os << "Object($root." << outputPortName << "[" << bitPos << "])";
309 }
310}
311
312void DataflowPath::print(llvm::raw_ostream &os) {
313 os << "root=" << root.getModuleName() << ", ";
314 os << "endPoint=";
315 printEndPoint(os);
316 os << ", ";
317 os << "startPoint=";
318 path.print(os);
319}
320
321//===----------------------------------------------------------------------===//
322// OpenPath
323//===----------------------------------------------------------------------===//
324
327 instancePath = cache.concatPath(path, instancePath);
328 return *this;
329}
330
333 llvm::ImmutableListFactory<DebugPoint> *debugPointFactory,
335 startPoint.prependPaths(cache, path);
336 if (debugPointFactory)
337 this->history = mapList(debugPointFactory, this->history,
338 [&](DebugPoint p) -> DebugPoint {
339 p.object.prependPaths(cache, path);
340 return p;
341 });
342 return *this;
343}
344
345//===----------------------------------------------------------------------===//
346// DataflowPath
347//===----------------------------------------------------------------------===//
348
351 llvm::ImmutableListFactory<DebugPoint> *debugPointFactory,
353 this->path.prependPaths(cache, debugPointFactory, path);
354 if (!path.empty()) {
355 auto root = path.top()->getParentOfType<hw::HWModuleOp>();
356 assert(root && "root is not a hw::HWModuleOp");
357 this->root = root;
358 }
359
360 // If the end point is an object, prepend the path.
361 if (auto *object = std::get_if<Object>(&endPoint))
362 object->prependPaths(cache, path);
363
364 return *this;
365}
366
368 // If the end point is an object, return the location of the object.
369 if (auto *object = std::get_if<Object>(&endPoint))
370 return object->value.getLoc();
371
372 // Return output port location.
373 auto &[module, resultNumber, bitPos] =
374 *std::get_if<DataflowPath::OutputPort>(&endPoint);
375 return module.getOutputLoc(resultNumber);
376}
377
378//===----------------------------------------------------------------------===//
379// JSON serialization
380//===----------------------------------------------------------------------===//
381
382static llvm::json::Value toJSON(const circt::igraph::InstancePath &path) {
383 llvm::json::Array result;
384 for (auto op : path) {
385 llvm::json::Object obj;
386 obj["instance_name"] = op.getInstanceName();
387 obj["module_name"] = op.getReferencedModuleNames()[0];
388 result.push_back(std::move(obj));
389 }
390 return result;
391}
392
393static llvm::json::Value toJSON(const circt::synth::Object &object) {
394 return llvm::json::Object{
395 {"instance_path", toJSON(object.instancePath)},
396 {"name", object.getName().getValue()},
397 {"bit_pos", object.bitPos},
398 };
399}
400
401static llvm::json::Value toJSON(const DataflowPath::EndPointType &path,
402 hw::HWModuleOp root) {
403 using namespace llvm::json;
404 if (auto *object = std::get_if<circt::synth::Object>(&path))
405 return toJSON(*object);
406
407 auto &[module, resultNumber, bitPos] =
408 *std::get_if<DataflowPath::OutputPort>(&path);
409 return llvm::json::Object{
410 {"instance_path", {}}, // Instance path is empty for output ports.
411 {"name", root.getOutputName(resultNumber)},
412 {"bit_pos", bitPos},
413 };
414}
415
416static llvm::json::Value toJSON(const DebugPoint &point) {
417 return llvm::json::Object{
418 {"object", toJSON(point.object)},
419 {"delay", point.delay},
420 {"comment", point.comment},
421 };
422}
423
424static llvm::json::Value toJSON(const OpenPath &path) {
425 llvm::json::Array history;
426 for (auto &point : path.history)
427 history.push_back(toJSON(point));
428 return llvm::json::Object{{"start_point", toJSON(path.startPoint)},
429 {"delay", path.delay},
430 {"history", std::move(history)}};
431}
432
433llvm::json::Value circt::synth::toJSON(const DataflowPath &path) {
434 return llvm::json::Object{
435 {"end_point", ::toJSON(path.getEndPoint(), path.getRoot())},
436 {"path", ::toJSON(path.getPath())},
437 {"root", path.getRoot().getModuleName()},
438 };
439}
440
441class LocalVisitor;
442
443//===----------------------------------------------------------------------===//
444// Context
445//===----------------------------------------------------------------------===//
446/// This class provides a thread-safe interface to access the analysis results.
447
448class Context {
449public:
451 const LongestPathAnalysisOptions &option)
452 : instanceGraph(instanceGraph), option(option) {}
453 void notifyStart(StringAttr name) {
454 std::lock_guard<llvm::sys::SmartMutex<true>> lock(mutex);
455 running.insert(name);
456 llvm::dbgs() << "[Timing] " << name << " started. running=[";
457 for (auto &name : running)
458 llvm::dbgs() << name << " ";
459 llvm::dbgs() << "]\n";
460 }
461
462 void notifyEnd(StringAttr name) {
463 std::lock_guard<llvm::sys::SmartMutex<true>> lock(mutex);
464 running.remove(name);
465
466 llvm::dbgs() << "[Timing] " << name << " finished. running=[";
467 for (auto &name : running)
468 llvm::dbgs() << name << " ";
469 llvm::dbgs() << "]\n";
470 }
471
472 // Lookup a local visitor for `name`.
473 const LocalVisitor *getLocalVisitor(StringAttr name) const;
474
475 // Lookup a mutable local visitor for `name`.
476 LocalVisitor *getLocalVisitorMutable(StringAttr name) const;
477
478 // A map from the module name to the local visitor.
479 llvm::MapVector<StringAttr, std::unique_ptr<LocalVisitor>> localVisitors;
480
481 // This is non-null only if `module` is a ModuleOp.
482 circt::igraph::InstanceGraph *instanceGraph = nullptr;
483
484 bool doTraceDebugPoints() const { return option.collectDebugInfo; }
485 bool doLazyComputation() const { return option.lazyComputation; }
486 bool doKeepOnlyMaxDelayPaths() const { return option.keepOnlyMaxDelayPaths; }
487 bool isLocalScope() const { return instanceGraph == nullptr; }
488 StringAttr getTopModuleName() const { return option.topModuleName; }
489
490private:
491 bool isRunningParallel() const { return !doLazyComputation(); }
492 llvm::sys::SmartMutex<true> mutex;
493 llvm::SetVector<StringAttr> running;
494 LongestPathAnalysisOptions option;
495};
496
497//===----------------------------------------------------------------------===//
498// OperationAnalyzer
499//===----------------------------------------------------------------------===//
500
501// OperationAnalyzer handles timing analysis for individual operations that
502// haven't been converted to AIG yet. It creates isolated modules for each
503// unique operation type/signature combination, runs the conversion pipeline
504// (HW -> Comb -> AIG), and analyzes the resulting AIG representation.
505//
506// This is used as a fallback when the main analysis encounters operations
507// that don't have direct AIG equivalents. The analyzer:
508// 1. Creates a wrapper HW module for the operation
509// 2. Runs the standard conversion pipeline to lower it to AIG
510// 3. Analyzes the resulting AIG to compute timing paths
511// 4. Caches results based on operation name and function signature
513public:
514 // Constructor creates a new analyzer with an empty module for building
515 // temporary operation wrappers.
516 // The analyzer needs its own Context separate from the main analysis
517 // to avoid using onlyMaxDelay mode, as we require precise input
518 // dependency information for accurate delay propagation
519 OperationAnalyzer(Location loc)
520 : ctx(nullptr, LongestPathAnalysisOptions(false, true, false)), loc(loc) {
521 mlir::OpBuilder builder(loc->getContext());
522 moduleOp = mlir::ModuleOp::create(builder, loc);
523 emptyName = StringAttr::get(loc->getContext(), "");
524 }
525
526 // Initialize the pass pipeline used to lower operations to AIG.
527 // This sets up the standard conversion sequence: HW -> Comb -> AIG -> cleanup
528 LogicalResult initializePipeline();
529
530 // Analyze timing paths for a specific operation result and bit position.
531 // Creates a wrapper module if needed, runs conversion, and returns timing
532 // information as input dependencies.
533 // Results are tuples of (input operand index, bit position in operand, delay)
534 LogicalResult analyzeOperation(
535 OpResult value, size_t bitPos,
536 SmallVectorImpl<std::tuple<size_t, size_t, int64_t>> &results);
537
538private:
539 // Get or create a LocalVisitor for analyzing the given operation.
540 // Uses caching based on operation name and function signature to avoid
541 // recomputing analysis for identical operations.
542 FailureOr<LocalVisitor *> getOrComputeLocalVisitor(Operation *op);
543
544 // Extract the function signature (input/output types) from an operation.
545 // This is used as part of the cache key to distinguish operations with
546 // different type signatures.
547 static mlir::FunctionType getFunctionTypeForOp(Operation *op);
548
549 // Cache mapping (operation_name, function_type) -> analyzed LocalVisitor
550 // This avoids recomputing analysis for operations with identical signatures
551 llvm::DenseMap<std::pair<mlir::OperationName, mlir::FunctionType>,
552 std::unique_ptr<LocalVisitor>>
554
555 // Standard conversion pipeline: HW -> Comb -> AIG -> cleanup passes
556 // This pipeline is applied to wrapper modules to prepare them for analysis
557 constexpr static StringRef pipelineStr =
558 "hw.module(hw-aggregate-to-comb,convert-comb-to-synth,cse,canonicalize)";
559 std::unique_ptr<mlir::PassManager> passManager;
560
561 // Temporary module used to hold wrapper modules during analysis
562 // Each operation gets its own wrapper module created inside this parent
564
565 Context ctx; // Analysis context and configuration
566 Location loc; // Source location for error reporting
567 StringAttr emptyName;
568};
569
570mlir::FunctionType OperationAnalyzer::getFunctionTypeForOp(Operation *op) {
571 return mlir::FunctionType::get(op->getContext(), op->getOperandTypes(),
572 op->getResultTypes());
573}
574
575// -----------------------------------------------------------------------------
576// LocalVisitor
577// -----------------------------------------------------------------------------
578
580public:
581 LocalVisitor(hw::HWModuleOp module, Context *ctx);
582 LogicalResult initializeAndRun();
583 // Wait until the thread is done.
584 void waitUntilDone() const;
585
586 // Get the longest paths for the given value and bit position.
587 // If the result is not cached, compute it and cache it.
588 FailureOr<ArrayRef<OpenPath>> getOrComputePaths(Value value, size_t bitPos);
589
590 // Get the longest paths for the given value and bit position. This returns
591 // an empty array if not pre-computed.
592 ArrayRef<OpenPath> getCachedPaths(Value value, size_t bitPos) const;
593
594 // A map from the object to the maximum distance and history.
596 llvm::MapVector<Object,
597 std::pair<int64_t, llvm::ImmutableList<DebugPoint>>>;
598
599 void getInternalPaths(SmallVectorImpl<DataflowPath> &results) const;
600 void setTopLevel() { this->topLevel = true; }
601 bool isTopLevel() const { return topLevel; }
602 hw::HWModuleOp getHWModuleOp() const { return module; }
603
604 const auto &getFromInputPortToEndPoint() const {
605 return fromInputPortToEndPoint;
606 }
607 const auto &getFromOutputPortToStartPoint() const {
608 return fromOutputPortToStartPoint;
609 }
610 const auto &getEndPointResults() const { return endPointResults; }
611
613 return instancePathCache.get();
614 }
615
616 llvm::ImmutableListFactory<DebugPoint> *getDebugPointFactory() const {
617 return debugPointFactory.get();
618 }
619
620private:
621 void putUnclosedResult(const Object &object, int64_t delay,
622 llvm::ImmutableList<DebugPoint> history,
623 ObjectToMaxDistance &objectToMaxDistance);
624
625 // A map from the input port to the farthest end point.
626 llvm::MapVector<std::pair<BlockArgument, size_t>, ObjectToMaxDistance>
628
629 // A map from the output port to the farthest start point.
630 llvm::MapVector<std::tuple<size_t, size_t>, ObjectToMaxDistance>
632
633 LogicalResult initializeAndRun(hw::InstanceOp instance);
634 LogicalResult initializeAndRun(hw::OutputOp output);
635
636 // --------------------------------------------------------------------------
637 // Visitors
638 // --------------------------------------------------------------------------
639 LogicalResult visitValue(Value value, size_t bitPos,
640 SmallVectorImpl<OpenPath> &results);
641 // Module boundary.
642 LogicalResult visit(mlir::BlockArgument argument, size_t bitPos,
643 SmallVectorImpl<OpenPath> &results);
644 LogicalResult visit(hw::InstanceOp op, size_t bitPos, size_t resultNum,
645 SmallVectorImpl<OpenPath> &results);
646
647 // Data-movement ops.
648 LogicalResult visit(hw::WireOp op, size_t bitPos,
649 SmallVectorImpl<OpenPath> &results);
650 LogicalResult visit(comb::ConcatOp op, size_t bitPos,
651 SmallVectorImpl<OpenPath> &results);
652 LogicalResult visit(comb::ExtractOp op, size_t bitPos,
653 SmallVectorImpl<OpenPath> &results);
654 LogicalResult visit(comb::ReplicateOp op, size_t bitPos,
655 SmallVectorImpl<OpenPath> &results);
656 // Track equivalence classes for data movement ops
657 // Treat output as equivalent to input for pure data movement operations
658 llvm::EquivalenceClasses<std::pair<Value, size_t>> ec;
659 DenseMap<std::pair<Value, size_t>, std::pair<Value, size_t>> ecMap;
660 std::pair<Value, size_t> findLeader(Value value, size_t bitpos) const {
661 return ec.getLeaderValue({value, bitpos});
662 }
663 LogicalResult markEquivalent(Value from, size_t fromBitPos, Value to,
664 size_t toBitPos,
665 SmallVectorImpl<OpenPath> &results);
666
667 // Bit-logical ops.
668 LogicalResult visit(aig::AndInverterOp op, size_t bitPos,
669 SmallVectorImpl<OpenPath> &results);
670 LogicalResult visit(mig::MajorityInverterOp op, size_t bitPos,
671 SmallVectorImpl<OpenPath> &results);
672 LogicalResult visit(comb::AndOp op, size_t bitPos,
673 SmallVectorImpl<OpenPath> &results);
674 LogicalResult visit(comb::XorOp op, size_t bitPos,
675 SmallVectorImpl<OpenPath> &results);
676 LogicalResult visit(comb::OrOp op, size_t bitPos,
677 SmallVectorImpl<OpenPath> &results);
678 LogicalResult visit(comb::MuxOp op, size_t bitPos,
679 SmallVectorImpl<OpenPath> &results);
680 LogicalResult addLogicOp(Operation *op, size_t bitPos,
681 SmallVectorImpl<OpenPath> &results);
682 LogicalResult visit(comb::TruthTableOp op, size_t bitPos,
683 SmallVectorImpl<OpenPath> &results);
684
685 // Constants.
686 LogicalResult visit(hw::ConstantOp op, size_t bitPos,
687 SmallVectorImpl<OpenPath> &results) {
688 return success();
689 }
690
691 // Registers are start point.
692 LogicalResult visit(seq::FirRegOp op, size_t bitPos,
693 SmallVectorImpl<OpenPath> &results) {
694 return markStartPoint(op, bitPos, results);
695 }
696
697 LogicalResult visit(seq::CompRegOp op, size_t bitPos,
698 SmallVectorImpl<OpenPath> &results) {
699 return markStartPoint(op, bitPos, results);
700 }
701
702 LogicalResult visit(seq::FirMemReadOp op, size_t bitPos,
703 SmallVectorImpl<OpenPath> &results) {
704 return markStartPoint(op, bitPos, results);
705 }
706
707 LogicalResult visit(seq::FirMemReadWriteOp op, size_t bitPos,
708 SmallVectorImpl<OpenPath> &results) {
709 return markStartPoint(op, bitPos, results);
710 }
711
712 LogicalResult visitDefault(OpResult result, size_t bitPos,
713 SmallVectorImpl<OpenPath> &results);
714
715 // Helper functions.
716 LogicalResult addEdge(Value to, size_t toBitPos, int64_t delay,
717 SmallVectorImpl<OpenPath> &results);
718 LogicalResult markStartPoint(Value value, size_t bitPos,
719 SmallVectorImpl<OpenPath> &results);
720 LogicalResult markRegEndPoint(Value endPoint, Value start, Value reset = {},
721 Value resetValue = {}, Value enable = {});
722
723 // The module we are visiting.
724 hw::HWModuleOp module;
726
727 // Thread-local data structures.
728 std::unique_ptr<circt::igraph::InstancePathCache> instancePathCache;
729 // TODO: Make debug points optional.
730 std::unique_ptr<llvm::ImmutableListFactory<DebugPoint>> debugPointFactory;
731
732 // A map from the value point to the longest paths.
733 DenseMap<std::pair<Value, size_t>, std::unique_ptr<SmallVector<OpenPath>>>
735
736 // A map from the object to the longest paths.
737 DenseMap<Object, SmallVector<OpenPath>> endPointResults;
738
739 // The operation analyzer for comb/hw operations remaining in the circuit.
740 std::unique_ptr<OperationAnalyzer> operationAnalyzer;
741
742 // This is set to true when the thread is done.
743 std::atomic_bool done;
744 mutable std::condition_variable cv;
745 mutable std::mutex mutex;
746
747 // A flag to indicate the module is top-level.
748 bool topLevel = false;
749};
750
752 : module(module), ctx(ctx) {
754 std::make_unique<llvm::ImmutableListFactory<DebugPoint>>();
755 instancePathCache = ctx->instanceGraph
756 ? std::make_unique<circt::igraph::InstancePathCache>(
757 *ctx->instanceGraph)
758 : nullptr;
759 operationAnalyzer = std::make_unique<OperationAnalyzer>(module->getLoc());
760
761 done = false;
762}
763
764ArrayRef<OpenPath> LocalVisitor::getCachedPaths(Value value,
765 size_t bitPos) const {
766 std::pair<Value, size_t> valueAndBitPos(value, bitPos);
767 auto leader = ec.findLeader(valueAndBitPos);
768 if (leader != ec.member_end()) {
769 if (*leader != valueAndBitPos) {
770 // If this is not the leader, then use the leader.
771 return getCachedPaths(leader->first, leader->second);
772 }
773 }
774
775 auto it = cachedResults.find(valueAndBitPos);
776 // If not found, then consider it to be a constant.
777 if (it == cachedResults.end())
778 return {};
779 return *it->second;
780}
781
782void LocalVisitor::putUnclosedResult(const Object &object, int64_t delay,
783 llvm::ImmutableList<DebugPoint> history,
784 ObjectToMaxDistance &objectToMaxDistance) {
785 auto &slot = objectToMaxDistance[object];
786 if (slot.first >= delay && delay != 0)
787 return;
788 slot = {delay, history};
789}
790
792 // Wait for the thread to finish.
793 std::unique_lock<std::mutex> lock(mutex);
794 cv.wait(lock, [this] { return done.load(); });
795}
796
797LogicalResult LocalVisitor::markRegEndPoint(Value endPoint, Value start,
798 Value reset, Value resetValue,
799 Value enable) {
800 auto bitWidth = getBitWidth(endPoint);
801 auto record = [&](size_t endPointBitPos, Value value, size_t bitPos) {
802 auto result = getOrComputePaths(value, bitPos);
803 if (failed(result))
804 return failure();
805 for (auto &path : *result) {
806 if (auto blockArg = dyn_cast<BlockArgument>(path.startPoint.value)) {
807 // Not closed.
809 {{}, endPoint, endPointBitPos}, path.delay, path.history,
810 fromInputPortToEndPoint[{blockArg, path.startPoint.bitPos}]);
811 } else {
812 // If the start point is not a port, record to the results.
813 endPointResults[{{}, endPoint, endPointBitPos}].push_back(path);
814 }
815 }
816 return success();
817 };
818
819 // Get paths for each bit, and record them.
820 for (size_t i = 0, e = bitWidth; i < e; ++i) {
821 // Call getOrComputePaths to make sure the paths are computed for endPoint.
822 // This avoids a race condition.
823 if (failed(getOrComputePaths(endPoint, i)))
824 return failure();
825 if (failed(record(i, start, i)))
826 return failure();
827 }
828
829 // Edge from a reg to reset/resetValue.
830 if (reset)
831 for (size_t i = 0, e = bitWidth; i < e; ++i) {
832 if (failed(record(i, reset, 0)) || failed(record(i, resetValue, i)))
833 return failure();
834 }
835
836 // Edge from a reg to enable signal.
837 if (enable)
838 for (size_t i = 0, e = bitWidth; i < e; ++i) {
839 if (failed(record(i, enable, 0)))
840 return failure();
841 }
842
843 return success();
844}
845
846LogicalResult LocalVisitor::markEquivalent(Value from, size_t fromBitPos,
847 Value to, size_t toBitPos,
848 SmallVectorImpl<OpenPath> &results) {
849 [[maybe_unused]] auto leader = ec.getOrInsertLeaderValue({to, toBitPos});
850 // Merge classes, and visit the leader.
851 [[maybe_unused]] auto newLeader =
852 ec.unionSets({to, toBitPos}, {from, fromBitPos});
853 assert(leader == *newLeader);
854 return visitValue(to, toBitPos, results);
855}
856
857LogicalResult LocalVisitor::addEdge(Value to, size_t bitPos, int64_t delay,
858 SmallVectorImpl<OpenPath> &results) {
859 auto result = getOrComputePaths(to, bitPos);
860 if (failed(result))
861 return failure();
862 for (auto &path : *result) {
863 auto newPath = path;
864 newPath.delay += delay;
865 results.push_back(newPath);
866 }
867 return success();
868}
869
870LogicalResult LocalVisitor::visit(aig::AndInverterOp op, size_t bitPos,
871 SmallVectorImpl<OpenPath> &results) {
872
873 return addLogicOp(op, bitPos, results);
874}
875
876LogicalResult LocalVisitor::visit(mig::MajorityInverterOp op, size_t bitPos,
877 SmallVectorImpl<OpenPath> &results) {
878 // Use a 3-input majority inverter as the basic unit.
879 // An n-input majority inverter requires n/2 stages of 3-input gates.
880 size_t depth = op.getInputs().size() / 2;
881 for (auto input : op.getInputs()) {
882 if (failed(addEdge(input, bitPos, depth, results)))
883 return failure();
884 }
885 return success();
886}
887
888LogicalResult LocalVisitor::visit(comb::AndOp op, size_t bitPos,
889 SmallVectorImpl<OpenPath> &results) {
890 return addLogicOp(op, bitPos, results);
891}
892
893LogicalResult LocalVisitor::visit(comb::OrOp op, size_t bitPos,
894 SmallVectorImpl<OpenPath> &results) {
895 return addLogicOp(op, bitPos, results);
896}
897
898LogicalResult LocalVisitor::visit(comb::XorOp op, size_t bitPos,
899 SmallVectorImpl<OpenPath> &results) {
900 return addLogicOp(op, bitPos, results);
901}
902
903LogicalResult LocalVisitor::visit(comb::MuxOp op, size_t bitPos,
904 SmallVectorImpl<OpenPath> &results) {
905 // Add a cost of 1 for the mux.
906 if (failed(addEdge(op.getCond(), 0, 1, results)) ||
907 failed(addEdge(op.getTrueValue(), bitPos, 1, results)) ||
908 failed(addEdge(op.getFalseValue(), bitPos, 1, results)))
909 return failure();
910 filterPaths(results, ctx->doKeepOnlyMaxDelayPaths(), ctx->isLocalScope());
911 return success();
912}
913
914LogicalResult LocalVisitor::visit(comb::TruthTableOp op, size_t bitPos,
915 SmallVectorImpl<OpenPath> &results) {
916 for (auto input : op.getInputs()) {
917 if (failed(addEdge(input, 0, 1, results)))
918 return failure();
919 }
920 return success();
921}
922
923LogicalResult LocalVisitor::visit(comb::ExtractOp op, size_t bitPos,
924 SmallVectorImpl<OpenPath> &results) {
925 assert(getBitWidth(op.getInput()) > bitPos + op.getLowBit());
926 auto result = markEquivalent(op, bitPos, op.getInput(),
927 bitPos + op.getLowBit(), results);
928 return result;
929}
930
931LogicalResult LocalVisitor::visit(comb::ReplicateOp op, size_t bitPos,
932 SmallVectorImpl<OpenPath> &results) {
933 return markEquivalent(op, bitPos, op.getInput(),
934 bitPos % getBitWidth(op.getInput()), results);
935}
936
937LogicalResult LocalVisitor::markStartPoint(Value value, size_t bitPos,
938 SmallVectorImpl<OpenPath> &results) {
939 results.emplace_back(circt::igraph::InstancePath(), value, bitPos);
940 return success();
941}
942
943LogicalResult LocalVisitor::visit(hw::WireOp op, size_t bitPos,
944 SmallVectorImpl<OpenPath> &results) {
945 return markEquivalent(op, bitPos, op.getInput(), bitPos, results);
946}
947
948LogicalResult LocalVisitor::visit(hw::InstanceOp op, size_t bitPos,
949 size_t resultNum,
950 SmallVectorImpl<OpenPath> &results) {
951 auto moduleName = op.getReferencedModuleNameAttr();
952 auto value = op->getResult(resultNum);
953
954 // If an instance graph is not available, we treat instance results as a
955 // start point.
956 if (!ctx->instanceGraph)
957 return markStartPoint(value, bitPos, results);
958
959 // If an instance graph is available, then we can look up the module.
960 auto *node = ctx->instanceGraph->lookup(moduleName);
961 assert(node && "module not found");
962
963 // Otherwise, if the module is not a HWModuleOp, then we should treat it as a
964 // start point.
965 if (!isa<hw::HWModuleOp>(node->getModule()))
966 return markStartPoint(value, bitPos, results);
967
968 auto *localVisitor = ctx->getLocalVisitorMutable(moduleName);
969
970 auto module = localVisitor->getHWModuleOp();
971 auto operand = module.getBodyBlock()->getTerminator()->getOperand(resultNum);
972 auto result = localVisitor->getOrComputePaths(operand, bitPos);
973 if (failed(result))
974 return failure();
975
976 for (auto &path : *result) {
977 auto delay = path.delay;
978 auto history = path.history;
979 auto newPath =
980 instancePathCache->prependInstance(op, path.startPoint.instancePath);
981 auto startPointPoint = path.startPoint;
982 // If the start point is not a block argument, record it directly.
983 auto arg = dyn_cast<BlockArgument>(startPointPoint.value);
984 if (!arg) {
985 // Update the history to have correct instance path.
986 auto newHistory = debugPointFactory->getEmptyList();
987 if (ctx->doTraceDebugPoints()) {
988 newHistory =
989 mapList(debugPointFactory.get(), history, [&](DebugPoint p) {
990 p.object.instancePath =
991 instancePathCache->prependInstance(op, p.object.instancePath);
992 return p;
993 });
994 newHistory = debugPointFactory->add(
995 DebugPoint({}, value, bitPos, delay, "output port"), newHistory);
996 }
997
998 results.emplace_back(newPath, startPointPoint.value,
999 startPointPoint.bitPos, delay, newHistory);
1000 continue;
1001 }
1002
1003 // Otherwise, we need to look up the instance operand.
1004 auto result = getOrComputePaths(op->getOperand(arg.getArgNumber()),
1005 startPointPoint.bitPos);
1006 if (failed(result))
1007 return failure();
1008 for (auto path : *result) {
1009 auto newHistory = debugPointFactory->getEmptyList();
1010 if (ctx->doTraceDebugPoints()) {
1011 // Update the history to have correct instance path.
1012 newHistory =
1013 mapList(debugPointFactory.get(), history, [&](DebugPoint p) {
1014 p.object.instancePath =
1015 instancePathCache->prependInstance(op, p.object.instancePath);
1016 p.delay += path.delay;
1017 return p;
1018 });
1019 DebugPoint debugPoint({}, value, bitPos, delay + path.delay,
1020 "output port");
1021 newHistory = debugPointFactory->add(debugPoint, newHistory);
1022 }
1023
1024 path.delay += delay;
1025 path.history =
1026 concatList(debugPointFactory.get(), newHistory, path.history);
1027 results.push_back(path);
1028 }
1029 }
1030 return success();
1031}
1032
1033LogicalResult LocalVisitor::visit(comb::ConcatOp op, size_t bitPos,
1034 SmallVectorImpl<OpenPath> &results) {
1035 // Find the exact bit pos in the concat.
1036 size_t newBitPos = bitPos;
1037 for (auto operand : llvm::reverse(op.getInputs())) {
1038 auto size = getBitWidth(operand);
1039 if (newBitPos >= size) {
1040 newBitPos -= size;
1041 continue;
1042 }
1043 return markEquivalent(op, bitPos, operand, newBitPos, results);
1044 }
1045
1046 llvm::report_fatal_error("Should not reach here");
1047 return failure();
1048}
1049
1050LogicalResult LocalVisitor::addLogicOp(Operation *op, size_t bitPos,
1051 SmallVectorImpl<OpenPath> &results) {
1052 auto size = op->getNumOperands();
1053 auto cost = llvm::Log2_64_Ceil(size);
1054 // Create edges each operand with cost ceil(log(size)).
1055 for (auto operand : op->getOperands())
1056 if (failed(addEdge(operand, bitPos, cost, results)))
1057 return failure();
1058 filterPaths(results, ctx->doKeepOnlyMaxDelayPaths(), ctx->isLocalScope());
1059 return success();
1060}
1061
1062LogicalResult LocalVisitor::visitDefault(OpResult value, size_t bitPos,
1063 SmallVectorImpl<OpenPath> &results) {
1064 if (!isa_and_nonnull<hw::HWDialect, comb::CombDialect>(
1065 value.getDefiningOp()->getDialect()))
1066 return success();
1067 // Query it to an operation analyzer.
1068 LLVM_DEBUG({
1069 llvm::dbgs() << "Visiting default: ";
1070 llvm::dbgs() << " " << value << "[" << bitPos << "]\n";
1071 });
1072 SmallVector<std::tuple<size_t, size_t, int64_t>> oracleResults;
1073 auto paths =
1074 operationAnalyzer->analyzeOperation(value, bitPos, oracleResults);
1075 if (failed(paths)) {
1076 LLVM_DEBUG({
1077 llvm::dbgs() << "Failed to get results for: " << value << "[" << bitPos
1078 << "]\n";
1079 });
1080 return success();
1081 }
1082 auto *op = value.getDefiningOp();
1083 for (auto [inputPortIndex, startPointBitPos, delay] : oracleResults) {
1084 LLVM_DEBUG({
1085 llvm::dbgs() << "Adding edge: " << value << "[" << bitPos << "] -> "
1086 << op->getOperand(inputPortIndex) << "[" << startPointBitPos
1087 << "] with delay " << delay << "\n";
1088 });
1089 if (failed(addEdge(op->getOperand(inputPortIndex), startPointBitPos, delay,
1090 results)))
1091 return failure();
1092 }
1093 return success();
1094}
1095
1096LogicalResult LocalVisitor::visit(mlir::BlockArgument arg, size_t bitPos,
1097 SmallVectorImpl<OpenPath> &results) {
1098 assert(arg.getOwner() == module.getBodyBlock());
1099
1100 // Record a debug point.
1101 auto newHistory = ctx->doTraceDebugPoints()
1102 ? debugPointFactory->add(
1103 DebugPoint({}, arg, bitPos, 0, "input port"), {})
1104 : debugPointFactory->getEmptyList();
1105 OpenPath newPoint({}, arg, bitPos, 0, newHistory);
1106 results.push_back(newPoint);
1107 return success();
1108}
1109
1110FailureOr<ArrayRef<OpenPath>> LocalVisitor::getOrComputePaths(Value value,
1111 size_t bitPos) {
1112
1113 if (value.getDefiningOp<hw::ConstantOp>())
1114 return ArrayRef<OpenPath>{};
1115
1116 if (ec.contains({value, bitPos})) {
1117 auto leader = ec.findLeader({value, bitPos});
1118 // If this is not the leader, then use the leader.
1119 if (*leader != std::pair(value, bitPos)) {
1120 return getOrComputePaths(leader->first, leader->second);
1121 }
1122 }
1123
1124 auto it = cachedResults.find({value, bitPos});
1125 if (it != cachedResults.end())
1126 return ArrayRef<OpenPath>(*it->second);
1127
1128 auto results = std::make_unique<SmallVector<OpenPath>>();
1129 if (failed(visitValue(value, bitPos, *results)))
1130 return {};
1131
1132 // Unique the results.
1133 filterPaths(*results, ctx->doKeepOnlyMaxDelayPaths(), ctx->isLocalScope());
1134 LLVM_DEBUG({
1135 llvm::dbgs() << value << "[" << bitPos << "] "
1136 << "Found " << results->size() << " paths\n";
1137 llvm::dbgs() << "====Paths:\n";
1138 for (auto &path : *results) {
1139 path.print(llvm::dbgs());
1140 llvm::dbgs() << "\n";
1141 }
1142 llvm::dbgs() << "====\n";
1143 });
1144
1145 auto insertedResult =
1146 cachedResults.try_emplace({value, bitPos}, std::move(results));
1147 assert(insertedResult.second);
1148 return ArrayRef<OpenPath>(*insertedResult.first->second);
1149}
1150
1151LogicalResult LocalVisitor::visitValue(Value value, size_t bitPos,
1152 SmallVectorImpl<OpenPath> &results) {
1153 LLVM_DEBUG({
1154 llvm::dbgs() << "Visiting: ";
1155 llvm::dbgs() << " " << value << "[" << bitPos << "]\n";
1156 });
1157
1158 if (auto blockArg = dyn_cast<mlir::BlockArgument>(value))
1159 return visit(blockArg, bitPos, results);
1160
1161 auto *op = value.getDefiningOp();
1162 auto result =
1163 TypeSwitch<Operation *, LogicalResult>(op)
1164 .Case<comb::ConcatOp, comb::ExtractOp, comb::ReplicateOp,
1165 aig::AndInverterOp, mig::MajorityInverterOp, comb::AndOp,
1166 comb::OrOp, comb::MuxOp, comb::XorOp, comb::TruthTableOp,
1167 seq::FirRegOp, seq::CompRegOp, hw::ConstantOp,
1168 seq::FirMemReadOp, seq::FirMemReadWriteOp, hw::WireOp>(
1169 [&](auto op) {
1170 size_t idx = results.size();
1171 auto result = visit(op, bitPos, results);
1172 if (ctx->doTraceDebugPoints())
1173 if (auto name = op->template getAttrOfType<StringAttr>(
1174 "sv.namehint")) {
1175
1176 for (auto i = idx, e = results.size(); i < e; ++i) {
1177 DebugPoint debugPoint({}, value, bitPos, results[i].delay,
1178 "namehint");
1179 auto newHistory = debugPointFactory->add(
1180 debugPoint, results[i].history);
1181 results[i].history = newHistory;
1182 }
1183 }
1184 return result;
1185 })
1186 .Case<hw::InstanceOp>([&](hw::InstanceOp op) {
1187 return visit(op, bitPos, cast<OpResult>(value).getResultNumber(),
1188 results);
1189 })
1190 .Default([&](auto op) {
1191 return visitDefault(cast<OpResult>(value), bitPos, results);
1192 });
1193 return result;
1194}
1195
1196LogicalResult LocalVisitor::initializeAndRun(hw::InstanceOp instance) {
1197 const auto *childVisitor =
1198 ctx->getLocalVisitorMutable(instance.getReferencedModuleNameAttr());
1199 // If not found, the module is blackbox so skip it.
1200 if (!childVisitor)
1201 return success();
1202
1203 // Connect dataflow from instance input ports to end point in the child.
1204 for (const auto &[object, openPaths] :
1205 childVisitor->getFromInputPortToEndPoint()) {
1206 auto [arg, argBitPos] = object;
1207 for (auto [point, delayAndHistory] : openPaths) {
1208 auto [instancePath, endPoint, endPointBitPos] = point;
1209 auto [delay, history] = delayAndHistory;
1210 // Prepend the instance path.
1212 auto newPath = instancePathCache->prependInstance(instance, instancePath);
1213 auto computedResults =
1214 getOrComputePaths(instance.getOperand(arg.getArgNumber()), argBitPos);
1215 if (failed(computedResults))
1216 return failure();
1217
1218 for (auto &result : *computedResults) {
1219 auto newHistory = ctx->doTraceDebugPoints()
1220 ? mapList(debugPointFactory.get(), history,
1221 [&](DebugPoint p) {
1222 // Update the instance path to
1223 // prepend the current instance.
1224 p.object.instancePath = newPath;
1225 p.delay += result.delay;
1226 return p;
1227 })
1228 : debugPointFactory->getEmptyList();
1229 if (auto newPort = dyn_cast<BlockArgument>(result.startPoint.value)) {
1231 {newPath, endPoint, endPointBitPos}, result.delay + delay,
1232 newHistory,
1233 fromInputPortToEndPoint[{newPort, result.startPoint.bitPos}]);
1234 } else {
1235 endPointResults[{newPath, endPoint, endPointBitPos}].emplace_back(
1236 newPath, result.startPoint.value, result.startPoint.bitPos,
1237 result.delay + delay,
1238 ctx->doTraceDebugPoints() ? concatList(debugPointFactory.get(),
1239 newHistory, result.history)
1240 : debugPointFactory->getEmptyList());
1241 }
1242 }
1243 }
1244 }
1245
1246 // Pre-compute the results for the instance output ports.
1247 for (auto instance : instance->getResults()) {
1248 for (size_t i = 0, e = getBitWidth(instance); i < e; ++i) {
1249 auto computedResults = getOrComputePaths(instance, i);
1250 if (failed(computedResults))
1251 return failure();
1252 }
1253 }
1254 return success();
1255}
1256
1257LogicalResult LocalVisitor::initializeAndRun(hw::OutputOp output) {
1258 for (OpOperand &operand : output->getOpOperands()) {
1259 for (size_t i = 0, e = getBitWidth(operand.get()); i < e; ++i) {
1260 auto &recordOutput =
1261 fromOutputPortToStartPoint[{operand.getOperandNumber(), i}];
1262 auto computedResults = getOrComputePaths(operand.get(), i);
1263 if (failed(computedResults))
1264 return failure();
1265 for (const auto &result : *computedResults) {
1266 putUnclosedResult(result.startPoint, result.delay, result.history,
1267 recordOutput);
1268 }
1269 }
1270 }
1271 return success();
1272}
1273
1275 LLVM_DEBUG({ ctx->notifyStart(module.getModuleNameAttr()); });
1276 if (ctx->doLazyComputation())
1277 return success();
1278
1279 // Initialize the results for the block arguments.
1280 for (auto blockArgument : module.getBodyBlock()->getArguments())
1281 for (size_t i = 0, e = getBitWidth(blockArgument); i < e; ++i)
1282 (void)getOrComputePaths(blockArgument, i);
1283
1284 auto walkResult = module->walk([&](Operation *op) {
1285 auto result =
1286 mlir::TypeSwitch<Operation *, LogicalResult>(op)
1287 .Case<seq::FirRegOp>([&](seq::FirRegOp op) {
1288 return markRegEndPoint(op, op.getNext(), op.getReset(),
1289 op.getResetValue());
1290 })
1291 .Case<seq::CompRegOp>([&](auto op) {
1292 return markRegEndPoint(op, op.getInput(), op.getReset(),
1293 op.getResetValue());
1294 })
1295 .Case<seq::FirMemWriteOp>([&](auto op) {
1296 // TODO: Add address.
1297 return markRegEndPoint(op.getMemory(), op.getData(), {}, {},
1298 op.getEnable());
1299 })
1300 .Case<seq::FirMemReadWriteOp>([&](seq::FirMemReadWriteOp op) {
1301 // TODO: Add address.
1302 return markRegEndPoint(op.getMemory(), op.getWriteData(), {}, {},
1303 op.getEnable());
1304 })
1305 .Case<aig::AndInverterOp, comb::AndOp, comb::OrOp, comb::XorOp,
1306 comb::MuxOp, seq::FirMemReadOp>([&](auto op) {
1307 // NOTE: Visiting and-inverter is not necessary but
1308 // useful to reduce recursion depth.
1309 for (size_t i = 0, e = getBitWidth(op); i < e; ++i)
1310 if (failed(getOrComputePaths(op, i)))
1311 return failure();
1312 return success();
1313 })
1314 .Case<hw::InstanceOp, hw::OutputOp>(
1315 [&](auto op) { return initializeAndRun(op); })
1316 .Default([](auto op) { return success(); });
1317 if (failed(result))
1318 return WalkResult::interrupt();
1319 return WalkResult::advance();
1320 });
1321
1322 {
1323 std::lock_guard<std::mutex> lock(mutex);
1324 done.store(true);
1325 cv.notify_all();
1326 }
1327 LLVM_DEBUG({ ctx->notifyEnd(module.getModuleNameAttr()); });
1328 return failure(walkResult.wasInterrupted());
1329}
1330
1331//===----------------------------------------------------------------------===//
1332// Context
1333//===----------------------------------------------------------------------===//
1334
1335const LocalVisitor *Context::getLocalVisitor(StringAttr name) const {
1336 return getLocalVisitorMutable(name);
1337}
1338
1340 auto *it = localVisitors.find(name);
1341 if (it == localVisitors.end())
1342 return nullptr;
1343
1344 if (isRunningParallel())
1345 it->second->waitUntilDone();
1346
1347 // NOTE: Don't call waitUntilDone here.
1348 return it->second.get();
1349}
1350
1351// ===----------------------------------------------------------------------===//
1352// OperationAnalyzer
1353// ===----------------------------------------------------------------------===//
1354
1355FailureOr<LocalVisitor *>
1357 // Check cache first.
1358 auto opName = op->getName();
1359 auto functionType = getFunctionTypeForOp(op);
1360 auto key = std::make_pair(opName, functionType);
1361 auto it = cache.find(key);
1362 if (it != cache.end())
1363 return it->second.get();
1364
1365 SmallVector<hw::PortInfo> ports;
1366 // Helper to convert types into integer types.
1367 auto getType = [&](Type type) -> Type {
1368 if (type.isInteger())
1369 return type;
1370 auto bitWidth = hw::getBitWidth(type);
1371 if (bitWidth < 0)
1372 return Type(); // Unsupported type
1373 return IntegerType::get(op->getContext(), bitWidth);
1374 };
1375
1376 // Helper to add a port to the module definition
1377 auto addPort = [&](Type type, hw::ModulePort::Direction dir) {
1378 hw::PortInfo portInfo;
1379 portInfo.dir = dir;
1380 portInfo.name = emptyName;
1381 portInfo.type = type;
1382 ports.push_back(portInfo);
1383 };
1384
1385 // Create input ports for each operand
1386 for (auto input : op->getOperands()) {
1387 auto type = getType(input.getType());
1388 if (!type)
1389 return failure(); // Unsupported operand type
1390 addPort(type, hw::ModulePort::Direction::Input);
1391 }
1392
1393 // Create output ports for each result.
1394 SmallVector<Type> resultsTypes;
1395 for (Value result : op->getResults()) {
1396 auto type = getType(result.getType());
1397 if (!type)
1398 return failure(); // Unsupported result type
1399 addPort(type, hw::ModulePort::Direction::Output);
1400 resultsTypes.push_back(type);
1401 }
1402
1403 // Build the wrapper HW module
1404 OpBuilder builder(op->getContext());
1405 builder.setInsertionPointToEnd(moduleOp->getBody());
1406
1407 // Generate unique module name.
1408 auto moduleName = builder.getStringAttr("module_" + Twine(cache.size()));
1409 hw::HWModuleOp hwModule =
1410 hw::HWModuleOp::create(builder, op->getLoc(), moduleName, ports);
1411
1412 // Clone the operation inside the wrapper module
1413 builder.setInsertionPointToStart(hwModule.getBodyBlock());
1414 auto *cloned = builder.clone(*op);
1415
1416 // Connect module inputs to cloned operation operands
1417 // Handle type mismatches with bitcast operations
1418 // Type mismatches can occur when the original operation uses non-integer
1419 // types (e.g., structs, arrays) that get converted to integer types for the
1420 // wrapper module ports. Since we use the same bit width via
1421 // hw::getBitWidth(), bitcast is safe for bit-compatible types.
1422 for (auto arg : hwModule.getBodyBlock()->getArguments()) {
1423 Value input = arg;
1424 auto idx = arg.getArgNumber();
1425
1426 // Insert bitcast if input port type differs from operand type
1427 if (input.getType() != cloned->getOperand(idx).getType())
1428 input = hw::BitcastOp::create(builder, op->getLoc(),
1429 cloned->getOperand(idx).getType(), input);
1430
1431 cloned->setOperand(idx, input);
1432 }
1433
1434 // Connect cloned operation results to module outputs
1435 // Handle type mismatches with bitcast operations
1436 SmallVector<Value> outputs;
1437 for (auto result : cloned->getResults()) {
1438 auto idx = result.getResultNumber();
1439
1440 // Insert bitcast if result type differs from output port type
1441 if (result.getType() != resultsTypes[idx])
1442 result = hw::BitcastOp::create(builder, op->getLoc(), resultsTypes[idx],
1443 result)
1444 ->getResult(0);
1445
1446 outputs.push_back(result);
1447 }
1448
1449 hwModule.getBodyBlock()->getTerminator()->setOperands(outputs);
1450
1451 // Run conversion pipeline (HW -> Comb -> AIG -> cleanup)
1452 if (!passManager && failed(initializePipeline()))
1453 return mlir::emitError(loc)
1454 << "Failed to initialize pipeline, possibly passes used in the "
1455 "analysis are not registered";
1456
1457 if (failed(passManager->run(moduleOp->getOperation())))
1458 return mlir::emitError(loc) << "Failed to run lowering pipeline";
1459
1460 // Create LocalVisitor to analyze the converted AIG module
1461 auto localVisitor = std::make_unique<LocalVisitor>(hwModule, &ctx);
1462 if (failed(localVisitor->initializeAndRun()))
1463 return failure();
1464
1465 // Cache the result and return
1466 auto [iterator, inserted] = cache.insert({key, std::move(localVisitor)});
1467 assert(inserted && "Cache insertion must succeed for new key");
1468 return iterator->second.get();
1469}
1470
1472 OpResult value, size_t bitPos,
1473 SmallVectorImpl<std::tuple<size_t, size_t, int64_t>> &results) {
1474 auto *op = value.getDefiningOp();
1475 auto localVisitorResult = getOrComputeLocalVisitor(op);
1476 if (failed(localVisitorResult))
1477 return failure();
1478
1479 auto *localVisitor = *localVisitorResult;
1480
1481 // Get output.
1482 Value operand =
1483 localVisitor->getHWModuleOp().getBodyBlock()->getTerminator()->getOperand(
1484 value.getResultNumber());
1485 auto openPaths = localVisitor->getOrComputePaths(operand, bitPos);
1486 if (failed(openPaths))
1487 return failure();
1488
1489 results.reserve(openPaths->size() + results.size());
1490 for (auto &path : *openPaths) {
1491 // end point is always a block argument since there is no other value that
1492 // could be a start point.
1493 BlockArgument blockArg = cast<BlockArgument>(path.startPoint.value);
1494 auto inputPortIndex = blockArg.getArgNumber();
1495 results.push_back(
1496 std::make_tuple(inputPortIndex, path.startPoint.bitPos, path.delay));
1497 }
1498
1499 return success();
1500}
1501
1503 passManager = std::make_unique<mlir::PassManager>(loc->getContext());
1504 return parsePassPipeline(pipelineStr, *passManager);
1505}
1506
1507//===----------------------------------------------------------------------===//
1508// LongestPathAnalysis::Impl
1509//===----------------------------------------------------------------------===//
1510
1511/// Internal implementation for LongestPathAnalysis.
1512///
1513/// This class owns per-module LocalVisitors, orchestrates initialization over
1514/// the instance graph, and provides the concrete implementations for the
1515/// public LongestPathAnalysis API.
1517 Impl(Operation *module, mlir::AnalysisManager &am,
1518 const LongestPathAnalysisOptions &option);
1519
1520 /// Initialize and run analysis for a full MLIR module (hierarchical).
1521 LogicalResult initializeAndRun(mlir::ModuleOp module);
1522
1523 /// Initialize and run analysis for a single HW module (local scope).
1524 LogicalResult initializeAndRun(hw::HWModuleOp module);
1525
1526 /// Return true if we have a LocalVisitor for the given HW module.
1527 bool isAnalysisAvailable(StringAttr moduleName) const;
1528
1529 /// Compute hierarchical timing paths to (value, bitPos) and append to
1530 /// results.
1531 LogicalResult computeGlobalPaths(Value value, size_t bitPos,
1532 SmallVectorImpl<DataflowPath> &results);
1533
1534 /// Collect register-to-register (closed) paths within the module. When
1535 /// 'elaborate' is true, paths are elaborated with instance paths from
1536 /// `moduleName`.
1537 template <bool elaborate>
1538 LogicalResult
1539 collectClosedPaths(StringAttr moduleName,
1540 SmallVectorImpl<DataflowPath> &results) const;
1541
1542 /// Collect open paths from module input ports to internal sequential sinks.
1543 LogicalResult
1544 collectInputToInternalPaths(StringAttr moduleName,
1545 SmallVectorImpl<DataflowPath> &results) const;
1546
1547 /// Collect open paths from internal sequential sources to module output
1548 /// ports.
1549 LogicalResult
1550 collectInternalToOutputPaths(StringAttr moduleName,
1551 SmallVectorImpl<DataflowPath> &results) const;
1552
1553 /// Top modules inferred or specified for this analysis run.
1554 llvm::ArrayRef<hw::HWModuleOp> getTopModules() const { return topModules; }
1555
1556 /// Return average of per-bit max delays for a value.
1557 FailureOr<int64_t> getAverageMaxDelay(Value value);
1558
1559 /// Return the max delay for a value.
1560 FailureOr<int64_t> getMaxDelay(Value value, int64_t bitPos);
1561
1562 /// Compute local open paths to (value, bitPos).
1563 FailureOr<ArrayRef<OpenPath>> computeLocalPaths(Value value, size_t bitPos);
1564
1565protected:
1567
1568private:
1569 /// Recursive helper for computeGlobalPaths.
1570 LogicalResult computeGlobalPaths(const Object &originalObject, Value value,
1571 size_t bitPos,
1572 SmallVectorImpl<DataflowPath> &results);
1573
1574 /// Analysis context.
1576 /// Top-level HW modules that seed hierarchical analysis.
1577 SmallVector<hw::HWModuleOp> topModules;
1578};
1579
1581 Value value, size_t bitPos, SmallVectorImpl<DataflowPath> &results) {
1582 return computeGlobalPaths(Object({}, value, bitPos), value, bitPos, results);
1583}
1584
1586 const Object &originalObject, Value value, size_t bitPos,
1587 SmallVectorImpl<DataflowPath> &results) {
1588 auto parentHWModule =
1589 value.getParentRegion()->getParentOfType<hw::HWModuleOp>();
1590 if (!parentHWModule)
1591 return mlir::emitError(value.getLoc())
1592 << "query value is not in a HWModuleOp";
1593 auto *localVisitor =
1594 ctx.getLocalVisitorMutable(parentHWModule.getModuleNameAttr());
1595 if (!localVisitor)
1596 return success();
1597
1598 auto *instancePathCache = localVisitor->getInstancePathCache();
1599 size_t oldIndex = results.size();
1600 auto *node =
1601 ctx.instanceGraph
1602 ? ctx.instanceGraph->lookup(parentHWModule.getModuleNameAttr())
1603 : nullptr;
1604 LLVM_DEBUG({
1605 llvm::dbgs() << "Running " << parentHWModule.getModuleNameAttr() << " "
1606 << value << " " << bitPos << "\n";
1607 });
1608 auto paths = localVisitor->getOrComputePaths(value, bitPos);
1609 if (failed(paths))
1610 return failure();
1611
1612 for (auto &path : *paths) {
1613 auto arg = dyn_cast<BlockArgument>(path.startPoint.value);
1614 if (!arg || localVisitor->isTopLevel()) {
1615 // If the value is not a block argument, then we are done.
1616 results.push_back({originalObject, path, parentHWModule});
1617 continue;
1618 }
1619
1620 auto newObject = originalObject;
1621 assert(node && "If an instance graph is not available, localVisitor must "
1622 "be a toplevel");
1623 for (auto *inst : node->uses()) {
1624 auto startIndex = results.size();
1625 if (instancePathCache)
1626 newObject.instancePath = instancePathCache->appendInstance(
1627 originalObject.instancePath, inst->getInstance());
1628
1629 auto result = computeGlobalPaths(
1630 newObject, inst->getInstance()->getOperand(arg.getArgNumber()),
1631 path.startPoint.bitPos, results);
1632 if (failed(result))
1633 return result;
1634 for (auto i = startIndex, e = results.size(); i < e; ++i)
1635 results[i].setDelay(results[i].getDelay() + path.delay);
1636 }
1637 }
1638
1639 filterPaths(results, oldIndex);
1640 return success();
1641}
1642
1643template <bool elaborate>
1645 StringAttr moduleName, SmallVectorImpl<DataflowPath> &results) const {
1646 auto collectClosedPaths = [&](StringAttr name,
1647 SmallVectorImpl<DataflowPath> &localResults,
1648 igraph::InstanceGraphNode *top = nullptr) {
1649 if (!isAnalysisAvailable(name))
1650 return;
1651 auto *visitor = ctx.getLocalVisitorMutable(name);
1652 for (auto &[point, state] : visitor->getEndPointResults()) {
1653 for (const auto &dataFlow : state) {
1654 if constexpr (elaborate) {
1655 // If elaborate, we need to prepend the path to the root.
1656 auto *instancePathCache = visitor->getInstancePathCache();
1657 auto topToRoot = instancePathCache->getRelativePaths(
1658 visitor->getHWModuleOp(), top);
1659 for (auto &instancePath : topToRoot) {
1660 localResults.emplace_back(point, dataFlow,
1661 top->getModule<hw::HWModuleOp>());
1662 localResults.back().prependPaths(*visitor->getInstancePathCache(),
1663 visitor->getDebugPointFactory(),
1664 instancePath);
1665 }
1666 } else {
1667 localResults.emplace_back(point, dataFlow, visitor->getHWModuleOp());
1668 }
1669 }
1670 }
1671 };
1672
1673 if (ctx.instanceGraph) {
1674 // Accumulate all closed results under the given module.
1675 auto *node = ctx.instanceGraph->lookup(moduleName);
1676 llvm::MapVector<StringAttr, SmallVector<DataflowPath>> resultsMap;
1677 // Prepare the results map for parallel processing.
1678 for (auto *child : llvm::post_order(node))
1679 resultsMap[child->getModule().getModuleNameAttr()] = {};
1680
1681 mlir::parallelForEach(
1682 node->getModule().getContext(), resultsMap,
1683 [&](auto &it) { collectClosedPaths(it.first, it.second, node); });
1684
1685 for (auto &[name, localResults] : resultsMap)
1686 results.append(localResults.begin(), localResults.end());
1687 } else {
1688 collectClosedPaths(moduleName, results);
1689 }
1690
1691 return success();
1692}
1693
1695 StringAttr moduleName, SmallVectorImpl<DataflowPath> &results) const {
1696 auto *visitor = ctx.getLocalVisitor(moduleName);
1697 if (!visitor)
1698 return failure();
1699
1700 for (auto &[key, value] : visitor->getFromInputPortToEndPoint()) {
1701 auto [arg, argBitPos] = key;
1702 for (auto [point, delayAndHistory] : value) {
1703 auto [path, start, startBitPos] = point;
1704 auto [delay, history] = delayAndHistory;
1705 results.emplace_back(Object(path, start, startBitPos),
1706 OpenPath({}, arg, argBitPos, delay, history),
1707 visitor->getHWModuleOp());
1708 }
1709 }
1710
1711 return success();
1712}
1713
1715 StringAttr moduleName, SmallVectorImpl<DataflowPath> &results) const {
1716 auto *visitor = ctx.getLocalVisitor(moduleName);
1717 if (!visitor)
1718 return failure();
1719
1720 for (auto &[key, value] : visitor->getFromOutputPortToStartPoint()) {
1721 auto [resultNum, bitPos] = key;
1722 for (auto [point, delayAndHistory] : value) {
1723 auto [path, start, startBitPos] = point;
1724 auto [delay, history] = delayAndHistory;
1725 results.emplace_back(
1726 std::make_tuple(visitor->getHWModuleOp(), resultNum, bitPos),
1727 OpenPath(path, start, startBitPos, delay, history),
1728 visitor->getHWModuleOp());
1729 }
1730 }
1731
1732 return success();
1733}
1734
1735LongestPathAnalysis::Impl::Impl(Operation *moduleOp, mlir::AnalysisManager &am,
1736 const LongestPathAnalysisOptions &option)
1737 : ctx(isa<mlir::ModuleOp>(moduleOp)
1738 ? &am.getAnalysis<igraph::InstanceGraph>()
1739 : nullptr,
1740 option) {
1741 if (auto module = dyn_cast<mlir::ModuleOp>(moduleOp)) {
1742 if (failed(initializeAndRun(module)))
1743 llvm::report_fatal_error("Failed to run longest path analysis");
1744 } else if (auto hwMod = dyn_cast<hw::HWModuleOp>(moduleOp)) {
1745 if (failed(initializeAndRun(hwMod)))
1746 llvm::report_fatal_error("Failed to run longest path analysis");
1747 } else {
1748 llvm::report_fatal_error("Analysis scheduled on invalid operation");
1749 }
1750}
1751
1752LogicalResult
1754 auto it =
1755 ctx.localVisitors.insert({module.getModuleNameAttr(),
1756 std::make_unique<LocalVisitor>(module, &ctx)});
1757 assert(it.second);
1758 it.first->second->setTopLevel();
1759 return it.first->second->initializeAndRun();
1760}
1761
1762LogicalResult
1764 auto topNameAttr = ctx.getTopModuleName();
1765 topModules.clear();
1766 llvm::SetVector<Operation *> visited;
1767 auto *instanceGraph = ctx.instanceGraph;
1768 if (topNameAttr && topNameAttr.getValue() != "") {
1769 auto *topNode = instanceGraph->lookupOrNull(topNameAttr);
1770 if (!topNode || !topNode->getModule() ||
1771 !isa<hw::HWModuleOp>(topNode->getModule())) {
1772 module.emitError() << "top module not found in instance graph "
1773 << topNameAttr;
1774 return failure();
1775 }
1776 topModules.push_back(topNode->getModule<hw::HWModuleOp>());
1777 } else {
1778 auto inferredResults = instanceGraph->getInferredTopLevelNodes();
1779 if (failed(inferredResults))
1780 return inferredResults;
1781
1782 for (auto *node : *inferredResults) {
1783 if (auto top = dyn_cast<hw::HWModuleOp>(*node->getModule()))
1784 topModules.push_back(top);
1785 }
1786 }
1787
1788 SmallVector<igraph::InstanceGraphNode *> worklist;
1789 for (auto topNode : topModules)
1790 worklist.push_back(instanceGraph->lookup(topNode.getModuleNameAttr()));
1791 // Get a set of design modules that are reachable from the top nodes,
1792 // excluding bound instances.
1793 while (!worklist.empty()) {
1794 auto *node = worklist.pop_back_val();
1795 assert(node && "node should not be null");
1796 auto op = node->getModule();
1797 if (!isa_and_nonnull<hw::HWModuleOp>(op) || !visited.insert(op))
1798 continue;
1799
1800 for (auto *child : *node) {
1801 auto childOp = child->getInstance();
1802 if (!childOp || childOp->hasAttr("doNotPrint"))
1803 continue;
1804
1805 worklist.push_back(child->getTarget());
1806 }
1807 }
1808
1809 // Initialize the local visitors if the module was visited, in the
1810 // post-order of the instance graph.
1811 for (auto module : topModules) {
1812 auto *topNode = instanceGraph->lookup(module.getModuleNameAttr());
1813 for (auto *node : llvm::post_order(topNode))
1814 if (node && node->getModule())
1815 if (auto hwMod = dyn_cast<hw::HWModuleOp>(*node->getModule())) {
1816 if (visited.contains(hwMod))
1817 ctx.localVisitors.insert(
1818 {hwMod.getModuleNameAttr(),
1819 std::make_unique<LocalVisitor>(hwMod, &ctx)});
1820 }
1821
1822 ctx.localVisitors[topNode->getModule().getModuleNameAttr()]->setTopLevel();
1823 }
1824
1825 return mlir::failableParallelForEach(
1826 module.getContext(), ctx.localVisitors,
1827 [&](auto &it) { return it.second->initializeAndRun(); });
1828}
1829
1831 StringAttr moduleName) const {
1832 return ctx.localVisitors.find(moduleName) != ctx.localVisitors.end();
1833}
1834// Return the average of the maximum delays across all bits of the given
1835// value, which is useful approximation for the delay of the value. For each
1836// bit position, finds all paths and takes the maximum delay. Then averages
1837// these maximum delays across all bits of the value.
1838FailureOr<int64_t> LongestPathAnalysis::Impl::getAverageMaxDelay(Value value) {
1839 SmallVector<DataflowPath> results;
1840 size_t bitWidth = getBitWidth(value);
1841 if (bitWidth == 0)
1842 return 0;
1843 int64_t totalDelay = 0;
1844 for (size_t i = 0; i < bitWidth; ++i) {
1845 // Clear results from previous iteration.
1846 results.clear();
1847 auto result = computeGlobalPaths(value, i, results);
1848 if (failed(result))
1849 return failure();
1850
1851 int64_t maxDelay = getMaxDelayInPaths(ArrayRef<DataflowPath>(results));
1852 totalDelay += maxDelay;
1853 }
1854 return llvm::divideCeil(totalDelay, bitWidth);
1855}
1856
1857FailureOr<int64_t> LongestPathAnalysis::Impl::getMaxDelay(Value value,
1858 int64_t bitPos) {
1859 SmallVector<DataflowPath> results;
1860 auto collectAndFindMax = ([&](int64_t bitPos) -> FailureOr<int64_t> {
1861 results.clear();
1862 auto result = computeGlobalPaths(value, bitPos, results);
1863 if (failed(result))
1864 return failure();
1865 return getMaxDelayInPaths(ArrayRef<DataflowPath>(results));
1866 });
1867 if (bitPos >= 0)
1868 return collectAndFindMax(bitPos);
1869
1870 size_t bitWidth = getBitWidth(value);
1871 if (bitWidth == 0)
1872 return 0;
1873
1874 int64_t maxDelay = 0;
1875 for (size_t i = 0; i < bitWidth; ++i) {
1876 auto result = collectAndFindMax(i);
1877 if (failed(result))
1878 return failure();
1879 maxDelay = std::max(maxDelay, *result);
1880 }
1881 return maxDelay;
1882}
1883
1884FailureOr<ArrayRef<OpenPath>>
1886 auto parentHWModule =
1887 value.getParentRegion()->getParentOfType<hw::HWModuleOp>();
1888 if (!parentHWModule)
1889 return mlir::emitError(value.getLoc())
1890 << "query value is not in a HWModuleOp";
1891 assert(ctx.localVisitors.size() == 1 &&
1892 "In incremental mode, there should be only one local visitor");
1893
1894 auto *localVisitor =
1895 ctx.getLocalVisitorMutable(parentHWModule.getModuleNameAttr());
1896 if (!localVisitor)
1897 return mlir::emitError(value.getLoc())
1898 << "the local visitor for the given value does not exist";
1899 return localVisitor->getOrComputePaths(value, bitPos);
1900}
1901
1902//===----------------------------------------------------------------------===//
1903// LongestPathAnalysis
1904//===----------------------------------------------------------------------===//
1905
1906LongestPathAnalysis::~LongestPathAnalysis() { delete impl; }
1907
1908LongestPathAnalysis::LongestPathAnalysis(
1909 Operation *moduleOp, mlir::AnalysisManager &am,
1910 const LongestPathAnalysisOptions &option)
1911 : impl(new Impl(moduleOp, am, option)), ctx(moduleOp->getContext()) {
1912 LLVM_DEBUG({
1913 llvm::dbgs() << "LongestPathAnalysis created\n";
1914 if (option.collectDebugInfo)
1915 llvm::dbgs() << " - Collecting debug info\n";
1916 if (option.lazyComputation)
1917 llvm::dbgs() << " - Lazy computation enabled\n";
1918 if (option.keepOnlyMaxDelayPaths)
1919 llvm::dbgs() << " - Keeping only max delay paths\n";
1920 });
1921}
1922
1923bool LongestPathAnalysis::isAnalysisAvailable(StringAttr moduleName) const {
1924 return impl->isAnalysisAvailable(moduleName);
1925}
1926
1927FailureOr<int64_t> LongestPathAnalysis::getAverageMaxDelay(Value value) {
1928 return impl->getAverageMaxDelay(value);
1929}
1930
1931FailureOr<int64_t> LongestPathAnalysis::getMaxDelay(Value value,
1932 int64_t bitPos) {
1933 return impl->getMaxDelay(value, bitPos);
1934}
1935
1936LogicalResult
1938 SmallVectorImpl<DataflowPath> &results,
1939 bool elaboratePaths) const {
1940 if (!isAnalysisValid)
1941 return failure();
1942 if (elaboratePaths)
1943 return impl->collectClosedPaths<true>(moduleName, results);
1944 return impl->collectClosedPaths<false>(moduleName, results);
1945}
1946
1948 StringAttr moduleName, SmallVectorImpl<DataflowPath> &results) const {
1949 if (!isAnalysisValid)
1950 return failure();
1951
1952 return impl->collectInputToInternalPaths(moduleName, results);
1953}
1954
1956 StringAttr moduleName, SmallVectorImpl<DataflowPath> &results) const {
1957 if (!isAnalysisValid)
1958 return failure();
1959
1960 return impl->collectInternalToOutputPaths(moduleName, results);
1961}
1962
1963LogicalResult
1965 SmallVectorImpl<DataflowPath> &results,
1966 bool elaboratePaths) const {
1967 if (failed(getInternalPaths(moduleName, results, elaboratePaths)))
1968 return failure();
1969 if (failed(getOpenPathsFromInputPortsToInternal(moduleName, results)))
1970 return failure();
1971 if (failed(getOpenPathsFromInternalToOutputPorts(moduleName, results)))
1972 return failure();
1973 return success();
1974}
1975
1976ArrayRef<hw::HWModuleOp> LongestPathAnalysis::getTopModules() const {
1977 return impl->getTopModules();
1978}
1979
1980FailureOr<ArrayRef<OpenPath>>
1981LongestPathAnalysis::computeLocalPaths(Value value, size_t bitPos) {
1982 if (!isAnalysisValid)
1983 return failure();
1984
1985 return impl->computeLocalPaths(value, bitPos);
1986}
1987
1989 Value value, size_t bitPos, SmallVectorImpl<DataflowPath> &results) {
1990 if (!isAnalysisValid)
1991 return mlir::emitError(value.getLoc()) << "analysis has been invalidated";
1992
1993 return impl->computeGlobalPaths(value, bitPos, results);
1994}
1995
1996//===----------------------------------------------------------------------===//
1997// IncrementalLongestPathAnalysis
1998//===----------------------------------------------------------------------===//
1999
2001 Operation *op) const {
2002 if (!isAnalysisValid)
2003 return false;
2004
2005 auto parentHWModule =
2006 op->getParentRegion()->getParentOfType<hw::HWModuleOp>();
2007 if (!parentHWModule)
2008 return true;
2009 auto *localVisitor =
2010 impl->ctx.getLocalVisitor(parentHWModule.getModuleNameAttr());
2011 if (!localVisitor)
2012 return true;
2013
2014 // If all results of the operation have no paths, then it is safe to mutate
2015 // the operation.
2016 return llvm::all_of(op->getResults(), [localVisitor](Value value) {
2017 for (int64_t i = 0, e = getBitWidth(value); i < e; ++i) {
2018 auto path = localVisitor->getCachedPaths(value, i);
2019 if (!path.empty())
2020 return false;
2021 }
2022 return true;
2023 });
2024}
2025
2031 Operation *op, ValueRange replacement) {
2033}
2034
2038
2039// ===----------------------------------------------------------------------===//
2040// LongestPathCollection
2041// ===----------------------------------------------------------------------===//
2042
2044 llvm::stable_sort(paths, [](const DataflowPath &a, const DataflowPath &b) {
2045 return a.getDelay() > b.getDelay();
2046 });
2047}
2048
2050 // Deduplicate paths by start/end-point, keeping only the worst-case delay.
2051 if (perEndPoint) {
2052 llvm::DenseSet<DataflowPath::EndPointType> seen;
2053 for (size_t i = 0; i < paths.size(); ++i) {
2054 if (seen.insert(paths[i].getEndPoint()).second)
2055 paths[seen.size() - 1] = std::move(paths[i]);
2056 }
2057
2058 paths.resize(seen.size());
2059 } else {
2060 llvm::DenseSet<Object> seen;
2061 for (size_t i = 0; i < paths.size(); ++i) {
2062 if (seen.insert(paths[i].getStartPoint()).second)
2063 paths[seen.size() - 1] = std::move(paths[i]);
2064 }
2065 paths.resize(seen.size());
2066 }
2067}
2068
2070 paths.append(other.paths.begin(), other.paths.end());
2072}
assert(baseType &&"element must be base type")
static void printObjectImpl(llvm::raw_ostream &os, const Object &object, int64_t delay=-1, llvm::ImmutableList< DebugPoint > history={}, StringRef comment="")
static llvm::ImmutableList< DebugPoint > mapList(llvm::ImmutableListFactory< DebugPoint > *debugPointFactory, llvm::ImmutableList< DebugPoint > list, llvm::function_ref< DebugPoint(DebugPoint)> fn)
static llvm::ImmutableList< DebugPoint > concatList(llvm::ImmutableListFactory< DebugPoint > *debugPointFactory, llvm::ImmutableList< DebugPoint > lhs, llvm::ImmutableList< DebugPoint > rhs)
static void filterPaths(SmallVectorImpl< OpenPath > &results, bool keepOnlyMaxDelay, bool isLocalScope)
static void deduplicatePathsImpl(SmallVectorImpl< T > &results, size_t startIndex, llvm::function_ref< Key(const T &)> keyFn, llvm::function_ref< int64_t(const T &)> delayFn)
static StringAttr getNameImpl(Value value)
static int64_t getMaxDelayInPaths(ArrayRef< T > paths)
This class provides a thread-safe interface to access the analysis results.
circt::igraph::InstanceGraph * instanceGraph
const LocalVisitor * getLocalVisitor(StringAttr name) const
void notifyEnd(StringAttr name)
bool doTraceDebugPoints() const
llvm::sys::SmartMutex< true > mutex
bool doKeepOnlyMaxDelayPaths() const
LongestPathAnalysisOptions option
llvm::MapVector< StringAttr, std::unique_ptr< LocalVisitor > > localVisitors
bool isRunningParallel() const
LocalVisitor * getLocalVisitorMutable(StringAttr name) const
StringAttr getTopModuleName() const
Context(igraph::InstanceGraph *instanceGraph, const LongestPathAnalysisOptions &option)
llvm::SetVector< StringAttr > running
bool isLocalScope() const
bool doLazyComputation() const
void notifyStart(StringAttr name)
hw::HWModuleOp getHWModuleOp() const
ArrayRef< OpenPath > getCachedPaths(Value value, size_t bitPos) const
LogicalResult addEdge(Value to, size_t toBitPos, int64_t delay, SmallVectorImpl< OpenPath > &results)
LogicalResult visitValue(Value value, size_t bitPos, SmallVectorImpl< OpenPath > &results)
LogicalResult addLogicOp(Operation *op, size_t bitPos, SmallVectorImpl< OpenPath > &results)
std::unique_ptr< llvm::ImmutableListFactory< DebugPoint > > debugPointFactory
DenseMap< std::pair< Value, size_t >, std::pair< Value, size_t > > ecMap
llvm::MapVector< Object, std::pair< int64_t, llvm::ImmutableList< DebugPoint > > > ObjectToMaxDistance
LogicalResult markRegEndPoint(Value endPoint, Value start, Value reset={}, Value resetValue={}, Value enable={})
std::pair< Value, size_t > findLeader(Value value, size_t bitpos) const
LogicalResult visitDefault(OpResult result, size_t bitPos, SmallVectorImpl< OpenPath > &results)
FailureOr< ArrayRef< OpenPath > > getOrComputePaths(Value value, size_t bitPos)
const auto & getFromInputPortToEndPoint() const
llvm::ImmutableListFactory< DebugPoint > * getDebugPointFactory() const
LogicalResult visit(seq::FirMemReadOp op, size_t bitPos, SmallVectorImpl< OpenPath > &results)
LogicalResult markStartPoint(Value value, size_t bitPos, SmallVectorImpl< OpenPath > &results)
LogicalResult markEquivalent(Value from, size_t fromBitPos, Value to, size_t toBitPos, SmallVectorImpl< OpenPath > &results)
const auto & getEndPointResults() const
llvm::MapVector< std::pair< BlockArgument, size_t >, ObjectToMaxDistance > fromInputPortToEndPoint
std::atomic_bool done
DenseMap< std::pair< Value, size_t >, std::unique_ptr< SmallVector< OpenPath > > > cachedResults
hw::HWModuleOp Context * ctx
llvm::MapVector< std::tuple< size_t, size_t >, ObjectToMaxDistance > fromOutputPortToStartPoint
LogicalResult visit(seq::CompRegOp op, size_t bitPos, SmallVectorImpl< OpenPath > &results)
void getInternalPaths(SmallVectorImpl< DataflowPath > &results) const
DenseMap< Object, SmallVector< OpenPath > > endPointResults
LogicalResult visit(seq::FirRegOp op, size_t bitPos, SmallVectorImpl< OpenPath > &results)
LogicalResult initializeAndRun()
llvm::EquivalenceClasses< std::pair< Value, size_t > > ec
bool isTopLevel() const
LogicalResult visit(seq::FirMemReadWriteOp op, size_t bitPos, SmallVectorImpl< OpenPath > &results)
LogicalResult visit(mlir::BlockArgument argument, size_t bitPos, SmallVectorImpl< OpenPath > &results)
std::unique_ptr< OperationAnalyzer > operationAnalyzer
LogicalResult visit(hw::ConstantOp op, size_t bitPos, SmallVectorImpl< OpenPath > &results)
std::condition_variable cv
void putUnclosedResult(const Object &object, int64_t delay, llvm::ImmutableList< DebugPoint > history, ObjectToMaxDistance &objectToMaxDistance)
std::unique_ptr< circt::igraph::InstancePathCache > instancePathCache
circt::igraph::InstancePathCache * getInstancePathCache() const
LocalVisitor(hw::HWModuleOp module, Context *ctx)
const auto & getFromOutputPortToStartPoint() const
void waitUntilDone() const
std::unique_ptr< mlir::PassManager > passManager
static constexpr StringRef pipelineStr
mlir::OwningOpRef< mlir::ModuleOp > moduleOp
FailureOr< LocalVisitor * > getOrComputeLocalVisitor(Operation *op)
llvm::DenseMap< std::pair< mlir::OperationName, mlir::FunctionType >, std::unique_ptr< LocalVisitor > > cache
static mlir::FunctionType getFunctionTypeForOp(Operation *op)
LogicalResult analyzeOperation(OpResult value, size_t bitPos, SmallVectorImpl< std::tuple< size_t, size_t, int64_t > > &results)
LogicalResult initializePipeline()
OperationAnalyzer(Location loc)
HW-specific instance graph with a virtual entry node linking to all publicly visible modules.
This is a Node in the InstanceGraph.
This graph tracks modules and where they are instantiated.
InstanceGraphNode * lookupOrNull(StringAttr name)
Lookup an module by name.
InstanceGraphNode * lookup(ModuleOpInterface op)
Look up an InstanceGraphNode for a module.
An instance path composed of a series of instances.
InstanceOpInterface top() const
std::variant< Object, OutputPort > EndPointType
DataflowPath & prependPaths(circt::igraph::InstancePathCache &cache, llvm::ImmutableListFactory< DebugPoint > *debugPointFactory, circt::igraph::InstancePath path)
const OpenPath & getPath() const
hw::HWModuleOp getRoot() const
void print(llvm::raw_ostream &os)
void printEndPoint(llvm::raw_ostream &os)
const EndPointType & getEndPoint() const
void notifyOperationReplaced(Operation *op, ValueRange replacement) override
FailureOr< int64_t > getAverageMaxDelay(Value value)
LogicalResult computeGlobalPaths(Value value, size_t bitPos, SmallVectorImpl< DataflowPath > &results)
FailureOr< int64_t > getMaxDelay(Value value, int64_t bitPos=-1)
LogicalResult getInternalPaths(StringAttr moduleName, SmallVectorImpl< DataflowPath > &results, bool elaboratePaths=false) const
FailureOr< ArrayRef< OpenPath > > computeLocalPaths(Value value, size_t bitPos)
LogicalResult getAllPaths(StringAttr moduleName, SmallVectorImpl< DataflowPath > &results, bool elaboratePaths=false) const
LogicalResult getOpenPathsFromInternalToOutputPorts(StringAttr moduleName, SmallVectorImpl< DataflowPath > &results) const
llvm::ArrayRef< hw::HWModuleOp > getTopModules() const
bool isAnalysisAvailable(StringAttr moduleName) const
LogicalResult getOpenPathsFromInputPortsToInternal(StringAttr moduleName, SmallVectorImpl< DataflowPath > &results) const
void merge(const LongestPathCollection &other)
llvm::SmallVector< DataflowPath, 64 > paths
void dropNonCriticalPaths(bool perEndPoint=true)
Impl(int port)
Start a server on the given port. -1 means to let the OS pick a port.
create(data_type, value)
Definition hw.py:441
str root(self)
Definition synth.py:152
Object object
Definition synth.py:102
int64_t getBitWidth(mlir::Type type)
Return the hardware bit width of a type.
Definition HWTypes.cpp:110
llvm::json::Value toJSON(const circt::synth::DataflowPath &path)
The InstanceGraph op interface, see InstanceGraphInterface.td for more details.
Definition synth.py:1
Internal implementation for LongestPathAnalysis.
LogicalResult collectInternalToOutputPaths(StringAttr moduleName, SmallVectorImpl< DataflowPath > &results) const
Collect open paths from internal sequential sources to module output ports.
LogicalResult computeGlobalPaths(Value value, size_t bitPos, SmallVectorImpl< DataflowPath > &results)
Compute hierarchical timing paths to (value, bitPos) and append to results.
llvm::ArrayRef< hw::HWModuleOp > getTopModules() const
Top modules inferred or specified for this analysis run.
FailureOr< ArrayRef< OpenPath > > computeLocalPaths(Value value, size_t bitPos)
Compute local open paths to (value, bitPos).
FailureOr< int64_t > getMaxDelay(Value value, int64_t bitPos)
Return the max delay for a value.
LogicalResult collectClosedPaths(StringAttr moduleName, SmallVectorImpl< DataflowPath > &results) const
Collect register-to-register (closed) paths within the module.
bool isAnalysisAvailable(StringAttr moduleName) const
Return true if we have a LocalVisitor for the given HW module.
LogicalResult initializeAndRun(mlir::ModuleOp module)
Initialize and run analysis for a full MLIR module (hierarchical).
FailureOr< int64_t > getAverageMaxDelay(Value value)
Return average of per-bit max delays for a value.
LogicalResult collectInputToInternalPaths(StringAttr moduleName, SmallVectorImpl< DataflowPath > &results) const
Collect open paths from module input ports to internal sequential sinks.
SmallVector< hw::HWModuleOp > topModules
Top-level HW modules that seed hierarchical analysis.
mlir::Type type
Definition HWTypes.h:31
mlir::StringAttr name
Definition HWTypes.h:30
This holds the name, type, direction of a module's ports.
A data structure that caches and provides paths to module instances in the IR.
ArrayRef< InstancePath > getRelativePaths(ModuleOpInterface op, InstanceGraphNode *node)
InstancePath concatPath(InstancePath path1, InstancePath path2)
Concatenate two paths.
void print(llvm::raw_ostream &os) const
Configuration options for the longest path analysis.
bool collectDebugInfo
Enable collection of debug points along timing paths.
bool lazyComputation
Enable lazy computation mode for on-demand analysis.
bool keepOnlyMaxDelayPaths
Keep only the maximum delay path per end point.
Object & prependPaths(circt::igraph::InstancePathCache &cache, circt::igraph::InstancePath path)
void print(llvm::raw_ostream &os) const
OpenPath & prependPaths(circt::igraph::InstancePathCache &cache, llvm::ImmutableListFactory< DebugPoint > *debugPointFactory, circt::igraph::InstancePath path)