Skip to content

Commit 8899a75

Browse files
committed
[objectfifo] Choose packet switching per fifo instead of per run
Packet switching was a pass flag, so a device was either all circuit-switched or all packet-switched. An `aie.objectfifo` now carries the choice itself, split hands it to the flow it creates, and the two kinds coexist in one design. The choice sits on the flow rather than on an endpoint. A packet route is one id agreed by the source and every destination, so marking endpoints would let the two ends of a wire disagree; the `packet` attribute an endpoint already carries stays what it was, the header its buffer descriptors stamp, written by allocation. The pass flag remains as the default for flows that express no preference. `packet_id` pins the id. Allocation assigns the rest around it, taking the lowest id no other flow uses rather than counting up from the highest already taken -- which is what makes pinning usable, since a design pinning 31 would otherwise leave nothing assignable.
1 parent f48275d commit 8899a75

8 files changed

Lines changed: 178 additions & 63 deletions

File tree

docs/design/objfifo-stages/00-model.mlir

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,15 @@
102102
// route. --aie-objectfifo-allocate turns a flow into a channel on each endpoint
103103
// plus an aie.flow, and consumes it.
104104
//
105+
// A flow marked `packet` becomes an aie.packet_flow instead, sharing the stream
106+
// rather than reserving a circuit; the two kinds coexist in one device. The
107+
// choice sits on the flow because a packet route is one id agreed by the source
108+
// and every destination -- per-endpoint marks could disagree with each other.
109+
// `packet_id` pins that id; otherwise allocation picks the lowest one no other
110+
// flow uses. The source endpoint's `packet` attribute is the consequence, not
111+
// the request: it is the header its buffer descriptors stamp, written by
112+
// allocation.
113+
//
105114
//===----------------------------------------------------------------------===//
106115
//
107116
// BD EMISSION

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

Lines changed: 51 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -2057,42 +2057,48 @@ def AIE_ObjectFifoCreateOp: AIE_Op<"objectfifo", [HasParent<"DeviceOp">, Symbol]
20572057
When `repeat_count == 1`, this simplifies to `iter_count * elemNumber`.
20582058
}];
20592059

