Skip to content

Commit 294bfb4

Browse files
committed
[AIG][DatapathToComb] Implement incremental longest path analysis for IR transformations
This commit introduces IncrementalLongestPathAnalysis to support lazy computation of delay information during IR mutations in synthesis passes. The analysis is updated incrementally so that we can lazily compute the delay while performing IR mutations. A first use case is the DatapathToComb conversion pass. Key changes include adding the IncrementalLongestPathAnalysis class that extends the existing longest path analysis with incremental update capabilities. The implementation includes the PatternRewriter::Listener interface to track IR mutations and maintain analysis validity. New getOrComputeDelay() and getOrComputePaths() methods enable on-demand delay computation, and the incremental analysis is integrated into DatapathCompressOpConversion for timing-aware compress operation lowering. The longest path analysis is inherently complex and use-def chains must be taken into account when rewriting the IR. A top-down rewriting approach is necessary to maintain analysis correctness, as bottom-up mutations can invalidate paths that depend on erased values. The incremental analysis validates that IR mutations don't break existing timing paths and provides early error detection when values used in critical paths are inappropriately modified.
1 parent dda0235 commit 294bfb4

File tree

5 files changed

+233
-13
lines changed

5 files changed

+233
-13
lines changed

include/circt/Dialect/AIG/Analysis/LongestPathAnalysis.h

Lines changed: 36 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
#include "circt/Support/LLVM.h"
2323
#include "mlir/IR/BuiltinOps.h"
2424
#include "mlir/IR/MLIRContext.h"
25+
#include "mlir/IR/Operation.h"
2526
#include "llvm/ADT/ArrayRef.h"
2627
#include "llvm/ADT/ImmutableList.h"
2728
#include "llvm/ADT/SmallVector.h"
@@ -171,6 +172,11 @@ llvm::json::Value toJSON(const circt::aig::DataflowPath &path);
171172
// Options for the longest path analysis.
172173
struct LongestPathAnalysisOption {
173174
bool traceDebugPoints = false;
175+
bool incremental = false;
176+
177+
LongestPathAnalysisOption(bool traceDebugPoints, bool incremental)
178+
: traceDebugPoints(traceDebugPoints), incremental(incremental) {}
179+
LongestPathAnalysisOption() = default;
174180
};
175181

176182
// This analysis finds the longest paths in the dataflow graph across modules.
@@ -248,19 +254,47 @@ class LongestPathAnalysis {
248254

249255
MLIRContext *getContext() const { return ctx; }
250256

251-
private:
257+
protected:
258+
friend class IncrementalLongestPathAnalysis;
252259
struct Impl;
253260
Impl *impl;
254261

262+
private:
255263
mlir::MLIRContext *ctx;
256264
};
257265

266+
// For incremental updates, this class exposes subset of the analysis results
267+
// and allows incremental updates.
268+
class IncrementalLongestPathAnalysis : private LongestPathAnalysis,
269+
public mlir::PatternRewriter::Listener {
270+
public:
271+
IncrementalLongestPathAnalysis(Operation *moduleOp, mlir::AnalysisManager &am)
272+
: LongestPathAnalysis(moduleOp, am,
273+
LongestPathAnalysisOption(false, true)) {}
274+
275+
FailureOr<int64_t> getOrComputeDelay(Value value, size_t bitPos);
276+
FailureOr<ArrayRef<OpenPath>> getOrComputePaths(Value value, size_t bitPos);
277+
278+
// Returns true if the analysis is still valid after the value is erased.
279+
// If the client erased a value, then the analyais cannot be used anymore.
280+
// A client should check this before erasing a value, and if this
281+
// returns false, the client should raise an error or should not rely on the
282+
// analysis.
283+
bool isValueValidToErase(Value value) const;
284+
285+
// MLIR PatternRewriter::Listener interface.
286+
void notifyOperationReplaced(Operation *op, ValueRange replacement) override;
287+
void notifyOperationErased(Operation *op) override;
288+
289+
bool isAnalysisValid = true;
290+
};
291+
258292
// A wrapper class for the longest path analysis that also traces debug points.
259293
// This is necessary for analysis manager to cache the analysis results.
260294
class LongestPathAnalysisWithTrace : public LongestPathAnalysis {
261295
public:
262296
LongestPathAnalysisWithTrace(Operation *moduleOp, mlir::AnalysisManager &am)
263-
: LongestPathAnalysis(moduleOp, am, {true}) {}
297+
: LongestPathAnalysis(moduleOp, am, {true, false}) {}
264298
};
265299

