Skip to content

Commit 24af672

Browse files
jgmelberclaude
andcommitted
Add C ObjectFIFO API for kernel-managed lock synchronization
Enable precompiled C kernels to directly manage ObjectFIFO synchronization by receiving lock IDs and buffer references from MLIR, rather than relying on the compiler to insert acquire/release ops. New MLIR ops: - aie.objectfifo.lock: returns (acq_lock, rel_lock) for a port - aie.objectfifo.buffer: returns a buffer memref at a given index Stateful transform lowering resolves these ops to concrete lock/buffer SSA values, handling AIE1 single-lock and AIE2/AIE2P dual-lock semantics. C API header (aie_runtime_lib/AIE2{,P}/aie_objectfifo.h): objectfifo_port_t struct with inline acquire/release functions using compiler-provided lock intrinsics. Use acq_value=-1 for AcquireGreaterEqual semantics matching the DMA protocol. Python API: get_lock()/get_buffer() on object_fifo and ObjectFifoHandle. Bug fixes discovered during hardware bring-up: - python/dialects/aie.py: get_lock() passed incorrect extra args to ObjectFifoGetLockOp auto-generated constructor - AIELocalizeLocks: used getParentOp() == coreOp which failed for lock uses nested inside scf.for; fixed to use isProperAncestor() Includes: - CMake install rules for aie_objectfifo.h into install tree - LIT tests for lock localization with external function calls - E2E NPU tests (lock_pass_to_c, objectfifo_lock_c_api) - Programming example: passthrough_kernel_c_objfifo Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent 8f22058 commit 24af672

27 files changed

Lines changed: 1553 additions & 4 deletions

