MLIR-AIE 1.4.5.dev
AIEFlowsToJSON.cpp
Go to the documentation of this file.
1//===- AIEFlowsToJSON.cpp ---------------------------------------*- C++ -*-===//
2//
3// Copyright (C) 2021-2022 Xilinx, Inc.
4// Copyright (C) 2022-2026 Advanced Micro Devices, Inc.
5// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6//
7//===----------------------------------------------------------------------===//
8
9/*
10 * Takes as input the mlir after AIECreateFlows and AIEFindFlows.
11 * Converts the flows into a JSON file to be read by other tools.
12 */
13
15
17
18#include "mlir/IR/Attributes.h"
19#include "mlir/Target/LLVMIR/Import.h"
20#include "mlir/Tools/mlir-translate/MlirTranslateMain.h"
21
22#include <queue>
23#include <set>
24
25using namespace mlir;
26using namespace xilinx;
27using namespace xilinx::AIE;
28
29namespace xilinx::AIE {
30
31// returns coordinates in the direction indicated by bundle
32static TileID getNextCoords(int col, int row, WireBundle bundle) {
33 switch (bundle) {
34 case WireBundle::North:
35 return {col, row + 1};
36 case WireBundle::South:
37 return {col, row - 1};
38 case WireBundle::East:
39 return {col + 1, row};
40 case WireBundle::West:
41 return {col - 1, row};
42 default:
43 return {col, row};
44 }
45}
46
47static void translateSwitchboxes(DeviceOp targetOp, raw_ostream &output) {
48 // count flow sources and destinations
49 std::map<TileID, int> sourceCounts;
50 std::map<TileID, int> destinationCounts;
51 for (FlowOp flowOp : targetOp.getOps<FlowOp>()) {
52 TileOp source = cast<TileOp>(flowOp.getSource().getDefiningOp());
53 TileOp dest = cast<TileOp>(flowOp.getDest().getDefiningOp());
54 TileID srcID = {source.colIndex(), source.rowIndex()};
55 TileID dstID = {dest.colIndex(), dest.rowIndex()};
56 sourceCounts[srcID]++;
57 destinationCounts[dstID]++;
58 }
59 for (PacketFlowOp pktFlowOp : targetOp.getOps<PacketFlowOp>()) {
60 Region &r = pktFlowOp.getPorts();
61 Block &b = r.front();
62 for (Operation &Op : b.getOperations()) {
63 if (auto pktSource = dyn_cast<PacketSourceOp>(Op)) {
64 TileOp source = cast<TileOp>(pktSource.getTile().getDefiningOp());
65 TileID srcID = {source.colIndex(), source.rowIndex()};
66 sourceCounts[srcID]++;
67 } else if (auto pktDest = dyn_cast<PacketDestOp>(Op)) {
68 TileOp dest = cast<TileOp>(pktDest.getTile().getDefiningOp());
69 TileID dstID = {dest.colIndex(), dest.rowIndex()};
70 destinationCounts[dstID]++;
71 }
72 }
73 }
74
75 std::map<TileID, ShimMuxOp> shimMuxes;
76 for (ShimMuxOp shimMuxOp : targetOp.getOps<ShimMuxOp>()) {
77 shimMuxes[{shimMuxOp.colIndex(), shimMuxOp.rowIndex()}] = shimMuxOp;
78 }
79
80 int totalPathLength = 0;
81 // for each switchbox, write name, coordinates, and routing demand info
82 for (SwitchboxOp switchboxOp : targetOp.getOps<SwitchboxOp>()) {
83 int col = switchboxOp.colIndex();
84 int row = switchboxOp.rowIndex();
85 std::string switchString = "\"switchbox" + std::to_string(col) +
86 std::to_string(row) + "\": {\n" +
87 "\"col\": " + std::to_string(col) + ",\n" +
88 "\"row\": " + std::to_string(row) + ",\n";
89
90 // write source and destination info
91 switchString +=
92 "\"source_count\": " + std::to_string(sourceCounts[{col, row}]) + ",\n";
93 switchString += "\"destination_count\": " +
94 std::to_string(destinationCounts[{col, row}]) + ",\n";
95
96 // write routing demand info
97 std::vector<uint32_t> connectCounts(getMaxEnumValForWireBundle() + 1, 0);
98 for (auto &connectCount : connectCounts)
99 connectCount = 0;
100
101 std::set<Port> ports;
102 for (ConnectOp connectOp : switchboxOp.getOps<ConnectOp>())
103 ports.insert(connectOp.destPort());
104 for (MasterSetOp masterSetOp : switchboxOp.getOps<MasterSetOp>())
105 ports.insert(masterSetOp.destPort());
106 for (Port port : ports) {
107 connectCounts[int(port.bundle)]++;
108 }
109
110 switchString += "\"northbound\": " +
111 std::to_string(connectCounts[int(WireBundle::North)]) +
112 ",\n";
113 switchString += "\"eastbound\": " +
114 std::to_string(connectCounts[int(WireBundle::East)]) +
115 ",\n";
116 switchString += "\"southbound\": " +
117 std::to_string(connectCounts[int(WireBundle::South)]) +
118 ",\n";
119 switchString += "\"westbound\": " +
120 std::to_string(connectCounts[int(WireBundle::West)]) + "\n";
121 switchString += "},\n";
122 output << switchString;
123
124 // calculate total path length
125 totalPathLength += connectCounts[int(WireBundle::North)];
126 totalPathLength += connectCounts[int(WireBundle::East)];
127 totalPathLength += connectCounts[int(WireBundle::South)];
128 totalPathLength += connectCounts[int(WireBundle::West)];
129
130 // deduct shim muxes from total path length
131 if (shimMuxes.count({col, row})) {
132 auto shimMuxOp = shimMuxes[{col, row}];
133 std::set<Port> ports;
134 for (ConnectOp connectOp : shimMuxOp.getOps<ConnectOp>()) {
135 Port port = connectOp.sourcePort();
136 if (port.bundle == WireBundle::North) {
137 ports.insert(port);
138 }
139 }
140 for (PacketRulesOp packetRulesOp : shimMuxOp.getOps<PacketRulesOp>()) {
141 Port port = packetRulesOp.sourcePort();
142 if (port.bundle == WireBundle::North) {
143 ports.insert(port);
144 }
145 }
146 totalPathLength -= ports.size();
147 }
148 }
149
150 // write total path length to JSON
151 output << "\"total_path_length\": " << totalPathLength << ",\n";
152}
153
154static void translateCircuitFlows(DeviceOp targetOp, int &flowCount,
155 raw_ostream &output) {
156 // for each flow, trace it through switchboxes and write the route to JSON
157 std::set<std::pair<TileOp, Port>> flowSources;
158 for (FlowOp flowOp : targetOp.getOps<FlowOp>()) {
159 // objects used to trace through the flow
160 Port currPort = {flowOp.getSourceBundle(), flowOp.getSourceChannel()};
161 SwitchboxOp currSwitchbox;
162
163 TileOp source = cast<TileOp>(flowOp.getSource().getDefiningOp());
164 // TileOp dest = cast<TileOp>(flowOp.dest().getDefiningOp());
165
166 // track flow sources to avoid duplicate routes
167 std::pair<TileOp, Port> flowSource = {source, currPort};
168 if (flowSources.count(flowSource)) {
169 continue;
170 }
171 flowSources.insert(flowSource);
172
173 std::string routeString =
174 "\"route" + std::to_string(flowCount++) + "\": [ ";
175
176 // FIFO to handle fanouts
177 std::queue<Port> nextPorts;
178 std::queue<SwitchboxOp> nextSwitches;
179
180 // find the starting switchbox
181 for (SwitchboxOp switchboxOp : targetOp.getOps<SwitchboxOp>()) {
182 if (switchboxOp.colIndex() == source.colIndex() &&
183 switchboxOp.rowIndex() == source.rowIndex()) {
184 currSwitchbox = switchboxOp;
185 break;
186 }
187 }
188
189 // if the flow starts in a shim, handle seperately
190 for (ShimMuxOp shimMuxOp : targetOp.getOps<ShimMuxOp>()) {
191 if (shimMuxOp.colIndex() == source.colIndex() &&
192 shimMuxOp.rowIndex() == source.rowIndex()) {
193 for (ConnectOp connectOp : shimMuxOp.getOps<ConnectOp>()) {
194 if (connectOp.getSourceBundle() == currPort.bundle &&
195 connectOp.getSourceChannel() == currPort.channel) {
196 currPort.bundle = getConnectingBundle(connectOp.getDestBundle());
197 currPort.channel = connectOp.getDestChannel();
198 break;
199 }
200 }
201 break;
202 }
203 }
204
205 // trace through the flow and add switchbox coordinates to JSON
206 bool done = false;
207 do {
208 // get the coordinates for the next switchbox in the flow
209 for (ConnectOp connectOp : currSwitchbox.getOps<ConnectOp>()) {
210 // if this connectOp is the end of a flow, skip
211 if ((currSwitchbox.rowIndex() == 0 &&
212 connectOp.getDestBundle() == WireBundle::South) ||
213 connectOp.getDestBundle() == WireBundle::DMA ||
214 connectOp.getDestBundle() == WireBundle::Core)
215 continue;
216
217 if (connectOp.getSourceBundle() == currPort.bundle &&
218 connectOp.getSourceChannel() == currPort.channel) {
219 nextPorts.push({getConnectingBundle(connectOp.getDestBundle()),
220 connectOp.getDestChannel()});
221
222 TileID nextCoords =
223 getNextCoords(currSwitchbox.colIndex(), currSwitchbox.rowIndex(),
224 connectOp.getDestBundle());
225
226 // search for next switchbox to connect to
227 for (SwitchboxOp switchboxOp : targetOp.getOps<SwitchboxOp>()) {
228 if (switchboxOp.colIndex() == nextCoords.col &&
229 switchboxOp.rowIndex() == nextCoords.row) {
230 nextSwitches.push(switchboxOp);
231 break;
232 }
233 }
234 }
235 }
236
237 // add switchbox to the routeString
238 std::string dirString = std::string("[[") +
239 std::to_string(currSwitchbox.colIndex()) + ", " +
240 std::to_string(currSwitchbox.rowIndex()) + "], [";
241 int opCount = 0, dirCount = 0;
242 for (ConnectOp connectOp : currSwitchbox.getOps<ConnectOp>()) {
243 if (connectOp.getSourceBundle() == currPort.bundle &&
244 connectOp.getSourceChannel() == currPort.channel) {
245 if (opCount++ > 0)
246 dirString += ", ";
247 dirCount++;
248 dirString +=
249 "\"" +
250 (std::string)stringifyWireBundle(connectOp.getDestBundle()) +
251 "\"";
252 }
253 }
254 dirString += "]], ";
255 if (dirCount > 0)
256 routeString += dirString;
257
258 if (nextPorts.empty() || nextSwitches.empty()) {
259 done = true;
260 routeString += "[]";
261 } else {
262 currPort = nextPorts.front();
263 currSwitchbox = nextSwitches.front();
264 nextPorts.pop();
265 nextSwitches.pop();
266 }
267 } while (!done);
268 // write string to JSON
269 routeString += std::string(" ],\n");
270 output << routeString;
271 }
272}
273
274static void translatePacketFlows(DeviceOp targetOp, int &flowCount,
275 raw_ostream &output) {
276 // for each flow, trace it through switchboxes and write the route to JSON
277 std::set<std::pair<TileOp, Port>> flowSources;
278 for (PacketFlowOp pktFlowOp : targetOp.getOps<PacketFlowOp>()) {
279 Region &r = pktFlowOp.getPorts();
280 Block &b = r.front();
281 Port sourcePort, destPort;
282 TileOp source, dest;
283 for (Operation &Op : b.getOperations()) {
284 if (auto pktSource = dyn_cast<PacketSourceOp>(Op)) {
285 source = cast<TileOp>(pktSource.getTile().getDefiningOp());
286 sourcePort = pktSource.port();
287 } else if (auto pktDest = dyn_cast<PacketDestOp>(Op)) {
288 dest = cast<TileOp>(pktDest.getTile().getDefiningOp());
289 destPort = pktDest.port();
290 }
291 }
292
293 // objects used to trace through the flow
294 Port currPort = sourcePort;
295 SwitchboxOp currSwitchbox;
296
297 // track flow sources to avoid duplicate routes
298 std::pair<TileOp, Port> flowSource = {source, currPort};
299 if (flowSources.count(flowSource)) {
300 continue;
301 }
302 flowSources.insert(flowSource);
303
304 std::string routeString =
305 "\"route" + std::to_string(flowCount++) + "\": [ ";
306
307 // FIFO to handle fanouts
308 std::queue<Port> nextPorts;
309 std::queue<SwitchboxOp> nextSwitches;
310
311 // find the starting switchbox
312 for (SwitchboxOp switchboxOp : targetOp.getOps<SwitchboxOp>()) {
313 if (switchboxOp.colIndex() == source.colIndex() &&
314 switchboxOp.rowIndex() == source.rowIndex()) {
315 currSwitchbox = switchboxOp;
316 break;
317 }
318 }
319
320 // if the flow starts in a shim, handle seperately
321 for (ShimMuxOp shimMuxOp : targetOp.getOps<ShimMuxOp>()) {
322 if (shimMuxOp.colIndex() == source.colIndex() &&
323 shimMuxOp.rowIndex() == source.rowIndex()) {
324 for (ConnectOp connectOp : shimMuxOp.getOps<ConnectOp>()) {
325 if (connectOp.getSourceBundle() == currPort.bundle &&
326 connectOp.getSourceChannel() == currPort.channel) {
327 currPort.bundle = getConnectingBundle(connectOp.getDestBundle());
328 currPort.channel = connectOp.getDestChannel();
329 break;
330 }
331 }
332 break;
333 }
334 }
335
336 // trace through the flow and add switchbox coordinates to JSON
337 bool done = false;
338 do {
339 for (auto packetRulesOp : currSwitchbox.getOps<PacketRulesOp>()) {
340 Port destPort;
341 for (auto masterSetOp : currSwitchbox.getOps<MasterSetOp>()) {
342 for (Value amsel : masterSetOp.getAmsels()) {
343 for (auto ruleOp :
344 packetRulesOp.getRules().front().getOps<PacketRuleOp>()) {
345 if (ruleOp.getAmsel() == amsel) {
346 destPort = masterSetOp.destPort();
347 }
348 }
349 }
350 }
351
352 // if this packetRulesOp is the end of a flow, skip
353 if ((currSwitchbox.rowIndex() == 0 &&
354 destPort.bundle == WireBundle::South) ||
355 destPort.bundle == WireBundle::DMA ||
356 destPort.bundle == WireBundle::Core)
357 continue;
358
359 if (packetRulesOp.sourcePort() == currPort) {
360 nextPorts.push(
361 {getConnectingBundle(destPort.bundle), destPort.channel});
362
363 TileID nextCoords =
364 getNextCoords(currSwitchbox.colIndex(), currSwitchbox.rowIndex(),
365 destPort.bundle);
366
367 // search for next switchbox to connect to
368 for (SwitchboxOp switchboxOp : targetOp.getOps<SwitchboxOp>()) {
369 if (switchboxOp.colIndex() == nextCoords.col &&
370 switchboxOp.rowIndex() == nextCoords.row) {
371 nextSwitches.push(switchboxOp);
372 break;
373 }
374 }
375 }
376 }
377
378 // add switchbox to the routeString
379 std::string dirString = std::string("[[") +
380 std::to_string(currSwitchbox.colIndex()) + ", " +
381 std::to_string(currSwitchbox.rowIndex()) + "], [";
382 int opCount = 0, dirCount = 0;
383 for (auto packetRulesOp : currSwitchbox.getOps<PacketRulesOp>()) {
384 if (packetRulesOp.sourcePort() == currPort) {
385 Port destPort;
386 for (auto masterSetOp : currSwitchbox.getOps<MasterSetOp>()) {
387 for (Value amsel : masterSetOp.getAmsels()) {
388 for (auto ruleOp :
389 packetRulesOp.getRules().front().getOps<PacketRuleOp>()) {
390 if (ruleOp.getAmsel() == amsel) {
391 destPort = masterSetOp.destPort();
392 }
393 }
394 }
395 }
396 if (opCount++ > 0)
397 dirString += ", ";
398 dirCount++;
399 dirString +=
400 "\"" + (std::string)stringifyWireBundle(destPort.bundle) + "\"";
401 }
402 }
403 dirString += "]], ";
404 if (dirCount > 0)
405 routeString += dirString;
406
407 if (nextPorts.empty() || nextSwitches.empty()) {
408 done = true;
409 routeString += "[]";
410 } else {
411 currPort = nextPorts.front();
412 currSwitchbox = nextSwitches.front();
413 nextPorts.pop();
414 nextSwitches.pop();
415 }
416 } while (!done);
417 // write string to JSON
418 routeString += std::string(" ],\n");
419 output << routeString;
420 }
421}
422
423mlir::LogicalResult AIEFlowsToJSON(ModuleOp module, raw_ostream &output,
424 llvm::StringRef deviceName) {
425 DeviceOp targetOp = AIE::DeviceOp::getForSymbolInModule(module, deviceName);
426 if (!targetOp)
427 return module.emitOpError("expected AIE.device operation at toplevel");
428
429 std::set<TileID> switchboxes;
430 for (SwitchboxOp switchboxOp : targetOp.getOps<SwitchboxOp>())
431 switchboxes.insert({switchboxOp.colIndex(), switchboxOp.rowIndex()});
432 WalkResult untraceable = targetOp.walk([&](Operation *op) {
433 Value source;
434 if (auto flowOp = dyn_cast<FlowOp>(op))
435 source = flowOp.getSource();
436 else if (auto pktSource = dyn_cast<PacketSourceOp>(op))
437 source = pktSource.getTile();
438 else
439 return WalkResult::advance();
440 if (switchboxes.count(cast<TileOp>(source.getDefiningOp()).getTileID()))
441 return WalkResult::advance();
442 op->emitError("no switchbox at the flow's source to trace it through; "
443 "keep the routed switchboxes with "
444 "--aie-find-flows=remove-lifted=false");
445 return WalkResult::interrupt();
446 });
447 if (untraceable.wasInterrupted())
448 return failure();
449
450 output << "{\n";
451 int flowCount = 0;
452
453 translateSwitchboxes(targetOp, output);
454 translateCircuitFlows(targetOp, flowCount, output);
455 translatePacketFlows(targetOp, flowCount, output);
456
457 output << "\"route_all\": [],\n";
458 output << "\n\"end json\": 0\n"; // dummy line to avoid errors from commas
459 output << "}";
460 return success();
461} // end AIETranslateToJSON
462} // namespace xilinx::AIE
mlir::LogicalResult AIEFlowsToJSON(mlir::ModuleOp module, llvm::raw_ostream &output, llvm::StringRef deviceName="")
struct TileID { friend std::ostream &operator<<(std::ostream &os, const TileID &s) { os<< "TileID("<< s.col<< ", "<< s.row<< ")"; return os; } friend std::string to_string(const TileID &s) { std::ostringstream ss; ss<< s; return ss.str(); } friend llvm::raw_ostream &operator<<(llvm::raw_ostream &os, const TileID &s) { os<< to_string(s); return os; } inline bool operator<(const TileID &rhs) const { return std::tie(col, row)< std::tie(rhs.col, rhs.row); } bool operator==(const TileID &rhs) const { return std::tie(col, row)==std::tie(rhs.col, rhs.row); } bool operator!=(const TileID &rhs) const { return !(*this==rhs);} int col, row;} TileID
struct Port { WireBundle bundle; int channel; bool operator==(const Port &rhs) const { return std::tie(bundle, channel)==std::tie(rhs.bundle, rhs.channel); } bool operator!=(const Port &rhs) const { return !(*this==rhs);} bool operator<(const Port &rhs) const { return std::tie(bundle, channel)< std::tie(rhs.bundle, rhs.channel); } friend std::ostream &operator<<(std::ostream &os, const Port &port) { os<< "("; os<< stringifyWireBundle(port.bundle).str(); os<< ": "<< std::to_string(port.channel)<< ")"; return os; } friend llvm::raw_ostream &operator<<(llvm::raw_ostream &os, const Port &port) { os<< to_string(port); return os; } } Port
Definition AIEDialect.h:265
WireBundle getConnectingBundle(WireBundle dir)