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
5 changes: 2 additions & 3 deletions lib/Dialect/AIE/IR/AIEDialect.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1715,9 +1715,8 @@ LogicalResult PacketFlowOp::verify() {
++numDests;
}

if (numSources != 1)
return emitOpError("must have exactly one aie.packet_source (got ")
<< numSources << ")";
if (numSources < 1)
return emitOpError("must have at least one aie.packet_source");
if (numDests < 1)
return emitOpError("must have at least one aie.packet_dest");

Expand Down
92 changes: 46 additions & 46 deletions lib/Dialect/AIE/Transforms/AIECreatePathFindFlows.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -297,60 +297,60 @@ AIEPathfinderPass::runOnPacketFlow(DeviceOp device, OpBuilder &builder,
Region &r = pktFlowOp.getPorts();
Block &b = r.front();
int flowID = pktFlowOp.IDInt();
Port srcPort, destPort;
TileOp srcTile, destTile;
TileID srcCoords, destCoords;
SmallVector<std::pair<TileID, Port>, 4> sources;

// Pass 1: extract source (order-independent: dest may appear before source)
// Pass 1: collect all sources (order-independent; supports fan-in).
for (Operation &Op : b.getOperations()) {
if (auto pktSource = dyn_cast<PacketSourceOp>(Op)) {
srcTile = dyn_cast<TileOp>(pktSource.getTile().getDefiningOp());
srcPort = pktSource.port();
srcCoords = {srcTile.colIndex(), srcTile.rowIndex()};
auto srcTile = dyn_cast<TileOp>(pktSource.getTile().getDefiningOp());
sources.push_back(
{{srcTile.colIndex(), srcTile.rowIndex()}, pktSource.port()});
}
}
if (!srcTile)
if (sources.empty())
return pktFlowOp.emitOpError("packet_flow has no packet_source");
// Pass 2: process each destination using the source extracted above
// Pass 2: lower each (source, destination) pair so fan-in flows lay
// down switchbox connections for every source, not just the last one.
for (Operation &Op : b.getOperations()) {
if (auto pktDest = dyn_cast<PacketDestOp>(Op)) {
destTile = dyn_cast<TileOp>(pktDest.getTile().getDefiningOp());
destPort = pktDest.port();
destCoords = {destTile.colIndex(), destTile.rowIndex()};
// Assign "keep_pkt_header flag"
auto keep = pktFlowOp.getKeepPktHeader();
keepPktHeaderAttr[{destTile.getTileID(), destPort}] =
keep ? BoolAttr::get(Op.getContext(), *keep) : nullptr;

auto pktDest = dyn_cast<PacketDestOp>(Op);
if (!pktDest)
continue;
auto destTile = dyn_cast<TileOp>(pktDest.getTile().getDefiningOp());
Port destPort = pktDest.port();
TileID destCoords = {destTile.colIndex(), destTile.rowIndex()};
// Assign "keep_pkt_header flag"
auto keep = pktFlowOp.getKeepPktHeader();
keepPktHeaderAttr[{destTile.getTileID(), destPort}] =
keep ? BoolAttr::get(Op.getContext(), *keep) : nullptr;

for (auto &[srcCoords, srcPort] : sources) {
TileID srcSB = {srcCoords.col, srcCoords.row};
if (PathEndPoint srcPoint = {srcSB, srcPort};
!analyzer.processedFlows[srcPoint]) {
SwitchSettings settings = analyzer.flowSolutions[srcPoint];
// add connections for all the Switchboxes in SwitchSettings
for (const auto &[curr, setting] : settings) {
assert(setting.srcs.size() == setting.dsts.size());
TileID currTile = {curr.col, curr.row};
for (size_t i = 0; i < setting.srcs.size(); i++) {
Port src = setting.srcs[i];
Port dest = setting.dsts[i];
// reject false broadcast
if (!findPathToDest(settings, currTile, dest.bundle, dest.channel,
destCoords, destPort.bundle,
destPort.channel))
continue;
Connect connect = {{src.bundle, src.channel},
{dest.bundle, dest.channel}};
if (std::find(switchboxes[currTile].begin(),
switchboxes[currTile].end(),
std::pair{connect, flowID}) ==
switchboxes[currTile].end())
switchboxes[currTile].push_back({connect, flowID});
// Assign "control packet flows" flag per switchbox, based on
// packet flow op attribute
auto ctrlPkt = pktFlowOp.getPriorityRoute();
ctrlPktFlows[{{currTile, dest}, flowID}] =
ctrlPkt ? *ctrlPkt : false;
}
PathEndPoint srcPoint = {srcSB, srcPort};
if (analyzer.processedFlows[srcPoint])
continue;
SwitchSettings settings = analyzer.flowSolutions[srcPoint];
// add connections for all the Switchboxes in SwitchSettings
for (const auto &[curr, setting] : settings) {
assert(setting.srcs.size() == setting.dsts.size());
TileID currTile = {curr.col, curr.row};
for (size_t i = 0; i < setting.srcs.size(); i++) {
Port src = setting.srcs[i];
Port dest = setting.dsts[i];
// reject false broadcast
if (!findPathToDest(settings, currTile, dest.bundle, dest.channel,
destCoords, destPort.bundle, destPort.channel))
continue;
Connect connect = {{src.bundle, src.channel},
{dest.bundle, dest.channel}};
if (std::find(
switchboxes[currTile].begin(), switchboxes[currTile].end(),
std::pair{connect, flowID}) == switchboxes[currTile].end())
switchboxes[currTile].push_back({connect, flowID});
// Assign "control packet flows" flag per switchbox, based on
// packet flow op attribute
auto ctrlPkt = pktFlowOp.getPriorityRoute();
ctrlPktFlows[{{currTile, dest}, flowID}] =
ctrlPkt ? *ctrlPkt : false;
}
}
}
Expand Down
51 changes: 24 additions & 27 deletions lib/Dialect/AIE/Transforms/AIEPathFinder.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -37,41 +37,38 @@ LogicalResult DynamicTileAnalysis::runAnalysis(DeviceOp &device) {
for (PacketFlowOp pktFlowOp : device.getOps<PacketFlowOp>()) {
Region &r = pktFlowOp.getPorts();
Block &b = r.front();
Port srcPort, dstPort;
TileOp srcTile, dstTile;
TileID srcCoords, dstCoords;
// Pass 1: extract source (order-independent: dest may appear before source)
SmallVector<std::pair<TileID, Port>, 4> sources;
// Pass 1: collect all sources (order-independent; supports fan-in).
for (Operation &Op : b.getOperations()) {
if (auto pktSource = dyn_cast<PacketSourceOp>(Op)) {
srcTile = dyn_cast<TileOp>(pktSource.getTile().getDefiningOp());
srcPort = pktSource.port();
srcCoords = {srcTile.colIndex(), srcTile.rowIndex()};
auto srcTile = dyn_cast<TileOp>(pktSource.getTile().getDefiningOp());
sources.push_back(
{{srcTile.colIndex(), srcTile.rowIndex()}, pktSource.port()});
}
}
if (!srcTile)
if (sources.empty())
return pktFlowOp.emitOpError("packet_flow has no packet_source");

// Pass 2: process each destination using the source extracted above
bool priorityFlow =
pktFlowOp.getPriorityRoute() ? *pktFlowOp.getPriorityRoute() : false;
// Pass 2: add a flow from every source to every destination so
// fan-in topologies are routed (not just the last source).
for (Operation &Op : b.getOperations()) {
if (auto pktDest = dyn_cast<PacketDestOp>(Op)) {
dstTile = dyn_cast<TileOp>(pktDest.getTile().getDefiningOp());
dstPort = pktDest.port();
dstCoords = {dstTile.colIndex(), dstTile.rowIndex()};
LLVM_DEBUG(llvm::dbgs()
<< "\tAdding Packet Flow: (" << srcCoords.col << ", "
<< srcCoords.row << ")"
<< stringifyWireBundle(srcPort.bundle) << srcPort.channel
<< " -> (" << dstCoords.col << ", " << dstCoords.row << ")"
<< stringifyWireBundle(dstPort.bundle) << dstPort.channel
<< "\n");
// todo: support many-to-one & many-to-many?
bool priorityFlow =
pktFlowOp.getPriorityRoute()
? *pktFlowOp.getPriorityRoute()
: false; // Flows such as control packet flows are routed in
// priority, to ensure routing consistency.
pathfinder->addFlow(srcCoords, srcPort, dstCoords, dstPort,
/*isPktFlow*/ true, priorityFlow);
auto dstTile = dyn_cast<TileOp>(pktDest.getTile().getDefiningOp());
Port dstPort = pktDest.port();
TileID dstCoords = {dstTile.colIndex(), dstTile.rowIndex()};
for (auto &[srcCoords, srcPort] : sources) {
LLVM_DEBUG(llvm::dbgs()
<< "\tAdding Packet Flow: (" << srcCoords.col << ", "
<< srcCoords.row << ")"
<< stringifyWireBundle(srcPort.bundle) << srcPort.channel
<< " -> (" << dstCoords.col << ", " << dstCoords.row << ")"
<< stringifyWireBundle(dstPort.bundle) << dstPort.channel
<< "\n");
pathfinder->addFlow(srcCoords, srcPort, dstCoords, dstPort,
/*isPktFlow*/ true, priorityFlow);
}
}
}
}
Expand Down
26 changes: 5 additions & 21 deletions test/create-packet-flows/badpacket_flow_source_count.mlir
Original file line number Diff line number Diff line change
Expand Up @@ -11,15 +11,15 @@
// RUN: aie-opt --verify-diagnostics --split-input-file %s

// Regression tests for issue #2583 verifier gap:
// PacketFlowOp must have exactly one packet_source and at least one
// packet_dest. Verify that the verifier rejects zero-source and
// multiple-source flows.
// PacketFlowOp must have at least one packet_source and at least one
// packet_dest. Verify that the verifier rejects zero-source and zero-dest
// flows. (Multi-source flows are valid; see multi_source_packet_flow.mlir.)

// Test 1: zero sources — must be rejected by verifier.
module {
aie.device(xcvc1902) {
%t11 = aie.tile(1, 1)
// expected-error@+1 {{must have exactly one aie.packet_source (got 0)}}
// expected-error@+1 {{must have at least one aie.packet_source}}
aie.packet_flow(0x0) {
aie.packet_dest<%t11, Core : 0>
}
Expand All @@ -28,23 +28,7 @@ module {

// -----

// Test 2: multiple sources — must be rejected by verifier.
module {
aie.device(xcvc1902) {
%t11 = aie.tile(1, 1)
%t12 = aie.tile(1, 2)
// expected-error@+1 {{must have exactly one aie.packet_source (got 2)}}
aie.packet_flow(0x0) {
aie.packet_source<%t11, West : 0>
aie.packet_source<%t12, West : 0>
aie.packet_dest<%t11, Core : 0>
}
}
}

// -----

// Test 3: zero dests — must be rejected by verifier.
// Test 2: zero dests — must be rejected by verifier.
module {
aie.device(xcvc1902) {
%t11 = aie.tile(1, 1)
Expand Down
30 changes: 30 additions & 0 deletions test/create-packet-flows/multi_source_packet_flow.mlir
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
//===- multi_source_packet_flow.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
//
// (c) Copyright 2026 Advanced Micro Devices, Inc.
//
//===----------------------------------------------------------------------===//

// RUN: aie-opt %s | FileCheck %s

// Regression test: a packet_flow with multiple aie.packet_source ops is valid
// (fan-in: multiple source ports emit packets with the same flow ID).
// Verifier must not reject this. Regression for issue #2583 / PR #2919.

// CHECK-LABEL: module @multi_source_packet_flow
module @multi_source_packet_flow {
aie.device(xcvc1902) {
%t11 = aie.tile(1, 1)
%t21 = aie.tile(2, 1)
%t31 = aie.tile(3, 1)
// CHECK: aie.packet_flow(0)
aie.packet_flow(0x0) {
aie.packet_source<%t11, West : 0>
aie.packet_source<%t21, West : 0>
aie.packet_dest<%t31, Core : 0>
}
}
}
52 changes: 52 additions & 0 deletions test/create-packet-flows/multi_source_pathfinder.mlir
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
//===- multi_source_pathfinder.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
//
// (c) Copyright 2026 Advanced Micro Devices, Inc.
//
//===----------------------------------------------------------------------===//

// RUN: aie-opt --aie-create-pathfinder-flows %s | FileCheck %s

// Regression test: when a packet_flow has multiple aie.packet_source ops,
// the pathfinder must route ALL sources to the destination, not just the last
// one encountered (the "last source wins" bug).
//
// Topology: two sources (tiles 1,1 and 2,1) fan-in to one dest (tile 3,1).
//
// (1,1) --East--> (2,1) --East--> (3,1)
// src0 src1 dest

// Both source switchboxes must emit packet rules for flow id 0.
// CHECK-LABEL: aie.device(xcvc1902)

// Switchbox at (1,1): must have packet_rules routing Core:0 eastward.
// CHECK: %[[SW11:.*]] = aie.switchbox(%[[T11:.*]]) {
// CHECK: aie.packet_rules(Core : 0) {
// CHECK: aie.rule(31, 0,

// Switchbox at (2,1): must have packet_rules routing Core:0 eastward
// AND forwarding incoming West traffic toward (3,1).
// CHECK: %[[SW21:.*]] = aie.switchbox(%[[T21:.*]]) {
// CHECK: aie.packet_rules(Core : 0) {
// CHECK: aie.rule(31, 0,

// Switchbox at (3,1): must receive and deliver to Core:0.
// CHECK: %[[SW31:.*]] = aie.switchbox(%[[T31:.*]]) {
// CHECK: aie.masterset(Core : 0,
// CHECK: aie.packet_rules(West :

module @multi_source_pathfinder {
aie.device(xcvc1902) {
%t11 = aie.tile(1, 1)
%t21 = aie.tile(2, 1)
%t31 = aie.tile(3, 1)
aie.packet_flow(0x0) {
aie.packet_source<%t11, Core : 0>
aie.packet_source<%t21, Core : 0>
aie.packet_dest<%t31, Core : 0>
}
}
}
Loading
Loading