266300
// A collection of longest paths. The data structure owns the paths, and used

lib/Conversion/DatapathToComb/DatapathToComb.cpp

Lines changed: 56 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,11 @@
77
//===----------------------------------------------------------------------===//
88

99
#include "circt/Conversion/DatapathToComb.h"
10+
#include "circt/Dialect/AIG/Analysis/LongestPathAnalysis.h"
1011
#include "circt/Dialect/Comb/CombOps.h"
1112
#include "circt/Dialect/Datapath/DatapathOps.h"
1213
#include "circt/Dialect/HW/HWOps.h"
14+
#include "mlir/Analysis/TopologicalSortUtils.h"
1315
#include "mlir/Dialect/Func/IR/FuncOps.h"
1416
#include "mlir/Pass/Pass.h"
1517
#include "mlir/Transforms/DialectConversion.h"
@@ -62,6 +64,10 @@ struct DatapathCompressOpAddConversion : OpConversionPattern<CompressOp> {
6264
// Replace compressor by a wallace tree of full-adders
6365
struct DatapathCompressOpConversion : OpConversionPattern<CompressOp> {
6466
using OpConversionPattern<CompressOp>::OpConversionPattern;
67+
DatapathCompressOpConversion(MLIRContext *context,
68+
aig::IncrementalLongestPathAnalysis *analysis)
69+
: OpConversionPattern<CompressOp>(context), analysis(analysis) {}
70+
6571
LogicalResult
6672
matchAndRewrite(CompressOp op, OpAdaptor adaptor,
6773
ConversionPatternRewriter &rewriter) const override {
@@ -73,17 +79,45 @@ struct DatapathCompressOpConversion : OpConversionPattern<CompressOp> {
7379
for (auto input : inputs) {
7480
addends.push_back(
7581
extractBits(rewriter, input)); // Extract bits from each input
82+
83+
// NOTE: Following change will be splitted into a separate PR.
84+
if (analysis) {
85+
auto delay = analysis->getOrComputePaths(input, 0);
86+
if (failed(delay))
87+
return op.emitError("Failed to get delay for input");
88+
// TODO: Use the delay information to sort the inputs.
89+
}
90+
91+
LLVM_DEBUG({
92+
llvm::dbgs() << "Input: " << input << " delay: ";
93+
assert(analysis && "Expected analysis to be set");
94+
if (analysis) {
95+
auto delay = analysis->getOrComputeDelay(
96+
input, 0); // Query delay for each input
97+
if (llvm::succeeded(delay))
98+
llvm::dbgs() << *delay;
99+
else
100+
llvm::dbgs() << "N/A";
101+
} else {
102+
llvm::dbgs() << "N/A(analysis not set)";
103+
}
104+
llvm::dbgs() << "\n";
105+
});
76106
}
77107

78108
// Wallace tree reduction
79-
// TODO - implement a more efficient compression algorithm to compete with
109+
// TODO: Implement a more efficient compression algorithm to compete with
80110
// yosys's `alumacc` lowering - a coarse grained timing model would help to
81111
// sort the inputs according to arrival time.
112+
// TODO: Use the listener to get arrival time information.
82113
auto targetAddends = op.getNumResults();
83114
rewriter.replaceOp(op, comb::wallaceReduction(rewriter, loc, width,
84115
targetAddends, addends));
85116
return success();
86117
}
118+
119+
private:
120+
aig::IncrementalLongestPathAnalysis *analysis = nullptr;
87121
};
88122

89123
struct DatapathPartialProductOpConversion
@@ -254,6 +288,22 @@ struct ConvertDatapathToCombPass
254288
};
255289
} // namespace
256290

