Skip to content
Open
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
13 changes: 13 additions & 0 deletions include/aie/Dialect/AIE/IR/AIEOps.td
Original file line number Diff line number Diff line change
Expand Up @@ -1834,6 +1834,11 @@ def AIE_ObjectFifoCreateOp: AIE_Op<"objectfifo", [HasParent<"DeviceOp">, Symbol]
Variadic<Index>:$consumerTiles,
AIE_ObjectFifo_Depth:$elemNumber,
TypeAttrOf<AIE_ObjectFifoType>:$elemType,
// Optional consumer element type for asymmetric transfer granularity.
// Producer sends elemType-sized transfers, consumer receives
// consumerElemType-sized transfers. Producer element size must be
// an integer multiple of consumer element size.
OptionalAttr<TypeAttrOf<AIE_ObjectFifoType>>:$consumerElemType,
BDDimLayoutArrayAttr:$dimensionsToStream,
BDDimLayoutArrayArrayAttr:$dimensionsFromStreamPerConsumer,
DefaultValuedAttr<BoolAttr, "false">:$via_DMA,
Expand Down Expand Up @@ -1863,6 +1868,7 @@ def AIE_ObjectFifoCreateOp: AIE_Op<"objectfifo", [HasParent<"DeviceOp">, Symbol]
`,`
$elemNumber
`)` attr-dict `:` $elemType
custom<ObjectFifoConsumerElemType>($consumerElemType)
custom<ObjectFifoInitValues>(ref($elemNumber), ref($elemType), $initValues)
}];

Expand All @@ -1886,6 +1892,13 @@ def AIE_ObjectFifoCreateOp: AIE_Op<"objectfifo", [HasParent<"DeviceOp">, Symbol]
}

AIE::BDDimLayoutArrayAttr getDimensionsFromStream(mlir::Value consumerTile);

/// Returns the consumer element type if set, otherwise the producer type.
mlir::Type getConsumerElemTypeOrDefault() {
if (auto ct = getConsumerElemType())
return ct.value();
return getElemType();
}
}];

let builders = [
Expand Down
52 changes: 51 additions & 1 deletion lib/Dialect/AIE/IR/AIEDialect.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -536,6 +536,30 @@ LogicalResult ObjectFifoCreateOp::verify() {
return emitError("`iter_count` is currently only supported on MemTiles");
}

if (getConsumerElemType().has_value()) {
auto consType =
llvm::dyn_cast<AIEObjectFifoType>(getConsumerElemType().value());
if (!consType)
return emitError("consumer element type must be an "
"!aie.objectfifo<memref<...>> type");
auto prodType = llvm::cast<AIEObjectFifoType>(getElemType());
auto prodMemref = prodType.getElementType();
auto consMemref = consType.getElementType();
if (prodMemref.getElementType() != consMemref.getElementType())
return emitError("producer and consumer must have the same scalar "
"element type, but got ")
<< prodMemref.getElementType() << " vs "
<< consMemref.getElementType();
int64_t prodSize = prodMemref.getNumElements();
int64_t consSize = consMemref.getNumElements();
if (consSize <= 0)
return emitError("consumer element count must be positive");
if (prodSize % consSize != 0)
return emitError("producer element size (")
<< prodSize << ") must be an integer multiple of consumer "
<< "element size (" << consSize << ")";
}
Comment thread
yenjames marked this conversation as resolved.

return success();
}

Expand Down Expand Up @@ -637,6 +661,24 @@ void xilinx::AIE::printObjectFifoConsumerTiles(
}
}

static void printObjectFifoConsumerElemType(OpAsmPrinter &p,
ObjectFifoCreateOp op,
TypeAttr consumerElemType) {
if (consumerElemType)
p << " -> " << consumerElemType;
}

static ParseResult parseObjectFifoConsumerElemType(OpAsmParser &parser,
TypeAttr &consumerElemType) {
if (failed(parser.parseOptionalArrow()))
return success(); // no consumer type
Type type;
if (parser.parseType(type))
return failure();
consumerElemType = TypeAttr::get(type);
return success();
Comment thread
yenjames marked this conversation as resolved.
}