2060-
let arguments = (
2061-
ins SymbolNameAttr:$sym_name,
2062-
Index:$producerTile,
2063-
Variadic<Index>:$consumerTiles,
2064-
AIE_ObjectFifo_Depth:$elemNumber,
2065-
TypeAttrOf<AIE_ObjectFifoType>:$elemType,
2066-
// Optional consumer element type for asymmetric transfer granularity.
2067-
// Producer sends elemType-sized transfers, consumer receives
2068-
// consumerElemType-sized transfers. Producer element size must be
2069-
// an integer multiple of consumer element size.
2070-
OptionalAttr<TypeAttrOf<AIE_ObjectFifoType>>:$consumerElemType,
2071-
BDDimLayoutArrayAttr:$dimensionsToStream,
2072-
BDDimLayoutArrayArrayAttr:$dimensionsFromStreamPerConsumer,
2073-
DefaultValuedAttr<BoolAttr, "false">:$via_DMA,
2074-
DefaultValuedAttr<BoolAttr, "false">:$plio,
2075-
// disable_synchronization==true will skip lock generation for
2076-
// objectfifo synchronous accesses
2077-
DefaultValuedAttr<BoolAttr, "false">:$disable_synchronization,
2078-
// repeat_count==1 means "do it once"
2079-
OptionalAttr<ConfinedAttr<AIEI32Attr, [IntMinValue<1>]>>:$repeat_count,
2080-
// aie_stream==0 means enable aie stream port on producer tile
2081-
// aie_stream==1 means enable aie stream port on consumer tile
2082-
// aie_stream==2 means enable aie stream ports on producer and consumer tiles
2083-
OptionalAttr<ConfinedAttr<AIEI32Attr, [IntMinValue<0>, IntMaxValue<2>]>>:$aie_stream,
2084-
OptionalAttr<ConfinedAttr<AIEI32Attr, [IntMinValue<0>, IntMaxValue<1>]>>:$aie_stream_port,
2085-
// Pin the DMA channel for an endpoint instead of first-free assignment.
2086-
// prod_dma_channel pins the producer endpoint's channel; cons_dma_channels
2087-
// holds one entry per consumer (-1 = auto-assign that consumer).
2088-
OptionalAttr<ConfinedAttr<AIEI32Attr, [IntMinValue<0>]>>:$prod_dma_channel,
2089-
OptionalAttr<DenseI32ArrayAttr>:$cons_dma_channels,
2090-
InitValuesArrayAttr:$initValues,
2091-
OptionalAttr<BDPadLayoutArrayAttr>:$padDimensions,
2092-
// Lowered onto the memtile MM2S channel; see aie.dma_start's pad_value.
2093-
DefaultValuedOptionalAttr<AIEI32Attr, "0">:$padValue,
2094-
OptionalAttr<AIEI32Attr>:$iter_count
2095-
);
2060+
let arguments = (ins SymbolNameAttr:$sym_name, Index:$producerTile,
2061+
Variadic<Index>:$consumerTiles, AIE_ObjectFifo_Depth:$elemNumber,
2062+
TypeAttrOf<AIE_ObjectFifoType>:$elemType,
2063+
// Optional consumer element type for asymmetric transfer granularity.
2064+
// Producer sends elemType-sized transfers, consumer receives
2065+
// consumerElemType-sized transfers. Producer element size must be
2066+
// an integer multiple of consumer element size.
2067+
OptionalAttr<TypeAttrOf<AIE_ObjectFifoType>>:$consumerElemType,
2068+
BDDimLayoutArrayAttr:$dimensionsToStream,
2069+
BDDimLayoutArrayArrayAttr:$dimensionsFromStreamPerConsumer,
2070+
DefaultValuedAttr<BoolAttr, "false">:$via_DMA,
2071+
DefaultValuedAttr<BoolAttr, "false">:$plio,
2072+
// disable_synchronization==true will skip lock generation for
2073+
// objectfifo synchronous accesses
2074+
DefaultValuedAttr<BoolAttr, "false">:$disable_synchronization,
2075+
// repeat_count==1 means "do it once"
2076+
OptionalAttr<ConfinedAttr<AIEI32Attr, [IntMinValue<1>]>>:$repeat_count,
2077+
// aie_stream==0 means enable aie stream port on producer tile
2078+
// aie_stream==1 means enable aie stream port on consumer tile
2079+
// aie_stream==2 means enable aie stream ports on producer and consumer
2080+
// tiles
2081+
OptionalAttr<ConfinedAttr<AIEI32Attr, [IntMinValue<0>,
2082+
IntMaxValue<2>]>>:$aie_stream,
2083+
OptionalAttr<ConfinedAttr<AIEI32Attr, [IntMinValue<0>,
2084+
IntMaxValue<1>]>>:$aie_stream_port,
2085+
// Pin the DMA channel for an endpoint instead of first-free assignment.
2086+
// prod_dma_channel pins the producer endpoint's channel;
2087+
// cons_dma_channels holds one entry per consumer (-1 = auto-assign that
2088+
// consumer).
2089+
OptionalAttr<ConfinedAttr<
2090+
AIEI32Attr, [IntMinValue<0>]>>:$prod_dma_channel,
2091+
OptionalAttr<DenseI32ArrayAttr>:$cons_dma_channels,
2092+
InitValuesArrayAttr:$initValues,
2093+
OptionalAttr<BDPadLayoutArrayAttr>:$padDimensions,
2094+
// Lowered onto the memtile MM2S channel; see aie.dma_start's pad_value.
2095+
DefaultValuedOptionalAttr<AIEI32Attr, "0">:$padValue,
2096+
OptionalAttr<AIEI32Attr>:$iter_count,
2097+
// Route this fifo's stream connections as packet flows rather than
2098+
// reserving a circuit for each. packet_id pins the header; without it
2099+
// one is assigned that no other flow uses.
2100+
UnitAttr:$packet,
2101+
OptionalAttr<ConfinedAttr<AIEI8Attr, [IntMinValue<0>]>>:$packet_id);
20962102

20972103
let assemblyFormat = [{
20982104
$sym_name
@@ -2430,14 +2436,22 @@ def AIE_ObjectFifoFlowOp : AIE_Op<"objectfifo.flow", [HasParent<"DeviceOp">]> {
24302436
`--aie-objectfifo-allocate` assigns a channel to each endpoint, emits the
24312437
corresponding `aie.flow`, and consumes this op.
24322438

2439+
`packet` routes the connection as an `aie.packet_flow` instead, sharing the
2440+
stream with every other packet flow rather than reserving a circuit for it.
2441+
`packet_id` pins the header the source stamps; without it allocation picks
2442+
an id no other flow is using. Circuit- and packet-switched flows coexist in
2443+
one device.
2444+
24332445
Example:
24342446
```
24352447
aie.objectfifo.flow from @src to [@dst0, @dst1]
2448+
aie.objectfifo.flow from @src to [@dst] {packet, packet_id = 3 : i8}
24362449
```
24372450
}];
24382451

24392452
let arguments = (ins FlatSymbolRefAttr:$source,
2440-
FlatSymbolRefArrayAttr:$destinations);
2453+
FlatSymbolRefArrayAttr:$destinations, UnitAttr:$packet,
2454+
OptionalAttr<ConfinedAttr<AIEI8Attr, [IntMinValue<0>]>>:$packet_id);
24412455

