Skip to content

Commit 1b72f97

Browse files
committed
add objectFIFO unrolling pass with constant folding and tests
1 parent e9be3d4 commit 1b72f97

51 files changed

Lines changed: 4063 additions & 3873 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

include/aie/Dialect/AIE/IR/AIEOps.td

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -366,7 +366,7 @@ def AIE_ShimDMAOp: AIE_Op<"shim_dma", [
366366
}
367367

368368
def AIE_CoreOp: AIE_Op<"core", [
369-
IsFlowEndPoint, IsCoreTile, TileElement,
369+
IsFlowEndPoint, IsCoreTile, TileElement, AutomaticAllocationScope,
370370
DeclareOpInterfaceMethods<InferTypeOpInterface>
371371
]>, Results<(outs Index)> {
372372
let arguments = (

include/aie/Dialect/AIE/Transforms/AIEPasses.h

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,13 @@
1919

2020
namespace xilinx::AIE {
2121

22+
/// Discardable attribute set by `AIEObjectFifoStatefulTransform` on each
23+
/// `scf.for` loop that carries an objectFifo access. Its i64 value is the loop
24+
/// unroll factor (the least common multiple of the depths of the objectFifos
25+
/// accessed within the loop) consumed by the `AIEObjectFifoUnroll` pass.
26+
inline constexpr llvm::StringLiteral kObjectFifoUnrollHintAttrName =
27+
"aie.unroll_hint";
28+
2229
#define GEN_PASS_DECL
2330
#define GEN_PASS_DEF_AIEROUTEPATHFINDERFLOWS
2431
#include "aie/Dialect/AIE/Transforms/AIEPasses.h.inc"
@@ -54,6 +61,7 @@ createAIEHoistVectorTransferPointersPass();
5461
std::unique_ptr<mlir::OperationPass<DeviceOp>> createAIEPathfinderPass();
5562
std::unique_ptr<mlir::OperationPass<DeviceOp>>
5663
createAIEObjectFifoStatefulTransformPass();
64+
std::unique_ptr<mlir::OperationPass<DeviceOp>> createAIEObjectFifoUnrollPass();
5765
std::unique_ptr<mlir::OperationPass<DeviceOp>> createAIELowerCascadeFlowsPass();
5866
std::unique_ptr<mlir::OperationPass<DeviceOp>>
5967
createAIEAssignBufferDescriptorIDsPass();

include/aie/Dialect/AIE/Transforms/AIEPasses.td

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -344,6 +344,31 @@ def AIEObjectFifoStatefulTransform : Pass<"aie-objectFifo-stateful-transform", "
344344
}
345345

346346

347+
def AIEObjectFifoUnroll : Pass<"aie-objectFifo-unroll", "DeviceOp"> {
348+
let summary = "Unroll scf.for loops that contain objectFifo operations";
349+
let description = [{
350+
Unroll each `scf.for` loop (and its ancestor loops) inside an `aie.core`
351+
that contains `aie.objectfifo.acquire`/`aie.objectfifo.release` operations,
352+
using the standard `scf` loop unrolling infrastructure.
353+
354+
Each such loop is unrolled by the least common multiple of the depths of the
355+
objectFifos it accesses, so that after the objectFifo stateful transform and
356+
subsequent `mem2reg`/`canonicalize` the runtime buffer/lock bookkeeping folds
357+
into constant buffer references and constant lock counts (reproducing the
358+
behavior of the legacy static, unrolled lowering).
359+
360+
This pass is intended to run before `aie-objectFifo-stateful-transform` and
361+
is only used when dynamic objectFifos are disabled (`--dynamic-objFifos=false`).
362+
}];
363+
364+
let constructor = "xilinx::AIE::createAIEObjectFifoUnrollPass()";
365+
let dependentDialects = [
366+
"mlir::scf::SCFDialect",
367+
"xilinx::AIE::AIEDialect",
368+
];
369+
}
370+
371+
347372
def AIELowerCascadeFlows : Pass<"aie-lower-cascade-flows", "DeviceOp"> {
348373
let summary = "Lower aie.cascade_flow operations through `aie.configure_cascade` operations";
349374
let description = [{

lib/Dialect/AIE/Transforms/AIEObjectFifoStatefulTransform.cpp

Lines changed: 434 additions & 224 deletions
Large diffs are not rendered by default.
Lines changed: 133 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,133 @@
1+
//===- AIEObjectFifoUnroll.cpp ----------------------------------*- C++ -*-===//
2+
//
3+
// Copyright (C) 2026 Advanced Micro Devices, Inc.
4+
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
5+
//
6+
//===----------------------------------------------------------------------===//
7+
8+
#include "aie/Dialect/AIE/IR/AIEDialect.h"
9+
#include "aie/Dialect/AIE/Transforms/AIEPasses.h"
10+
11+
#include "mlir/Dialect/SCF/IR/SCF.h"
12+
#include "mlir/Dialect/SCF/Utils/Utils.h"
13+
#include "mlir/Pass/Pass.h"
14+
15+
namespace xilinx::AIE {
16+
#define GEN_PASS_DEF_AIEOBJECTFIFOUNROLL
17+
#include "aie/Dialect/AIE/Transforms/AIEPasses.h.inc"
18+
} // namespace xilinx::AIE
19+
20+
#define DEBUG_TYPE "aie-objectFifo-unroll"
21+
22+
using namespace mlir;
23+
using namespace xilinx;
24+
using namespace xilinx::AIE;
25+
26+
namespace {
27+
28+
// The unroll factor for a loop is the value of the `aie.unroll_hint` attribute
29+
// attached by AIEObjectFifoStatefulTransform (the least common multiple of the
30+
// depths of the objectFifos accessed within the loop). Loops without the hint
31+
// carry no objectFifo access and are not unrolled (factor 1).
32+
static int64_t unrollFactorForLoop(scf::ForOp forOp) {
33+
if (auto hint =
34+
forOp->getAttrOfType<IntegerAttr>(kObjectFifoUnrollHintAttrName))
35+
return hint.getInt();
36+
return 1;
37+
}
38+
39+
// Statically known trip count of a loop, or nullopt if it cannot be computed.
40+
static std::optional<int64_t> staticTripCount(scf::ForOp forOp) {
41+
if (forOp.getSingleLowerBound() && forOp.getSingleUpperBound() &&
42+
forOp.getSingleStep())
43+
if (std::optional<llvm::APInt> tc = forOp.getStaticTripCount())
44+
return tc->getSExtValue();
45+
return std::nullopt;
46+
}
47+
48+
// True if the loop carries an objectFifo access, i.e. it was annotated with the
49+
// unroll hint by the objectFifo stateful transform.
50+
static bool loopHasObjectFifoOp(scf::ForOp forOp) {
51+
return forOp->hasAttr(kObjectFifoUnrollHintAttrName);
52+
}
53+
54+
struct AIEObjectFifoUnrollPass
55+
: xilinx::AIE::impl::AIEObjectFifoUnrollBase<AIEObjectFifoUnrollPass> {
56+
void getDependentDialects(DialectRegistry &registry) const override {
57+
registry.insert<AIEDialect>();
58+
registry.insert<scf::SCFDialect>();
59+
}
60+
61+
void runOnOperation() override {
62+
DeviceOp device = getOperation();
63+
64+
for (auto coreOp : device.getOps<CoreOp>()) {
65+
// Collect every scf.for loop that carries an objectFifo access (i.e. was
66+
// annotated with the unroll hint). Ancestor loops are annotated too, so
67+
// they are naturally included.
68+
SmallVector<scf::ForOp> loops;
69+
coreOp.walk([&](scf::ForOp forOp) {
70+
if (loopHasObjectFifoOp(forOp))
71+
loops.push_back(forOp);
72+
});
73+
74+
// Operation::walk uses post-order traversal by default, so a nested loop
75+
// is visited before its enclosing loop; iterating the list in order thus
76+
// processes the innermost loops first. Unrolling innermost loops first
77+
// avoids invalidating references to inner loops when an outer loop (which
78+
// duplicates its nested loops) is unrolled.
79+
for (scf::ForOp forOp : loops) {
80+
int64_t unrollFactor = unrollFactorForLoop(forOp);
81+
if (unrollFactor <= 1)
82+
continue;
83+
84+
std::optional<int64_t> trip = staticTripCount(forOp);
85+
// When the loop performs fewer iterations than a full rotation of the
86+
// objectFifos, unroll it completely: every iteration must map to an
87+
// explicit buffer/lock slot.
88+
if (trip && *trip <= unrollFactor) {
89+
if (failed(mlir::loopUnrollFull(forOp))) {
90+
forOp.emitOpError()
91+
<< "failed to fully unroll objectFifo loop (trip count "
92+
<< *trip << ")";
93+
return signalPassFailure();
94+
}
95+
continue;
96+
}
97+
98+
// Otherwise unroll by the rotation period. loopUnrollByFactor peels a
99+
// cleanup/epilogue loop for the remaining iterations when the trip
100+
// count is not an exact multiple of the factor.
101+
FailureOr<mlir::UnrolledLoopInfo> info =
102+
mlir::loopUnrollByFactor(forOp, static_cast<uint64_t>(unrollFactor));
103+
if (failed(info)) {
104+
forOp.emitOpError()
105+
<< "failed to unroll objectFifo loop by factor " << unrollFactor;
106+
return signalPassFailure();
107+
}
108+
109+
// The epilogue runs the remaining (< factor) iterations. Fully unroll
110+
// it as well so that each of those iterations maps to an explicit
111+
// buffer/lock rotation slot. This is best-effort: an epilogue with a
112+
// non-constant trip count cannot be fully unrolled and is left rolled.
113+
if (info->epilogueLoopOp) {
114+
scf::ForOp epilogue = *info->epilogueLoopOp;
115+
if (loopHasObjectFifoOp(epilogue))
116+
(void)mlir::loopUnrollFull(epilogue);
117+
}
118+
}
119+
120+
// Drop any lingering unroll hints so they do not leak into the output.
121+
coreOp.walk([&](scf::ForOp forOp) {
122+
forOp->removeAttr(kObjectFifoUnrollHintAttrName);
123+
});
124+
}
125+
}
126+
};
127+
128+
} // namespace
129+
130+
std::unique_ptr<mlir::OperationPass<xilinx::AIE::DeviceOp>>
131+
xilinx::AIE::createAIEObjectFifoUnrollPass() {
132+
return std::make_unique<AIEObjectFifoUnrollPass>();
133+
}

lib/Dialect/AIE/Transforms/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ add_mlir_dialect_library(
2424
AIEVectorTransferLowering.cpp
2525
AIEHoistVectorTransferPointers.cpp
2626
AIEObjectFifoStatefulTransform.cpp
27+
AIEObjectFifoUnroll.cpp
2728
AIELowerCascadeFlows.cpp
2829
AIEGenerateColumnControlOverlay.cpp
2930
AIETraceToConfig.cpp
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
//===- uselock_dynamic_value.mlir ------------------------------*- MLIR -*-===//
2+
//
3+
// Copyright (C) 2026 Advanced Micro Devices, Inc.
4+
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
5+
//
6+
//===----------------------------------------------------------------------===//
7+
8+
// Roundtrip test for runtime (SSA) lock values on AIE2. The lock value is a
9+
// general-purpose i32 register operand rather than a static attribute.
10+
11+
// RUN: aie-opt %s | FileCheck %s
12+
13+
module {
14+
aie.device(npu1) {
15+
%tile = aie.tile(1, 2)
16+
%lock = aie.lock(%tile, 0) {init = 1 : i32}
17+
%core = aie.core(%tile) {
18+
%v = arith.constant 2 : i32
19+
// CHECK: aie.use_lock(%{{.*}}, AcquireGreaterEqual, %{{.*}})
20+
aie.use_lock(%lock, AcquireGreaterEqual, %v)
21+
// CHECK: aie.use_lock(%{{.*}}, Release, %{{.*}})
22+
aie.use_lock(%lock, Release, %v)
23+
aie.end
24+
}
25+
}
26+
}

0 commit comments

Comments
 (0)