Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
79 changes: 79 additions & 0 deletions aie_runtime_lib/AIE2/aie_objectfifo.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
//===- aie_objectfifo.h - ObjectFIFO C API for AIE2 -------------*- C++ -*-===//
//
// This file is licensed under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
// Copyright (C) 2026, Advanced Micro Devices, Inc.
//
//===----------------------------------------------------------------------===//
//
// C API for ObjectFIFO operations in AIE2 kernels.
// Provides a self-contained struct that bundles locks, buffers, and depth,
// hiding the dual-lock (producer + consumer) semantics of AIE2 semaphore locks.
//
// On AIE2, each ObjectFIFO element has two locks:
// - Producer lock (acq_lock for producer, rel_lock for consumer)
// - Consumer lock (rel_lock for producer, acq_lock for consumer)
//
// The MLIR `aie.objectfifo.lock` and `aie.objectfifo.buffer` ops resolve
// the correct lock IDs and buffer references for each port, passing them
// as function arguments. This header provides a struct and inline functions
// to use them.
//
//===----------------------------------------------------------------------===//

#ifndef AIE_OBJECTFIFO_H
#define AIE_OBJECTFIFO_H

#include <stdint.h>

// Lock intrinsics (acquire_equal, release) are provided by the compiler:
// - Peano: auto-included via aiev2intrin.h / aie2pintrin.h
// - Chess: compiler built-ins
#ifndef __AIENGINE__
#error \
"aie_objectfifo.h must be compiled for an AIE target (__AIENGINE__ not defined)"
#endif

// Maximum supported ObjectFIFO depth (number of buffers).
#define OBJECTFIFO_MAX_DEPTH 4

// ObjectFIFO handle for C kernels.
// Encapsulates everything needed to acquire/release and access buffers
// for a given ObjectFIFO port (producer or consumer side).
//
// The MLIR compiler fills in the correct lock IDs, buffer pointers, and
// depth based on ObjectFIFO configuration and port direction.
typedef struct {
int32_t acq_lock; // Lock ID for acquire operation
int32_t rel_lock; // Lock ID for release operation
int32_t acq_value; // Value for acquire_equal(): -1 for AcquireGreaterEqual
int32_t rel_value; // Value for release() call (typically 1)
int32_t depth; // Number of buffers (ObjectFIFO depth)
void *buffers[OBJECTFIFO_MAX_DEPTH]; // Buffer pointers
} objectfifo_t;

// Acquire an ObjectFIFO (blocks until available).
// For producers: waits until a buffer is free to write.
// For consumers: waits until data is ready to read.
static inline void objectfifo_acquire(const objectfifo_t *of) {
acquire_equal(of->acq_lock, of->acq_value);
}

// Release an ObjectFIFO.
// For producers: signals that data has been written.
// For consumers: signals that the buffer is free.
static inline void objectfifo_release(const objectfifo_t *of) {
release(of->rel_lock, of->rel_value);
}

// Get the buffer pointer for the current iteration.
// Handles buffer rotation using modular indexing: buffers[iter % depth].
// The caller should cast the returned void* to the appropriate type.
static inline void *objectfifo_get_buffer(const objectfifo_t *of,
int32_t iter) {
return of->buffers[iter % of->depth];
}

#endif // AIE_OBJECTFIFO_H
80 changes: 80 additions & 0 deletions aie_runtime_lib/AIE2P/aie_objectfifo.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
//===- aie_objectfifo.h - ObjectFIFO C API for AIE2P ------------*- C++ -*-===//
//
// This file is licensed under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
// Copyright (C) 2026, Advanced Micro Devices, Inc.
//
//===----------------------------------------------------------------------===//
//
// C API for ObjectFIFO operations in AIE2P kernels.
// Provides a self-contained struct that bundles locks, buffers, and depth,
// hiding the dual-lock (producer + consumer) semantics of AIE2P semaphore
// locks.
//
// On AIE2P, each ObjectFIFO element has two locks:
// - Producer lock (acq_lock for producer, rel_lock for consumer)
// - Consumer lock (rel_lock for producer, acq_lock for consumer)
//
// The MLIR `aie.objectfifo.lock` and `aie.objectfifo.buffer` ops resolve
// the correct lock IDs and buffer references for each port, passing them
// as function arguments. This header provides a struct and inline functions
// to use them.
//
//===----------------------------------------------------------------------===//

#ifndef AIE_OBJECTFIFO_H
#define AIE_OBJECTFIFO_H

#include <stdint.h>

// Lock intrinsics (acquire_equal, release) are provided by the compiler:
// - Peano: auto-included via aiev2intrin.h / aie2pintrin.h
// - Chess: compiler built-ins
#ifndef __AIENGINE__
#error \
"aie_objectfifo.h must be compiled for an AIE target (__AIENGINE__ not defined)"
#endif

// Maximum supported ObjectFIFO depth (number of buffers).
#define OBJECTFIFO_MAX_DEPTH 4

