MLIR-AIE 1.4.5.dev
AIETargetUcCert.cpp
Go to the documentation of this file.
1//===- AIETargetUcCert.cpp --------------------------------------*- C++ -*-===//
2//
3// Copyright (C) 2025 Advanced Micro Devices, Inc.
4// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
5//
6//===----------------------------------------------------------------------===//
7
9
12
13#include "mlir/Dialect/Func/IR/FuncOps.h"
14#include "mlir/Tools/mlir-translate/MlirTranslateMain.h"
15
16#include "llvm/ADT/ArrayRef.h"
17#include "llvm/ADT/StringSet.h"
18#include "llvm/ADT/TypeSwitch.h"
19#include "llvm/Support/FileSystem.h"
20#include "llvm/Support/Format.h"
21#include "llvm/Support/Path.h"
22
23#include <map>
24#include <vector>
25
26using namespace mlir;
27using namespace xilinx;
28using namespace xilinx::AIE;
29using namespace xilinx::AIEX;
30
31// Emission ordering invariant: CERT jobs, pages, and the ops within a job are
32// emitted in IR/textual order. `job_id` is a per-page-unique label and a
33// firmware job-table key, NOT an execution/emission order (see CertJobOp docs).
34// Within a page the firmware starts jobs in textual order; between pages on a
35// uC execution is strictly sequential. Do not sort jobs/pages by job_id here.
36// (attach_to_group groups are sorted by group_id, which is the uC index.)
37
38namespace {
39
40// NOP
41void emitNop(CertNopOp op, std::string &text) { text += " NOP\n"; }
42
43// MASK_WRITE_32 0x6838000, 0x2, 0x2
44void emitMaskWrite32(CertMaskWrite32Op op, std::string &text) {
45 std::string s;
46 llvm::raw_string_ostream ss(s);
47 ss << " MASK_WRITE_32 ";
48 ss << llvm::format("0x%08x, ", op.getAddress());
49 ss << llvm::format("0x%08x, ", op.getMask());
50 ss << llvm::format("0x%08x\n", op.getValue());
51 text += ss.str();
52}
53
54void emitMaskPoll32(CertMaskPoll32Op op, std::string &text) {
55 std::string s;
56 llvm::raw_string_ostream ss(s);
57 ss << " MASK_POLL_32 ";
58 ss << llvm::format("0x%08x, ", op.getAddress());
59 ss << llvm::format("0x%08x, ", op.getMask());
60 ss << llvm::format("0x%08x\n", op.getValue());
61 text += ss.str();
62}
63
64// WRITE_32 0x01A0634, 0x80000004
65void emitWrite32(CertWrite32Op op, std::string &text) {
66 std::string s;
67 llvm::raw_string_ostream ss(s);
68 ss << " WRITE_32 ";
69 ss << llvm::format("0x%08x, ", op.getAddress());
70 ss << llvm::format("0x%08x\n", op.getValue());
71 text += ss.str();
72}
73
74// uC_DMA_WRITE_DES_SYNC @INPUT_row1_actor6_task6_ucbds
75void emitUcDmaWriteDesSync(CertUcDmaWriteDesSyncOp op, std::string &text) {
76 text += " uC_DMA_WRITE_DES_SYNC ";
77 text += "@" + op.getSymbol().str() + "\n";
78}
79
80// WAIT_TCTS TILE_0_1, MEM_MM2S_0, 1
81void emitWaitTCTS(CertWaitTCTSOp op, std::string &text) {
82 uint32_t tileId = op.getTileId();
83 uint32_t channelId = op.getChannelId();
84 text += " WAIT_TCTS ";
85 text += std::to_string(tileId) + ", ";
86 text += std::to_string(channelId) + ", ";
87 text += std::to_string(op.getTargetTcts()) + "\n";
88}
89
90// APPLY_OFFSET_57 @mem21_bd0, 1, 65535
91void emitApplyOffset57(CertApplyOffset57Op op, std::string &text) {
92 uint16_t num_entries = op.getNumEntries();
93 uint16_t offset = op.getOffset();
94 std::string s;
95 llvm::raw_string_ostream ss(s);
96 ss << " APPLY_OFFSET_57 ";
97 ss << "@" << op.getSymbol().str() << ", ";
98 ss << num_entries << ", ";
99 ss << offset << "\n";
100 text += ss.str();
101}
102
103// LOCAL_BARRIER $lb0, 2
104void emitLocalBarrier(CertLocalBarrierOp op, std::string &text) {
105 text += " LOCAL_BARRIER ";
106 text += "$lb" + std::to_string(op.getLocalBarrierId()) + ", ";
107 text += std::to_string(op.getNumParticipants()) + "\n";
108}
109
110// REMOTE_BARRIER $rb3, 3
111void emitRemoteBarrier(CertRemoteBarrierOp op, std::string &text) {
112 text += " REMOTE_BARRIER ";
113 text += "$rb" + std::to_string(op.getRemoteBarrierId()) + ", ";
114 text += std::to_string(op.getPartyMask()) + "\n";
115}
116
117// REL_ACQ_SYNC 0x0205720c, 0x02057004
118void emitRelAcqSync(CertRelAcqSyncOp op, std::string &text) {
119 std::string s;
120 llvm::raw_string_ostream ss(s);
121 ss << " REL_ACQ_SYNC ";
122 ss << llvm::format("0x%08x, ", op.getRelAddress());
123 ss << llvm::format("0x%08x\n", op.getAcqAddress());
124 text += ss.str();
125}
126
127// LOAD_PDI 1, @device_config
128void emitLoadPdi(CertLoadPdiOp op, std::string &text) {
129 text += " LOAD_PDI ";
130 text += std::to_string(op.getPdiId()) + ", ";
131 text += "@" + op.getSymbol().str() + "\n";
132}
133
134// PREEMPT 0x0001, @save, @restore
135void emitPreempt(CertPreemptOp op, std::string &text) {
136 std::string s;
137 llvm::raw_string_ostream ss(s);
138 ss << " PREEMPT ";
139 ss << llvm::format("0x%04x, ", op.getId());
140 ss << "@" << op.getSaveSection().str() << ", ";
141 ss << "@" << op.getRestoreSection().str() << "\n";
142 text += ss.str();
143}
144
145// START_JOB 0
146// <body>
147// END_JOB
148LogicalResult emitJob(CertJobOp jobOp, std::string &text, std::string &data) {
149 LogicalResult result = success();
150
151 text += "START_JOB ";
152 text += std::to_string(jobOp.getJobId()) + "\n";
153
154 for (auto &o : jobOp.getBody().front()) {
155 llvm::TypeSwitch<Operation *>(&o)
156 .Case<CertApplyOffset57Op>(
157 [&](auto op) { emitApplyOffset57(op, text); })
158 .Case<CertLocalBarrierOp>([&](auto op) { emitLocalBarrier(op, text); })
159 .Case<CertLoadPdiOp>([&](auto op) { emitLoadPdi(op, text); })
160 .Case<CertPreemptOp>([&](auto op) { emitPreempt(op, text); })
161 .Case<CertRemoteBarrierOp>(
162 [&](auto op) { emitRemoteBarrier(op, text); })
163 .Case<CertRelAcqSyncOp>([&](auto op) { emitRelAcqSync(op, text); })
164 .Case<CertMaskWrite32Op>([&](auto op) { emitMaskWrite32(op, text); })
165 .Case<CertMaskPoll32Op>([&](auto op) { emitMaskPoll32(op, text); })
166 .Case<CertNopOp>([&](auto op) { emitNop(op, text); })
167 .Case<CertUcDmaWriteDesSyncOp>(
168 [&](auto op) { emitUcDmaWriteDesSync(op, text); })
169 .Case<CertWaitTCTSOp>([&](auto op) { emitWaitTCTS(op, text); })
170 .Case<CertWrite32Op>([&](auto op) { emitWrite32(op, text); })
171 .Case<AIE::EndOp>([](auto) { /* implicit region terminator, skip */ })
172 .Default([&](Operation *op) {
173 op->emitError("Unsupported operation in CertJobOp");
174 result = failure();
175 });
176 }
177
178 text += "END_JOB\n\n";
179 return result;
180}
181
182LogicalResult emitJobs(llvm::SmallVector<CertJobOp> &jobs, std::string &text,
183 std::string &data) {
184 LogicalResult result = success();
185
186 // Emit jobs in IR/textual order. job_id is a per-page-unique label, not an
187 // execution/emission order (the firmware treats it as a job-table key and
188 // schedules jobs on a page cooperatively, starting them in textual order).
189 for (auto job : jobs) {
190 if (failed(emitJob(job, text, data)))
191 result = failure();
192 text += ".eop\n\n";
193 }
194 return result;
195}
196
197// .align 16
198// <page contents>
199// .eop
200LogicalResult emitPage(CertPageOp pageOp, std::string &text,
201 std::string &data) {
202 LogicalResult result = success();
203 text += ".align 16\n";
204
205 // Process jobs within the page in IR/textual order. Textual order is the
206 // execution order the firmware relies on within a page (jobs are started in
207 // textual order); do not reorder by job_id.
208 for (auto job : pageOp.getBody().getOps<CertJobOp>()) {
209 if (failed(emitJob(job, text, data)))
210 result = failure();
211 }
212
213 text += ".eop\n\n";
214 return result;
215}
216
217// label:
218// .include section_file.asm
219// .endl label
220//
221// The section content is emitted to a separate file
222LogicalResult emitSection(CertSectionOp sectionOp, std::string &text,
223 llvm::StringRef outputPath) {
224 LogicalResult result = success();
225 std::string sectionName = sectionOp.getSymName().str();
226 std::string sectionFileName = sectionName + ".asm";
227
228 // Build the section content in separate strings
229 std::string sectionText;
230 std::string sectionData;
231
232 // Process pages within the section
233 for (auto page : sectionOp.getBody().getOps<CertPageOp>()) {
234 if (failed(emitPage(page, sectionText, sectionData)))
235 result = failure();
236 }
237
238 // Process standalone jobs (not inside pages) for backwards compatibility
239 auto jobs =
240 llvm::to_vector_of<CertJobOp>(sectionOp.getBody().getOps<CertJobOp>());
241 for (auto job : jobs) {
242 if (failed(emitJob(job, sectionText, sectionData)))
243 result = failure();
244 sectionText += ".eop\n\n";
245 }
246
247 // Write section content to separate file
248 std::string sectionFilePath;
249 if (!outputPath.empty()) {
250 llvm::SmallString<128> path(outputPath);
251 llvm::sys::path::remove_filename(path);
252 llvm::sys::path::append(path, sectionFileName);
253 sectionFilePath = path.str().str();
254 } else {
255 sectionFilePath = sectionFileName;
256 }
257
258 std::error_code EC;
259 llvm::raw_fd_ostream sectionFile(sectionFilePath, EC);
260 if (EC) {
261 // Don't emit a .include directive pointing at a file we failed to write.
262 return sectionOp.emitError("failed to open section file '")
263 << sectionFilePath << "': " << EC.message();
264 }
265
266 // Emit text section
267 if (!sectionText.empty()) {
268 sectionFile << ";\n;text\n;\n";
269 sectionFile << sectionText;
270 }
271
272 // Emit EOF before data section
273 sectionFile << "EOF\n\n";
274
275 // Emit data section
276 if (!sectionData.empty()) {
277 sectionFile << ";\n;data\n;\n";
278 sectionFile << sectionData;
279 }
280
281 sectionFile.close();
282 if (sectionFile.has_error()) {
283 std::error_code writeEC = sectionFile.error();
284 // Clear the error so the raw_fd_ostream destructor doesn't report_fatal.
285 sectionFile.clear_error();
286 return sectionOp.emitError("failed to write section file '")
287 << sectionFilePath << "': " << writeEC.message();
288 }
289
290 // In main file, emit label with .include directive
291 text += sectionName + ":\n";
292 text += ".include " + sectionFileName + "\n";
293 text += ".endl " + sectionName + "\n\n";
294 return result;
295}
296
297LogicalResult emitAttachToGroupOp(CertAttachToGroupOp groupOp,
298 std::string &text, std::string &data,
299 llvm::StringRef outputPath) {
300 LogicalResult result = success();
301
302 // Process sections first (they may be referenced by main code)
303 for (auto section : groupOp.getBody().getOps<CertSectionOp>()) {
304 if (failed(emitSection(section, text, outputPath)))
305 result = failure();
306 }
307
308 if (groupOp.getGroupId())
309 text += ".attach_to_group " + std::to_string(groupOp.getGroupId()) + "\n\n";
310
311 // Process pages (if any)
312 for (auto page : groupOp.getBody().getOps<CertPageOp>()) {
313 if (failed(emitPage(page, text, data)))
314 result = failure();
315 }
316
317 // Process standalone jobs (not inside pages) for backwards compatibility
318 auto jobs =
319 llvm::to_vector_of<CertJobOp>(groupOp.getBody().getOps<CertJobOp>());
320 if (failed(emitJobs(jobs, text, data)))
321 result = failure();
322 return result;
323}
324
325void emitUcDmaBdData(CertUcDmaBdOp op, std::string &data,
326 llvm::StringSet<> &emittedGlobals) {
327 // lookup data from operation
328 auto dataSymbol = op.getRemoteAddress();
329 if (emittedGlobals.contains(dataSymbol))
330 return;
331
332 auto global = dyn_cast_if_present<memref::GlobalOp>(
333 op->getParentOfType<AIE::DeviceOp>().lookupSymbol(dataSymbol));
334 if (!global) {
335 op.emitError("Global symbol not found");
336 return;
337 }
338
339 auto initVal = global.getInitialValue();
340 if (!initVal) {
341 op.emitError("Global symbol has no initial value");
342 return;
343 }
344
345 auto initData = dyn_cast<DenseIntElementsAttr>(*initVal);
346 if (!initData) {
347 op.emitError("Global symbol initial value is not a dense int array");
348 return;
349 }
350
351 // data0:
352 // .long 0x00005A00
353 std::string s;
354 llvm::raw_string_ostream ss(s);
355 ss << " .align 16\n";
356 ss << dataSymbol.str() << ":\n";
357 for (auto d : initData)
358 ss << llvm::format(" .long 0x%08x\n", d.getZExtValue());
359 data += ss.str();
360 emittedGlobals.insert(dataSymbol);
361}
362
363// UC_DMA_BD 0, 0x001A05C0, @data, 8, 0, 1
364void emitUcDmaBd(CertUcDmaBdOp op, std::string &chains, std::string &data,
365 llvm::StringSet<> &emittedGlobals) {
366 std::string s;
367 llvm::raw_string_ostream ss(s);
368 ss << " UC_DMA_BD 0, ";
369 ss << llvm::format("0x%08x, ", op.getLocalAddress());
370 ss << "@" + op.getRemoteAddress().str() + ", ";
371 ss << op.getLength() << ", ";
372 ss << "0, ";
373 ss << (op.getNextBd() ? "1\n" : "0\n");
374 chains += ss.str();
375 emitUcDmaBdData(op, data, emittedGlobals);
376}
377
378// .align 16
379// name_of_chain:
380// UC_DMA_BD 0, 0x001A05C0, @data0, 8, 0, 1
381void emitUcDmaChain(CertUcDmaChainOp op, std::string &chains, std::string &data,
382 llvm::StringSet<> &emittedGlobals) {
383 chains += " .align 16\n";
384 chains += op.getName().str() + ":\n";
385 for (auto &o : op.getBody().front()) {
386 llvm::TypeSwitch<Operation *>(&o).Case<CertUcDmaBdOp>(
387 [&](auto op) { emitUcDmaBd(op, chains, data, emittedGlobals); });
388 }
389}
390
391} // namespace
392
393LogicalResult xilinx::AIE::AIETranslateToUcDma(ModuleOp module,
394 std::string &assembly,
395 llvm::StringRef outputPath) {
396
397 DeviceOp deviceOp = *module.getOps<DeviceOp>().begin();
398
399 std::vector<std::string> text;
400 std::vector<std::string> data;
401 std::vector<std::string> chains;
402 llvm::StringSet<> emittedGlobals;
403
404 text.emplace_back("\n;\n; Code\n;\n\n");
405 data.emplace_back("\n;\n; Data\n;\n\n");
406 chains.emplace_back("\n;\n; Data (chains)\n;\n\n");
407
408 auto &tm = deviceOp.getTargetModel();
409 text[0] += ".partition " + std::to_string(tm.columns()) + "column\n";
410
411 // Process sections first
412 text[0] += ".attach_to_group 0\n\n";
413
414 LogicalResult result = success();
415
416 // Emit each microcontroller's (uC / group) content as its own ordered stream,
417 // keyed by the *actual* group id. Group 0 (device-level content plus any
418 // attach_to_group(0)) shares block 0; every other uC id gets its own
419 // code/data/EOF block (ops sharing a non-zero id share a block).
420 std::map<uint32_t, size_t> groupIdToBlock;
421 groupIdToBlock[0] = 0; // device-level content lives in block 0
422 auto blockFor = [&](uint32_t gid) -> size_t {
423 auto it = groupIdToBlock.find(gid);
424 if (it != groupIdToBlock.end())
425 return it->second;
426 text.emplace_back("\n;\n; Code\n;\n\n");
427 data.emplace_back("\n;\n; Data\n;\n\n");
428 chains.emplace_back("\n;\n; Data (chains)\n;\n\n");
429 size_t block = text.size() - 1;
430 groupIdToBlock[gid] = block;
431 return block;
432 };
433
434 // Sections are hoisted ahead of the executable stream: they are labeled
435 // `.include` blocks, not pages, and the code below them may reference them
436 // (e.g. `LOAD_PDI 1, @config_device`), so their labels must already be
437 // defined. `emitAttachToGroupOp` hoists a group's sections the same way.
438 for (auto section : deviceOp.getBody()->getOps<CertSectionOp>()) {
439 if (failed(emitSection(section, text[0], outputPath)))
440 result = failure();
441 }
442
443 // Preserve encounter order: emitting all bare pages first could put a release
444 // after its WAIT_TCTS consumer on controller 0.
445 for (Operation &op : *deviceOp.getBody()) {
446 if (auto page = dyn_cast<CertPageOp>(&op)) {
447 if (failed(emitPage(page, text[0], data[0])))
448 result = failure();
449 } else if (auto job = dyn_cast<CertJobOp>(&op)) {
450 if (failed(emitJob(job, text[0], data[0])))
451 result = failure();
452 } else if (auto grp = dyn_cast<CertAttachToGroupOp>(&op)) {
453 size_t block = blockFor(grp.getGroupId());
454 if (failed(
455 emitAttachToGroupOp(grp, text[block], data[block], outputPath)))
456 result = failure();
457 } else if (auto chain = dyn_cast<CertUcDmaChainOp>(&op)) {
458 emitUcDmaChain(chain, chains[0], data[0], emittedGlobals);
459 }
460 }
461
462 if (failed(result))
463 return result;
464
465 // Emit blocks in group-id order (block 0 = uC 0 first). Each block is a
466 // self-contained stream: code + EOF, then its chains and data.
467 for (auto const &entry : groupIdToBlock) {
468 size_t block = entry.second;
469 if (!text[block].empty()) {
470 assembly += text[block];
471 // Add final EOF after all code (sections emit their own EOF before .endl)
472 assembly += "EOF\n";
473 }
474 if (!chains[block].empty())
475 assembly += chains[block];
476 if (!data[block].empty())
477 assembly += "\n" + data[block];
478 }
479 return success();
480}
481
482LogicalResult xilinx::AIE::AIETranslateToUcDma(ModuleOp module,
483 raw_ostream &output) {
484 std::string assembly;
485
486 // Section files will be created in the current directory
487 // since we can't reliably extract the output path from raw_ostream
488 llvm::StringRef outputPath = "";
489
490 auto r = AIETranslateToUcDma(module, assembly, outputPath);
491 if (failed(r))
492 return r;
493 output << assembly;
494 return success();
495}
mlir::LogicalResult AIETranslateToUcDma(mlir::ModuleOp module, llvm::raw_ostream &output)