24422456
let assemblyFormat = [{
24432457
`from` $source `to` $destinations attr-dict

lib/Dialect/AIE/IR/AIEDialect.cpp

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -490,6 +490,9 @@ LogicalResult ObjectFifoCreateOp::verify() {
490490
"cannot pin a DMA channel on an objectfifo that also uses aie_stream "
491491
"(stream ports bypass DMA channels)");
492492

493+
if (getPacketId() && !getPacket())
494+
return emitOpError("packet_id is only meaningful on a packet objectfifo");
495+
493496
// Helper to get tile interface from Value
494497
auto getTileLikeFromValue = [](Value v) -> TileLike {
495498
return llvm::dyn_cast<TileLike>(v.getDefiningOp());
@@ -848,6 +851,9 @@ LogicalResult ObjectFifoFlowOp::verify() {
848851
if (getDestinations().empty())
849852
return emitOpError("expects at least one destination");
850853

854+
if (getPacketId() && !getPacket())
855+
return emitOpError("packet_id is only meaningful on a packet flow");
856+
851857
auto device = (*this)->getParentOfType<DeviceOp>();
852858
auto lookup = [&](StringRef name) {
853859
return SymbolTable::lookupNearestSymbolFrom<ObjectFifoDmaEndpointOp>(

lib/Dialect/AIE/Transforms/AIEObjectFifoAllocate.cpp

Lines changed: 32 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -290,21 +290,21 @@ struct AIEObjectFifoAllocatePass
290290
: WireBundle::DMA;
291291
}
292292

293-
/// Packet IDs already spoken for elsewhere in the device.
294-
int nextPacketID() {
295-
int next = 0;
296-
device.walk(
297-
[&](PacketFlowOp flow) { next = std::max<int>(next, flow.IDInt() + 1); });
298-
return next;
293+
/// Packet IDs already spoken for, by an existing packet flow or by a flow
294+
/// that pinned one.
295+
llvm::SmallDenseSet<int> takenPacketIDs() {
296+
llvm::SmallDenseSet<int> taken;
297+
device.walk([&](PacketFlowOp flow) { taken.insert(flow.IDInt()); });
298+
for (auto flow : device.getOps<ObjectFifoFlowOp>())
299+
if (auto pinned = flow.getPacketId())
300+
taken.insert(*pinned);
301+
return taken;
299302
}
300303

301304
/// A packet-switched flow shares the stream with others, so every buffer
302305
/// descriptor the source emits has to carry the packet header.
303306
LogicalResult lowerPacketFlow(ObjectFifoFlowOp flow,
304307
ObjectFifoDmaEndpointOp source, int packetID) {
305-
if (packetID > 31)
306-
return device.emitOpError("max number of packet IDs reached");
307-
308308

309309
auto info =
310310
PacketInfoAttr::get(builder.getContext(), /*pkt_type=*/0, packetID);
@@ -345,14 +345,34 @@ struct AIEObjectFifoAllocatePass
345345
}
346346

347347
LogicalResult lowerFlows() {
348-
int packetID = nextPacketID();
348+
// A packet header carries five bits of id.
349+
constexpr int maxPacketID = 31;
350+
llvm::SmallDenseSet<int> taken = takenPacketIDs();
351+
int nextFree = 0;
349352
SmallVector<Operation *> toErase;
350353
for (auto flow : device.getOps<ObjectFifoFlowOp>()) {
351354
auto source = lookupEndpoint(flow.getSourceAttr());
352355
toErase.push_back(flow);
353356

354-
if (clPacketSwObjectFifos) {
355-
if (failed(lowerPacketFlow(flow, source, packetID++)))
357+
// The pass flag is a default for flows that express no preference, so a
358+
// device may mix circuit- and packet-switched connections.
359+
if (flow.getPacket() || clPacketSwObjectFifos) {
360+
int packetID;
361+
if (auto pinned = flow.getPacketId()) {
362+
packetID = *pinned;
363+
if (packetID > maxPacketID)
364+
return flow.emitOpError("packet_id ")
365+
<< packetID << " is out of range (max " << maxPacketID
366+
<< ")";
367+
} else {
368+
while (taken.contains(nextFree))
369+
nextFree++;
370+
if (nextFree > maxPacketID)
371+
return flow.emitOpError("max number of packet IDs reached");
372+
packetID = nextFree;
373+
taken.insert(packetID);
374+
}
375+
if (failed(lowerPacketFlow(flow, source, packetID)))
356376
return failure();
357377
continue;
358378
}

lib/Dialect/AIE/Transforms/AIEObjectFifoSplit.cpp

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -658,7 +658,9 @@ void AIEObjectFifoSplitPass::runOnOperation() {
658658
ObjectFifoFlowOp::create(
659659
builder, loc,
660660
FlatSymbolRefAttr::get(builder.getContext(), prodDma.getSymName()),
661-
builder.getArrayAttr(destinations));
661+
builder.getArrayAttr(destinations),
662+
fifo.getPacket() ? builder.getUnitAttr() : UnitAttr(),
663+
fifo.getPacketIdAttr());
662664
}
663665

664666
SmallVector<Operation *> toErase;
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
//===- mixed_switching.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+
// Switching is chosen per fifo, so circuit- and packet-switched connections
9+
// coexist. A pinned packet_id is honoured; the rest are assigned around it and
10+
// around packet flows the design already declares.
11+
12+
// RUN: aie-opt --aie-objectfifo-split %s | FileCheck %s --check-prefix=SPLIT
13+
// RUN: aie-opt --aie-objectFifo-stateful-transform %s | FileCheck %s
14+
15+
module @mixed {
16+
aie.device(xcve2302) {
17+
%a = aie.tile(1, 2)
18+
%b = aie.tile(3, 2)
19+
%c = aie.tile(1, 3)
20+
%d = aie.tile(3, 3)
21+
22+
aie.objectfifo @plain (%a, {%b}, 2 : i32) : !aie.objectfifo<memref<16xi32>>
23+
aie.objectfifo @auto (%c, {%d}, 2 : i32) {packet} : !aie.objectfifo<memref<16xi32>>
24+
aie.objectfifo @pinned (%b, {%c}, 2 : i32) {packet, packet_id = 7 : i8} : !aie.objectfifo<memref<16xi32>>
25+
}
26+
}
27+
28+
// The choice rides on the flow, which is what becomes one kind of route or the
29+
// other.
30+
// SPLIT: aie.objectfifo.flow from @plain_prod_dma to [@plain_cons_dma]
31+
// SPLIT-NOT: packet
32+
// SPLIT: aie.objectfifo.flow from @auto_prod_dma to [@auto_cons_dma] {packet}
33+
// SPLIT: aie.objectfifo.flow from @pinned_prod_dma to [@pinned_cons_dma] {packet, packet_id = 7 : i8}
34+
35+
// CHECK-DAG: %[[A:.*]] = aie.tile(1, 2)
36+
// CHECK-DAG: %[[B:.*]] = aie.tile(3, 2)
37+
// CHECK-DAG: %[[C:.*]] = aie.tile(1, 3)
38+
// CHECK-DAG: %[[D:.*]] = aie.tile(3, 3)
39+
40+
// CHECK: aie.flow(%[[A]], DMA : 0, %[[B]], DMA : 0)
41+
// CHECK: aie.packet_flow(0) {
42+
// CHECK: aie.packet_source<%[[C]], DMA : 0>
43+
// CHECK: aie.packet_dest<%[[D]], DMA : 0>
44+
// CHECK: }
45+
// CHECK: aie.packet_flow(7) {
46+
// CHECK: aie.packet_source<%[[B]], DMA : 0>
47+
// CHECK: aie.packet_dest<%[[C]], DMA : 0>
48+
// CHECK: }
49+
50+
// Only the packet-switched sources stamp a header.
51+
// CHECK: aie.mem(%[[A]])
52+
// CHECK-NOT: aie.dma_bd_packet
53+
// CHECK: aie.mem(%[[B]])
54+
// CHECK: aie.dma_bd_packet(0, 7)

test/objectFifo-stateful-transform/packet_switched_flows/packet_id_bad.mlir

Lines changed: 20 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -5,21 +5,31 @@
55
//
66
//===----------------------------------------------------------------------===//
77

8-
// RUN: aie-opt --aie-objectFifo-stateful-transform="packet-sw-objFifos=true" -split-input-file --verify-diagnostics %s
8+
// RUN: aie-opt --aie-objectFifo-stateful-transform -split-input-file --verify-diagnostics %s
99

10-
module @packet_id {
11-
// expected-error@+1 {{'aie.device' op max number of packet IDs reached}}
10+
// A packet header carries five bits of id, so a pinned one above 31 cannot be
11+
// expressed on the wire.
12+
13+
module @pinned_out_of_range {
1214
aie.device(xcve2302) {
13-
%tile00 = aie.tile(0, 0)
14-
%tile02 = aie.tile(0, 2)
1515
%tile12 = aie.tile(1, 2)
1616
%tile33 = aie.tile(3, 3)
1717

18-
aie.objectfifo @of1 (%tile12, {%tile33}, 2 : i32) : !aie.objectfifo<memref<16xi32>>
18+
// expected-error@+1 {{packet_id 32 is out of range (max 31)}}
19+
aie.objectfifo @of1 (%tile12, {%tile33}, 2 : i32) {packet, packet_id = 32 : i8} : !aie.objectfifo<memref<16xi32>>
20+
}
21+
}
22+
23+
// -----
24+
25+
// A pinned id says nothing unless the flow is packet-switched.
26+
27+
module @pinned_without_packet {
28+
aie.device(xcve2302) {
29+
%tile12 = aie.tile(1, 2)
30+
%tile33 = aie.tile(3, 3)
1931

20-
aie.packet_flow(31) {
21-
aie.packet_source<%tile02, Trace : 0>
22-
aie.packet_dest<%tile00, DMA : 1>
23-
} {keep_pkt_header = true}
32+
// expected-error@+1 {{packet_id is only meaningful on a packet objectfifo}}
33+
aie.objectfifo @of1 (%tile12, {%tile33}, 2 : i32) {packet_id = 3 : i8} : !aie.objectfifo<memref<16xi32>>
2434
}
2535
}

test/objectFifo-stateful-transform/packet_switched_flows/packet_id_test.mlir

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@
2121
// CHECK: %[[VAL_10:.*]] = aie.buffer(%[[VAL_2]]) {sym_name = "of1_buff_1"} : memref<16xi32>
2222
// CHECK: %[[VAL_11:.*]] = aie.lock(%[[VAL_2]]) {init = 2 : i32, sym_name = "of1_prod_lock_0"}
2323
// CHECK: %[[VAL_12:.*]] = aie.lock(%[[VAL_2]]) {init = 0 : i32, sym_name = "of1_cons_lock_0"}
24-
// CHECK: aie.packet_flow(2) {
24+
// CHECK: aie.packet_flow(0) {
2525
// CHECK: aie.packet_source<%[[VAL_2]], DMA : 0>
2626
// CHECK: aie.packet_dest<%[[VAL_4]], DMA : 0>
2727
// CHECK: }
@@ -33,13 +33,13 @@
3333
// CHECK: aie.dma_start(MM2S, 0, ^bb1, ^bb3)
3434
// CHECK: ^bb1: // 2 preds: ^bb0, ^bb2
3535
// CHECK: aie.use_lock(%[[VAL_12]], AcquireGreaterEqual, %{{.*}})
36-
// CHECK: aie.dma_bd_packet(0, 2)
36+
// CHECK: aie.dma_bd_packet(0, 0)
3737
// CHECK: aie.dma_bd(%[[VAL_9]] : memref<16xi32> offset = {{.*}} len = {{.*}})
3838
// CHECK: aie.use_lock(%[[VAL_11]], Release, %{{.*}})
3939
// CHECK: aie.next_bd ^bb2
4040
// CHECK: ^bb2: // pred: ^bb1
4141
// CHECK: aie.use_lock(%[[VAL_12]], AcquireGreaterEqual, %{{.*}})
42-
// CHECK: aie.dma_bd_packet(0, 2)
42+
// CHECK: aie.dma_bd_packet(0, 0)
4343
// CHECK: aie.dma_bd(%[[VAL_10]] : memref<16xi32> offset = {{.*}} len = {{.*}})
4444
// CHECK: aie.use_lock(%[[VAL_11]], Release, %{{.*}})
4545
// CHECK: aie.next_bd ^bb1

0 commit comments

Comments
 (0)