// ObjectFIFO handle for C kernels.
// Encapsulates everything needed to acquire/release and access buffers
// for a given ObjectFIFO port (producer or consumer side).
//
// The MLIR compiler fills in the correct lock IDs, buffer pointers, and
// depth based on ObjectFIFO configuration and port direction.
typedef struct {
int32_t acq_lock; // Lock ID for acquire operation
int32_t rel_lock; // Lock ID for release operation
int32_t acq_value; // Value for acquire_equal(): -1 for AcquireGreaterEqual
int32_t rel_value; // Value for release() call (typically 1)
int32_t depth; // Number of buffers (ObjectFIFO depth)
void *buffers[OBJECTFIFO_MAX_DEPTH]; // Buffer pointers
} objectfifo_t;

// Acquire an ObjectFIFO (blocks until available).
// For producers: waits until a buffer is free to write.
// For consumers: waits until data is ready to read.
static inline void objectfifo_acquire(const objectfifo_t *of) {
acquire_equal(of->acq_lock, of->acq_value);
}

// Release an ObjectFIFO.
// For producers: signals that data has been written.
// For consumers: signals that the buffer is free.
static inline void objectfifo_release(const objectfifo_t *of) {
release(of->rel_lock, of->rel_value);
}

// Get the buffer pointer for the current iteration.
// Handles buffer rotation using modular indexing: buffers[iter % depth].
// The caller should cast the returned void* to the appropriate type.
static inline void *objectfifo_get_buffer(const objectfifo_t *of,
int32_t iter) {
return of->buffers[iter % of->depth];
}

#endif // AIE_OBJECTFIFO_H
16 changes: 15 additions & 1 deletion aie_runtime_lib/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -61,4 +61,18 @@ if(AIETools_AIE2P_FOUND)
add_subdirectory(AIE2P)
endif()


# Install and copy aie_objectfifo.h unconditionally (no AIETools dependency)
foreach(arch AIE2 AIE2P)
if(EXISTS ${CMAKE_CURRENT_SOURCE_DIR}/${arch}/aie_objectfifo.h)
install(FILES ${CMAKE_CURRENT_SOURCE_DIR}/${arch}/aie_objectfifo.h
DESTINATION ${CMAKE_INSTALL_PREFIX}/aie_runtime_lib/${arch})
add_custom_target(aie-copy-${arch}-runtime-libs-aie_objectfifo.h ALL
DEPENDS ${CMAKE_CURRENT_BINARY_DIR}/${arch}/aie_objectfifo.h)
add_custom_command(OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/${arch}/aie_objectfifo.h
COMMAND ${CMAKE_COMMAND} -E copy
${CMAKE_CURRENT_SOURCE_DIR}/${arch}/aie_objectfifo.h
${CMAKE_CURRENT_BINARY_DIR}/${arch}/aie_objectfifo.h
DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/${arch}/aie_objectfifo.h)
add_dependencies(aie-runtime-libs aie-copy-${arch}-runtime-libs-aie_objectfifo.h)
endif()
endforeach()
72 changes: 72 additions & 0 deletions include/aie/Dialect/AIE/IR/AIEOps.td
Original file line number Diff line number Diff line change
Expand Up @@ -2038,6 +2038,78 @@ def AIE_ObjectFifoSubviewAccessOp : AIE_Op<"objectfifo.subview.access", []> {
}];
}

def AIE_ObjectFifoGetLockOp : AIE_Op<"objectfifo.lock", []> {
let summary = "Get acquire and release lock IDs for an ObjectFIFO port";
let description = [{
Returns the acquire and release lock IDs for the given ObjectFIFO port.
These lock IDs can be passed to precompiled C kernels that call
`acquire_equal()` / `release()` directly.

On AIE2 (semaphore locks), each ObjectFIFO element has two locks
(producer + consumer). The acquire lock differs from the release lock:
- Producer port: acquire = prod_lock, release = cons_lock
- Consumer port: acquire = cons_lock, release = prod_lock

Example:
```
%acq_lock, %rel_lock = aie.objectfifo.lock @of1 (Produce) : (index, index)
```
The returned `index` values are the localized lock IDs, suitable for passing
to external C functions as integer arguments.
}];

let arguments = (
ins ObjectFifoPort:$port,
FlatSymbolRefAttr:$objFifo_name
);

let results = (outs Index:$acq_lock, Index:$rel_lock);

let assemblyFormat = [{
attr-dict $objFifo_name `(` $port `)` `:` `(` type($acq_lock) `,` type($rel_lock) `)`
}];

let hasVerifier = 1;

let extraClassDeclaration = [{
ObjectFifoCreateOp getObjectFifo();
}];
}