291+
static LogicalResult
292+
applyConversionWithTimingInfo(Operation *op, const ConversionTarget &target,
293+
RewritePatternSet &&patterns,
294+
aig::IncrementalLongestPathAnalysis *analysis) {
295+
// TODO: Topologically sort the operations in the module to ensure that all
296+
// dependencies are processed before their users.
297+
mlir::ConversionConfig config;
298+
config.listener = analysis;
299+
300+
// Apply the conversion patterns
301+
if (failed(mlir::applyPartialConversion(op, target, std::move(patterns))))
302+
return failure();
303+
304+
return success();
305+
}
306+
257307
void ConvertDatapathToCombPass::runOnOperation() {
258308
ConversionTarget target(getContext());
259309

@@ -264,15 +314,16 @@ void ConvertDatapathToCombPass::runOnOperation() {
264314

265315
patterns.add<DatapathPartialProductOpConversion>(patterns.getContext(),
266316
forceBooth);
267-
317+
auto &analysis = getAnalysis<aig::IncrementalLongestPathAnalysis>();
268318
if (lowerCompressToAdd)
269319
// Lower compressors to simple add operations for downstream optimisations
270320
patterns.add<DatapathCompressOpAddConversion>(patterns.getContext());
271321
else
272322
// Lower compressors to a complete gate-level implementation
273-
patterns.add<DatapathCompressOpConversion>(patterns.getContext());
323+
patterns.add<DatapathCompressOpConversion>(patterns.getContext(),
324+
&analysis);
274325

275-
if (failed(mlir::applyPartialConversion(getOperation(), target,
276-
std::move(patterns))))
326+
if (failed(applyConversionWithTimingInfo(getOperation(), target,
327+
std::move(patterns), &analysis)))
277328
return signalPassFailure();
278329
}

lib/Dialect/AIG/Analysis/LongestPathAnalysis.cpp

Lines changed: 134 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,7 @@
6565
#include "llvm/Support/Mutex.h"
6666
#include "llvm/Support/raw_ostream.h"
6767
#include <condition_variable>
68+
#include <cstddef>
6869
#include <cstdint>
6970
#include <memory>
7071
#include <mutex>
@@ -73,7 +74,7 @@
7374
using namespace circt;
7475
using namespace aig;
7576

76-
static size_t getBitWidth(Value value) {
77+
static int64_t getBitWidth(Value value) {
7778
if (auto vecType = dyn_cast<seq::ClockType>(value.getType()))
7879
return 1;
7980
if (auto memory = dyn_cast<seq::FirMemType>(value.getType()))
@@ -409,12 +410,16 @@ class Context {
409410
// Lookup a local visitor for `name`, and wait until it's done.
410411
const LocalVisitor *getAndWaitLocalVisitor(StringAttr name) const;
411412

413+
// Lookup a mutable local visitor for `name`.
414+
LocalVisitor *getLocalVisitorMutable(StringAttr name) const;
415+
412416
// A map from the module name to the local visitor.
413417
llvm::MapVector<StringAttr, std::unique_ptr<LocalVisitor>> localVisitors;
414418
// This is non-null only if `module` is a ModuleOp.
415419
circt::igraph::InstanceGraph *instanceGraph = nullptr;
416420

417421
bool doTraceDebugPoints() const { return option.traceDebugPoints; }
422+
bool doIncremental() const { return option.incremental; }
418423

419424
private:
420425
llvm::sys::SmartMutex<true> mutex;
@@ -1057,6 +1062,9 @@ LogicalResult LocalVisitor::initializeAndRun(hw::OutputOp output) {
10571062
}
10581063

10591064
LogicalResult LocalVisitor::initializeAndRun() {
1065+
if (ctx->doIncremental())
1066+
return success();
1067+
10601068
LLVM_DEBUG({ ctx->notifyStart(module.getModuleNameAttr()); });
10611069
// Initialize the results for the block arguments.
10621070
for (auto blockArgument : module.getBodyBlock()->getArguments())
@@ -1129,6 +1137,15 @@ const LocalVisitor *Context::getAndWaitLocalVisitor(StringAttr name) const {
11291137
return visitor;
11301138
}
11311139

1140+
LocalVisitor *Context::getLocalVisitorMutable(StringAttr name) const {
1141+
auto *it = localVisitors.find(name);
1142+
if (it == localVisitors.end())
1143+
return nullptr;
1144+
1145+
// NOTE: Don't call waitUntilDone here.
1146+
return it->second.get();
1147+
}
1148+
11321149
//===----------------------------------------------------------------------===//
11331150
// LongestPathAnalysis::Impl
11341151
//===----------------------------------------------------------------------===//
@@ -1159,6 +1176,13 @@ struct LongestPathAnalysis::Impl {
11591176

11601177
llvm::ArrayRef<hw::HWModuleOp> getTopModules() const { return topModules; }
11611178

1179+
// Incremental mode.
1180+
bool doIncremental() const { return ctx.doIncremental(); }
1181+
1182+
protected:
1183+
friend class IncrementalLongestPathAnalysis;
1184+
FailureOr<ArrayRef<OpenPath>> getOrComputePaths(Value value, size_t bitPos);
1185+
11621186
private:
11631187
LogicalResult getResultsImpl(
11641188
const Object &originalObject, Value value, size_t bitPos,
@@ -1330,6 +1354,7 @@ LongestPathAnalysis::Impl::Impl(Operation *moduleOp, mlir::AnalysisManager &am,
13301354
llvm::report_fatal_error("Analysis scheduled on invalid operation");
13311355
}
13321356
}
1357+
13331358
LogicalResult
13341359
LongestPathAnalysis::Impl::initializeAndRun(hw::HWModuleOp module) {
13351360
auto it =
@@ -1459,6 +1484,31 @@ int64_t LongestPathAnalysis::Impl::getMaxDelay(Value value) const {
14591484
return maxDelay;
14601485
}
14611486

1487+
FailureOr<ArrayRef<OpenPath>>
1488+
LongestPathAnalysis::Impl::getOrComputePaths(Value value, size_t bitPos) {
1489+
if (!doIncremental())
1490+
return mlir::emitError(value.getLoc())
1491+
<< "getOrComputeMaxDelay is only available in incremental mode";
1492+
if (ctx.instanceGraph)
1493+
return mlir::emitError(value.getLoc())
1494+
<< "inceremental mode is not supported for global analysis";
1495+
auto parentHWModule =
1496+
value.getParentRegion()->getParentOfType<hw::HWModuleOp>();
1497+
if (!parentHWModule)
1498+
return mlir::emitError(value.getLoc())
1499+
<< "query value is not in a HWModuleOp";
1500+
assert(ctx.localVisitors.size() == 1 &&
1501+
"In incremental mode, there should be only one local visitor");
1502+
1503+
auto *localVisitor =
1504+
ctx.getLocalVisitorMutable(parentHWModule.getModuleNameAttr());
1505+
if (!localVisitor)
1506+
return mlir::emitError(value.getLoc())
1507+
<< "the local visitor for the given value does not exist";
1508+
auto path = localVisitor->getOrComputeResults(value, bitPos);
1509+
return path;
1510+
}
1511+
14621512
//===----------------------------------------------------------------------===//
14631513
// LongestPathAnalysis
14641514
//===----------------------------------------------------------------------===//
@@ -1468,7 +1518,13 @@ LongestPathAnalysis::~LongestPathAnalysis() { delete impl; }
14681518
LongestPathAnalysis::LongestPathAnalysis(
14691519
Operation *moduleOp, mlir::AnalysisManager &am,
14701520
const LongestPathAnalysisOption &option)
1471-
: impl(new Impl(moduleOp, am, option)), ctx(moduleOp->getContext()) {}
1521+
: impl(new Impl(moduleOp, am, option)), ctx(moduleOp->getContext()) {
1522+
llvm::errs() << "LongestPathAnalysis created\n";
1523+
if (option.traceDebugPoints)
1524+
llvm::errs() << " - Tracing debug points\n";
1525+
if (option.incremental)
1526+
llvm::errs() << " - Incremental analysis enabled\n";
1527+
}
14721528

14731529
bool LongestPathAnalysis::isAnalysisAvailable(StringAttr moduleName) const {
14741530
return impl->isAnalysisAvailable(moduleName);
@@ -1518,6 +1574,82 @@ ArrayRef<hw::HWModuleOp> LongestPathAnalysis::getTopModules() const {
15181574
return impl->getTopModules();
15191575
}
15201576

1577+
// === --------------------------------------------------------------------===//
1578+
// InrementalLongestPathAnalysis
1579+
// === --------------------------------------------------------------------===//
1580+
1581+
FailureOr<ArrayRef<OpenPath>>
1582+
IncrementalLongestPathAnalysis::getOrComputePaths(Value value, size_t bitPos) {
1583+
if (!isAnalysisValid)
1584+
return failure();
1585+
1586+
return impl->getOrComputePaths(value, bitPos);
1587+
}
1588+
1589+
FailureOr<int64_t>
1590+
IncrementalLongestPathAnalysis::getOrComputeDelay(Value value, size_t bitPos) {
1591+
if (!isAnalysisValid)
1592+
return failure();
1593+
1594+
auto result = impl->getOrComputePaths(value, bitPos);
1595+
if (failed(result))
1596+
return failure();
1597+
int64_t maxDelay = 0;
1598+
for (auto &path : *result)
1599+
maxDelay = std::max(maxDelay, path.delay);
1600+
return maxDelay;
1601+
}
1602+
1603+
bool IncrementalLongestPathAnalysis::isValueValidToErase(Value value) const {
1604+
if (!isAnalysisValid)
1605+
return false;
1606+
1607+
auto parentHWModule =
1608+
value.getParentRegion()->getParentOfType<hw::HWModuleOp>();
1609+
if (!parentHWModule)
1610+
return true;
1611+
auto *localVisitor =
1612+
impl->ctx.getLocalVisitor(parentHWModule.getModuleNameAttr());
1613+
if (!localVisitor)
1614+
return true;
1615+
1616+
auto end = getBitWidth(value);
1617+
if (end <= 0)
1618+
return true;
1619+
1620+
for (int64_t i = 0, e = getBitWidth(value); i < e; ++i) {
1621+
auto path = localVisitor->getResults(value, i);
1622+
if (!path.empty())
1623+
return false;
1624+
}
1625+
return true;
1626+
}
1627+
1628+
void IncrementalLongestPathAnalysis::notifyOperationReplaced(
1629+
Operation *op, ValueRange replacement) {
1630+
for (auto value : op->getResults()) {
1631+
if (isValueValidToErase(value)) {
1632+
mlir::emitError(value.getLoc())
1633+
<< "value " << value
1634+
<< " replaced but contained in longest path analysis. It's required "
1635+
"to perform the IR mutation from buttom-up";
1636+
isAnalysisValid = false;
1637+
}
1638+
}
1639+
}
1640+
1641+
void IncrementalLongestPathAnalysis::notifyOperationErased(Operation *op) {
1642+
for (auto value : op->getResults()) {
1643+
if (isValueValidToErase(value)) {
1644+
mlir::emitError(value.getLoc())
1645+
<< "value " << value
1646+
<< " replaced but contained in longest path analysis. It's required "
1647+
"to perform the IR mutation from buttom-up";
1648+
isAnalysisValid = false;
1649+
}
1650+
}
1651+
}
1652+
15211653
// ===----------------------------------------------------------------------===//
15221654
// LongestPathCollection
15231655
// ===----------------------------------------------------------------------===//

0 commit comments

Comments
 (0)