File tree

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
//===- aie_objectfifo.h - ObjectFIFO C API for AIE2 -------------*- C++ -*-===//
2+
//
3+
// This file is licensed 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+
// Copyright (C) 2026, Advanced Micro Devices, Inc.
8+
//
9+
//===----------------------------------------------------------------------===//
10+
//
11+
// Clean C API for ObjectFIFO acquire/release operations in AIE2 kernels.
12+
// Hides the dual-lock (producer + consumer) semantics of AIE2 semaphore locks.
13+
//
14+
// On AIE2, each ObjectFIFO element has two locks:
15+
// - Producer lock (acq_lock for producer, rel_lock for consumer)
16+
// - Consumer lock (rel_lock for producer, acq_lock for consumer)
17+
//
18+
// The MLIR `aie.objectfifo.lock` op resolves the correct lock IDs for each
19+
// port and passes them as function arguments. This header provides a struct
20+
// and inline functions to use them.
21+
//
22+
//===----------------------------------------------------------------------===//
23+
24+
#ifndef AIE_OBJECTFIFO_H
25+
#define AIE_OBJECTFIFO_H
26+
27+
#include <stdint.h>
28+
29+
// Lock intrinsics (acquire_equal, release) are provided by the compiler:
30+
// - Peano: auto-included via aiev2intrin.h / aie2pintrin.h
31+
// - Chess: compiler built-ins
32+
#ifndef __AIENGINE__
33+
#error \
34+
"aie_objectfifo.h must be compiled for an AIE target (__AIENGINE__ not defined)"
35+
#endif
36+
37+
// ObjectFIFO port handle for C kernels.
38+
// Encapsulates everything needed to acquire/release from a given port.
39+
// The MLIR compiler fills in the correct lock IDs and values based on
40+
// ObjectFIFO configuration and port direction.
41+
typedef struct {
42+
int32_t acq_lock; // Lock ID for acquire operation
43+
int32_t rel_lock; // Lock ID for release operation
44+
int32_t
45+
acq_value; // Value for acquire_equal(): use -1 for AcquireGreaterEqual
46+
int32_t rel_value; // Value for release() call (typically 1)
47+
} objectfifo_port_t;
48+
49+
// Acquire an ObjectFIFO port (blocks until available).
50+
// For producers: waits until the buffer is free to write.
51+
// For consumers: waits until data is ready to read.
52+
static inline void objectfifo_acquire(const objectfifo_port_t *port) {
53+
acquire_equal(port->acq_lock, port->acq_value);
54+
}
55+
56+
// Release an ObjectFIFO port.
57+
// For producers: signals that data has been written.
58+
// For consumers: signals that the buffer is free.
59+
static inline void objectfifo_release(const objectfifo_port_t *port) {
60+
release(port->rel_lock, port->rel_value);
61+
}
62+
63+
#endif // AIE_OBJECTFIFO_H
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
//===- aie_objectfifo.h - ObjectFIFO C API for AIE2P ------------*- C++ -*-===//
2+
//
3+
// This file is licensed 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+
// Copyright (C) 2026, Advanced Micro Devices, Inc.
8+
//
9+
//===----------------------------------------------------------------------===//
10+
//
11+
// Clean C API for ObjectFIFO acquire/release operations in AIE2P kernels.
12+
// Hides the dual-lock (producer + consumer) semantics of AIE2P semaphore locks.
13+
//
14+
// On AIE2P, each ObjectFIFO element has two locks:
15+
// - Producer lock (acq_lock for producer, rel_lock for consumer)
16+
// - Consumer lock (rel_lock for producer, acq_lock for consumer)
17+
//
18+
// The MLIR `aie.objectfifo.lock` op resolves the correct lock IDs for each
19+
// port and passes them as function arguments. This header provides a struct
20+
// and inline functions to use them.
21+
//
22+
//===----------------------------------------------------------------------===//
23+
24+
#ifndef AIE_OBJECTFIFO_H
25+
#define AIE_OBJECTFIFO_H
26+
27+
#include <stdint.h>
28+
29+
// Lock intrinsics (acquire_equal, release) are provided by the compiler:
30+
// - Peano: auto-included via aiev2intrin.h / aie2pintrin.h
31+
// - Chess: compiler built-ins
32+
#ifndef __AIENGINE__
33+
#error \
34+
"aie_objectfifo.h must be compiled for an AIE target (__AIENGINE__ not defined)"
35+
#endif
36+
37+
// ObjectFIFO port handle for C kernels.
38+
// Encapsulates everything needed to acquire/release from a given port.
39+
// The MLIR compiler fills in the correct lock IDs and values based on
40+
// ObjectFIFO configuration and port direction.
41+
typedef struct {
42+
int32_t acq_lock; // Lock ID for acquire operation
43+
int32_t rel_lock; // Lock ID for release operation
44+
int32_t
45+
acq_value; // Value for acquire_equal(): use -1 for AcquireGreaterEqual
46+
int32_t rel_value; // Value for release() call (typically 1)
47+
} objectfifo_port_t;
48+
49+
// Acquire an ObjectFIFO port (blocks until available).
50+
// For producers: waits until the buffer is free to write.
51+
// For consumers: waits until data is ready to read.
52+
static inline void objectfifo_acquire(const objectfifo_port_t *port) {
53+
acquire_equal(port->acq_lock, port->acq_value);
54+
}
55+
56+
// Release an ObjectFIFO port.
57+
// For producers: signals that data has been written.
58+
// For consumers: signals that the buffer is free.
59+
static inline void objectfifo_release(const objectfifo_port_t *port) {
60+
release(port->rel_lock, port->rel_value);
61+
}
62+
63+
#endif // AIE_OBJECTFIFO_H

aie_runtime_lib/CMakeLists.txt

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -61,4 +61,10 @@ if(AIETools_AIE2P_FOUND)
6161
add_subdirectory(AIE2P)
6262
endif()
6363

64-
64+
# Install aie_objectfifo.h unconditionally (no AIETools dependency)
65+
foreach(arch AIE2 AIE2P)
66+
if(EXISTS ${CMAKE_CURRENT_SOURCE_DIR}/${arch}/aie_objectfifo.h)
67+
install(FILES ${CMAKE_CURRENT_SOURCE_DIR}/${arch}/aie_objectfifo.h
68+
DESTINATION ${CMAKE_INSTALL_PREFIX}/aie_runtime_lib/${arch})
69+
endif()
70+
endforeach()

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

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2038,6 +2038,78 @@ def AIE_ObjectFifoSubviewAccessOp : AIE_Op<"objectfifo.subview.access", []> {
20382038
}];
20392039
}
20402040

