diff --git a/.github/workflows/buildAndTest.yml b/.github/workflows/buildAndTest.yml index 729d5dd69f..41653ce9b2 100644 --- a/.github/workflows/buildAndTest.yml +++ b/.github/workflows/buildAndTest.yml @@ -18,7 +18,6 @@ defaults: env: # Run apt package manager in the CI in non-interactive mode. # Otherwise, on Ubuntu 20.04 the installation of tzdata asking question - # freezes libboost installation. DEBIAN_FRONTEND: noninteractive jobs: @@ -120,9 +119,6 @@ jobs: fetch-depth: 2 submodules: "true" - - name: Install libboost - run: apt-get install -y libboost-all-dev - - name: Install Python and other packages # Install cmake here to get the latest version to compile # LLVM. The Ubuntu 20.04 cmake version is only 3.16.3 diff --git a/.github/workflows/generateDocs.yml b/.github/workflows/generateDocs.yml index ed1425ef1e..2979c73b6c 100644 --- a/.github/workflows/generateDocs.yml +++ b/.github/workflows/generateDocs.yml @@ -30,7 +30,7 @@ jobs: submodules: "true" - name: Install packages - run: sudo apt-get install -y libboost-all-dev graphviz lld + run: sudo apt-get install -y graphviz lld - name: Install Python packages run: sudo pip install psutil rich numpy pybind11 diff --git a/CMakeLists.txt b/CMakeLists.txt index 34c3244aa4..5644b9ed84 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -70,7 +70,6 @@ option(AIE_INCLUDE_INTEGRATION_TESTS "Generate build targets for the mlir-aie integration tests." OFF) find_package(MLIR REQUIRED CONFIG) -find_package(Boost REQUIRED) message(STATUS "Using MLIRConfig.cmake in: ${MLIR_DIR}") message(STATUS "Using LLVMConfig.cmake in: ${LLVM_DIR}") @@ -131,7 +130,6 @@ include_directories(${LLVM_INCLUDE_DIRS}) include_directories(${MLIR_INCLUDE_DIRS}) include_directories(${PROJECT_SOURCE_DIR}/include) include_directories(${PROJECT_BINARY_DIR}/include) -include_directories(${Boost_INCLUDE_DIRS}) add_definitions(${LLVM_DEFINITIONS}) # Silence a false positive GCC -Wunused-but-set-parameter warning in constexpr diff --git a/aie_runtime_lib/AIE/aiesim/Makefile b/aie_runtime_lib/AIE/aiesim/Makefile index 990fa6a8aa..722af0b6b6 100644 --- a/aie_runtime_lib/AIE/aiesim/Makefile +++ b/aie_runtime_lib/AIE/aiesim/Makefile @@ -32,7 +32,7 @@ all: sim CC_ENV := (export LD_LIBRARY_PATH=${XILINX_VITIS_AIETOOLS}/lib/lnx64.o:$(LD_LIBRARY_PATH)) CC := "${XILINX_VITIS_AIETOOLS}/tps/lnx64/gcc/bin/g++" -CC_ARGS := -fPIC -fpermissive -c -std=c++17 -D__AIEARCH__=10 -DAIE_OPTION_SCALAR_FLOAT_ON_VECTOR -Wno-deprecated-declarations -DSC_INCLUDE_DYNAMIC_PROCESSES -D__AIESIM__ -D__PS_INIT_AIE__ -DXAIE_DEBUG -Og -flto -D main\(...\)=ps_main\(...\) -I${XILINX_VITIS_AIETOOLS}/include -I${XILINX_VITIS_AIETOOLS}/include/drivers/aiengine -I${XILINX_HLS}/include -I${XILINX_VITIS_AIETOOLS}/tps/lnx64/gcc/include/c++/8.3.0 -I${XILINX_VITIS_AIETOOLS}/tps/lnx64/gcc/include/c++/8.3.0/backward -I${XILINX_VITIS_AIETOOLS}/tps/lnx64/gcc/include/c++/8.3.0/x86_64-pc-linux-gnu -I${XILINX_VITIS_AIETOOLS}/data/osci_systemc/include -I${XILINX_VITIS_AIETOOLS}/tps/boost_1_72_0 -I. -I$(MLIR_AIE_SRC_DIR) -I${XILINX_VITIS_AIETOOLS}/include/xtlm/include -I${XILINX_VITIS_AIETOOLS}/include/common_cpp/common_cpp_v1_0/include -I${MLIR_AIE_INSTALL}/runtime_lib/x86_64/test_lib/include -I../../ -I../ +CC_ARGS := -fPIC -fpermissive -c -std=c++17 -D__AIEARCH__=10 -DAIE_OPTION_SCALAR_FLOAT_ON_VECTOR -Wno-deprecated-declarations -DSC_INCLUDE_DYNAMIC_PROCESSES -D__AIESIM__ -D__PS_INIT_AIE__ -DXAIE_DEBUG -Og -flto -D main\(...\)=ps_main\(...\) -I${XILINX_VITIS_AIETOOLS}/include -I${XILINX_VITIS_AIETOOLS}/include/drivers/aiengine -I${XILINX_HLS}/include -I${XILINX_VITIS_AIETOOLS}/tps/lnx64/gcc/include/c++/8.3.0 -I${XILINX_VITIS_AIETOOLS}/tps/lnx64/gcc/include/c++/8.3.0/backward -I${XILINX_VITIS_AIETOOLS}/tps/lnx64/gcc/include/c++/8.3.0/x86_64-pc-linux-gnu -I${XILINX_VITIS_AIETOOLS}/data/osci_systemc/include -I. -I$(MLIR_AIE_SRC_DIR) -I${XILINX_VITIS_AIETOOLS}/include/xtlm/include -I${XILINX_VITIS_AIETOOLS}/include/common_cpp/common_cpp_v1_0/include -I${MLIR_AIE_INSTALL}/runtime_lib/x86_64/test_lib/include -I../../ -I../ ps/test.o: $(host) $(CC_ENV);$(CC) $(CC_ARGS) -o $@ $< diff --git a/aie_runtime_lib/AIE2/aiesim/Makefile b/aie_runtime_lib/AIE2/aiesim/Makefile index 53d4f3bdb2..1e7c476b7a 100644 --- a/aie_runtime_lib/AIE2/aiesim/Makefile +++ b/aie_runtime_lib/AIE2/aiesim/Makefile @@ -32,7 +32,7 @@ all: sim CC_ENV := (export LD_LIBRARY_PATH=${XILINX_VITIS_AIETOOLS}/lib/lnx64.o:$(LD_LIBRARY_PATH)) CC := "${XILINX_VITIS_AIETOOLS}/tps/lnx64/gcc/bin/g++" -CC_ARGS := -fPIC -fpermissive -c -std=c++17 -D__AIEARCH__=20 -DAIE_OPTION_SCALAR_FLOAT_ON_VECTOR -DAIE2_FP32_EMULATION_ACCURACY_FAST -Wno-deprecated-declarations -DSC_INCLUDE_DYNAMIC_PROCESSES -D__AIESIM__ -D__PS_INIT_AIE__ -DXAIE_DEBUG -Og -flto -D main\(...\)=ps_main\(...\) -I${XILINX_VITIS_AIETOOLS}/include -I${XILINX_VITIS_AIETOOLS}/include/drivers/aiengine -I${XILINX_HLS}/include -I${XILINX_VITIS_AIETOOLS}/tps/lnx64/gcc/include/c++/8.3.0 -I${XILINX_VITIS_AIETOOLS}/tps/lnx64/gcc/include/c++/8.3.0/backward -I${XILINX_VITIS_AIETOOLS}/tps/lnx64/gcc/include/c++/8.3.0/x86_64-pc-linux-gnu -I${XILINX_VITIS_AIETOOLS}/data/osci_systemc/include -I${XILINX_VITIS_AIETOOLS}/tps/boost_1_72_0 -I. -I$(MLIR_AIE_SRC_DIR) -I${XILINX_VITIS_AIETOOLS}/include/xtlm/include -I${XILINX_VITIS_AIETOOLS}/include/common_cpp/common_cpp_v1_0/include -I${MLIR_AIE_INSTALL}/runtime_lib/x86_64/test_lib/include -I../../ -I../ +CC_ARGS := -fPIC -fpermissive -c -std=c++17 -D__AIEARCH__=20 -DAIE_OPTION_SCALAR_FLOAT_ON_VECTOR -DAIE2_FP32_EMULATION_ACCURACY_FAST -Wno-deprecated-declarations -DSC_INCLUDE_DYNAMIC_PROCESSES -D__AIESIM__ -D__PS_INIT_AIE__ -DXAIE_DEBUG -Og -flto -D main\(...\)=ps_main\(...\) -I${XILINX_VITIS_AIETOOLS}/include -I${XILINX_VITIS_AIETOOLS}/include/drivers/aiengine -I${XILINX_HLS}/include -I${XILINX_VITIS_AIETOOLS}/tps/lnx64/gcc/include/c++/8.3.0 -I${XILINX_VITIS_AIETOOLS}/tps/lnx64/gcc/include/c++/8.3.0/backward -I${XILINX_VITIS_AIETOOLS}/tps/lnx64/gcc/include/c++/8.3.0/x86_64-pc-linux-gnu -I${XILINX_VITIS_AIETOOLS}/data/osci_systemc/include -I. -I$(MLIR_AIE_SRC_DIR) -I${XILINX_VITIS_AIETOOLS}/include/xtlm/include -I${XILINX_VITIS_AIETOOLS}/include/common_cpp/common_cpp_v1_0/include -I${MLIR_AIE_INSTALL}/runtime_lib/x86_64/test_lib/include -I../../ -I../ ps/test.o: $(host) $(CC_ENV);$(CC) $(CC_ARGS) -o $@ $< diff --git a/docs/Building.md b/docs/Building.md index 13e2a1a250..b5d707e403 100644 --- a/docs/Building.md +++ b/docs/Building.md @@ -15,7 +15,7 @@ clang/llvm 14+ from source https://github.com/llvm/llvm-project Xilinx Vitis can be downloaded and installed from the [Xilinx Downloads](https://www.xilinx.com/support/download/index.html/content/xilinx/en/downloadNav/vitis.html) site. -In order to successfully install Vitis on a fresh bare-bones Ubuntu install, some additional prerequisites are required, [documented here](https://support.xilinx.com/s/article/63794?language=en_US). For Ubuntu 20.04, the installation should succeed if you additionally install the following packages: `libncurses5 libtinfo5 libncurses5-dev libncursesw5-dev ncurses-compat-libs libstdc++6:i386 libgtk2.0-0:i386 dpkg-dev:i386 python3-pip libboost-all-dev` Further note that the above mentioned cmake prerequisite is _not_ satisfied by the package provided by Ubuntu; you will need to obtain a more current version. +In order to successfully install Vitis on a fresh bare-bones Ubuntu install, some additional prerequisites are required, [documented here](https://support.xilinx.com/s/article/63794?language=en_US). For Ubuntu 20.04, the installation should succeed if you additionally install the following packages: `libncurses5 libtinfo5 libncurses5-dev libncursesw5-dev ncurses-compat-libs libstdc++6:i386 libgtk2.0-0:i386 dpkg-dev:i386 python3-pip` Further note that the above mentioned cmake prerequisite is _not_ satisfied by the package provided by Ubuntu; you will need to obtain a more current version. NOTE: Using the Vitis recommended `settings64.sh` script to set up your environement can cause tool conflicts. Setup your environment in the following order for aietools and Vitis: diff --git a/include/aie/Dialect/AIE/IR/AIEDialect.h b/include/aie/Dialect/AIE/IR/AIEDialect.h index 07fc28d1b9..d7c85d9c98 100644 --- a/include/aie/Dialect/AIE/IR/AIEDialect.h +++ b/include/aie/Dialect/AIE/IR/AIEDialect.h @@ -11,7 +11,10 @@ #ifndef MLIR_AIE_DIALECT_H #define MLIR_AIE_DIALECT_H +#include "AIEEnums.h" + #include "aie/Dialect/AIE/IR/AIETargetModel.h" + #include "mlir/Dialect/Arith/IR/Arith.h" #include "mlir/Dialect/ControlFlow/IR/ControlFlow.h" #include "mlir/Dialect/Func/IR/FuncOps.h" @@ -30,13 +33,12 @@ #include "mlir/Pass/Pass.h" #include "llvm/ADT/StringSwitch.h" #include "llvm/Support/Debug.h" + #include #include using namespace mlir; -#include "AIEEnums.h" - namespace xilinx { namespace AIE { // template @@ -149,23 +151,10 @@ class AIEObjectFifoSubviewType namespace xilinx { namespace AIE { -// #include "AIEOpInterfaces.h.inc" - typedef std::pair Port; typedef std::pair Connect; typedef std::pair DMAChannel; -struct AIEArchDesc { - bool checkerboard; -}; - -// xcve2302 17x2, xcvc1902 50x8 -struct AIEDevDesc { - unsigned int rows; - unsigned int cols; - AIEArchDesc arch; -}; - const xilinx::AIE::AIETargetModel &getTargetModel(Operation *op); mlir::ParseResult diff --git a/include/aie/Dialect/AIE/Transforms/AIEPathFinder.h b/include/aie/Dialect/AIE/Transforms/AIEPathFinder.h new file mode 100644 index 0000000000..0f01e9bb5e --- /dev/null +++ b/include/aie/Dialect/AIE/Transforms/AIEPathFinder.h @@ -0,0 +1,180 @@ +//===- AIEPathfinder.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 2021 Xilinx Inc. +// +//===----------------------------------------------------------------------===// + +#ifndef AIE_PATHFINDER_H +#define AIE_PATHFINDER_H + +#include "aie/Dialect/AIE/IR/AIEDialect.h" // for WireBundle and Port + +#include "llvm/ADT/DirectedGraph.h" +#include "llvm/ADT/GraphTraits.h" + +#include +#include + +namespace xilinx::AIE { + +class Switchbox; +class Channel; +using SwitchboxBase = llvm::DGNode; +using ChannelBase = llvm::DGEdge; +using SwitchboxGraphBase = llvm::DirectedGraph; + +class Switchbox : public SwitchboxBase { +public: + Switchbox() = delete; + Switchbox(const int col, const int row) : col(col), row(row) {} + + int col, row; +}; + +class Channel : public ChannelBase { +public: + explicit Channel(Switchbox &target) = delete; + Channel(Switchbox &src, Switchbox &target, WireBundle bundle, + uint32_t maxCapacity) + : ChannelBase(target), src(src), bundle(bundle), + maxCapacity(maxCapacity) {} + + // Default deleted because of &src and &ChannelBase::TargetNode. + Channel(const Channel &E) + : ChannelBase(E), src(E.src), bundle(E.bundle), + maxCapacity(E.maxCapacity), demand(E.demand), + usedCapacity(E.usedCapacity), fixedCapacity(E.fixedCapacity), + overCapacityCount(E.overCapacityCount) {} + + // Default deleted because of &src and &ChannelBase::TargetNode. + Channel &operator=(Channel &&E) { + ChannelBase::operator=(std::move(E)); + src = std::move(E.src); + bundle = E.bundle; + maxCapacity = E.maxCapacity; + demand = E.demand; + usedCapacity = E.usedCapacity; + fixedCapacity = E.fixedCapacity; + overCapacityCount = E.overCapacityCount; + return *this; + } + + Switchbox &src; + WireBundle bundle; + uint32_t maxCapacity = 0; // maximum number of routing resources + double demand = 0.0; // indicates how many flows want to use this Channel + uint32_t usedCapacity = 0; // how many flows are actually using this Channel + std::set fixedCapacity; // channels not available to the algorithm + uint32_t overCapacityCount = 0; // history of Channel being over capacity +}; + +class SwitchboxGraph : public SwitchboxGraphBase { +public: + SwitchboxGraph() = default; + ~SwitchboxGraph() = default; +}; + +// A SwitchSetting defines the required settings for a Switchbox for a flow +// SwitchSetting.first is the incoming signal +// SwitchSetting.second is the fanout +typedef std::pair> SwitchSetting; +typedef std::map SwitchSettings; + +// A Flow defines source and destination vertices +// Only one source, but any number of destinations (fanout) +typedef std::pair PathEndPoint; +typedef std::pair> Flow; + +class Pathfinder { + SwitchboxGraph graph; + std::vector flows; + bool maxIterReached{}; + std::map grid; + // Use a list instead of a vector because nodes have an edge list of raw + // pointers to edges (so growing a vector would invalidate the pointers). + std::list edges; + +public: + Pathfinder() = default; + Pathfinder(int maxCol, int maxRow, DeviceOp &d); + void addFlow(TileID srcCoords, Port srcPort, TileID dstCoords, Port dstPort); + bool addFixedConnection(TileID coord, Port port); + bool isLegal(); + std::map findPaths(int maxIterations = 1000); + + Switchbox *getSwitchbox(TileID coords) { + auto sb = std::find_if(graph.begin(), graph.end(), [&](Switchbox *sb) { + return sb->col == coords.first && sb->row == coords.second; + }); + assert(sb != graph.end() && "couldn't find sb"); + return *sb; + } +}; + +} // namespace xilinx::AIE + +namespace llvm { +using namespace xilinx::AIE; + +template <> struct GraphTraits { + using NodeRef = Switchbox *; + + static Switchbox *SwitchboxGraphGetSwitchbox(DGEdge *P) { + return &P->getTargetNode(); + } + + // Provide a mapped iterator so that the GraphTrait-based implementations can + // find the target nodes without having to explicitly go through the edges. + using ChildIteratorType = + mapped_iterator; + using ChildEdgeIteratorType = Switchbox::iterator; + + static NodeRef getEntryNode(NodeRef N) { return N; } + static ChildIteratorType child_begin(NodeRef N) { + return ChildIteratorType(N->begin(), &SwitchboxGraphGetSwitchbox); + } + static ChildIteratorType child_end(NodeRef N) { + return ChildIteratorType(N->end(), &SwitchboxGraphGetSwitchbox); + } + + static ChildEdgeIteratorType child_edge_begin(NodeRef N) { + return N->begin(); + } + static ChildEdgeIteratorType child_edge_end(NodeRef N) { return N->end(); } +}; + +template <> +struct GraphTraits : public GraphTraits { + using nodes_iterator = SwitchboxGraph::iterator; + static NodeRef getEntryNode(SwitchboxGraph *DG) { return *DG->begin(); } + static nodes_iterator nodes_begin(SwitchboxGraph *DG) { return DG->begin(); } + static nodes_iterator nodes_end(SwitchboxGraph *DG) { return DG->end(); } +}; + +inline raw_ostream &operator<<(raw_ostream &OS, const Switchbox &S) { + OS << "Switchbox(" << S.col << ", " << S.row << ")"; + return OS; +} + +inline raw_ostream &operator<<(raw_ostream &OS, const Channel &C) { + OS << "Channel(src=" << C.src << ", dst=" << C.getTargetNode() << ")"; + return OS; +} + +} // namespace llvm + +namespace std { +using namespace xilinx::AIE; +template <> struct less { + bool operator()(const Switchbox *a, const Switchbox *b) const { + return a->col == b->col ? a->row < b->row : a->col < b->col; + } +}; +} // namespace std + +#endif diff --git a/include/aie/Dialect/AIE/Transforms/AIEPathfinder.h b/include/aie/Dialect/AIE/Transforms/AIEPathfinder.h deleted file mode 100644 index 23e84c2489..0000000000 --- a/include/aie/Dialect/AIE/Transforms/AIEPathfinder.h +++ /dev/null @@ -1,109 +0,0 @@ -//===- AIEPathfinder.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 2021 Xilinx Inc. -// -//===----------------------------------------------------------------------===// - -#ifndef AIE_PATHFINDER_H -#define AIE_PATHFINDER_H - -#include "aie/Dialect/AIE/IR/AIEDialect.h" // for WireBundle and Port - -// builds against at least boost graph 1.7.1 -#pragma GCC diagnostic push -#pragma GCC diagnostic ignored "-Wunused-local-typedef" -#pragma GCC diagnostic ignored "-Wdeprecated-declarations" -#pragma GCC diagnostic ignored "-Wdeprecated-copy" -#pragma GCC diagnostic ignored "-Wsuggest-override" - -#include -#include -#include - -#pragma GCC diagnostic pop - -#include -#include -#include //for std::pair -#include - -namespace xilinx { -namespace AIE { - -using namespace boost; - -struct Switchbox { // acts as a vertex - unsigned short col, row; - // int dist; - unsigned int pred; // predecessor for dijkstra's - bool processed; // denotes this switchbox has already been processed -}; - -struct Channel { // acts as an edge - float demand; // indicates how many flows want to use this Channel - unsigned short - used_capacity; // how many flows are actually using this Channel - unsigned short max_capacity; // maximum number of routing resources - std::set fixed_capacity; // channels not available to the algorithm - unsigned short over_capacity_count; // history of Channel being over capacity - WireBundle bundle; -}; - -// create a graph type that uses Switchboxes as vertices and Channels as edges -typedef adjacency_list - SwitchboxGraph; - -typedef graph_traits::vertex_descriptor vertex_descriptor; -typedef graph_traits::edge_descriptor edge_descriptor; -typedef graph_traits::vertex_iterator vertex_iterator; -typedef graph_traits::edge_iterator edge_iterator; -typedef graph_traits::in_edge_iterator in_edge_iterator; - -typedef std::pair Coord; -// A SwitchSetting defines the required settings for a Switchbox for a flow -// SwitchSetting.first is the incoming signal -// SwitchSetting.second is the fanout -typedef std::pair> SwitchSetting; -typedef std::map SwitchSettings; - -// A Flow defines source and destination vertices -// Only one source, but any number of destinations (fanout) -typedef std::pair PathEndPoint; -typedef std::pair> Flow; - -class Pathfinder { -private: - SwitchboxGraph graph; - std::vector flows; - bool maxIterReached; - -public: - Pathfinder(); - Pathfinder(int maxcol, int maxrow, DeviceOp &d); - void initializeGraph(int maxcol, int maxrow, DeviceOp &d); - void addFlow(Coord srcCoords, Port srcPort, Coord dstCoords, Port dstPort); - void addFixedConnection(Coord coord, Port port); - bool isLegal(); - std::map - findPaths(const int MAX_ITERATIONS = 1000); - - Switchbox *getSwitchbox(TileID coords) { - auto vpair = vertices(graph); - Switchbox *sb; - for (vertex_iterator v = vpair.first; v != vpair.second; v++) { - sb = &graph[*v]; - if (sb->col == coords.first && sb->row == coords.second) - return sb; - } - return nullptr; - } -}; - -} // namespace AIE -} // namespace xilinx - -#endif diff --git a/lib/Dialect/AIE/Transforms/AIECreatePathfindFlows.cpp b/lib/Dialect/AIE/Transforms/AIECreatePathFindFlows.cpp similarity index 81% rename from lib/Dialect/AIE/Transforms/AIECreatePathfindFlows.cpp rename to lib/Dialect/AIE/Transforms/AIECreatePathFindFlows.cpp index c95faa447f..41d0e3dfeb 100644 --- a/lib/Dialect/AIE/Transforms/AIECreatePathfindFlows.cpp +++ b/lib/Dialect/AIE/Transforms/AIECreatePathFindFlows.cpp @@ -9,37 +9,21 @@ //===----------------------------------------------------------------------===// #include "aie/Dialect/AIE/IR/AIEDialect.h" +#include "aie/Dialect/AIE/Transforms/AIEPathFinder.h" + #include "mlir/IR/Attributes.h" #include "mlir/IR/IRMapping.h" -#include "mlir/IR/Location.h" #include "mlir/IR/PatternMatch.h" #include "mlir/Pass/Pass.h" #include "mlir/Tools/mlir-translate/MlirTranslateMain.h" #include "mlir/Transforms/DialectConversion.h" #include "llvm/Support/Debug.h" -#include "llvm/Support/raw_os_ostream.h" - -#include using namespace mlir; using namespace xilinx; using namespace xilinx::AIE; #define DEBUG_TYPE "aie-create-pathfinder-flows" -static llvm::cl::opt - debugRoute("debug-pathfinder", - llvm::cl::desc("Enable Debugging of Pathfinder routing process"), - llvm::cl::init(false)); - -#define BOOST_NO_EXCEPTIONS -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Winvalid-noreturn" -#include -void boost::throw_exception(std::exception const &e) { - // boost expects this exception to be defined. - assert(false); -} -#pragma clang diagnostic pop std::string stringifyDirs(std::set dirs) { unsigned int count = 0; @@ -80,11 +64,10 @@ std::string stringifyDir(Port dir) { } std::string stringifySwitchSettings(SwitchSettings settings) { std::string out = "\tSwitchSettings: "; - for (auto iter = settings.begin(); iter != settings.end(); iter++) { - out += (std::string) "(" + std::to_string((*iter).first->col) + ", " + - std::to_string((*iter).first->row) + ") " + - stringifyDir((*iter).second.first) + " -> " + - stringifyDirs((*iter).second.second) + " | "; + for (const auto &[sb, setting] : settings) { + out += (std::string) "(" + std::to_string(sb->col) + ", " + + std::to_string(sb->row) + ") " + stringifyDir(setting.first) + + " -> " + stringifyDirs(setting.second) + " | "; } return out + "\n"; } @@ -96,38 +79,38 @@ std::string stringifySwitchSettings(SwitchSettings settings) { class DynamicTileAnalysis { public: DeviceOp &device; - int maxcol, maxrow; + int maxCol, maxRow; Pathfinder pathfinder; - std::map flow_solutions; - std::map processed_flows; + std::map flowSolutions; + std::map processedFlows; - DenseMap coordToTile; - DenseMap coordToSwitchbox; - DenseMap coordToShimMux; + DenseMap coordToTile; + DenseMap coordToSwitchbox; + DenseMap coordToShimMux; DenseMap coordToPLIO; - const int MAX_ITERATIONS = 1000; // how long until declared unroutable + const int maxIterations = 1000; // how long until declared unroutable DynamicTileAnalysis(DeviceOp &d) : device(d) { LLVM_DEBUG(llvm::dbgs() << "\t---Begin DynamicTileAnalysis Constructor---\n"); - // find the maxcol and maxrow - maxcol = 0; - maxrow = 0; + // find the maxCol and maxRow + maxCol = 0; + maxRow = 0; for (TileOp tileOp : d.getOps()) { - maxcol = std::max(maxcol, tileOp.colIndex()); - maxrow = std::max(maxrow, tileOp.rowIndex()); + maxCol = std::max(maxCol, tileOp.colIndex()); + maxRow = std::max(maxRow, tileOp.rowIndex()); } - pathfinder = Pathfinder(maxcol, maxrow, d); + pathfinder = Pathfinder(maxCol, maxRow, d); // for each flow in the device, add it to pathfinder // each source can map to multiple different destinations (fanout) for (FlowOp flowOp : device.getOps()) { TileOp srcTile = cast(flowOp.getSource().getDefiningOp()); TileOp dstTile = cast(flowOp.getDest().getDefiningOp()); - Coord srcCoords = std::make_pair(srcTile.colIndex(), srcTile.rowIndex()); - Coord dstCoords = std::make_pair(dstTile.colIndex(), dstTile.rowIndex()); + TileID srcCoords = std::make_pair(srcTile.colIndex(), srcTile.rowIndex()); + TileID dstCoords = std::make_pair(dstTile.colIndex(), dstTile.rowIndex()); Port srcPort = std::make_pair(flowOp.getSourceBundle(), flowOp.getSourceChannel()); Port dstPort = @@ -146,29 +129,32 @@ class DynamicTileAnalysis { // available search all existing SwitchBoxOps for exising connections for (SwitchboxOp switchboxOp : device.getOps()) { for (ConnectOp connectOp : switchboxOp.getOps()) { - Coord existing_coord = + TileID existingCoord = std::make_pair(switchboxOp.colIndex(), switchboxOp.rowIndex()); - Port existing_port = std::make_pair(connectOp.getDestBundle(), - connectOp.getDestChannel()); - pathfinder.addFixedConnection(existing_coord, existing_port); + Port existingPort = std::make_pair(connectOp.getDestBundle(), + connectOp.getDestChannel()); + if (!pathfinder.addFixedConnection(existingCoord, existingPort)) + switchboxOp.emitOpError( + "Couldn't connect tile (" + std::to_string(existingCoord.first) + + ", " + std::to_string(existingCoord.second) + ") to port (" + + stringifyWireBundle(existingPort.first) + ", " + + std::to_string(existingPort.second) + ")\n"); } } // all flows are now populated, call the congestion-aware pathfinder // algorithm // check whether the pathfinder algorithm creates a legal routing - flow_solutions = pathfinder.findPaths(MAX_ITERATIONS); + flowSolutions = pathfinder.findPaths(maxIterations); if (!pathfinder.isLegal()) d.emitError("Unable to find a legal routing"); // initialize all flows as unprocessed to prep for rewrite - for (auto iter = flow_solutions.begin(); iter != flow_solutions.end(); - iter++) { - processed_flows[(*iter).first] = false; - LLVM_DEBUG(llvm::dbgs() - << "Flow starting at (" << (*iter).first.first->col << "," - << (*iter).first.first->row << "):\t"); - LLVM_DEBUG(llvm::dbgs() << stringifySwitchSettings((*iter).second)); + for (const auto &[pathEndPoint, switchSetting] : flowSolutions) { + processedFlows[pathEndPoint] = false; + LLVM_DEBUG(llvm::dbgs() << "Flow starting at (" << pathEndPoint.first->col + << "," << pathEndPoint.first->row << "):\t"); + LLVM_DEBUG(llvm::dbgs() << stringifySwitchSettings(switchSetting)); } // fill in coords to TileOps, SwitchboxOps, and ShimMuxOps @@ -176,8 +162,8 @@ class DynamicTileAnalysis { int col, row; col = tileOp.colIndex(); row = tileOp.rowIndex(); - maxcol = std::max(maxcol, col); - maxrow = std::max(maxrow, row); + maxCol = std::max(maxCol, col); + maxRow = std::max(maxRow, row); assert(coordToTile.count(std::make_pair(col, row)) == 0); coordToTile[std::make_pair(col, row)] = tileOp; } @@ -199,8 +185,8 @@ class DynamicTileAnalysis { LLVM_DEBUG(llvm::dbgs() << "\t---End DynamicTileAnalysis Constructor---\n"); } - int getMaxCol() { return maxcol; } - int getMaxRow() { return maxrow; } + int getMaxCol() { return maxCol; } + int getMaxRow() { return maxRow; } TileOp getTile(OpBuilder &builder, int col, int row) { if (coordToTile.count(std::make_pair(col, row))) { @@ -208,8 +194,8 @@ class DynamicTileAnalysis { } else { TileOp tileOp = builder.create(builder.getUnknownLoc(), col, row); coordToTile[std::make_pair(col, row)] = tileOp; - maxcol = std::max(maxcol, col); - maxrow = std::max(maxrow, row); + maxCol = std::max(maxCol, col); + maxRow = std::max(maxRow, row); return tileOp; } } @@ -222,12 +208,11 @@ class DynamicTileAnalysis { } else { SwitchboxOp switchboxOp = builder.create( builder.getUnknownLoc(), getTile(builder, col, row)); - // coordToTile[std::make_pair(col, row)]); switchboxOp.ensureTerminator(switchboxOp.getConnections(), builder, builder.getUnknownLoc()); coordToSwitchbox[std::make_pair(col, row)] = switchboxOp; - maxcol = std::max(maxcol, col); - maxrow = std::max(maxrow, row); + maxCol = std::max(maxCol, col); + maxRow = std::max(maxRow, row); return switchboxOp; } } @@ -244,8 +229,8 @@ class DynamicTileAnalysis { switchboxOp.ensureTerminator(switchboxOp.getConnections(), builder, builder.getUnknownLoc()); coordToShimMux[std::make_pair(col, row)] = switchboxOp; - maxcol = std::max(maxcol, col); - maxrow = std::max(maxrow, row); + maxCol = std::max(maxCol, col); + maxRow = std::max(maxRow, row); return switchboxOp; } } @@ -295,7 +280,6 @@ struct ConvertFlowsToInterconnect : public OpConversionPattern { auto srcBundle = flowOp.getSourceBundle(); auto srcChannel = flowOp.getSourceChannel(); Port srcPort = std::make_pair(srcBundle, srcChannel); - // Port dstPort = std::make_pair(dstBundle, dstChannel); #ifndef NDEBUG TileOp dstTile = cast(flowOp.getDest().getDefiningOp()); @@ -314,36 +298,34 @@ struct ConvertFlowsToInterconnect : public OpConversionPattern { // add all switchbox connections to implement the flow Switchbox *srcSB = analyzer.pathfinder.getSwitchbox(srcCoords); PathEndPoint srcPoint = std::make_pair(srcSB, srcPort); - if (analyzer.processed_flows[srcPoint] == false) { - SwitchSettings settings = analyzer.flow_solutions[srcPoint]; - // add connections for all of the Switchboxes in SwitchSettings - for (auto map_iter = settings.begin(); map_iter != settings.end(); - map_iter++) { - Switchbox *curr = (*map_iter).first; - SwitchSetting s = (*map_iter).second; + if (!analyzer.processedFlows[srcPoint]) { + SwitchSettings settings = analyzer.flowSolutions[srcPoint]; + // add connections for all the Switchboxes in SwitchSettings + for (const auto &[curr, setting] : settings) { SwitchboxOp swOp = analyzer.getSwitchbox(rewriter, curr->col, curr->row); - int shim_ch = srcChannel; + int shimCh = srcChannel; // TODO: must reserve N3, N7, S2, S3 for DMA connections - if (curr == srcSB && analyzer.getTile(rewriter, srcSB->col, srcSB->row) - .isShimNOCTile()) { + if (*curr == *srcSB && + analyzer.getTile(rewriter, srcSB->col, srcSB->row) + .isShimNOCTile()) { // shim DMAs at start of flows if (srcBundle == WireBundle::DMA) { - shim_ch = (srcChannel == 0 - ? 3 - : 7); // must be either DMA0 -> N3 or DMA1 -> N7 + shimCh = (srcChannel == 0 + ? 3 + : 7); // must be either DMA0 -> N3 or DMA1 -> N7 ShimMuxOp shimMuxOp = analyzer.getShimMux(rewriter, srcSB->col); addConnection(rewriter, cast(shimMuxOp.getOperation()), flowOp, - srcBundle, srcChannel, WireBundle::North, shim_ch); + srcBundle, srcChannel, WireBundle::North, shimCh); } else if (srcBundle == WireBundle::NOC) { // must be NOC0/NOC1 -> N2/N3 or // NOC2/NOC3 -> N6/N7 - shim_ch = (srcChannel >= 2 ? srcChannel + 4 : srcChannel + 2); + shimCh = (srcChannel >= 2 ? srcChannel + 4 : srcChannel + 2); ShimMuxOp shimMuxOp = analyzer.getShimMux(rewriter, srcSB->col); addConnection(rewriter, cast(shimMuxOp.getOperation()), flowOp, - srcBundle, srcChannel, WireBundle::North, shim_ch); + srcBundle, srcChannel, WireBundle::North, shimCh); } else if (srcBundle == WireBundle::PLIO) { // PLIO at start of flows with mux if ((srcChannel == 2) || (srcChannel == 3) || (srcChannel == 6) || @@ -351,69 +333,66 @@ struct ConvertFlowsToInterconnect : public OpConversionPattern { ShimMuxOp shimMuxOp = analyzer.getShimMux(rewriter, srcSB->col); addConnection( rewriter, cast(shimMuxOp.getOperation()), - flowOp, srcBundle, srcChannel, WireBundle::North, shim_ch); + flowOp, srcBundle, srcChannel, WireBundle::North, shimCh); } } } - for (auto it = s.second.begin(); it != s.second.end(); it++) { - WireBundle bundle = (*it).first; - int channel = (*it).second; + for (const auto &[bundle, channel] : setting.second) { // handle special shim connectivity - if (curr == srcSB && + if (*curr == *srcSB && analyzer.getTile(rewriter, srcSB->col, srcSB->row) .isShimNOCorPLTile()) { addConnection(rewriter, cast(swOp.getOperation()), - flowOp, WireBundle::South, shim_ch, bundle, channel); + flowOp, WireBundle::South, shimCh, bundle, channel); } else if (analyzer.getTile(rewriter, curr->col, curr->row) .isShimNOCorPLTile() && (bundle == WireBundle::DMA || bundle == WireBundle::PLIO || bundle == WireBundle::NOC)) { - shim_ch = channel; + shimCh = channel; if (analyzer.getTile(rewriter, curr->col, curr->row) .isShimNOCTile()) { // shim DMAs at end of flows if (bundle == WireBundle::DMA) { - shim_ch = (channel == 0 - ? 2 - : 3); // must be either N2 -> DMA0 or N3 -> DMA1 + shimCh = (channel == 0 + ? 2 + : 3); // must be either N2 -> DMA0 or N3 -> DMA1 ShimMuxOp shimMuxOp = analyzer.getShimMux(rewriter, curr->col); addConnection( rewriter, cast(shimMuxOp.getOperation()), - flowOp, WireBundle::North, shim_ch, bundle, channel); + flowOp, WireBundle::North, shimCh, bundle, channel); } else if (bundle == WireBundle::NOC) { - shim_ch = - (channel + 2); // must be either N2/3/4/5 -> NOC0/1/2/3 + shimCh = (channel + 2); // must be either N2/3/4/5 -> NOC0/1/2/3 ShimMuxOp shimMuxOp = analyzer.getShimMux(rewriter, curr->col); addConnection( rewriter, cast(shimMuxOp.getOperation()), - flowOp, WireBundle::North, shim_ch, bundle, channel); + flowOp, WireBundle::North, shimCh, bundle, channel); } else if (channel >= 2) { // must be PLIO...only PLIO >= 2 require mux ShimMuxOp shimMuxOp = analyzer.getShimMux(rewriter, curr->col); addConnection( rewriter, cast(shimMuxOp.getOperation()), - flowOp, WireBundle::North, shim_ch, bundle, channel); + flowOp, WireBundle::North, shimCh, bundle, channel); } } addConnection(rewriter, cast(swOp.getOperation()), - flowOp, s.first.first, s.first.second, - WireBundle::South, shim_ch); + flowOp, setting.first.first, setting.first.second, + WireBundle::South, shimCh); } else { // otherwise, regular switchbox connection addConnection(rewriter, cast(swOp.getOperation()), - flowOp, s.first.first, s.first.second, bundle, - channel); + flowOp, setting.first.first, setting.first.second, + bundle, channel); } } LLVM_DEBUG(llvm::dbgs() << " (" << curr->col << "," << curr->row << ") " - << stringifyDir(s.first) << " -> " - << stringifyDirs(s.second) << " | "); + << stringifyDir(setting.first) << " -> " + << stringifyDirs(setting.second) << " | "); } LLVM_DEBUG(llvm::dbgs() << "\n\t\tFinished adding ConnectOps to implement flowOp.\n"); - analyzer.processed_flows[srcPoint] = true; + analyzer.processedFlows[srcPoint] = true; } else LLVM_DEBUG(llvm::dbgs() << "Flow already processed!\n"); @@ -533,15 +512,15 @@ struct AIEPathfinderPass } // If the routing violates architecture-specific routing constraints, then - // attempt to partial reroute. - const auto &target_model = d.getTargetModel(); + // attempt to partially reroute. + const auto &targetModel = d.getTargetModel(); std::vector problemConnects; d.walk([&](ConnectOp connect) { if (auto sw = connect->getParentOfType()) { auto tile = sw.getTileOp(); // Constraint: memtile stream switch constraints if (tile.isMemTile() && - !target_model.isLegalMemtileConnection( + !targetModel.isLegalMemtileConnection( connect.getSourceBundle(), connect.getSourceChannel(), connect.getDestBundle(), connect.getDestChannel())) { problemConnects.push_back(connect); @@ -549,17 +528,17 @@ struct AIEPathfinderPass } }); for (auto connect : problemConnects) { - auto sw_box = connect->getParentOfType(); + auto swBox = connect->getParentOfType(); builder.setInsertionPoint(connect); - auto northSw = getSwitchbox(d, sw_box.colIndex(), sw_box.rowIndex() + 1); - auto southSw = getSwitchbox(d, sw_box.colIndex(), sw_box.rowIndex() - 1); - attemptFixupMemtileRouting(builder, sw_box, northSw, southSw, connect); + auto northSw = getSwitchbox(d, swBox.colIndex(), swBox.rowIndex() + 1); + auto southSw = getSwitchbox(d, swBox.colIndex(), swBox.rowIndex() - 1); + attemptFixupMemTileRouting(builder, swBox, northSw, southSw, connect); } return; } - bool attemptFixupMemtileRouting(OpBuilder builder, SwitchboxOp memtileSwOp, + bool attemptFixupMemTileRouting(OpBuilder builder, SwitchboxOp memtileSwOp, SwitchboxOp northSwOp, SwitchboxOp southSwOp, ConnectOp &problemConnect) { unsigned problemNorthChannel; @@ -674,9 +653,9 @@ struct AIEPathfinderPass SwitchboxOp getSwitchbox(DeviceOp &d, int col, int row) { SwitchboxOp output = nullptr; - d.walk([&](SwitchboxOp sw_box) { - if (sw_box.colIndex() == col && sw_box.rowIndex() == row) { - output = sw_box; + d.walk([&](SwitchboxOp swBox) { + if (swBox.colIndex() == col && swBox.rowIndex() == row) { + output = swBox; } }); return output; diff --git a/lib/Dialect/AIE/Transforms/AIEPathFinder.cpp b/lib/Dialect/AIE/Transforms/AIEPathFinder.cpp new file mode 100644 index 0000000000..b6ef3d8212 --- /dev/null +++ b/lib/Dialect/AIE/Transforms/AIEPathFinder.cpp @@ -0,0 +1,316 @@ +//===- AIEPathfinder.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 2021 Xilinx Inc. +// +//===----------------------------------------------------------------------===// + +#include "aie/Dialect/AIE/Transforms/AIEPathFinder.h" + +#include "llvm/Support/Debug.h" +#include "llvm/Support/raw_os_ostream.h" + +using namespace xilinx; +using namespace xilinx::AIE; + +#define DEBUG_TYPE "aie-pathfinder" +#define OVER_CAPACITY_COEFF 0.02 +#define USED_CAPACITY_COEFF 0.02 +#define DEMAND_COEFF 1.1 + +WireBundle getConnectingBundle(WireBundle dir) { + switch (dir) { + case WireBundle::North: + return WireBundle::South; + case WireBundle::South: + return WireBundle::North; + case WireBundle::East: + return WireBundle::West; + case WireBundle::West: + return WireBundle::East; + default: + return dir; + } +} + +Pathfinder::Pathfinder(int maxCol, int maxRow, DeviceOp &d) { + const auto &targetModel = d.getTargetModel(); + // make grid of switchboxes + for (int col = 0; col <= maxCol; col++) { + for (int row = 0; row <= maxRow; row++) { + auto nodeIt = grid.insert({{col, row}, Switchbox{col, row}}); + (void)graph.addNode(nodeIt.first->second); + Switchbox &thisNode = grid.at({col, row}); + if (row > 0) { // if not in row 0 add channel to North/South + Switchbox &southernNeighbor = grid.at({col, row - 1}); + if (uint32_t maxCapacity = targetModel.getNumSourceSwitchboxConnections( + col, row, WireBundle::South)) { + edges.emplace_back(southernNeighbor, thisNode, WireBundle::North, + maxCapacity); + (void)graph.connect(southernNeighbor, thisNode, edges.back()); + } + if (uint32_t maxCapacity = targetModel.getNumDestSwitchboxConnections( + col, row, WireBundle::South)) { + edges.emplace_back(thisNode, southernNeighbor, WireBundle::South, + maxCapacity); + (void)graph.connect(thisNode, southernNeighbor, edges.back()); + } + } + + if (col > 0) { // if not in col 0 add channel to East/West + Switchbox &westernNeighbor = grid.at({col - 1, row}); + if (uint32_t maxCapacity = targetModel.getNumSourceSwitchboxConnections( + col, row, WireBundle::West)) { + edges.emplace_back(westernNeighbor, thisNode, WireBundle::East, + maxCapacity); + (void)graph.connect(westernNeighbor, thisNode, edges.back()); + } + if (uint32_t maxCapacity = targetModel.getNumDestSwitchboxConnections( + col, row, WireBundle::West)) { + edges.emplace_back(thisNode, westernNeighbor, WireBundle::West, + maxCapacity); + (void)graph.connect(thisNode, westernNeighbor, edges.back()); + } + } + } + } + + // initialize weights of all Channels to 1 + // initialize other variables + for (auto &edge : edges) { + edge.demand = 1.0; + edge.usedCapacity = 0; + edge.fixedCapacity.clear(); + edge.overCapacityCount = 0; + } + + // initialize maximum iterations flag + Pathfinder::maxIterReached = false; +} + +// Add a flow from src to dst can have an arbitrary number of dst locations due +// to fanout. +void Pathfinder::addFlow(TileID srcCoords, Port srcPort, TileID dstCoords, + Port dstPort) { + // check if a flow with this source already exists + for (auto &flow : flows) { + Switchbox *existingSrc = flow.first.first; + assert(existingSrc && "nullptr flow source"); + Port existingPort = flow.first.second; + if (existingSrc->col == srcCoords.first && + existingSrc->row == srcCoords.second && existingPort == srcPort) { + // find the vertex corresponding to the destination + auto matchingSb = + std::find_if(graph.begin(), graph.end(), [&](const Switchbox *sb) { + return sb->col == dstCoords.first && sb->row == dstCoords.second; + }); + assert(matchingSb != graph.end() && "didn't find flow dest"); + flow.second.emplace_back(*matchingSb, dstPort); + return; + } + } + + // If no existing flow was found with this source, create a new flow. + auto matchingSrcSb = + std::find_if(graph.begin(), graph.end(), [&](const Switchbox *sb) { + return sb->col == srcCoords.first && sb->row == srcCoords.second; + }); + assert(matchingSrcSb != graph.end() && "didn't find flow source"); + auto matchingDstSb = + std::find_if(graph.begin(), graph.end(), [&](const Switchbox *sb) { + return sb->col == dstCoords.first && sb->row == dstCoords.second; + }); + assert(matchingDstSb != graph.end() && "didn't add flow destinations"); + flows.emplace_back(PathEndPoint{*matchingSrcSb, srcPort}, + std::vector{{*matchingDstSb, dstPort}}); +} + +// Keep track of connections already used in the AIE; Pathfinder algorithm will +// avoid using these. +bool Pathfinder::addFixedConnection(TileID coords, Port port) { + // find the correct Channel and indicate the fixed direction + auto matchingCh = std::find_if(edges.begin(), edges.end(), [&](Channel &ch) { + return ch.src.col == coords.first && ch.src.row == coords.second && + ch.bundle == port.first; + }); + if (matchingCh == edges.end()) + return false; + + matchingCh->fixedCapacity.insert((uint32_t)port.second); + return true; +} + +static constexpr double INF = std::numeric_limits::max(); + +std::map +dijkstraShortestPaths(const SwitchboxGraph &graph, Switchbox *src) { + // Use std::map instead of DenseMap because DenseMap doesn't let you overwrite + // tombstones. + auto demand = std::map(); + auto preds = std::map(); + for (Switchbox *sb : graph) + demand.emplace(sb, INF); + demand[src] = 0.0; + auto cmp = [](const std::pair &p1, + const std::pair &p2) { + return std::fabs(p1.first - p2.first) < + std::numeric_limits::epsilon() + ? std::less()(p1.second, p2.second) + : p1.first < p2.first; + }; + std::set, decltype(cmp)> priorityQueue(cmp); + priorityQueue.insert({demand[src], src}); + + while (!priorityQueue.empty()) { + src = priorityQueue.begin()->second; + priorityQueue.erase(priorityQueue.begin()); + for (Channel *e : src->getEdges()) { + Switchbox *dst = &e->getTargetNode(); + if (demand[src] + e->demand < demand[dst]) { + priorityQueue.erase({demand[dst], dst}); + + demand[dst] = demand[src] + e->demand; + preds[dst] = src; + + priorityQueue.insert({demand[dst], dst}); + } + } + } + return preds; +} + +// Perform congestion-aware routing for all flows which have been added. +// Use Dijkstra's shortest path to find routes, and use "demand" as the weights. +// If the routing finds too much congestion, update the demand weights +// and repeat the process until a valid solution is found. +// Returns a map specifying switchbox settings for all flows. +// If no legal routing can be found after maxIterations, returns empty vector. +std::map +Pathfinder::findPaths(const int maxIterations) { + LLVM_DEBUG(llvm::dbgs() << "Begin Pathfinder::findPaths\n"); + int iterationCount = 0; + std::map routingSolution; + + // initialize all Channel histories to 0 + for (auto &ch : edges) + ch.overCapacityCount = 0; + + do { + LLVM_DEBUG(llvm::dbgs() + << "Begin findPaths iteration #" << iterationCount << "\n"); + // update demand on all channels + for (auto &ch : edges) { + if (ch.fixedCapacity.size() >= ch.maxCapacity) { + ch.demand = INF; + } else { + double history = 1.0 + OVER_CAPACITY_COEFF * ch.overCapacityCount; + double congestion = 1.0 + USED_CAPACITY_COEFF * ch.usedCapacity; + ch.demand = history * congestion; + } + } + // if reach maxIterations, throw an error since no routing can be found + // TODO: add error throwing mechanism + if (++iterationCount > maxIterations) { + LLVM_DEBUG(llvm::dbgs() + << "Pathfinder: maxIterations has been exceeded (" + << maxIterations + << " iterations)...unable to find routing for flows.\n"); + // return the invalid solution for debugging purposes + maxIterReached = true; + return routingSolution; + } + + // "rip up" all routes, i.e. set used capacity in each Channel to 0 + routingSolution = {}; + for (auto &ch : edges) + ch.usedCapacity = 0; + + // for each flow, find the shortest path from source to destination + // update used_capacity for the path between them + for (const auto &flow : flows) { + // Use dijkstra to find path given current demand from the start + // switchbox; find the shortest paths to each other switchbox. Output is + // in the predecessor map, which must then be processed to get individual + // switchbox settings + Switchbox *src = flow.first.first; + assert(src && "nonexistent flow source"); + std::set processed; + std::map preds = + dijkstraShortestPaths(graph, src); + + // trace the path of the flow backwards via predecessors + // increment used_capacity for the associated channels + SwitchSettings switchSettings; + // set the input bundle for the source endpoint + switchSettings[src].first = flow.first.second; + processed.insert(src); + for (const PathEndPoint &endPoint : flow.second) { + Switchbox *curr = endPoint.first; + assert(curr && "endpoint has no source switchbox"); + // set the output bundle for this destination endpoint + switchSettings[curr].second.insert(endPoint.second); + + // trace backwards until a vertex already processed is reached + while (!processed.count(curr)) { + // find the edge from the pred to curr by searching incident edges + SmallVector channels; + graph.findIncomingEdgesToNode(*curr, channels); + auto matchingCh = + std::find_if(channels.begin(), channels.end(), [&](Channel *ch) { + return ch->src == *preds[curr]; + }); + assert(matchingCh != channels.end() && "couldn't find ch"); + Channel *ch = *matchingCh; + + // don't use fixed channels + while (ch->fixedCapacity.count(ch->usedCapacity)) + ch->usedCapacity++; + + // add the entrance port for this Switchbox + switchSettings[curr].first = + std::make_pair(getConnectingBundle(ch->bundle), ch->usedCapacity); + // add the current Switchbox to the map of the predecessor + switchSettings[preds[curr]].second.insert( + std::make_pair(ch->bundle, ch->usedCapacity)); + + ch->usedCapacity++; + // if at capacity, bump demand to discourage using this Channel + if (ch->usedCapacity >= ch->maxCapacity) { + // this means the order matters! + ch->demand *= DEMAND_COEFF; + } + + processed.insert(curr); + curr = preds[curr]; + } + } + // add this flow to the proposed solution + routingSolution[flow.first] = switchSettings; + } + } while (!isLegal()); // continue iterations until a legal routing is found + return routingSolution; +} + +// Check that every channel does not exceed max capacity. +bool Pathfinder::isLegal() { + bool legal = true; // assume legal until found otherwise + // check if maximum number of iterations has been reached + if (maxIterReached) + legal = false; + for (auto &e : edges) + if (e.usedCapacity > e.maxCapacity) { + LLVM_DEBUG(llvm::dbgs() + << "Too much capacity on Edge (" << e.getTargetNode().col + << ", " << e.getTargetNode().row << ") . " + << stringifyWireBundle(e.bundle) << "\t: used_capacity = " + << e.usedCapacity << "\t: Demand = " << e.demand << "\n"); + e.overCapacityCount++; + LLVM_DEBUG(llvm::dbgs() + << "over_capacity_count = " << e.overCapacityCount << "\n"); + legal = false; + } + return legal; +} diff --git a/lib/Dialect/AIE/Transforms/AIEPathfinder.cpp b/lib/Dialect/AIE/Transforms/AIEPathfinder.cpp deleted file mode 100644 index 9406756e27..0000000000 --- a/lib/Dialect/AIE/Transforms/AIEPathfinder.cpp +++ /dev/null @@ -1,334 +0,0 @@ -//===- AIEPathfinder.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 2021 Xilinx Inc. -// -//===----------------------------------------------------------------------===// - -#include "aie/Dialect/AIE/Transforms/AIEPathfinder.h" - -#include "llvm/Support/Debug.h" -#include "llvm/Support/raw_os_ostream.h" - -using namespace xilinx; -using namespace xilinx::AIE; - -#define DEBUG_TYPE "aie-pathfinder" - -WireBundle getConnectingBundle(WireBundle dir) { - switch (dir) { - case WireBundle::North: - return WireBundle::South; - break; - case WireBundle::South: - return WireBundle::North; - break; - case WireBundle::East: - return WireBundle::West; - break; - case WireBundle::West: - return WireBundle::East; - break; - default: - return dir; - } -} - -Pathfinder::Pathfinder() {} - -Pathfinder::Pathfinder(int _maxcol, int _maxrow, DeviceOp &d) { - initializeGraph(_maxcol, _maxrow, d); -} - -void Pathfinder::initializeGraph(int maxcol, int maxrow, DeviceOp &d) { - const auto &targetModel = d.getTargetModel(); - // make grid of switchboxes - for (int row = 0; row <= maxrow; row++) { - for (int col = 0; col <= maxcol; col++) { - int id = add_vertex(graph); - graph[id].row = row; - graph[id].col = col; - graph[id].pred = 0; - graph[id].processed = false; - if (row > 0) { // if not in row 0 add channel to North/South - if (auto max_capacity = targetModel.getNumSourceSwitchboxConnections( - col, row, WireBundle::South)) { - auto north_edge = add_edge(id - maxcol - 1, id, graph).first; - graph[north_edge].bundle = WireBundle::North; - graph[north_edge].max_capacity = max_capacity; - } - if (auto max_capacity = targetModel.getNumDestSwitchboxConnections( - col, row, WireBundle::South)) { - auto south_edge = add_edge(id, id - maxcol - 1, graph).first; - graph[south_edge].bundle = WireBundle::South; - graph[south_edge].max_capacity = max_capacity; - } - } - if (col > 0) { // if not in col 0 add channel to East/West - if (auto max_capacity = targetModel.getNumSourceSwitchboxConnections( - col, row, WireBundle::West)) { - auto east_edge = add_edge(id - 1, id, graph).first; - graph[east_edge].bundle = WireBundle::East; - graph[east_edge].max_capacity = max_capacity; - } - if (auto max_capacity = targetModel.getNumDestSwitchboxConnections( - col, row, WireBundle::West)) { - auto west_edge = add_edge(id, id - 1, graph).first; - graph[west_edge].bundle = WireBundle::West; - graph[west_edge].max_capacity = max_capacity; - } - } - } - } - - // initialize weights of all Channels to 1 - // initialize other variables - auto edge_pair = edges(graph); - for (auto edge = edge_pair.first; edge != edge_pair.second; edge++) { - graph[*edge].demand = 1; - graph[*edge].used_capacity = 0; - graph[*edge].fixed_capacity.clear(); - graph[*edge].over_capacity_count = 0; - } - - // initialize maximum iterations flag - Pathfinder::maxIterReached = false; -} - -// Pathfinder::addFlow -// add a flow from src to dst -// can have an arbitrary number of dst locations due to fanout -void Pathfinder::addFlow(Coord srcCoords, Port srcPort, Coord dstCoords, - Port dstPort) { - // check if a flow with this source already exists - for (unsigned int i = 0; i < flows.size(); i++) { - Switchbox *otherSrc = flows[i].first.first; - Port otherPort = flows[i].first.second; - if (otherSrc->col == srcCoords.first && otherSrc->row == srcCoords.second && - otherPort == srcPort) { - // find the vertex corresponding to the destination - PathEndPoint dst; - auto vpair = vertices(graph); - for (vertex_iterator v = vpair.first; v != vpair.second; v++) { - Switchbox *sb = &graph[*v]; - if (sb->col == dstCoords.first && sb->row == dstCoords.second) { - dst = std::make_pair(sb, dstPort); - break; - } - } - // add the destination to this existing flow, and finish - flows[i].second.push_back(dst); - return; - } - } - - // if no existing flow was found with this source, create a new flow - Flow flow; - auto vpair = vertices(graph); - for (vertex_iterator v = vpair.first; v != vpair.second; v++) { - Switchbox *sb = &graph[*v]; - // check if this vertex matches the source - if (sb->col == srcCoords.first && sb->row == srcCoords.second) - flow.first = std::make_pair(sb, srcPort); - - // check if this vertex matches the destination - if (sb->col == dstCoords.first && sb->row == dstCoords.second) - flow.second.push_back(std::make_pair(sb, dstPort)); - } - - flows.push_back(flow); - return; -} - -// Pathfinder::addFixedConnection -// Keep track of connections already used in the AIE -// Pathfinder algorithm will avoid using these -void Pathfinder::addFixedConnection(Coord coords, Port port) { - // find the correct Channel and indicate the fixed direction - auto edge_pair = edges(graph); - for (edge_iterator e = edge_pair.first; e != edge_pair.second; e++) { - if (graph[source(*e, graph)].col == coords.first && - graph[source(*e, graph)].row == coords.second && - graph[*e].bundle == port.first) { - graph[*e].fixed_capacity.insert(port.second); - break; - } - } -} - -// Pathfinder::findPaths -// Primary function for the class -// Perform congestion-aware routing for all flows which have been added. -// Use Dijkstra's shortest path to find routes, and use "demand" as the weights -// if the routing finds too much congestion, update the demand weights -// and repeat the process until a vaild solution is found -// -// returns a map specifying switchbox settings for all flows -// if no legal routing can be found after MAX_ITERATIONS, returns empty vector -std::map -Pathfinder::findPaths(const int MAX_ITERATIONS) { - LLVM_DEBUG(llvm::dbgs() << "Begin Pathfinder::findPaths\n"); - int iteration_count = 0; - std::map routing_solution; - - // initialize all Channel histories to 0 - auto edge_pair = edges(graph); - for (auto edge = edge_pair.first; edge != edge_pair.second; edge++) { - graph[*edge].over_capacity_count = 0; - } - -// Pathfinder iteration loop -#define over_capacity_coeff 0.02 -#define used_capacity_coeff 0.02 - do { - LLVM_DEBUG(llvm::dbgs() - << "Begin findPaths iteration #" << iteration_count << "\n"); - // update demand on all channels - edge_pair = edges(graph); - for (edge_iterator it = edge_pair.first; it != edge_pair.second; it++) { - Channel *ch = &graph[*it]; - // LLVM_DEBUG(llvm::dbgs() << "Pre update:\tEdge " << *it << "\t: used = " - // << ch->used_capacity << - // "\t demand = " << ch->demand << "\t over_capacity_count= " << - // ch->over_capacity_count<< "\t"); - if (ch->fixed_capacity.size() >= ch->max_capacity) { - ch->demand = std::numeric_limits::max(); - } else { - float history = 1 + over_capacity_coeff * ch->over_capacity_count; - float congestion = 1 + used_capacity_coeff * ch->used_capacity; - // std::max(0, ch->used_capacity - ch->max_capacity); - ch->demand = history * congestion; - } - } - // if reach MAX_ITERATIONS, throw an error since no routing can be found - // TODO: add error throwing mechanism - if (++iteration_count > MAX_ITERATIONS) { - LLVM_DEBUG(llvm::dbgs() - << "Pathfinder: MAX_ITERATIONS has been exceeded (" - << MAX_ITERATIONS - << " iterations)...unable to find routing for flows.\n"); - // return {}; - // return the invalid solution for debugging purposes - maxIterReached = true; - return routing_solution; - } - - // "rip up" all routes, i.e. set used capacity in each Channel to 0 - routing_solution = {}; - auto edge_pair = edges(graph); - for (edge_iterator e = edge_pair.first; e != edge_pair.second; e++) { - graph[*e].used_capacity = 0; - } - - // for each flow, find the shortest path from source to destination - // update used_capacity for the path between them - for (auto flow : flows) { - auto vpair = vertices(graph); - - vertex_descriptor src; - for (vertex_iterator v = vpair.first; v != vpair.second; v++) { - Switchbox *sb = &graph[*v]; - sb->processed = false; - if (sb->col == flow.first.first->col && - sb->row == flow.first.first->row) - src = *v; - } - - // use dijkstra to find path given current demand - // from the start switchbox, find shortest path to each other switchbox - // output is in the predecessor map, which must then be processed to get - // individual switchbox settings - dijkstra_shortest_paths( - graph, src, - weight_map(get(&Channel::demand, graph)) - .predecessor_map(get(&Switchbox::pred, graph))); - - // trace the path of the flow backwards via predecessors - // increment used_capacity for the associated channels - SwitchSettings switchSettings = SwitchSettings(); - // set the input bundle for the source endpoint - switchSettings[&graph[src]].first = flow.first.second; - graph[src].processed = true; - for (unsigned int i = 0; i < flow.second.size(); i++) { - vertex_descriptor curr; - for (vertex_iterator v = vpair.first; v != vpair.second; v++) - if (graph[*v].col == flow.second[i].first->col && - graph[*v].row == flow.second[i].first->row) - curr = *v; - Switchbox *sb = &graph[curr]; - - // set the output bundle for this destination endpoint - switchSettings[sb].second.insert(flow.second[i].second); - - // trace backwards until a vertex already processed is reached - while (sb->processed == false) { - // find the edge from the pred to curr by searching incident edges - auto inedges = in_edges(curr, graph); - Channel *ch = nullptr; - for (in_edge_iterator it = inedges.first; it != inedges.second; - it++) { - if (source(*it, graph) == (unsigned)sb->pred) { - // found the channel used in the path - ch = &graph[*it]; - break; - } - } - assert(ch != nullptr); - - // don't use fixed channels - while (ch->fixed_capacity.count(ch->used_capacity)) - ch->used_capacity++; - - // add the entrance port for this Switchbox - switchSettings[sb].first = std::make_pair( - getConnectingBundle(ch->bundle), ch->used_capacity); - // add the current Switchbox to the map of the predecessor - switchSettings[&graph[sb->pred]].second.insert( - std::make_pair(ch->bundle, ch->used_capacity)); - - ch->used_capacity++; - // if at capacity, bump demand to discourage using this Channel - if (ch->used_capacity >= ch->max_capacity) { - // this means the order matters! - ch->demand *= 1.1; - } - - sb->processed = true; - curr = sb->pred; - sb = &graph[curr]; - } - } - // add this flow to the proposed solution - routing_solution[flow.first] = switchSettings; - } - } while (!isLegal()); // continue iterations until a legal routing is found - return routing_solution; -} - -// check that every channel does not exceed max capacity -bool Pathfinder::isLegal() { - auto edge_pair = edges(graph); - bool legal = true; // assume legal until found otherwise - // check if maximum number of iterations has been reached - if (maxIterReached) - legal = false; - for (edge_iterator e = edge_pair.first; e != edge_pair.second; e++) { - if (graph[*e].used_capacity > graph[*e].max_capacity) { - LLVM_DEBUG(llvm::dbgs() - << "Too much capacity on Edge (" - << graph[source(*e, graph)].col << ", " - << graph[source(*e, graph)].row << ") -> " - << stringifyWireBundle(graph[*e].bundle) - << "\t: used_capacity = " << graph[*e].used_capacity - << "\t: Demand = " << graph[*e].demand << "\n"); - graph[*e].over_capacity_count++; - LLVM_DEBUG(llvm::dbgs() << "over_capacity_count = " - << graph[*e].over_capacity_count << "\n"); - legal = false; - } - } - return legal; -} diff --git a/lib/Dialect/AIE/Transforms/CMakeLists.txt b/lib/Dialect/AIE/Transforms/CMakeLists.txt index 345dc03507..26a8072fe9 100644 --- a/lib/Dialect/AIE/Transforms/CMakeLists.txt +++ b/lib/Dialect/AIE/Transforms/CMakeLists.txt @@ -5,17 +5,13 @@ # # (c) Copyright 2021 Xilinx Inc. -if(NOT WIN32) - add_compile_options(-Wno-unknown-warning-option) -endif() - add_mlir_dialect_library( AIETransforms AIEAssignBuffers.cpp AIEAssignLockIDs.cpp AIEFindFlows.cpp - AIEPathfinder.cpp - AIECreatePathfindFlows.cpp + AIEPathFinder.cpp + AIECreatePathFindFlows.cpp AIECoreToStandard.cpp AIECreatePacketFlows.cpp AIECanonicalizeDevice.cpp diff --git a/python/compiler/aiecc/main.py b/python/compiler/aiecc/main.py index 64a09366b2..7007e012d8 100644 --- a/python/compiler/aiecc/main.py +++ b/python/compiler/aiecc/main.py @@ -354,7 +354,6 @@ def make_sim_dir(x): "-I" + opts.aietools_path + "/include", "-I" + opts.aietools_path + "/include/drivers/aiengine", "-I" + opts.aietools_path + "/data/osci_systemc/include", - "-I" + opts.aietools_path + "/tps/boost_1_72_0", "-I" + opts.aietools_path + "/include/xtlm/include", "-I" + opts.aietools_path + "/include/common_cpp/common_cpp_v1_0/include", "-I" + runtime_testlib_include_path, diff --git a/test/create-flows/broadcast.mlir b/test/create-flows/broadcast.mlir index 90795f232d..42202cee4a 100644 --- a/test/create-flows/broadcast.mlir +++ b/test/create-flows/broadcast.mlir @@ -29,12 +29,12 @@ // CHECK: %[[T83:.*]] = AIE.tile(8, 3) // // CHECK: AIE.flow(%[[T20]], DMA : 0, %[[T71]], DMA : 0) -// CHECK: AIE.flow(%[[T20]], DMA : 0, %[[T82]], DMA : 0) // CHECK: AIE.flow(%[[T20]], DMA : 0, %[[T31]], DMA : 0) +// CHECK: AIE.flow(%[[T20]], DMA : 0, %[[T82]], DMA : 0) // CHECK: AIE.flow(%[[T20]], DMA : 0, %[[T13]], DMA : 0) // CHECK: AIE.flow(%[[T60]], DMA : 0, %[[T83]], DMA : 1) -// CHECK: AIE.flow(%[[T60]], DMA : 0, %[[T22]], DMA : 1) // CHECK: AIE.flow(%[[T60]], DMA : 0, %[[T31]], DMA : 1) +// CHECK: AIE.flow(%[[T60]], DMA : 0, %[[T22]], DMA : 1) // CHECK: AIE.flow(%[[T60]], DMA : 0, %[[T02]], DMA : 1) module { diff --git a/test/create-flows/more_flows_shim.mlir b/test/create-flows/more_flows_shim.mlir index 2fd2d10ac1..0141388138 100644 --- a/test/create-flows/more_flows_shim.mlir +++ b/test/create-flows/more_flows_shim.mlir @@ -12,23 +12,24 @@ // These tests verify pathfinder routing flows to/from PLIO in shim tiles. // -// RUN: aie-opt --split-input-file --aie-create-pathfinder-flows %s | FileCheck %s -// CHECK: module +// RUN: aie-opt --split-input-file --aie-create-pathfinder-flows -split-input-file %s | FileCheck %s + +// CHECK-LABEL: test70 // CHECK: %[[T70:.*]] = AIE.tile(7, 0) // CHECK: %[[T71:.*]] = AIE.tile(7, 1) -// CHECK: %{{.*}} = AIE.switchbox(%[[T70]]) { +// CHECK: %[[SB70:.*]] = AIE.switchbox(%[[T70]]) { // CHECK: AIE.connect // CHECK: } -// CHECK: %{{.*}} = AIE.shimmux(%[[T70]]) { +// CHECK: %[[SH70:.*]] = AIE.shimmux(%[[T70]]) { // CHECK: AIE.connect // CHECK: } -// CHECK: %{{.*}} = AIE.switchbox(%[[T71]]) { +// CHECK: %[[SB71:.*]] = AIE.switchbox(%[[T71]]) { // CHECK: AIE.connect // CHECK: } // Tile 7,0 is a shim NoC tile that has a ShimMux. // The ShimMux must be configured for streams to PLIO 2,3,4,5 -module { +module @test70 { AIE.device(xcvc1902) { %t70 = AIE.tile(7, 0) %t71 = AIE.tile(7, 1) @@ -38,22 +39,22 @@ module { // ----- -// CHECK: module +// CHECK-LABEL: test60 // CHECK: %[[T60:.*]] = AIE.tile(6, 0) // CHECK: %[[T61:.*]] = AIE.tile(6, 1) -// CHECK: %{{.*}} = AIE.switchbox(%[[T60]]) { +// CHECK: %[[SB60:.*]] = AIE.switchbox(%[[T60]]) { // CHECK: AIE.connect // CHECK: } -// CHECK: %{{.*}} = AIE.shimmux(%[[T60]]) { +// CHECK: %[[SH60:.*]] = AIE.shimmux(%[[T60]]) { // CHECK: AIE.connect // CHECK: } -// CHECK: %{{.*}} = AIE.switchbox(%[[T61]]) { +// CHECK: %[[SB61:.*]] = AIE.switchbox(%[[T61]]) { // CHECK: AIE.connect // CHECK: } // Tile 6,0 is a shim NoC tile that has a ShimMux. // The ShimMux must be configured for streams from PLIO 2,3,6,7 -module { +module @test60 { AIE.device(xcvc1902) { %t60 = AIE.tile(6, 0) %t61 = AIE.tile(6, 1) @@ -63,20 +64,20 @@ module { // ----- -// CHECK: module +// CHECK-LABEL: test40 // CHECK: %[[T40:.*]] = AIE.tile(4, 0) // CHECK: %[[T41:.*]] = AIE.tile(4, 1) -// CHECK: %{{.*}} = AIE.switchbox(%[[T40]]) { +// CHECK: %[[SB40:.*]] = AIE.switchbox(%[[T40]]) { // CHECK: AIE.connect // CHECK: AIE.connect // CHECK: } -// CHECK: %{{.*}} = AIE.switchbox(%[[T41]]) { +// CHECK: %[[SB41:.*]] = AIE.switchbox(%[[T41]]) { // CHECK: AIE.connect // CHECK: AIE.connect // CHECK: } // Tile 4,0 is a shim PL tile and does not contain a ShimMux. -module { +module @test40 { AIE.device(xcvc1902) { %t40 = AIE.tile(4, 0) %t41 = AIE.tile(4, 1) @@ -87,22 +88,22 @@ module { // ----- -// CHECK: module +// CHECK-LABEL: test100 // CHECK: %[[T100:.*]] = AIE.tile(10, 0) // CHECK: %[[T101:.*]] = AIE.tile(10, 1) -// CHECK: %{{.*}} = AIE.switchbox(%[[T100]]) { +// CHECK: %[[SB100:.*]] = AIE.switchbox(%[[T100]]) { // CHECK: AIE.connect // CHECK: } -// CHECK: %{{.*}} = AIE.shimmux(%[[T100]]) { +// CHECK: %[[SH100:.*]] = AIE.shimmux(%[[T100]]) { // CHECK: AIE.connect // CHECK: } -// CHECK: %{{.*}} = AIE.switchbox(%[[T101]]) { +// CHECK: %[[SB101:.*]] = AIE.switchbox(%[[T101]]) { // CHECK: AIE.connect // CHECK: } // Tile 10,0 is a shim NoC tile that has a ShimMux. // The ShimMux must be configured for streams to NOC 0,1,2,3 -module { +module @test100 { AIE.device(xcvc1902) { %t100 = AIE.tile(10, 0) %t101 = AIE.tile(10, 1)