Skip to content

Commit 0830645

Browse files
authored
Make broadcast packet arbiter assignment respect existing assignments (#2647)
1 parent 36461f4 commit 0830645

File tree

2 files changed

+438
-73
lines changed

2 files changed

+438
-73
lines changed

lib/Dialect/AIE/Transforms/AIECreatePathFindFlows.cpp

Lines changed: 200 additions & 73 deletions
Original file line numberDiff line numberDiff line change
@@ -163,7 +163,8 @@ struct ConvertFlowsToInterconnect : OpConversionPattern<FlowOp> {
163163
}
164164
}
165165

166-
LLVM_DEBUG(llvm::dbgs() << tileId << ": " << setting << " | " << "\n");
166+
LLVM_DEBUG(llvm::dbgs() << tileId << ": " << setting << " | "
167+
<< "\n");
167168
}
168169

169170
LLVM_DEBUG(llvm::dbgs()
@@ -378,13 +379,20 @@ void AIEPathfinderPass::runOnPacketFlow(DeviceOp device, OpBuilder &builder,
378379
// Therefore, the number of "logical" arbiters is 6 x 4 = 24
379380
// A master port can only be associated with one arbiter
380381

382+
// Constants for arbiter configuration
383+
constexpr int INVALID_AMSEL_VALUE = -1;
384+
constexpr int INVALID_ARBITER_VALUE = -1;
385+
381386
// A map from Tile and master selectValue to the ports targetted by that
382387
// master select.
383388
std::map<std::pair<TileID, int>, SmallVector<Port, 4>> masterAMSels;
384389

390+
// Track which arbiter each port is assigned to (to prevent conflicts)
391+
std::map<PhysPort, int> portToArbiter;
392+
385393
// Count of currently used logical arbiters for each tile.
386394
DenseMap<Operation *, int> amselValues;
387-
int numMsels = 4;
395+
int numMselsPerArbiter = 4;
388396
int numArbiters = 6;
389397

390398
// Get arbiter id from amsel
@@ -398,36 +406,38 @@ void AIEPathfinderPass::runOnPacketFlow(DeviceOp device, OpBuilder &builder,
398406
// Get a new unique amsel from masterAMSels on tile op. Prioritize on
399407
// incrementing arbiter id, before incrementing msel
400408
auto getNewUniqueAmsel =
401-
[&](std::map<std::pair<TileID, int>, SmallVector<Port, 4>> masterAMSels,
409+
[&](const std::map<std::pair<TileID, int>, SmallVector<Port, 4>>
410+
&masterAMSels,
402411
TileOp tileOp, bool isCtrlPkt) {
403412
if (isCtrlPkt) { // Higher AMsel first
404-
for (int i = numMsels - 1; i >= 0; i--)
413+
for (int i = numMselsPerArbiter - 1; i >= 0; i--)
405414
for (int a = numArbiters - 1; a >= 0; a--)
406415
if (!masterAMSels.count(
407416
{tileOp.getTileID(), getAmselFromArbiterIDAndMsel(a, i)}))
408417
return getAmselFromArbiterIDAndMsel(a, i);
409418
} else { // Lower AMsel first
410-
for (int i = 0; i < numMsels; i++)
419+
for (int i = 0; i < numMselsPerArbiter; i++)
411420
for (int a = 0; a < numArbiters; a++)
412421
if (!masterAMSels.count(
413422
{tileOp.getTileID(), getAmselFromArbiterIDAndMsel(a, i)}))
414423
return getAmselFromArbiterIDAndMsel(a, i);
415424
}
416425
tileOp->emitOpError(
417426
"tile op has used up all arbiter-msel combinations");
418-
return -1;
427+
return INVALID_AMSEL_VALUE;
419428
};
420429
// Get a new unique amsel from masterAMSels on tile op with given arbiter id
421430
auto getNewUniqueAmselPerArbiterID =
422-
[&](std::map<std::pair<TileID, int>, SmallVector<Port, 4>> masterAMSels,
431+
[&](const std::map<std::pair<TileID, int>, SmallVector<Port, 4>>
432+
&masterAMSels,
423433
TileOp tileOp, int arbiter) {
424-
for (int i = 0; i < numMsels; i++)
434+
for (int i = 0; i < numMselsPerArbiter; i++)
425435
if (!masterAMSels.count({tileOp.getTileID(),
426436
getAmselFromArbiterIDAndMsel(arbiter, i)}))
427437
return getAmselFromArbiterIDAndMsel(arbiter, i);
428438
tileOp->emitOpError("tile op arbiter ")
429-
<< std::to_string(arbiter) << "has used up all its msels";
430-
return -1;
439+
<< std::to_string(arbiter) << " has used up all its msels";
440+
return INVALID_AMSEL_VALUE;
431441
};
432442

433443
// Order the packet flows in order to get determinsitic amsel allocation;
@@ -453,6 +463,46 @@ void AIEPathfinderPass::runOnPacketFlow(DeviceOp device, OpBuilder &builder,
453463

454464
packetFlows.insert(ctrlPacketFlows.begin(), ctrlPacketFlows.end());
455465

466+
// Helper function to assign ports to a given amsel value
467+
// Updates masterAMSels and portToArbiter, skipping ports already on different
468+
// arbiters
469+
auto assignPortsToAmsel = [&](TileID tileId, int amselValue,
470+
const SmallVector<PhysPort, 4> &destinations) {
471+
int targetArbiter = getArbiterIDFromAmsel(amselValue);
472+
for (auto dest : destinations) {
473+
Port port = dest.second;
474+
PhysPort physPort = {tileId, port};
475+
476+
// Skip this port if it's already assigned to a different arbiter
477+
if (portToArbiter.count(physPort) &&
478+
portToArbiter[physPort] != targetArbiter) {
479+
LLVM_DEBUG(llvm::dbgs()
480+
<< "Skipping port " << stringifyWireBundle(port.bundle)
481+
<< ":" << port.channel << " - already on arbiter "
482+
<< portToArbiter[physPort] << ", target is " << targetArbiter
483+
<< "\n");
484+
continue;
485+
}
486+
487+
masterAMSels[{tileId, amselValue}].push_back(port);
488+
portToArbiter[physPort] = targetArbiter;
489+
}
490+
};
491+
492+
// Helper function to find existing arbiter assignment for ports in a flow
493+
// Returns -1 if no existing assignment found
494+
auto findExistingArbiter =
495+
[&](TileID tileId, const SmallVector<PhysPort, 4> &destinations) -> int {
496+
for (auto dest : destinations) {
497+
Port port = dest.second;
498+
PhysPort physPort = {tileId, port};
499+
if (portToArbiter.count(physPort)) {
500+
return portToArbiter[physPort];
501+
}
502+
}
503+
return INVALID_ARBITER_VALUE;
504+
};
505+
456506
// Check all multi-cast flows (same source, same ID). They should be
457507
// assigned the same arbiter and msel so that the flow can reach all the
458508
// destination ports at the same time For destination ports that appear in
@@ -475,75 +525,128 @@ void AIEPathfinderPass::runOnPacketFlow(DeviceOp device, OpBuilder &builder,
475525
int amselValue = amselValues[tileOp];
476526
assert(amselValue < numArbiters && "Could not allocate new arbiter!");
477527

478-
// Find existing arbiter assignment
479-
// If there is an assignment of an arbiter to a master port before, we
480-
// assign all the master ports here with the same arbiter but different
481-
// msel
482-
bool foundMatchedDest =
483-
false; // This switchbox's output channels match completely or
484-
// partially with an existing amsel entry.
485-
int foundPartialMatchArbiter =
486-
-1; // This switchbox's output channels match partially with an
487-
// existing amsel entry on this arbiter ID (-1 means null).
488-
for (const auto &map : masterAMSels) {
489-
if (map.first.first != tileId)
528+
// Find existing arbiter and amsel assignments for this flow
529+
// Strategy: Look for existing amsel entries that match the flow's
530+
// destinations
531+
// - Complete match: reuse existing amsel
532+
// - Partial match: create new amsel on same arbiter
533+
// - No match: create new amsel (on existing arbiter if ports already
534+
// assigned)
535+
bool hasMatchingAmselEntry = false;
536+
int partialMatchArbiterID = INVALID_ARBITER_VALUE;
537+
538+
// Check if any ports in this flow already have arbiter assignments
539+
int existingArbiter = findExistingArbiter(tileId, packetFlow.second);
540+
541+
// Search for matching amsel entries, prioritizing those that match the
542+
// existing arbiter
543+
for (const auto &amselEntry : masterAMSels) {
544+
if (amselEntry.first.first != tileId)
545+
continue;
546+
amselValue = amselEntry.first.second;
547+
int thisArbiter = getArbiterIDFromAmsel(amselValue);
548+
549+
// If we have an existing arbiter assignment, only consider amsels from
550+
// that arbiter
551+
if (existingArbiter != INVALID_ARBITER_VALUE &&
552+
existingArbiter != thisArbiter) {
490553
continue;
491-
amselValue = map.first.second;
492-
493-
// check if same destinations
494-
SmallVector<Port, 4> ports(masterAMSels[{tileId, amselValue}]);
495-
// check for complete/partial overlapping amsel -> port mapping with any
496-
// previous amsel assignments
497-
bool matched =
498-
false; // Found at least one port with overlapping amsel assignment
499-
bool mismatched = false; // Found at least one port without any
500-
// overlapping amsel assignment
554+
}
555+
556+
// Check if destinations match (completely or partially)
557+
const SmallVector<Port, 4> &existingPorts =
558+
masterAMSels[{tileId, amselValue}];
559+
bool hasOverlap = false;
560+
bool hasNonOverlap = false;
561+
501562
for (auto dest : packetFlow.second) {
502563
Port port = dest.second;
503-
if (std::find(ports.begin(), ports.end(), port) == ports.end())
504-
mismatched = true;
564+
if (std::find(existingPorts.begin(), existingPorts.end(), port) ==
565+
existingPorts.end())
566+
hasNonOverlap = true;
505567
else
506-
matched = true;
568+
hasOverlap = true;
507569
}
508570

509-
if (matched) {
510-
foundMatchedDest = true;
511-
if (mismatched)
512-
foundPartialMatchArbiter = getArbiterIDFromAmsel(amselValue);
513-
else if (ports.size() != packetFlow.second.size())
514-
foundPartialMatchArbiter = getArbiterIDFromAmsel(amselValue);
571+
if (hasOverlap) {
572+
hasMatchingAmselEntry = true;
573+
// Partial match if some ports don't overlap or sizes differ
574+
if (hasNonOverlap || existingPorts.size() != packetFlow.second.size())
575+
partialMatchArbiterID = thisArbiter;
515576
break;
516577
}
517578
}
518579

519-
if (!foundMatchedDest) {
580+
if (!hasMatchingAmselEntry) {
520581
// This packet flow switchbox's output ports completely mismatches with
521582
// any existing amsel. Creating a new amsel.
522583

523-
// Check if any of the master ports have ever been used for ctrl pkts.
524-
// Ctrl pkt (i.e. prioritized packet flow) amsel assignment follows a
525-
// different strategy (see method below).
526-
bool ctrlPktAMsel =
527-
llvm::any_of(packetFlow.second, [&](PhysPort destPhysPort) {
528-
Port port = destPhysPort.second;
529-
return ctrlPktFlows[{{tileId, port}, packetFlow.first.second}];
530-
});
531-
532-
amselValue = getNewUniqueAmsel(masterAMSels, tileOp, ctrlPktAMsel);
533-
// Update masterAMSels with new amsel
534-
for (auto dest : packetFlow.second) {
535-
Port port = dest.second;
536-
masterAMSels[{tileId, amselValue}].push_back(port);
584+
// Determine target arbiter: use existing if available, otherwise allocate
585+
// based on priority
586+
int targetArbiter = existingArbiter;
587+
588+
if (targetArbiter == INVALID_ARBITER_VALUE) {
589+
// No existing assignment, choose based on control packet priority
590+
bool isCtrlPkt =
591+
llvm::any_of(packetFlow.second, [&](PhysPort destPhysPort) {
592+
Port port = destPhysPort.second;
593+
return ctrlPktFlows[{{tileId, port}, packetFlow.first.second}];
594+
});
595+
596+
amselValue = getNewUniqueAmsel(masterAMSels, tileOp, isCtrlPkt);
597+
} else {
598+
// Use existing arbiter to maintain consistency
599+
amselValue =
600+
getNewUniqueAmselPerArbiterID(masterAMSels, tileOp, targetArbiter);
601+
if (amselValue == INVALID_AMSEL_VALUE) {
602+
// No more msels available on this arbiter - this is a routing
603+
// conflict
604+
tileOp->emitOpError("cannot assign flow: arbiter ")
605+
<< targetArbiter
606+
<< " has no free msels, but flow requires this arbiter due to "
607+
"existing port assignments";
608+
return;
609+
}
537610
}
538-
} else if (foundPartialMatchArbiter >= 0) {
611+
612+
// Update masterAMSels with new amsel, skipping ports already assigned to
613+
// different arbiters
614+
assignPortsToAmsel(tileId, amselValue, packetFlow.second);
615+
} else if (partialMatchArbiterID != INVALID_ARBITER_VALUE) {
539616
// This packet flow switchbox's output ports partially overlaps with
540-
// some existing amsel. Creating a new amsel with the same arbiter.
541-
amselValue = getNewUniqueAmselPerArbiterID(masterAMSels, tileOp,
542-
foundPartialMatchArbiter);
543-
// Update masterAMSels with new amsel
617+
// some existing amsel. Create a NEW amsel with the SAME arbiter for this
618+
// flow. The comment states: "destination ports that appear in different
619+
// (multicast) flows should have a different <arbiterID, msel> value pair
620+
// for each flow" but use the same arbiter to maintain the constraint.
621+
622+
// Use the arbiter we found (which should match existingArbiter if set)
623+
int targetArbiter = partialMatchArbiterID;
624+
if (existingArbiter != INVALID_ARBITER_VALUE &&
625+
existingArbiter != targetArbiter) {
626+
// Conflict detected - should have been caught earlier, but add safety
627+
// check
628+
tileOp->emitOpError(
629+
"internal error: arbiter conflict in partial match");
630+
return;
631+
}
632+
633+
amselValue =
634+
getNewUniqueAmselPerArbiterID(masterAMSels, tileOp, targetArbiter);
635+
636+
// Update masterAMSels with new amsel, skipping ports already assigned to
637+
// different arbiters
638+
assignPortsToAmsel(tileId, amselValue, packetFlow.second);
639+
} else {
640+
// Complete match - reuse the existing amsel
641+
// Track arbiter assignments for all ports in this flow
642+
int arbiter = getArbiterIDFromAmsel(amselValue);
544643
for (auto dest : packetFlow.second) {
545644
Port port = dest.second;
546-
masterAMSels[{tileId, amselValue}].push_back(port);
645+
PhysPort physPort = {tileId, port};
646+
// Update tracking even for reused amsels
647+
if (!portToArbiter.count(physPort)) {
648+
portToArbiter[physPort] = arbiter;
649+
}
547650
}
548651
}
549652

@@ -552,14 +655,38 @@ void AIEPathfinderPass::runOnPacketFlow(DeviceOp device, OpBuilder &builder,
552655
}
553656

554657
// Compute the master set IDs
555-
// A map from a switchbox output port to the number of that port.
658+
// A map from a switchbox output port to its associated amsel values
556659
std::map<PhysPort, SmallVector<int, 4>> mastersets;
557660
for (const auto &[physPort, ports] : masterAMSels) {
558661
TileID tileId = physPort.first;
559662
int amselValue = physPort.second;
560663
for (auto port : ports) {
561-
PhysPort pp = {tileId, port};
562-
mastersets[pp].push_back(amselValue);
664+
PhysPort physPort = {tileId, port};
665+
mastersets[physPort].push_back(amselValue);
666+
}
667+
}
668+
669+
// Validate that each port only has amsels from a single arbiter
670+
for (const auto &[physPort, amselList] : mastersets) {
671+
int assignedArbiter = INVALID_ARBITER_VALUE;
672+
for (auto amsel : amselList) {
673+
int thisArbiter = getArbiterIDFromAmsel(amsel);
674+
if (assignedArbiter != INVALID_ARBITER_VALUE &&
675+
assignedArbiter != thisArbiter) {
676+
TileID tileId = physPort.first;
677+
Port port = physPort.second;
678+
TileOp tileOp = analyzer.getTile(builder, tileId);
679+
tileOp->emitOpError("port ")
680+
<< stringifyWireBundle(port.bundle) << ":" << port.channel
681+
<< " assigned to multiple arbiters: " << assignedArbiter << " and "
682+
<< thisArbiter << " (amsels: " << amselList[0];
683+
for (size_t i = 1; i < amselList.size(); i++) {
684+
llvm::errs() << ", " << amselList[i];
685+
}
686+
llvm::errs() << ")\n";
687+
return signalPassFailure();
688+
}
689+
assignedArbiter = thisArbiter;
563690
}
564691
}
565692

@@ -661,14 +788,14 @@ void AIEPathfinderPass::runOnPacketFlow(DeviceOp device, OpBuilder &builder,
661788
LLVM_DEBUG(llvm::dbgs()
662789
<< "Port " << tile << " " << stringifyWireBundle(bundle) << " "
663790
<< channel << '\n');
664-
LLVM_DEBUG(llvm::dbgs()
665-
<< "Mask " << "0x" << llvm::Twine::utohexstr(mask) << '\n');
666-
LLVM_DEBUG(llvm::dbgs()
667-
<< "ID " << "0x" << llvm::Twine::utohexstr(ID) << '\n');
791+
LLVM_DEBUG(llvm::dbgs() << "Mask "
792+
<< "0x" << llvm::Twine::utohexstr(mask) << '\n');
793+
LLVM_DEBUG(llvm::dbgs() << "ID "
794+
<< "0x" << llvm::Twine::utohexstr(ID) << '\n');
668795
for (int i = 0; i < 31; i++) {
669796
if ((i & mask) == (ID & mask))
670-
LLVM_DEBUG(llvm::dbgs() << "matches flow ID " << "0x"
671-
<< llvm::Twine::utohexstr(i) << '\n');
797+
LLVM_DEBUG(llvm::dbgs() << "matches flow ID "
798+
<< "0x" << llvm::Twine::utohexstr(i) << '\n');
672799
}
673800
}
674801
#endif
@@ -703,7 +830,7 @@ void AIEPathfinderPass::runOnPacketFlow(DeviceOp device, OpBuilder &builder,
703830
Block &b = swbox.getConnections().front();
704831
builder.setInsertionPoint(b.getTerminator());
705832

706-
std::vector<bool> amselOpNeededVector(numMsels * numArbiters);
833+
std::vector<bool> amselOpNeededVector(numMselsPerArbiter * numArbiters);
707834
for (const auto &map : mastersets) {
708835
if (tileId != map.first.first)
709836
continue;
@@ -714,7 +841,7 @@ void AIEPathfinderPass::runOnPacketFlow(DeviceOp device, OpBuilder &builder,
714841
}
715842
// Create all the amsel Ops
716843
std::map<int, AMSelOp> amselOps;
717-
for (int i = 0; i < numMsels; i++) {
844+
for (int i = 0; i < numMselsPerArbiter; i++) {
718845
for (int a = 0; a < numArbiters; a++) {
719846
auto amselValue = getAmselFromArbiterIDAndMsel(a, i);
720847
if (amselOpNeededVector[amselValue]) {

0 commit comments

Comments
 (0)