2041+
def AIE_ObjectFifoGetLockOp : AIE_Op<"objectfifo.lock", []> {
2042+
let summary = "Get acquire and release lock IDs for an ObjectFIFO port";
2043+
let description = [{
2044+
Returns the acquire and release lock IDs for the given ObjectFIFO port.
2045+
These lock IDs can be passed to precompiled C kernels that call
2046+
`acquire_equal()` / `release()` directly.
2047+
2048+
On AIE2 (semaphore locks), each ObjectFIFO element has two locks
2049+
(producer + consumer). The acquire lock differs from the release lock:
2050+
- Producer port: acquire = prod_lock, release = cons_lock
2051+
- Consumer port: acquire = cons_lock, release = prod_lock
2052+
2053+
Example:
2054+
```
2055+
%acq_lock, %rel_lock = aie.objectfifo.lock @of1 (Produce) : (index, index)
2056+
```
2057+
The returned `index` values are the localized lock IDs, suitable for passing
2058+
to external C functions as integer arguments.
2059+
}];
2060+
2061+
let arguments = (
2062+
ins ObjectFifoPort:$port,
2063+
FlatSymbolRefAttr:$objFifo_name
2064+
);
2065+
2066+
let results = (outs Index:$acq_lock, Index:$rel_lock);
2067+
2068+
let assemblyFormat = [{
2069+
attr-dict $objFifo_name `(` $port `)` `:` `(` type($acq_lock) `,` type($rel_lock) `)`
2070+
}];
2071+
2072+
let hasVerifier = 1;
2073+
2074+
let extraClassDeclaration = [{
2075+
ObjectFifoCreateOp getObjectFifo();
2076+
}];
2077+
}
2078+
2079+
def AIE_ObjectFifoGetBufferOp : AIE_Op<"objectfifo.buffer", []> {
2080+
let summary = "Get a buffer reference from an ObjectFIFO without acquiring";
2081+
let description = [{
2082+
Returns a memref to the ObjectFIFO buffer at the given element index,
2083+
without performing any lock acquisition. This is intended for use with
2084+
precompiled C kernels that manage their own locking via
2085+
`aie.objectfifo.lock`.
2086+
2087+
Example:
2088+
```
2089+
%buf = aie.objectfifo.buffer @of1 (0) : memref<256xi32>
2090+
```
2091+
The returned memref can be passed to an external C function along with
2092+
lock IDs from `aie.objectfifo.lock`.
2093+
}];
2094+
2095+
let arguments = (
2096+
ins FlatSymbolRefAttr:$objFifo_name,
2097+
ConfinedAttr<AIEI32Attr, [IntMinValue<0>]>:$index
2098+
);
2099+
2100+
let results = (outs AnyMemRef:$output);
2101+
2102+
let assemblyFormat = [{
2103+
attr-dict $objFifo_name `(` $index `)` `:` qualified(type($output))
2104+
}];
2105+
2106+
let hasVerifier = 1;
2107+
2108+
let extraClassDeclaration = [{
2109+
ObjectFifoCreateOp getObjectFifo();
2110+
}];
2111+
}
2112+
20412113
def AIE_ObjectFifoRegisterProcessOp: AIE_Op<"objectfifo.register_process", []> {
20422114
let summary = "Operation that produces the acquire/release patterns for a process registered to an objectFifo";
20432115
let description = [{

lib/Dialect/AIE/IR/AIEDialect.cpp

Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1044,6 +1044,91 @@ LogicalResult ObjectFifoSubviewAccessOp::verify() {
10441044
return success();
10451045
}
10461046

1047+
//===----------------------------------------------------------------------===//
1048+
// ObjectFifoGetLockOp
1049+
//===----------------------------------------------------------------------===//
1050+
1051+
LogicalResult ObjectFifoGetLockOp::verify() {
1052+
auto parent = getOperation()->getParentOfType<CoreOp>();
1053+
if (parent == nullptr)
1054+
return emitOpError("must be called from inside a CoreOp");
1055+
1056+
auto coreTile = parent.getTile();
1057+
auto objFifo = getObjectFifo();
1058+
if (!objFifo)
1059+
return emitError("cannot retrieve associated object FIFO");
1060+
if (getPort() == ObjectFifoPort::Produce) {
1061+
if (coreTile != objFifo.getProducerTile())
1062+
return parent.emitOpError(
1063+
"producer port of objectFifo accessed by core running "
1064+
"on non-producer tile");
1065+
} else if (getPort() == ObjectFifoPort::Consume) {
1066+
bool found = false;
1067+
for (auto consumerTile : objFifo.getConsumerTiles()) {
1068+
if (coreTile == consumerTile) {
1069+
found = true;
1070+
break;
1071+
}
1072+
}
1073+
if (!found)
1074+
return parent.emitOpError(
1075+
"consumer port of objectFifo accessed by core running "
1076+
"on non-consumer tile");
1077+
}
1078+
1079+
return success();
1080+
}
1081+
1082+
ObjectFifoCreateOp ObjectFifoGetLockOp::getObjectFifo() {
1083+
Operation *parent = getOperation();
1084+
while ((parent = parent->getParentOp())) {
1085+
if (parent->hasTrait<OpTrait::SymbolTable>()) {
1086+
if (auto *st = SymbolTable::lookupSymbolIn(parent, getObjFifoName());
1087+
isa_and_nonnull<ObjectFifoCreateOp>(st))
1088+
return dyn_cast<ObjectFifoCreateOp>(st);
1089+
}
1090+
}
1091+
return {};
1092+
}
1093+
1094+
//===----------------------------------------------------------------------===//
1095+
// ObjectFifoGetBufferOp
1096+
//===----------------------------------------------------------------------===//
1097+
1098+
LogicalResult ObjectFifoGetBufferOp::verify() {
1099+
auto parent = getOperation()->getParentOfType<CoreOp>();
1100+
if (parent == nullptr)
1101+
return emitOpError("must be called from inside a CoreOp");
1102+
1103+
auto objFifo = getObjectFifo();
1104+
if (!objFifo)
1105+
return emitError("cannot retrieve associated object FIFO");
1106+
1107+
auto objFifoElem =
1108+
llvm::cast<AIEObjectFifoType>(objFifo.getElemType()).getElementType();
1109+
if (objFifoElem != getOutput().getType())
1110+
return emitOpError("output memref type must match ObjectFifo element type");
1111+
1112+
int index = getIndex();
1113+
if (index >= objFifo.size())
1114+
return emitOpError("buffer index ")
1115+
<< index << " exceeds ObjectFifo depth " << objFifo.size();
1116+
1117+
return success();
1118+
}
1119+
1120+
ObjectFifoCreateOp ObjectFifoGetBufferOp::getObjectFifo() {
1121+
Operation *parent = getOperation();
1122+
while ((parent = parent->getParentOp())) {
1123+
if (parent->hasTrait<OpTrait::SymbolTable>()) {
1124+
if (auto *st = SymbolTable::lookupSymbolIn(parent, getObjFifoName());
1125+
isa_and_nonnull<ObjectFifoCreateOp>(st))
1126+
return dyn_cast<ObjectFifoCreateOp>(st);
1127+
}
1128+
}
1129+
return {};
1130+
}
1131+
10471132
//===----------------------------------------------------------------------===//
10481133
// ObjectFifoRegisterProcessOp
10491134
//===----------------------------------------------------------------------===//

lib/Dialect/AIE/Transforms/AIELocalizeLocks.cpp

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,7 @@ struct AIELocalizeLocksPass : AIELocalizeLocksBase<AIELocalizeLocksPass> {
5858
// it suffices to check if the parent of a UseLockOp is coreOp.
5959
if (llvm::none_of(lock.getResult().getUsers(),
6060
[&](Operation *user) {
61-
return user->getParentOp() == coreOp;
61+
return coreOp->isProperAncestor(user);
6262
}))
6363
continue;
6464

@@ -77,7 +77,7 @@ struct AIELocalizeLocksPass : AIELocalizeLocksBase<AIELocalizeLocksPass> {
7777
builder, builder.getUnknownLoc(), localLockIndex);
7878
lock.getResult().replaceUsesWithIf(
7979
coreLockIDValue, [&](OpOperand &opOperand) {
80-
return opOperand.getOwner()->getParentOp() == coreOp;
80+
return coreOp->isProperAncestor(opOperand.getOwner());
8181
});
8282
}
8383
}

0 commit comments

Comments
 (0)