static void printObjectFifoInitValues(OpAsmPrinter &p, ObjectFifoCreateOp op,
Attribute numElem, TypeAttr type,
Attribute initValues) {
Expand Down Expand Up @@ -999,7 +1041,15 @@ LogicalResult ObjectFifoAcquireOp::verify() {
auto objFifoSubviewElem =
llvm::cast<AIEObjectFifoSubviewType>(getResult().getType())
.getElementType();
if (objFifoElem != objFifoSubviewElem)
// Also accept the consumer element type for asymmetric ObjectFifos
auto objFifoConsType = llvm::dyn_cast<AIEObjectFifoType>(
getObjectFifo().getConsumerElemTypeOrDefault());
if (!objFifoConsType)
return emitOpError("ObjectFifo consumer element type must be an "
"!aie.objectfifo<memref<...>> type");
auto objFifoConsElem = objFifoConsType.getElementType();
if (objFifoElem != objFifoSubviewElem &&
objFifoConsElem != objFifoSubviewElem)
return emitOpError(
"ObjectFifo element and ObjectFifoSubview element must match.\n");
Comment thread
yenjames marked this conversation as resolved.

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -277,6 +277,9 @@ struct AIEObjectFifoStatefulTransformPass
if (createOp.getAieStream())
return true;

if (createOp.getConsumerElemType().has_value())
return true;

if (createOp.getConsumerTiles().size() == 1 &&
createOp.getDimensionsToStream().empty()) {

Expand Down Expand Up @@ -1898,7 +1901,9 @@ struct AIEObjectFifoStatefulTransformPass
}

builder.setInsertionPointAfter(createOp);
auto datatype = llvm::cast<AIEObjectFifoType>(createOp.getElemType());
// Use consumer element type if specified (asymmetric transfer)
auto datatype = llvm::cast<AIEObjectFifoType>(
createOp.getConsumerElemTypeOrDefault());
auto consumerObjFifoSize =
builder.getIntegerAttr(builder.getI32Type(), consumerDepth);
// rename and replace split objectFifo
Expand Down
25 changes: 18 additions & 7 deletions python/dialects/aie.py
Original file line number Diff line number Diff line change
Expand Up @@ -466,15 +466,24 @@ def __init__(
padDimensions=None,
disable_synchronization=None,
iter_count=None,
consumer_datatype=None,
):
self.datatype = try_convert_np_type_to_mlir_type(datatype)
self.consumer_datatype = (
try_convert_np_type_to_mlir_type(consumer_datatype)
if consumer_datatype is not None
else None
)
if not isinstance(consumerTiles, List):
consumerTiles = [consumerTiles]
if dimensionsFromStreamPerConsumer is None:
dimensionsFromStreamPerConsumer = [[] for _ in range(len(consumerTiles))]
if dimensionsToStream is None:
dimensionsToStream = []
of_Ty = TypeAttr.get(ObjectFifoType.get(self.datatype))
consumerElemType = None
if self.consumer_datatype is not None:
consumerElemType = TypeAttr.get(ObjectFifoType.get(self.consumer_datatype))
if initValues is not None:
values = []
for e in initValues:
Expand All @@ -498,20 +507,22 @@ def __init__(
initValues=initValues,
iter_count=iter_count,
)
if consumerElemType is not None:
self.attributes["consumerElemType"] = consumerElemType

def acquire(self, port, num_elem):
subview_t = ObjectFifoSubviewType.get(self.datatype)
# Use consumer_datatype for consumer-side acquire if available
dt = self.datatype
if self.consumer_datatype is not None and port == ObjectFifoPort.Consume:
dt = self.consumer_datatype
subview_t = ObjectFifoSubviewType.get(dt)
acq = ObjectFifoAcquireOp(subview_t, port, self.sym_name.value, num_elem)

objects = []
if acq.size.value == 1:
return ObjectFifoSubviewAccessOp(
self.datatype, acq.subview, acq.size.value - 1
).result
return ObjectFifoSubviewAccessOp(dt, acq.subview, acq.size.value - 1).result
for i in range(acq.size.value):
objects.append(
ObjectFifoSubviewAccessOp(self.datatype, acq.subview, i).result
)
objects.append(ObjectFifoSubviewAccessOp(dt, acq.subview, i).result)
return objects

def release(self, port, num_elem):
Expand Down
9 changes: 9 additions & 0 deletions python/iron/dataflow/objectfifo.py
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ def __init__(
plio: bool = False,
pad_dimensions: list[Sequence[int]] | None = None,
init_values: list[np.ndarray] | None = None,
consumer_obj_type: type[np.ndarray] | None = None,
):
"""Construct an ObjectFifo.

Expand All @@ -58,6 +59,7 @@ def __init__(
dims_from_stream_per_cons (list[Sequence[int]] | None, optional): List of data layout transformations applied by each consumer when data is read from the AXI stream, described as pairs of (size, stride) from highest to lowest dimension. Defaults to None.
plio (bool, optional): Whether the ObjectFifo uses PLIO connections. Defaults to False.
init_values (list[np.ndarray] | None, optional): Per-buffer static initial values for the producer endpoint. One ndarray per producer-side buffer; the producer tile must be able to hold static data at design startup (e.g. a MemTile). Lowers to the ``initValues`` attribute on the underlying ``aie.objectfifo`` op. Defaults to None.
consumer_obj_type (type[np.ndarray] | None, optional): Consumer element type for asymmetric transfer granularity. When set, the producer sends obj_type-sized transfers and the consumer receives consumer_obj_type-sized transfers. Producer element count must be an integer multiple of consumer element count. Defaults to None.

Raises:
ValueError: If ``depth`` is provided and is less than 1.
Expand All @@ -82,6 +84,7 @@ def __init__(
self._resolving = False
self._iter_count: int | None = None
self._init_values: list[np.ndarray] | None = init_values
self._consumer_obj_type: type[np.ndarray] | None = consumer_obj_type

@classmethod
def __get_index(cls) -> int:
Expand Down Expand Up @@ -290,6 +293,11 @@ def resolve(
for con in self._cons
]

consumer_datatype = (
np_ndarray_type_to_memref_type(self._consumer_obj_type)
if self._consumer_obj_type is not None
else None
)
self._op = object_fifo(
self.name,
self._prod_tile_op(),
Expand All @@ -302,6 +310,7 @@ def resolve(
padDimensions=self._pad_dimensions,
iter_count=self._iter_count,
initValues=self._init_values,
consumer_datatype=consumer_datatype,
)

if isinstance(self._prod.endpoint, ObjectFifoLink):
Expand Down
35 changes: 34 additions & 1 deletion test/dialect/AIE/objectfifo_bad.mlir
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,39 @@ aie.device(xcve2302) {
%tile12 = aie.tile(1, 2)
%tile23 = aie.tile(2, 3)

aie.objectfifo @of0 (%tile12, {%tile23}, 2 : i32) : !aie.objectfifo<memref<2x2xi32>> = [dense<[[4, 5], [6, 7]]> : memref<2x2xi32>,
aie.objectfifo @of0 (%tile12, {%tile23}, 2 : i32) : !aie.objectfifo<memref<2x2xi32>> = [dense<[[4, 5], [6, 7]]> : memref<2x2xi32>,
dense<[[0, 1, 2], [3, 4, 5]]> : memref<2x2xi32>]
}

// -----

// CHECK: producer element size (35) must be an integer multiple of consumer element size (10)

aie.device(npu2) {
%tile01 = aie.tile(0, 1)
%tile02 = aie.tile(0, 2)

aie.objectfifo @of0 (%tile01, {%tile02}, 1 : i32) : !aie.objectfifo<memref<35xi32>> -> !aie.objectfifo<memref<10xi32>>
}

// -----

// CHECK: producer and consumer must have the same scalar element type

aie.device(npu2) {
%tile01 = aie.tile(0, 1)
%tile02 = aie.tile(0, 2)

aie.objectfifo @of0 (%tile01, {%tile02}, 1 : i32) : !aie.objectfifo<memref<40xi32>> -> !aie.objectfifo<memref<10xf32>>
}

// -----

// CHECK: consumer element count must be positive

aie.device(npu2) {
%tile01 = aie.tile(0, 1)
%tile02 = aie.tile(0, 2)

aie.objectfifo @of0 (%tile01, {%tile02}, 1 : i32) : !aie.objectfifo<memref<40xi32>> -> !aie.objectfifo<memref<0xi32>>
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
//===- asymmetric_element_type.mlir ------------------------------*- MLIR -*-===//
//
// 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.
//
//===----------------------------------------------------------------------===//

// RUN: aie-opt --aie-objectFifo-stateful-transform %s | FileCheck %s

// ObjectFifo with asymmetric element types (producer 40xi32, consumer 10xi32).
// Producer sends 40 elements per BD, consumer receives 10 at a time (4:1 ratio).

// Consumer-side buffer should be the small type
// CHECK-DAG: aie.buffer(%{{.*}}) {sym_name = "test_wts_cons_buff_0"} : memref<10xi32>

// Producer-side buffer should be the large type
// CHECK-DAG: aie.buffer(%{{.*}}) {sym_name = "test_wts_buff_0"} : memref<40xi32>

// Flow connecting producer DMA to consumer DMA
// CHECK: aie.flow

// Producer DMA sends 40 elements per BD
// CHECK: aie.memtile_dma
// CHECK: aie.dma_bd(%{{.*}} : memref<40xi32>, 0, 40)

// Consumer DMA receives 10 elements per BD
// CHECK: aie.mem
// CHECK: aie.dma_bd(%{{.*}} : memref<10xi32>, 0, 10)

module {
aie.device(npu2) {
%mt = aie.tile(0, 1)
%ct = aie.tile(0, 2)

aie.objectfifo @test_wts(%mt, {%ct}, 1 : i32)
: !aie.objectfifo<memref<40xi32>>
-> !aie.objectfifo<memref<10xi32>>

%c = aie.core(%ct) {
%c0 = arith.constant 0 : index
%c1 = arith.constant 1 : index
%c4 = arith.constant 4 : index
scf.for %i = %c0 to %c4 step %c1 {
%sv = aie.objectfifo.acquire @test_wts(Consume, 1) : !aie.objectfifosubview<memref<10xi32>>
%elem = aie.objectfifo.subview.access %sv[0] : !aie.objectfifosubview<memref<10xi32>> -> memref<10xi32>
aie.objectfifo.release @test_wts(Consume, 1)
}
aie.end
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
//===- asymmetric_with_initvalues.mlir ---------------------------*- MLIR -*-===//
//
// 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.
//
//===----------------------------------------------------------------------===//

// RUN: aie-opt --aie-objectFifo-stateful-transform %s | FileCheck %s

// Asymmetric ObjectFifo with initValues and depth=2.
// Mobilenet weight loading pattern: 2 large buffers with static weight
// data on MemTile, consumer receives small chunks on CoreTile.

// Consumer buffers use the small type
// CHECK-DAG: aie.buffer(%{{.*}}) {sym_name = "wts_cons_buff_0"} : memref<10xi32>
// CHECK-DAG: aie.buffer(%{{.*}}) {sym_name = "wts_cons_buff_1"} : memref<10xi32>

// Producer buffers have initial values
// CHECK-DAG: aie.buffer(%{{.*}}) {sym_name = "wts_buff_0"} : memref<40xi32> = dense<1>
// CHECK-DAG: aie.buffer(%{{.*}}) {sym_name = "wts_buff_1"} : memref<40xi32> = dense<2>

// Producer lock: init = 0 (depth - initValues count = 0)
// CHECK-DAG: aie.lock(%{{.*}}) {init = 0 : i32, sym_name = "wts_prod_lock_0"}
// Consumer lock: init = 2 (initValues count)
// CHECK-DAG: aie.lock(%{{.*}}) {init = 2 : i32, sym_name = "wts_cons_lock_0"}

// MM2S: 2 BDs for depth=2, each 40 elements
// CHECK: aie.memtile_dma
// CHECK: aie.dma_bd(%{{.*}} : memref<40xi32>, 0, 40)
// CHECK: aie.dma_bd(%{{.*}} : memref<40xi32>, 0, 40)

// S2MM: 2 BDs (consumer depth=2), 10 elements each
// CHECK: aie.mem
// CHECK: aie.dma_bd(%{{.*}} : memref<10xi32>, 0, 10)
// CHECK: aie.dma_bd(%{{.*}} : memref<10xi32>, 0, 10)

module {
aie.device(npu2) {
%mt = aie.tile(0, 1)
%ct = aie.tile(0, 2)

aie.objectfifo @wts(%mt, {%ct}, 2 : i32)
: !aie.objectfifo<memref<40xi32>>
-> !aie.objectfifo<memref<10xi32>>
= [dense<1> : memref<40xi32>, dense<2> : memref<40xi32>]

%c = aie.core(%ct) {
%c0 = arith.constant 0 : index
%c1 = arith.constant 1 : index
%c8 = arith.constant 8 : index
scf.for %i = %c0 to %c8 step %c1 {
%sv = aie.objectfifo.acquire @wts(Consume, 1) : !aie.objectfifosubview<memref<10xi32>>
%elem = aie.objectfifo.subview.access %sv[0] : !aie.objectfifosubview<memref<10xi32>> -> memref<10xi32>
aie.objectfifo.release @wts(Consume, 1)
}
aie.end
}
}
}
Loading
Loading