def AIE_ObjectFifoGetBufferOp : AIE_Op<"objectfifo.buffer", []> {
let summary = "Get a buffer reference from an ObjectFIFO without acquiring";
let description = [{
Returns a memref to the ObjectFIFO buffer at the given element index,
without performing any lock acquisition. This is intended for use with
precompiled C kernels that manage their own locking via
`aie.objectfifo.lock`.

Example:
```
%buf = aie.objectfifo.buffer @of1 (0) : memref<256xi32>
```
The returned memref can be passed to an external C function along with
lock IDs from `aie.objectfifo.lock`.
}];

let arguments = (
ins FlatSymbolRefAttr:$objFifo_name,
ConfinedAttr<AIEI32Attr, [IntMinValue<0>]>:$index
);

let results = (outs AnyMemRef:$output);

let assemblyFormat = [{
attr-dict $objFifo_name `(` $index `)` `:` qualified(type($output))
}];

let hasVerifier = 1;

let extraClassDeclaration = [{
ObjectFifoCreateOp getObjectFifo();
}];
}

def AIE_ObjectFifoRegisterProcessOp: AIE_Op<"objectfifo.register_process", []> {
let summary = "Operation that produces the acquire/release patterns for a process registered to an objectFifo";
let description = [{
Expand Down
85 changes: 85 additions & 0 deletions lib/Dialect/AIE/IR/AIEDialect.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1044,6 +1044,91 @@ LogicalResult ObjectFifoSubviewAccessOp::verify() {
return success();
}

//===----------------------------------------------------------------------===//
// ObjectFifoGetLockOp
//===----------------------------------------------------------------------===//

LogicalResult ObjectFifoGetLockOp::verify() {
auto parent = getOperation()->getParentOfType<CoreOp>();
if (parent == nullptr)
return emitOpError("must be called from inside a CoreOp");

auto coreTile = parent.getTile();
auto objFifo = getObjectFifo();
if (!objFifo)
return emitError("cannot retrieve associated object FIFO");
if (getPort() == ObjectFifoPort::Produce) {
if (coreTile != objFifo.getProducerTile())
return parent.emitOpError(
"producer port of objectFifo accessed by core running "
"on non-producer tile");
} else if (getPort() == ObjectFifoPort::Consume) {
bool found = false;
for (auto consumerTile : objFifo.getConsumerTiles()) {
if (coreTile == consumerTile) {
found = true;
break;
}
}
if (!found)
return parent.emitOpError(
"consumer port of objectFifo accessed by core running "
"on non-consumer tile");
}

return success();
}

ObjectFifoCreateOp ObjectFifoGetLockOp::getObjectFifo() {
Operation *parent = getOperation();
while ((parent = parent->getParentOp())) {
if (parent->hasTrait<OpTrait::SymbolTable>()) {
if (auto *st = SymbolTable::lookupSymbolIn(parent, getObjFifoName());
isa_and_nonnull<ObjectFifoCreateOp>(st))
return dyn_cast<ObjectFifoCreateOp>(st);
}
}
return {};
}

//===----------------------------------------------------------------------===//
// ObjectFifoGetBufferOp
//===----------------------------------------------------------------------===//

LogicalResult ObjectFifoGetBufferOp::verify() {
auto parent = getOperation()->getParentOfType<CoreOp>();
if (parent == nullptr)
return emitOpError("must be called from inside a CoreOp");

auto objFifo = getObjectFifo();
if (!objFifo)
return emitError("cannot retrieve associated object FIFO");

auto objFifoElem =
llvm::cast<AIEObjectFifoType>(objFifo.getElemType()).getElementType();
if (objFifoElem != getOutput().getType())
return emitOpError("output memref type must match ObjectFifo element type");

int index = getIndex();
if (index >= objFifo.size())
return emitOpError("buffer index ")
<< index << " exceeds ObjectFifo depth " << objFifo.size();

return success();
}

ObjectFifoCreateOp ObjectFifoGetBufferOp::getObjectFifo() {
Operation *parent = getOperation();
while ((parent = parent->getParentOp())) {
if (parent->hasTrait<OpTrait::SymbolTable>()) {
if (auto *st = SymbolTable::lookupSymbolIn(parent, getObjFifoName());
isa_and_nonnull<ObjectFifoCreateOp>(st))
return dyn_cast<ObjectFifoCreateOp>(st);
}
}
return {};
}

//===----------------------------------------------------------------------===//
// ObjectFifoRegisterProcessOp
//===----------------------------------------------------------------------===//
Expand Down
4 changes: 2 additions & 2 deletions lib/Dialect/AIE/Transforms/AIELocalizeLocks.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ struct AIELocalizeLocksPass : AIELocalizeLocksBase<AIELocalizeLocksPass> {
// it suffices to check if the parent of a UseLockOp is coreOp.
if (llvm::none_of(lock.getResult().getUsers(),
[&](Operation *user) {
return user->getParentOp() == coreOp;
return coreOp->isProperAncestor(user);
}))
continue;

Expand All @@ -77,7 +77,7 @@ struct AIELocalizeLocksPass : AIELocalizeLocksBase<AIELocalizeLocksPass> {
builder, builder.getUnknownLoc(), localLockIndex);
lock.getResult().replaceUsesWithIf(
coreLockIDValue, [&](OpOperand &opOperand) {
return opOperand.getOwner()->getParentOp() == coreOp;
return coreOp->isProperAncestor(opOperand.getOwner());
});
}
}
Expand Down
Loading
Loading