diff --git a/include/aie/Dialect/AIE/Transforms/AIEPasses.h b/include/aie/Dialect/AIE/Transforms/AIEPasses.h index 042df53e4c3..9a71f47ec66 100644 --- a/include/aie/Dialect/AIE/Transforms/AIEPasses.h +++ b/include/aie/Dialect/AIE/Transforms/AIEPasses.h @@ -23,6 +23,7 @@ namespace xilinx::AIE { #define GEN_PASS_CLASSES #include "aie/Dialect/AIE/Transforms/AIEPasses.h.inc" +std::unique_ptr> createAIEPlaceTilesPass(); std::unique_ptr> createAIEAssignBufferAddressesPass(); std::unique_ptr> createAIEAssignLockIDsPass(); diff --git a/include/aie/Dialect/AIE/Transforms/AIEPasses.td b/include/aie/Dialect/AIE/Transforms/AIEPasses.td index 70299a0b30c..87facb58464 100644 --- a/include/aie/Dialect/AIE/Transforms/AIEPasses.td +++ b/include/aie/Dialect/AIE/Transforms/AIEPasses.td @@ -13,6 +13,26 @@ include "mlir/Pass/PassBase.td" +def AIEPlaceTiles : Pass<"aie-place-tiles", "DeviceOp"> { + let summary = "Place logical tiles onto physical AIE tiles"; + let description = [{ + Sequential placer algorithm places core tiles in a + column-major order and places fifo-connected Shim/Mem tiles + near its core tiles. One or more aie.logical_tile operation is mapped + to one aie.tile. + }]; + + let constructor = "xilinx::AIE::createAIEPlaceTilesPass()"; + + let dependentDialects = ["xilinx::AIE::AIEDialect"]; + + let options = [ + Option<"clPlacerName", "placer", "std::string", + /*default=*/"\"sequential_placer\"", + "Placement algorithm to use (currently only 'sequential_placer')"> + ]; +} + def AIEAssignBufferAddresses : Pass<"aie-assign-buffer-addresses", "DeviceOp"> { let summary = "Assign memory locations for buffers in each tile"; let description = [{ diff --git a/include/aie/Dialect/AIE/Transforms/AIEPlacer.h b/include/aie/Dialect/AIE/Transforms/AIEPlacer.h new file mode 100644 index 00000000000..3807a6e9964 --- /dev/null +++ b/include/aie/Dialect/AIE/Transforms/AIEPlacer.h @@ -0,0 +1,124 @@ +//===- AIEPlacer.h ----------------------------------------------*- 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 +// +// (c) Copyright 2024-2026 Advanced Micro Devices, Inc. +// +//===----------------------------------------------------------------------===// +// This file contains the interface for tile placement algorithms. +// Placers assign physical tile coordinates to logical tiles. +//===----------------------------------------------------------------------===// + +#ifndef AIE_PLACER_H +#define AIE_PLACER_H + +#include "aie/Dialect/AIE/IR/AIEDialect.h" +#include "aie/Dialect/AIE/IR/AIETargetModel.h" + +namespace xilinx::AIE { + +// maps logical tile operations to physical coordinates +using PlacementResult = llvm::DenseMap; + +// Track available tiles and resource usage +struct TileAvailability { + std::vector compTiles; + std::vector nonCompTiles; // Memory and shim tiles + + llvm::DenseMap inputChannelsUsed; + llvm::DenseMap outputChannelsUsed; + + void removeTile(TileID tile, AIETileType type); +}; + +// Abstract placer interface +class Placer { +public: + Placer() = default; + virtual ~Placer() = default; + + virtual void initialize(DeviceOp device, + const AIETargetModel &targetModel) = 0; + + virtual mlir::LogicalResult + place(llvm::ArrayRef logicalTiles, + llvm::ArrayRef objectFifos, + llvm::ArrayRef cores, PlacementResult &result) = 0; + + virtual llvm::StringRef getName() const = 0; +}; + +// Sequential placement algorithm +// +// Places logical tiles to physical tiles using a simple strategy: +// - Compute tiles: Sequential row-major placement +// - Memory/shim tiles: Channel capacity placement near common column +// +// Core-to-core connections are NOT validated because SequentialPlacer +// doesn't account for shared memory optimization. +// +// Shim/Mem tiles with identical placement constraints and sufficient +// DMA capacity are merged to the same physical tile. +class SequentialPlacer : public Placer { +public: + SequentialPlacer(std::optional coresPerCol = std::nullopt) + : coresPerCol(coresPerCol) {} + + void initialize(DeviceOp device, const AIETargetModel &targetModel) override; + + mlir::LogicalResult place(llvm::ArrayRef logicalTiles, + llvm::ArrayRef objectFifos, + llvm::ArrayRef cores, + PlacementResult &result) override; + + llvm::StringRef getName() const override { return "sequential_placer"; } + +private: + std::optional coresPerCol; + TileAvailability availability; + const AIETargetModel *targetModel; + DeviceOp device; + + int getCommonColumn(const PlacementResult &result); + + std::optional findTileWithCapacity(int targetCol, + std::vector &tiles, + int requiredInputChannels, + int requiredOutputChannels, + AIETileType requestedType); + + void updateChannelUsage(TileID tile, bool isOutput, int numChannels); + + bool hasAvailableChannels(TileID tile, int inputChannels, int outputChannels); + + mlir::LogicalResult validateAndUpdateChannelUsage( + LogicalTileOp logicalTile, TileID tile, + const llvm::DenseMap> + &channelRequirements, + bool isConstrained); +}; + +// PlacementAnalysis integrates the Pathfinder class into the MLIR +// environment. +class PlacementAnalysis { +public: + PlacementAnalysis() : placer(std::make_shared()) {} + explicit PlacementAnalysis(std::shared_ptr p) + : placer(std::move(p)) {} + + mlir::LogicalResult runAnalysis(DeviceOp &device); + + std::optional getPlacement(mlir::Operation *logicalTile) const; + + Placer &getPlacer() { return *placer; } + +private: + std::shared_ptr placer; + PlacementResult result; +}; + +} // namespace xilinx::AIE + +#endif // AIE_PLACER_H diff --git a/lib/Dialect/AIE/Transforms/AIEPlaceTiles.cpp b/lib/Dialect/AIE/Transforms/AIEPlaceTiles.cpp new file mode 100644 index 00000000000..c884c48f5c8 --- /dev/null +++ b/lib/Dialect/AIE/Transforms/AIEPlaceTiles.cpp @@ -0,0 +1,99 @@ +//===- AIEPlaceTiles.cpp ----------------------------------------*- 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 +// +// (c) Copyright 2024-2026 Advanced Micro Devices, Inc. +// +//===----------------------------------------------------------------------===// + +#include "aie/Dialect/AIE/IR/AIEDialect.h" +#include "aie/Dialect/AIE/Transforms/AIEPasses.h" +#include "aie/Dialect/AIE/Transforms/AIEPlacer.h" + +#include "mlir/IR/PatternMatch.h" +#include "mlir/Transforms/DialectConversion.h" + +#define DEBUG_TYPE "aie-place-tiles" + +using namespace mlir; +using namespace xilinx; +using namespace xilinx::AIE; + +namespace { + +struct ConvertLogicalTileToTile : OpConversionPattern { + ConvertLogicalTileToTile(MLIRContext *context, DeviceOp &d, + PlacementAnalysis &a, PatternBenefit benefit = 1) + : OpConversionPattern(context, benefit), device(d), analyzer(a) {} + + LogicalResult + matchAndRewrite(LogicalTileOp logicalTile, OpAdaptor adaptor, + ConversionPatternRewriter &rewriter) const override { + // Get pre-computed placement from analysis + auto placement = analyzer.getPlacement(logicalTile); + if (!placement) { + return logicalTile.emitError("no placement found for logical tile"); + } + + // handle merging multiple logical tiles to same physical tile + TileOp tileOp = + TileOp::getOrCreate(rewriter, device, placement->col, placement->row); + + // Copy allocation_scheme if present + if (auto scheme = logicalTile.getAllocationScheme()) + tileOp.setAllocationScheme(scheme); + + // Replace all uses and erase logical tile + rewriter.replaceOp(logicalTile, tileOp.getResult()); + return success(); + } + +private: + DeviceOp device; + PlacementAnalysis &analyzer; +}; + +} // namespace + +struct AIEPlaceTilesPass : AIEPlaceTilesBase { + void runOnOperation() override { + DeviceOp device = getOperation(); + + // Create placer + std::shared_ptr placer; + if (clPlacerName == "sequential_placer") { + placer = std::make_shared(); + } else { + device.emitError() << "Unknown placer: " << clPlacerName; + return signalPassFailure(); + } + + // Run placement analysis + PlacementAnalysis analyzer(placer); + if (failed(analyzer.runAnalysis(device))) + return signalPassFailure(); + + // Apply placement using conversion pattern + ConversionTarget target(getContext()); + target.addLegalOp(); + target.addIllegalOp(); + + // Mark all other AIE dialect operations as legal + // They will have their operands automatically updated when LogicalTileOp -> + // TileOp + target.addLegalDialect(); + + RewritePatternSet patterns(&getContext()); + patterns.add(device.getContext(), device, + analyzer); + + if (failed(applyPartialConversion(device, target, std::move(patterns)))) + return signalPassFailure(); + } +}; + +std::unique_ptr> AIE::createAIEPlaceTilesPass() { + return std::make_unique(); +} diff --git a/lib/Dialect/AIE/Transforms/AIEPlacer.cpp b/lib/Dialect/AIE/Transforms/AIEPlacer.cpp new file mode 100644 index 00000000000..064ab91764a --- /dev/null +++ b/lib/Dialect/AIE/Transforms/AIEPlacer.cpp @@ -0,0 +1,399 @@ +//===- AIEPlacer.cpp --------------------------------------------*- 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 +// +// (c) Copyright 2024-2026 Advanced Micro Devices, Inc. +// +//===----------------------------------------------------------------------===// + +#include "aie/Dialect/AIE/Transforms/AIEPlacer.h" +#include "llvm/Support/Debug.h" + +#include +#include + +using namespace mlir; +using namespace xilinx::AIE; + +#define DEBUG_TYPE "aie-placer" + +void SequentialPlacer::initialize(DeviceOp dev, const AIETargetModel &tm) { + device = dev; + targetModel = &tm; + + // Collect all available physical tiles from device + for (int col = 0; col < tm.columns(); col++) { + for (int row = 0; row < tm.rows(); row++) { + TileID id = {col, row}; + AIETileType type = tm.getTileType(col, row); + + switch (type) { + case AIETileType::CoreTile: + availability.compTiles.push_back(id); + break; + case AIETileType::MemTile: + case AIETileType::ShimNOCTile: + availability.nonCompTiles.push_back(id); + break; + default: + break; + } + } + } + + // Sort tiles for sequential placement + // Compute tiles: column-major (fill column vertically before next column) + auto compTileCmp = [](TileID a, TileID b) { + if (a.col != b.col) + return a.col < b.col; + return a.row < b.row; + }; + + // Non-compute tiles: row-major + auto rowMajorCmp = [](TileID a, TileID b) { + if (a.row != b.row) + return a.row < b.row; + return a.col < b.col; + }; + + std::sort(availability.compTiles.begin(), availability.compTiles.end(), + compTileCmp); + std::sort(availability.nonCompTiles.begin(), availability.nonCompTiles.end(), + rowMajorCmp); +} + +LogicalResult SequentialPlacer::place(ArrayRef logicalTiles, + ArrayRef objectFifos, + ArrayRef cores, + PlacementResult &result) { + // Build channel requirements map by analyzing ObjectFifo connectivity + llvm::DenseMap> channelRequirements; + + for (auto *op : objectFifos) { + auto ofOp = dyn_cast(op); + if (!ofOp) + continue; + + Value producerTile = ofOp.getProducerTile(); + auto *producerOp = producerTile.getDefiningOp(); + auto producerLogicalTile = dyn_cast_or_null(producerOp); + + // Check if ANY consumer is a different tile type (needs DMA channel) + bool producerNeedsDMA = false; + + for (Value consumerTile : ofOp.getConsumerTiles()) { + auto *consumerOp = consumerTile.getDefiningOp(); + auto consumerLogicalTile = dyn_cast_or_null(consumerOp); + + // Skip core-to-core connections (SequentialPlacer doesn't account for + // these) + if (producerLogicalTile && consumerLogicalTile && + producerLogicalTile.getTileType() == AIETileType::CoreTile && + consumerLogicalTile.getTileType() == AIETileType::CoreTile) + continue; + + // This consumer needs a DMA channel + if (consumerOp) + channelRequirements[consumerOp].first++; // input++ + + producerNeedsDMA = true; + } + + // Producer needs ONE output channel if any consumer needs DMA + if (producerNeedsDMA && producerOp) + channelRequirements[producerOp].second++; // output++ + } + + SmallVector computeLogicalTiles; + SmallVector memLogicalTiles; + SmallVector shimLogicalTiles; + + for (auto *op : logicalTiles) { + auto logicalTile = dyn_cast(op); + if (!logicalTile) + continue; + + switch (logicalTile.getTileType()) { + case AIETileType::CoreTile: + computeLogicalTiles.push_back(logicalTile); + break; + case AIETileType::MemTile: + memLogicalTiles.push_back(logicalTile); + break; + case AIETileType::ShimNOCTile: + shimLogicalTiles.push_back(logicalTile); + break; + default: + return logicalTile.emitError( + "unsupported tile type for SequentialPlacer"); + } + } + + // Place ALL constrained tiles at requested tile + for (auto *op : logicalTiles) { + auto logicalTile = dyn_cast(op); + if (!logicalTile) + continue; + + auto col = logicalTile.tryGetCol(); + auto row = logicalTile.tryGetRow(); + if (!col || !row) + continue; // Not fully constrained + + TileID tile{*col, *row}; + if (failed(validateAndUpdateChannelUsage(logicalTile, tile, + channelRequirements, true))) + return failure(); + + result[logicalTile] = tile; + availability.removeTile(tile, logicalTile.getTileType()); + } + + // Place unconstrained compute tiles sequentially + size_t nextCompIdx = 0; + for (auto logicalTile : computeLogicalTiles) { + if (result.count(logicalTile.getOperation())) + continue; // Already placed by constraint + + // Unconstrained: sequential placement + // NOTE: Partial constraints (col-only or row-only) are currently ignored + if (nextCompIdx >= availability.compTiles.size()) + return logicalTile.emitError("no available compute tiles for placement"); + + TileID tile = availability.compTiles[nextCompIdx++]; + if (failed(validateAndUpdateChannelUsage(logicalTile, tile, + channelRequirements, false))) + return failure(); + + result[logicalTile] = tile; + } + + // Place mem/shim tiles considering channel capacity + SmallVector nonComputeTiles; + nonComputeTiles.append(memLogicalTiles.begin(), memLogicalTiles.end()); + nonComputeTiles.append(shimLogicalTiles.begin(), shimLogicalTiles.end()); + + int commonCol = getCommonColumn(result); + for (auto logicalTile : nonComputeTiles) { + if (result.count(logicalTile.getOperation())) + continue; // Already placed by constraint + + // Get channel requirements + auto it = channelRequirements.find(logicalTile.getOperation()); + int numInputChannels = 0, numOutputChannels = 0; + if (it != channelRequirements.end()) { + numInputChannels = it->second.first; + numOutputChannels = it->second.second; + } + + // Find tile with capacity near common column + // Pass tile type to ensure we only search for matching tile types + auto maybeTile = findTileWithCapacity(commonCol, availability.nonCompTiles, + numInputChannels, numOutputChannels, + logicalTile.getTileType()); + if (!maybeTile) + return logicalTile.emitError("no tile with sufficient DMA capacity"); + + result[logicalTile] = *maybeTile; + + // Update channel usage + if (numInputChannels > 0) + updateChannelUsage(*maybeTile, false, numInputChannels); + if (numOutputChannels > 0) + updateChannelUsage(*maybeTile, true, numOutputChannels); + } + + return success(); +} + +LogicalResult SequentialPlacer::validateAndUpdateChannelUsage( + LogicalTileOp logicalTile, TileID tile, + const llvm::DenseMap> &channelRequirements, + bool isConstrained) { + + // Get channel requirements + auto it = channelRequirements.find(logicalTile.getOperation()); + int inChannels = 0, outChannels = 0; + if (it != channelRequirements.end()) { + inChannels = it->second.first; + outChannels = it->second.second; + } + + // Validate capacity + if (!hasAvailableChannels(tile, inChannels, outChannels)) { + // Get max channels + int maxIn = logicalTile.getNumDestConnections(WireBundle::DMA); + int maxOut = logicalTile.getNumSourceConnections(WireBundle::DMA); + int availIn = maxIn - availability.inputChannelsUsed[tile]; + int availOut = maxOut - availability.outputChannelsUsed[tile]; + + auto diag = logicalTile.emitError(); + if (isConstrained) + diag << "tile (" << tile.col << ", " << tile.row << ") requires "; + else + diag << "tile requires "; + diag << inChannels << " input/" << outChannels + << " output DMA channels, but only " << availIn << " input/" + << availOut << " output available"; + return failure(); + } + + // Update channel usage + if (inChannels > 0) + updateChannelUsage(tile, false, inChannels); + if (outChannels > 0) + updateChannelUsage(tile, true, outChannels); + + return success(); +} + +int SequentialPlacer::getCommonColumn(const PlacementResult &result) { + SmallVector computeCols; + + for (const auto &[op, tile] : result) { + if (auto logicalTile = dyn_cast(op)) { + if (logicalTile.getTileType() == AIETileType::CoreTile) { + computeCols.push_back(tile.col); + } + } + } + + if (computeCols.empty()) + return 0; + + int sum = std::accumulate(computeCols.begin(), computeCols.end(), 0); + return static_cast( + std::round(static_cast(sum) / computeCols.size())); +} + +LogicalResult PlacementAnalysis::runAnalysis(DeviceOp &device) { + SmallVector logicalTiles; + SmallVector objectFifos; + SmallVector cores; + + // Collect operations + device.walk([&](Operation *op) { + if (isa(op)) + logicalTiles.push_back(op); + if (isa(op)) + objectFifos.push_back(op); + if (isa(op)) + cores.push_back(op); + }); + + // Initialize placer + const auto &targetModel = device.getTargetModel(); + placer->initialize(device, targetModel); + + // Run placement + return placer->place(logicalTiles, objectFifos, cores, result); +} + +std::optional +PlacementAnalysis::getPlacement(Operation *logicalTile) const { + auto it = result.find(logicalTile); + if (it != result.end()) + return it->second; + return std::nullopt; +} + +// Find tile with available DMA capacity near target column +// This function checks capacity for BOTH input and output channels +// simultaneously. For unidirectional tiles, pass 0 for the unused direction: +// - Input-only: findTileWithCapacity(..., numInputChannels, 0, type) +// - Output-only: findTileWithCapacity(..., 0, numOutputChannels, type) +// - Both: findTileWithCapacity(..., numInputChannels, numOutputChannels, +// type) +// The requestedType parameter filters tiles to only consider matching types +// (e.g., only MemTiles for MemTile logical tiles, only ShimNOCTiles for shims). +std::optional SequentialPlacer::findTileWithCapacity( + int targetCol, std::vector &tiles, int requiredInputChannels, + int requiredOutputChannels, AIETileType requestedType) { + // Search starting from target column, expanding outward + int maxCol = targetModel->columns(); + + for (int offset = 0; offset < maxCol; ++offset) { + int searchCol = targetCol + offset; + if (searchCol >= maxCol) + continue; + + for (auto &tile : tiles) { + // Filter by tile type - only consider tiles of the requested type + AIETileType tileType = targetModel->getTileType(tile.col, tile.row); + if (tileType != requestedType) + continue; + + if (tile.col == searchCol) { + // Check if tile has capacity for both input and output channels + if (hasAvailableChannels(tile, requiredInputChannels, + requiredOutputChannels)) { + return tile; + } + } + } + } + + return std::nullopt; +} + +void SequentialPlacer::updateChannelUsage(TileID tile, bool isOutput, + int numChannels) { + if (isOutput) { + availability.outputChannelsUsed[tile] += numChannels; + } else { + availability.inputChannelsUsed[tile] += numChannels; + } + + // Check if tile is now exhausted and should be removed + if (!hasAvailableChannels(tile, 0, 0)) { + // Determine tile type to remove from appropriate list + AIETileType type = targetModel->getTileType(tile.col, tile.row); + availability.removeTile(tile, type); + } +} + +bool SequentialPlacer::hasAvailableChannels(TileID tile, int inputChannels, + int outputChannels) { + // Get max channels based on tile type and row + int maxIn, maxOut; + if (tile.row == 0) { + // Shim tiles use ShimMux connections + maxIn = targetModel->getNumDestShimMuxConnections(tile.col, tile.row, + WireBundle::DMA); + maxOut = targetModel->getNumSourceShimMuxConnections(tile.col, tile.row, + WireBundle::DMA); + } else { + // Other tiles use Switchbox connections + maxIn = targetModel->getNumDestSwitchboxConnections(tile.col, tile.row, + WireBundle::DMA); + maxOut = targetModel->getNumSourceSwitchboxConnections(tile.col, tile.row, + WireBundle::DMA); + } + + int currentIn = availability.inputChannelsUsed[tile]; + int currentOut = availability.outputChannelsUsed[tile]; + + return (currentIn + inputChannels <= maxIn) && + (currentOut + outputChannels <= maxOut); +} + +void TileAvailability::removeTile(TileID tile, AIETileType type) { + auto removeFromVector = [&](std::vector &vec) { + vec.erase(std::remove(vec.begin(), vec.end(), tile), vec.end()); + }; + + switch (type) { + case AIETileType::CoreTile: + removeFromVector(compTiles); + break; + case AIETileType::MemTile: + case AIETileType::ShimNOCTile: + case AIETileType::ShimPLTile: + removeFromVector(nonCompTiles); + break; + default: + break; + } +} diff --git a/lib/Dialect/AIE/Transforms/CMakeLists.txt b/lib/Dialect/AIE/Transforms/CMakeLists.txt index a8021411e40..d110b293ca1 100644 --- a/lib/Dialect/AIE/Transforms/CMakeLists.txt +++ b/lib/Dialect/AIE/Transforms/CMakeLists.txt @@ -7,6 +7,8 @@ add_mlir_dialect_library( AIETransforms + AIEPlacer.cpp + AIEPlaceTiles.cpp AIEAssignBuffers.cpp AIEAssignBufferDescriptorIDs.cpp AIEAssignLockIDs.cpp diff --git a/python/compiler/aiecc/main.py b/python/compiler/aiecc/main.py index a72ab0e9920..d6cd28287ae 100644 --- a/python/compiler/aiecc/main.py +++ b/python/compiler/aiecc/main.py @@ -70,6 +70,7 @@ def _create_input_with_addresses_pipeline( # Build nested device pipeline with conditional passes device_pipeline = ( Pipeline() + .add_pass("aie-place-tiles") .add_pass("aie-assign-lock-ids") .add_pass("aie-register-objectFifos") .add_pass( diff --git a/test/place-tiles/sequential_placer/channel_capacity_bad.mlir b/test/place-tiles/sequential_placer/channel_capacity_bad.mlir new file mode 100644 index 00000000000..136d7708fec --- /dev/null +++ b/test/place-tiles/sequential_placer/channel_capacity_bad.mlir @@ -0,0 +1,49 @@ +//===- channel_capacity_bad.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: not aie-opt --split-input-file --aie-place-tiles %s 2>&1 | FileCheck %s + +// This test verifies that the sequential placer correctly validates DMA channel +// capacity for ObjectFifos between cores and non-core tiles (shim/mem). + +module @three_inputs_exceeds_capacity { + aie.device(npu1) { + %shim1 = aie.logical_tile(?, ?) + %shim2 = aie.logical_tile(?, ?) + %shim3 = aie.logical_tile(?, ?) + + // CHECK: error: tile requires 3 input/0 output DMA channels, but only 2 input/2 output available + %core = aie.logical_tile(?, ?) + + aie.objectfifo @in1 (%shim1, {%core}, 2 : i32) : !aie.objectfifo> + aie.objectfifo @in2 (%shim2, {%core}, 2 : i32) : !aie.objectfifo> + aie.objectfifo @in3 (%shim3, {%core}, 2 : i32) : !aie.objectfifo> + + aie.core(%core) { aie.end } + } +} + +// ----- + +module @three_outputs_exceeds_capacity { + aie.device(npu1) { + // CHECK: error: tile (0, 2) requires 0 input/3 output DMA channels, but only 2 input/2 output available + %core = aie.logical_tile(0, 2) + %mem1 = aie.logical_tile(?, ?) + %mem2 = aie.logical_tile(?, ?) + %mem3 = aie.logical_tile(?, ?) + + aie.objectfifo @out1 (%core, {%mem1}, 2 : i32) : !aie.objectfifo> + aie.objectfifo @out2 (%core, {%mem2}, 2 : i32) : !aie.objectfifo> + aie.objectfifo @out3 (%core, {%mem3}, 2 : i32) : !aie.objectfifo> + + aie.core(%core) { aie.end } + } +} diff --git a/test/place-tiles/sequential_placer/edge_detect.mlir b/test/place-tiles/sequential_placer/edge_detect.mlir new file mode 100644 index 00000000000..925d91d94be --- /dev/null +++ b/test/place-tiles/sequential_placer/edge_detect.mlir @@ -0,0 +1,96 @@ +//===- edge_detect.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-place-tiles %s | FileCheck %s + +// Test based on programming_examples/vision/edge_detect +// +// This verifies that the C++ SequentialPlacer produces the same placement +// behavior as the original Python SequentialPlacer: +// - Compute tiles: Sequential placement +// - Mem/shim tiles: Merged and placed near common column +// - Final result: 1 shim + 1 mem + 4 cores (all in same column) + +// CHECK-LABEL: @edge_detect +module @edge_detect { + aie.device(npu2) { + %shim_in = aie.logical_tile(?, ?) + %shim_out = aie.logical_tile(?, ?) + + // Separate logical mems from separate forward() calls + %mem_in = aie.logical_tile(?, ?) + %mem_out = aie.logical_tile(?, ?) + + %core2 = aie.logical_tile(?, ?) + %core3 = aie.logical_tile(?, ?) + %core4 = aie.logical_tile(?, ?) + %core5 = aie.logical_tile(?, ?) + + // Compute tiles: column-major sequential placement (fill column vertically) + // CHECK-DAG: %[[T2:.*]] = aie.tile(0, 2) + // CHECK-DAG: %[[T3:.*]] = aie.tile(0, 3) + // CHECK-DAG: %[[T4:.*]] = aie.tile(0, 4) + // CHECK-DAG: %[[T5:.*]] = aie.tile(0, 5) + + // Common column = 0 (all cores in column 0) + // Both shims and both mems should merge to column 0 + + // CHECK-DAG: %[[SHIM:shim_noc_tile_0_0]] = aie.tile(0, 0) + // Both shim_in and shim_out should merge to ONE physical shim + + // CHECK-DAG: %[[MEM:mem_tile_0_1]] = aie.tile(0, 1) + // Both mem_in and mem_out should merge to ONE physical mem + + // Input path: Shim -> {Core, Mem} (consumer: mem_in) + // CHECK: aie.objectfifo @inOF_L3L2(%[[SHIM]], {%[[T2]], %[[MEM]]}, 2 : i32) + aie.objectfifo @inOF_L3L2 (%shim_in, {%core2, %mem_in}, 2 : i32) : !aie.objectfifo> + + // Skip connection + // CHECK: aie.objectfifo @inOF_L2L1(%[[MEM]], {%[[T5]]}, 2 : i32) + aie.objectfifo @inOF_L2L1 (%mem_in, {%core5}, 2 : i32) : !aie.objectfifo> + + // CHECK: aie.objectfifo.link [@inOF_L3L2] -> [@inOF_L2L1] + aie.objectfifo.link [@inOF_L3L2] -> [@inOF_L2L1] ([] []) + + // Output path: Core -> Mem (consumer: mem_out) + // CHECK: aie.objectfifo @outOF_L1L2(%[[T5]], {%[[MEM]]}, 2 : i32) + aie.objectfifo @outOF_L1L2 (%core5, {%mem_out}, 2 : i32) : !aie.objectfifo> + + // Mem -> Shim (producer: mem_out, same physical tile as mem_in) + // CHECK: aie.objectfifo @outOF_L2L3(%[[MEM]], {%[[SHIM]]}, 2 : i32) + aie.objectfifo @outOF_L2L3 (%mem_out, {%shim_out}, 2 : i32) : !aie.objectfifo> + + // CHECK: aie.objectfifo.link [@outOF_L1L2] -> [@outOF_L2L3] + aie.objectfifo.link [@outOF_L1L2] -> [@outOF_L2L3] ([] []) + + // CHECK: aie.objectfifo @OF_2to3(%[[T2]], {%[[T3]]}, 2 : i32) + aie.objectfifo @OF_2to3 (%core2, {%core3}, 2 : i32) : !aie.objectfifo> + + // CHECK: aie.objectfifo @OF_3to4(%[[T3]], {%[[T4]]}, 2 : i32) + aie.objectfifo @OF_3to4 (%core3, {%core4}, 2 : i32) : !aie.objectfifo> + + // CHECK: aie.objectfifo @OF_4to5(%[[T4]], {%[[T5]]}, 2 : i32) + aie.objectfifo @OF_4to5 (%core4, {%core5}, 2 : i32) : !aie.objectfifo> + + // CHECK: aie.core(%[[T2]]) + %c2 = aie.core(%core2) { aie.end } + + // CHECK: aie.core(%[[T3]]) + %c3 = aie.core(%core3) { aie.end } + + // CHECK: aie.core(%[[T4]]) + %c4 = aie.core(%core4) { aie.end } + + // CHECK: aie.core(%[[T5]]) + %c5 = aie.core(%core5) { aie.end } + + // CHECK-NOT: aie.logical_tile + } +} diff --git a/test/place-tiles/sequential_placer/partial_constraints_ignored.mlir b/test/place-tiles/sequential_placer/partial_constraints_ignored.mlir new file mode 100644 index 00000000000..655f348a9d6 --- /dev/null +++ b/test/place-tiles/sequential_placer/partial_constraints_ignored.mlir @@ -0,0 +1,60 @@ +//===- partial_constraints_ignored.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 --split-input-file --aie-place-tiles %s | FileCheck %s + +// This test demonstrates that partial constraints (col-only or row-only) +// are ignored by the current placer. A future placer implementation may +// honor partial constraints, at which point this test should be removed. + +// Test: Column-only constraint is ignored for CoreTiles +// CHECK-LABEL: @partial_constraint_col_ignored +module @partial_constraint_col_ignored { + aie.device(npu1) { + // Tile constrained to column 1, but placed at (0, 2) due to sequential placement + // CHECK: %[[TILE:.*]] = aie.tile(0, 2) + %tile = aie.logical_tile(1, ?) + // CHECK: aie.core(%[[TILE]]) + aie.core(%tile) { aie.end } + // CHECK-NOT: aie.logical_tile + } +} + +// ----- + +// Test: Row-only constraint is ignored for CoreTiles +// CHECK-LABEL: @partial_constraint_row_ignored +module @partial_constraint_row_ignored { + aie.device(npu1) { + // Tile constrained to row 3, but placed at (0, 2) due to sequential placement + // CHECK: %[[TILE:.*]] = aie.tile(0, 2) + %tile = aie.logical_tile(?, 3) + // CHECK: aie.core(%[[TILE]]) + aie.core(%tile) { aie.end } + // CHECK-NOT: aie.logical_tile + } +} + +// ----- + +// Test: Column constraint ignored for MemTiles (uses commonCol instead) +// CHECK-LABEL: @partial_constraint_memtile_col +module @partial_constraint_memtile_col { + aie.device(npu1) { + // CHECK-DAG: %[[CORE:.*]] = aie.tile(0, 2) + %core = aie.logical_tile(?, ?) + // MemTile constrained to column 2, but placed at (0, 1) using commonCol + // CHECK-DAG: %[[MEM:.*]] = aie.tile(0, 1) + %mem = aie.logical_tile(2, ?) + // CHECK: aie.objectfifo @of1(%[[MEM]], {%[[CORE]]}, 2 : i32) + aie.objectfifo @of1 (%mem, {%core}, 2 : i32) : !aie.objectfifo> + // CHECK-NOT: aie.logical_tile + } +} diff --git a/test/place-tiles/sequential_placer/place_tiles_constrained.mlir b/test/place-tiles/sequential_placer/place_tiles_constrained.mlir new file mode 100644 index 00000000000..ec079c009fd --- /dev/null +++ b/test/place-tiles/sequential_placer/place_tiles_constrained.mlir @@ -0,0 +1,83 @@ +//===- place_tiles_constrained.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 --split-input-file --aie-place-tiles %s | FileCheck %s + +// Test: Fully constrained placement +// CHECK-LABEL: @fully_constrained +module @fully_constrained { + aie.device(npu1) { + // Tile explicitly placed at (2, 3) + // CHECK: %[[TILE:.*]] = aie.tile(2, 3) + %tile = aie.logical_tile(2, 3) + + // CHECK: aie.core(%[[TILE]]) + aie.core(%tile) { + aie.end + } + + // CHECK-NOT: aie.logical_tile + } +} + +// ----- + +// Test: Mixed constrained and unconstrained +// CHECK-LABEL: @mixed_constraints +module @mixed_constraints { + aie.device(npu1) { + // Fully constrained to (1, 2) + // CHECK-DAG: %[[C1:.*]] = aie.tile(1, 2) + %c1 = aie.logical_tile(1, 2) + + // First available should be (0, 2) - sequential placement starts at column 0 + // CHECK-DAG: %[[C2:.*]] = aie.tile(0, 2) + %c2 = aie.logical_tile(?, ?) + // Second available should be (0, 3) - column-major sequential (fill column 0 first) + // CHECK-DAG: %[[C3:.*]] = aie.tile(0, 3) + %c3 = aie.logical_tile(?, ?) + + // CHECK: aie.core(%[[C1]]) + aie.core(%c1) { aie.end } + // CHECK: aie.core(%[[C2]]) + aie.core(%c2) { aie.end } + // CHECK: aie.core(%[[C3]]) + aie.core(%c3) { aie.end } + + // CHECK-NOT: aie.logical_tile + } +} + +// ----- + +// Test: Constrained MemTile and ShimTile +// CHECK-LABEL: @constrained_memtile_shimtile +module @constrained_memtile_shimtile { + aie.device(npu1) { + // CHECK-DAG: %[[CORE:.*]] = aie.tile(0, 2) + %core = aie.logical_tile(?, ?) + + // MemTile fully constrained to (1, 1) + // CHECK-DAG: %[[MEM:.*]] = aie.tile(1, 1) + %mem = aie.logical_tile(1, 1) + + // ShimTile fully constrained to (0, 0) + // CHECK-DAG: %[[SHIM:.*]] = aie.tile(0, 0) + %shim = aie.logical_tile(0, 0) + + // CHECK: aie.objectfifo @of1(%[[SHIM]], {%[[MEM]]}, 2 : i32) + aie.objectfifo @of1 (%shim, {%mem}, 2 : i32) : !aie.objectfifo> + + // CHECK: aie.objectfifo @of2(%[[MEM]], {%[[CORE]]}, 2 : i32) + aie.objectfifo @of2 (%mem, {%core}, 2 : i32) : !aie.objectfifo> + + // CHECK-NOT: aie.logical_tile + } +} diff --git a/test/place-tiles/sequential_placer/place_tiles_objectfifo.mlir b/test/place-tiles/sequential_placer/place_tiles_objectfifo.mlir new file mode 100644 index 00000000000..ac2815e2f04 --- /dev/null +++ b/test/place-tiles/sequential_placer/place_tiles_objectfifo.mlir @@ -0,0 +1,80 @@ +//===- place_tiles_objectfifo.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 --split-input-file --aie-place-tiles %s | FileCheck %s + +// Test: Multiple ObjectFifos with separate logical MemTiles that map to same physical tile +// CHECK-LABEL: @multi_fifo_same_tile +module @multi_fifo_same_tile { + aie.device(npu1) { + // CHECK-DAG: %[[CORE:.*]] = aie.tile(0, 2) + %core = aie.logical_tile(?, ?) + + // Two separate logical MemTiles + // Both should be placed at the same physical tile (0, 1) + // because it can fit and is common column + // CHECK-DAG: %[[MEM:.*]] = aie.tile(0, 1) + %mem1 = aie.logical_tile(?, ?) + %mem2 = aie.logical_tile(?, ?) + + // CHECK: aie.objectfifo @of1(%[[MEM]], {%[[CORE]]}, 2 : i32) + aie.objectfifo @of1 (%mem1, {%core}, 2 : i32) : !aie.objectfifo> + // CHECK: aie.objectfifo @of2(%[[MEM]], {%[[CORE]]}, 2 : i32) + aie.objectfifo @of2 (%mem2, {%core}, 2 : i32) : !aie.objectfifo> + + // CHECK-NOT: aie.logical_tile + } +} + +// ----- + +// Test: Worker with multiple input ObjectFifos +// CHECK-LABEL: @worker_multiple_inputs +module @worker_multiple_inputs { + aie.device(npu1) { + // CHECK-DAG: %[[SHIM:.*]] = aie.tile(0, 0) + %shim = aie.logical_tile(?, ?) + // CHECK-DAG: %[[CORE:.*]] = aie.tile(0, 2) + %core = aie.logical_tile(?, ?) + + // Core receives from multiple producers (same shim, needs 2 output channels) + aie.objectfifo @in1 (%shim, {%core}, 2 : i32) : !aie.objectfifo> + aie.objectfifo @in2 (%shim, {%core}, 2 : i32) : !aie.objectfifo> + + aie.core(%core) { + aie.end + } + + // CHECK-NOT: aie.logical_tile + } +} + +// ----- + +// Test: ObjectFifo with multiple consumers +// CHECK-LABEL: @multi_consumer +module @multi_consumer { + aie.device(npu1) { + // CHECK-DAG: %[[SHIM:.*]] = aie.tile(0, 0) + %shim = aie.logical_tile(?, ?) + // CHECK-DAG: %[[CORE1:.*]] = aie.tile(0, 2) + %core1 = aie.logical_tile(?, ?) + // CHECK-DAG: %[[CORE2:.*]] = aie.tile(0, 3) + %core2 = aie.logical_tile(?, ?) + + // One producer, multiple consumers (needs 2 output channels from shim) + aie.objectfifo @broadcast (%shim, {%core1, %core2}, 2 : i32) : !aie.objectfifo> + + aie.core(%core1) { aie.end } + aie.core(%core2) { aie.end } + + // CHECK-NOT: aie.logical_tile + } +} diff --git a/test/place-tiles/sequential_placer/place_tiles_simple.mlir b/test/place-tiles/sequential_placer/place_tiles_simple.mlir new file mode 100644 index 00000000000..c79b38a1849 --- /dev/null +++ b/test/place-tiles/sequential_placer/place_tiles_simple.mlir @@ -0,0 +1,25 @@ +//===- place_tiles_simple.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-place-tiles %s | FileCheck %s + +// Test simple worker placement +// CHECK-LABEL: @simple_worker +module @simple_worker { + aie.device(npu1) { + // CHECK: %[[TILE:.*]] = aie.tile(0, 2) + %logical_core = aie.logical_tile(?, ?) + // CHECK: aie.core(%[[TILE]]) + aie.core(%logical_core) { + aie.end + } + // CHECK-NOT: aie.logical_tile + } +}