MLIR-AIE 1.4.6.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/CommandLine.h"
20#include "llvm/Support/FileSystem.h"
21#include "llvm/Support/Format.h"
22#include "llvm/Support/Path.h"
23
24#include <map>
25#include <set>
26#include <vector>
27
28using namespace mlir;
29using namespace xilinx;
30using namespace xilinx::AIE;
31using namespace xilinx::AIEX;
32
33// Emission ordering invariant: CERT jobs, pages, and the ops within a job are
34// emitted in IR/textual order. `job_id` is a per-page-unique label and a
35// firmware job-table key, NOT an execution/emission order (see CertJobOp docs).
36// Within a page the firmware starts jobs in textual order; between pages on a
37// uC execution is strictly sequential. Do not sort jobs/pages by job_id here.
38// (attach_to_group groups are sorted by group_id, which is the uC index.)
39
40namespace {
41
42// NOP
43void emitNop(CertNopOp op, std::string &text) { text += " NOP\n"; }
44
45// MASK_WRITE_32 0x6838000, 0x2, 0x2
46void emitMaskWrite32(CertMaskWrite32Op op, std::string &text) {
47 std::string s;
48 llvm::raw_string_ostream ss(s);
49 ss << " MASK_WRITE_32 ";
50 ss << llvm::format("0x%08x, ", op.getAddress());
51 ss << llvm::format("0x%08x, ", op.getMask());
52 ss << llvm::format("0x%08x\n", op.getValue());
53 text += ss.str();
54}
55
56void emitMaskPoll32(CertMaskPoll32Op op, std::string &text) {
57 std::string s;
58 llvm::raw_string_ostream ss(s);
59 ss << " MASK_POLL_32 ";
60 ss << llvm::format("0x%08x, ", op.getAddress());
61 ss << llvm::format("0x%08x, ", op.getMask());
62 ss << llvm::format("0x%08x\n", op.getValue());
63 text += ss.str();
64}
65
66// WRITE_32 0x01A0634, 0x80000004
67void emitWrite32(CertWrite32Op op, std::string &text) {
68 std::string s;
69 llvm::raw_string_ostream ss(s);
70 ss << " WRITE_32 ";
71 ss << llvm::format("0x%08x, ", op.getAddress());
72 ss << llvm::format("0x%08x\n", op.getValue());
73 text += ss.str();
74}
75
76// uC_DMA_WRITE_DES_SYNC @INPUT_row1_actor6_task6_ucbds
77void emitUcDmaWriteDesSync(CertUcDmaWriteDesSyncOp op, std::string &text) {
78 text += " uC_DMA_WRITE_DES_SYNC ";
79 text += "@" + op.getSymbol().str() + "\n";
80}
81
82// READ_32 $r1, 0x00001000
83void emitRead32(CertRead32Op op, std::string &text) {
84 std::string s;
85 llvm::raw_string_ostream ss(s);
86 ss << " READ_32 $r" << op.getValue() << ", ";
87 ss << llvm::format("0x%08x\n", op.getAddress());
88 text += ss.str();
89}
90
91// READ_32_D $r0, $r1
92void emitRead32D(CertRead32DOp op, std::string &text) {
93 text += " READ_32_D $r" + std::to_string(op.getAddress()) +
94 ", $r" + std::to_string(op.getValue()) + "\n";
95}
96
97// WRITE_32_D 1, 0x00000003, 0x00000005
98// The flags byte has bit 0 set when the address is a register index and bit 1
99// when the value is.
100void emitWrite32D(CertWrite32DOp op, std::string &text) {
101 unsigned flags =
102 (op.getAddressIsReg() ? 1 : 0) | (op.getValueIsReg() ? 2 : 0);
103 std::string s;
104 llvm::raw_string_ostream ss(s);
105 ss << " WRITE_32_D " << flags << ", ";
106 ss << llvm::format("0x%08x, ", op.getAddress());
107 ss << llvm::format("0x%08x\n", op.getValue());
108 text += ss.str();
109}
110
111// SAVE_TIMESTAMPS 7
112void emitSaveTimestamps(CertSaveTimestampsOp op, std::string &text) {
113 text += " SAVE_TIMESTAMPS " + std::to_string(op.getUnqId()) + "\n";
114}
115
116// SAVE_REGISTER 0x00001000, 9
117void emitSaveRegister(CertSaveRegisterOp op, std::string &text) {
118 std::string s;
119 llvm::raw_string_ostream ss(s);
120 ss << " SAVE_REGISTER ";
121 ss << llvm::format("0x%08x, ", op.getAddress());
122 ss << op.getUnqId() << "\n";
123 text += ss.str();
124}
125
126// UC_DMA_WRITE_DES $r2, @chain0
127void emitUcDmaWriteDes(CertUcDmaWriteDesOp op, std::string &text) {
128 text += " UC_DMA_WRITE_DES $r" + std::to_string(op.getWaitHandle()) +
129 ", @" + op.getSymbol().str() + "\n";
130}
131
132// WAIT_UC_DMA $r2
133void emitWaitUcDma(CertWaitUcDmaOp op, std::string &text) {
134 text +=
135 " WAIT_UC_DMA $r" + std::to_string(op.getWaitHandle()) + "\n";
136}
137
138// WAIT_TCTS TILE_0_1, MEM_MM2S_0, 1
139void emitWaitTCTS(CertWaitTCTSOp op, std::string &text) {
140 uint32_t tileId = op.getTileId();
141 uint32_t channelId = op.getChannelId();
142 text += " WAIT_TCTS ";
143 text += std::to_string(tileId) + ", ";
144 text += std::to_string(channelId) + ", ";
145 text += std::to_string(op.getTargetTcts()) + "\n";
146}
147
148// APPLY_OFFSET_57 @mem21_bd0, 1, 65535
149void emitApplyOffset57(CertApplyOffset57Op op, std::string &text) {
150 uint16_t num_entries = op.getNumEntries();
151 uint16_t offset = op.getOffset();
152 std::string s;
153 llvm::raw_string_ostream ss(s);
154 ss << " APPLY_OFFSET_57 ";
155 ss << "@" << op.getSymbol().str() << ", ";
156 ss << num_entries << ", ";
157 ss << offset << "\n";
158 text += ss.str();
159}
160
161// LOCAL_BARRIER $lb0, 2
162void emitLocalBarrier(CertLocalBarrierOp op, std::string &text) {
163 text += " LOCAL_BARRIER ";
164 text +=
165 "$" + stringifyCertLocalBarrierId(op.getLocalBarrierId()).str() + ", ";
166 text += std::to_string(op.getNumParticipants()) + "\n";
167}
168
169// REMOTE_BARRIER $rb3, 3
170void emitRemoteBarrier(CertRemoteBarrierOp op, std::string &text) {
171 text += " REMOTE_BARRIER ";
172 text +=
173 "$" + stringifyCertRemoteBarrierId(op.getRemoteBarrierId()).str() + ", ";
174 text += std::to_string(op.getPartyMask()) + "\n";
175}
176
177// REL_ACQ_SYNC 0x0205720c, 0x02057004
178void emitRelAcqSync(CertRelAcqSyncOp op, std::string &text) {
179 std::string s;
180 llvm::raw_string_ostream ss(s);
181 ss << " REL_ACQ_SYNC ";
182 ss << llvm::format("0x%08x, ", op.getRelAddress());
183 ss << llvm::format("0x%08x\n", op.getAcqAddress());
184 text += ss.str();
185}
186
187// LOAD_PDI 1, @device_config
188void emitLoadPdi(CertLoadPdiOp op, std::string &text) {
189 text += " LOAD_PDI ";
190 text += std::to_string(op.getPdiId()) + ", ";
191 text += "@" + op.getSymbol().str() + "\n";
192}
193
194// PREEMPT 0x0001, @save, @restore
195void emitPreempt(CertPreemptOp op, std::string &text) {
196 std::string s;
197 llvm::raw_string_ostream ss(s);
198 ss << " PREEMPT ";
199 ss << llvm::format("0x%04x, ", op.getId());
200 ss << "@" << op.getSaveSection().str() << ", ";
201 ss << "@" << op.getRestoreSection().str() << "\n";
202 text += ss.str();
203}
204
205// START_JOB 0
206// <body>
207// END_JOB
208LogicalResult emitJob(CertJobOp jobOp, std::string &text, std::string &data) {
209 LogicalResult result = success();
210
211 text += "START_JOB ";
212 text += std::to_string(jobOp.getJobId()) + "\n";
213
214 for (auto &o : jobOp.getBody().front()) {
215 llvm::TypeSwitch<Operation *>(&o)
216 .Case<CertApplyOffset57Op>(
217 [&](auto op) { emitApplyOffset57(op, text); })
218 .Case<CertLocalBarrierOp>([&](auto op) { emitLocalBarrier(op, text); })
219 .Case<CertLoadPdiOp>([&](auto op) { emitLoadPdi(op, text); })
220 .Case<CertPreemptOp>([&](auto op) { emitPreempt(op, text); })
221 .Case<CertRemoteBarrierOp>(
222 [&](auto op) { emitRemoteBarrier(op, text); })
223 .Case<CertRelAcqSyncOp>([&](auto op) { emitRelAcqSync(op, text); })
224 .Case<CertMaskWrite32Op>([&](auto op) { emitMaskWrite32(op, text); })
225 .Case<CertMaskPoll32Op>([&](auto op) { emitMaskPoll32(op, text); })
226 .Case<CertNopOp>([&](auto op) { emitNop(op, text); })
227 .Case<CertUcDmaWriteDesSyncOp>(
228 [&](auto op) { emitUcDmaWriteDesSync(op, text); })
229 .Case<CertUcDmaWriteDesOp>(
230 [&](auto op) { emitUcDmaWriteDes(op, text); })
231 .Case<CertWaitUcDmaOp>([&](auto op) { emitWaitUcDma(op, text); })
232 .Case<CertRead32Op>([&](auto op) { emitRead32(op, text); })
233 .Case<CertRead32DOp>([&](auto op) { emitRead32D(op, text); })
234 .Case<CertWrite32DOp>([&](auto op) { emitWrite32D(op, text); })
235 .Case<CertSaveTimestampsOp>(
236 [&](auto op) { emitSaveTimestamps(op, text); })
237 .Case<CertSaveRegisterOp>([&](auto op) { emitSaveRegister(op, text); })
238 .Case<CertWaitTCTSOp>([&](auto op) { emitWaitTCTS(op, text); })
239 .Case<CertWrite32Op>([&](auto op) { emitWrite32(op, text); })
240 .Case<AIE::EndOp>([](auto) { /* implicit region terminator, skip */ })
241 .Default([&](Operation *op) {
242 op->emitError("Unsupported operation in CertJobOp");
243 result = failure();
244 });
245 }
246
247 text += "END_JOB\n\n";
248 return result;
249}
250
251LogicalResult emitJobs(llvm::SmallVector<CertJobOp> &jobs, std::string &text,
252 std::string &data) {
253 LogicalResult result = success();
254
255 // Emit jobs in IR/textual order. job_id is a per-page-unique label, not an
256 // execution/emission order (the firmware treats it as a job-table key and
257 // schedules jobs on a page cooperatively, starting them in textual order).
258 for (auto job : jobs) {
259 if (failed(emitJob(job, text, data)))
260 result = failure();
261 text += ".eop\n\n";
262 }
263 return result;
264}
265
266// .align 16
267// <page contents>
268// .eop
269LogicalResult emitPage(CertPageOp pageOp, std::string &text,
270 std::string &data) {
271 LogicalResult result = success();
272 text += ".align 16\n";
273
274 // Process jobs within the page in IR/textual order. Textual order is the
275 // execution order the firmware relies on within a page (jobs are started in
276 // textual order); do not reorder by job_id.
277 for (auto job : pageOp.getBody().getOps<CertJobOp>()) {
278 if (failed(emitJob(job, text, data)))
279 result = failure();
280 }
281
282 text += ".eop\n\n";
283 return result;
284}
285
286// label:
287// .include section_file.asm
288// .endl label
289//
290// The section content is emitted to a separate file
291LogicalResult emitUcDmaChain(CertUcDmaChainOp op, std::string &chains,
292 std::string &data,
293 llvm::StringSet<> &emittedGlobals);
294LogicalResult emitGlobalData(Operation *op, StringRef dataSymbol,
295 std::string &data,
296 llvm::StringSet<> &emittedGlobals);
297
298StringRef referencedChain(Operation *op) {
299 if (auto write = dyn_cast<CertUcDmaWriteDesSyncOp>(op))
300 return write.getSymbol();
301 if (auto write = dyn_cast<CertUcDmaWriteDesOp>(op))
302 return write.getSymbol();
303 return {};
304}
305
306LogicalResult emitSection(CertSectionOp sectionOp, std::string &text,
307 llvm::StringRef outputPath) {
308 LogicalResult result = success();
309 std::string sectionName = sectionOp.getSymName().str();
310 std::string sectionFileName = sectionName + ".asm";
311
312 // Build the section content in separate strings
313 std::string sectionText;
314 std::string sectionData;
315 std::string sectionChains;
316 llvm::StringSet<> emittedChains;
317 llvm::StringSet<> emittedGlobals;
318 auto device = sectionOp->getParentOfType<DeviceOp>();
319 // AIEBU resolves labels within the including file's scope. Each section
320 // needs its own chain and payload copies, even when another section shares
321 // them.
322 sectionOp.walk([&](Operation *op) {
323 if (auto patch = dyn_cast<CertApplyOffset57Op>(op)) {
324 if (failed(emitGlobalData(op, patch.getSymbol(), sectionData,
325 emittedGlobals)))
326 result = failure();
327 return;
328 }
329 StringRef name = referencedChain(op);
330 if (name.empty() || !emittedChains.insert(name).second)
331 return;
332 auto chain =
333 dyn_cast_if_present<CertUcDmaChainOp>(device.lookupSymbol(name));
334 if (!chain) {
335 op->emitError("uC DMA chain symbol not found: ") << name;
336 result = failure();
337 } else if (failed(emitUcDmaChain(chain, sectionChains, sectionData,
338 emittedGlobals))) {
339 result = failure();
340 }
341 });
342 if (failed(result))
343 return result;
344
345 // Process pages within the section
346 for (auto page : sectionOp.getBody().getOps<CertPageOp>()) {
347 if (failed(emitPage(page, sectionText, sectionData)))
348 result = failure();
349 }
350
351 // Process standalone jobs (not inside pages) for backwards compatibility
352 auto jobs =
353 llvm::to_vector_of<CertJobOp>(sectionOp.getBody().getOps<CertJobOp>());
354 for (auto job : jobs) {
355 if (failed(emitJob(job, sectionText, sectionData)))
356 result = failure();
357 sectionText += ".eop\n\n";
358 }
359
360 // Write section content to separate file
361 std::string sectionFilePath;
362 if (!outputPath.empty()) {
363 llvm::SmallString<128> path(outputPath);
364 llvm::sys::path::remove_filename(path);
365 llvm::sys::path::append(path, sectionFileName);
366 sectionFilePath = path.str().str();
367 } else {
368 sectionFilePath = sectionFileName;
369 }
370
371 std::error_code EC;
372 llvm::raw_fd_ostream sectionFile(sectionFilePath, EC);
373 if (EC) {
374 // Don't emit a .include directive pointing at a file we failed to write.
375 return sectionOp.emitError("failed to open section file '")
376 << sectionFilePath << "': " << EC.message();
377 }
378
379 // Emit text section
380 if (!sectionText.empty()) {
381 sectionFile << ";\n;text\n;\n";
382 sectionFile << sectionText;
383 }
384
385 // Emit EOF before data section
386 sectionFile << "EOF\n\n";
387 sectionFile << sectionChains;
388
389 // Emit data section
390 if (!sectionData.empty()) {
391 sectionFile << ";\n;data\n;\n";
392 sectionFile << sectionData;
393 }
394
395 sectionFile.close();
396 if (sectionFile.has_error()) {
397 std::error_code writeEC = sectionFile.error();
398 // Clear the error so the raw_fd_ostream destructor doesn't report_fatal.
399 sectionFile.clear_error();
400 return sectionOp.emitError("failed to write section file '")
401 << sectionFilePath << "': " << writeEC.message();
402 }
403
404 // In main file, emit label with .include directive
405 text += sectionName + ":\n";
406 text += ".include " + sectionFileName + "\n";
407 text += ".endl " + sectionName + "\n\n";
408 return result;
409}
410
411LogicalResult emitAttachToGroupOp(CertAttachToGroupOp groupOp,
412 std::string &text, std::string &data,
413 llvm::StringRef outputPath) {
414 LogicalResult result = success();
415
416 // Process sections first (they may be referenced by main code)
417 for (auto section : groupOp.getBody().getOps<CertSectionOp>()) {
418 if (failed(emitSection(section, text, outputPath)))
419 result = failure();
420 }
421
422 if (groupOp.getGroupId())
423 text += ".attach_to_group " + std::to_string(groupOp.getGroupId()) + "\n\n";
424
425 // Process pages (if any)
426 for (auto page : groupOp.getBody().getOps<CertPageOp>()) {
427 if (failed(emitPage(page, text, data)))
428 result = failure();
429 }
430
431 // Process standalone jobs (not inside pages) for backwards compatibility
432 auto jobs =
433 llvm::to_vector_of<CertJobOp>(groupOp.getBody().getOps<CertJobOp>());
434 if (failed(emitJobs(jobs, text, data)))
435 result = failure();
436 return result;
437}
438
439LogicalResult emitGlobalData(Operation *op, StringRef dataSymbol,
440 std::string &data,
441 llvm::StringSet<> &emittedGlobals) {
442 if (emittedGlobals.contains(dataSymbol))
443 return success();
444
445 auto global = dyn_cast_if_present<memref::GlobalOp>(
446 op->getParentOfType<AIE::DeviceOp>().lookupSymbol(dataSymbol));
447 if (!global) {
448 return op->emitError("Global symbol not found");
449 }
450
451 auto initVal = global.getInitialValue();
452 if (!initVal) {
453 return op->emitError("Global symbol has no initial value");
454 }
455
456 auto initData = dyn_cast<DenseIntElementsAttr>(*initVal);
457 if (!initData) {
458 return op->emitError(
459 "Global symbol initial value is not a dense int array");
460 }
461
462 // data0:
463 // .long 0x00005A00
464 std::string s;
465 llvm::raw_string_ostream ss(s);
466 ss << " .align 16\n";
467 ss << dataSymbol.str() << ":\n";
468 for (auto d : initData)
469 ss << llvm::format(" .long 0x%08x\n", d.getZExtValue());
470 data += ss.str();
471 emittedGlobals.insert(dataSymbol);
472 return success();
473}
474
475// UC_DMA_BD 0, 0x001A05C0, @data, 8, 0, 1
476LogicalResult emitUcDmaBd(CertUcDmaBdOp op, std::string &chains,
477 std::string &data,
478 llvm::StringSet<> &emittedGlobals) {
479 std::string s;
480 llvm::raw_string_ostream ss(s);
481 ss << " UC_DMA_BD 0, ";
482 ss << llvm::format("0x%08x, ", op.getLocalAddress());
483 ss << "@" + op.getRemoteAddress().str() + ", ";
484 ss << op.getLength() << ", ";
485 ss << "0, ";
486 ss << (op.getNextBd() ? "1\n" : "0\n");
487 chains += ss.str();
488 return emitGlobalData(op, op.getRemoteAddress(), data, emittedGlobals);
489}
490
491// .align 16
492// name_of_chain:
493// UC_DMA_BD 0, 0x001A05C0, @data0, 8, 0, 1
494LogicalResult emitUcDmaChain(CertUcDmaChainOp op, std::string &chains,
495 std::string &data,
496 llvm::StringSet<> &emittedGlobals) {
497 chains += " .align 16\n";
498 chains += op.getName().str() + ":\n";
499 for (auto &o : op.getBody().front()) {
500 if (auto bd = dyn_cast<CertUcDmaBdOp>(&o))
501 if (failed(emitUcDmaBd(bd, chains, data, emittedGlobals)))
502 return failure();
503 }
504 return success();
505}
506
507} // namespace
508
509LogicalResult xilinx::AIE::AIETranslateToUcDma(ModuleOp module,
510 std::string &assembly,
511 llvm::StringRef outputPath) {
512
513 DeviceOp deviceOp = *module.getOps<DeviceOp>().begin();
514
515 std::vector<std::string> text;
516 std::vector<std::string> data;
517 std::vector<std::string> chains;
518 std::map<uint32_t, llvm::StringSet<>> emittedGlobals;
519
520 text.emplace_back("\n;\n; Code\n;\n\n");
521 data.emplace_back("\n;\n; Data\n;\n\n");
522 chains.emplace_back("\n;\n; Data (chains)\n;\n\n");
523
524 auto &tm = deviceOp.getTargetModel();
525 text[0] += ".partition " + std::to_string(tm.columns()) + "column\n";
526
527 // Process sections first
528 text[0] += ".attach_to_group 0\n\n";
529
530 LogicalResult result = success();
531
532 // Emit each microcontroller's (uC / group) content as its own ordered stream,
533 // keyed by the *actual* group id. Group 0 (device-level content plus any
534 // attach_to_group(0)) shares block 0; every other uC id gets its own
535 // code/data/EOF block (ops sharing a non-zero id share a block).
536 std::map<uint32_t, size_t> groupIdToBlock;
537 groupIdToBlock[0] = 0; // device-level content lives in block 0
538 auto blockFor = [&](uint32_t gid) -> size_t {
539 auto it = groupIdToBlock.find(gid);
540 if (it != groupIdToBlock.end())
541 return it->second;
542 text.emplace_back("\n;\n; Code\n;\n\n");
543 data.emplace_back("\n;\n; Data\n;\n\n");
544 chains.emplace_back("\n;\n; Data (chains)\n;\n\n");
545 size_t block = text.size() - 1;
546 groupIdToBlock[gid] = block;
547 return block;
548 };
549
550 // AIEBU resolves labels within each controller's block. Record every group
551 // that needs a device-level chain; sections emit their own copies.
552 llvm::StringSet<> sectionReferences;
553 std::map<std::string, std::set<uint32_t>> chainUsers;
554 deviceOp.walk([&](Operation *op) {
555 StringRef name = referencedChain(op);
556 auto patch = dyn_cast<CertApplyOffset57Op>(op);
557 if (name.empty() && !patch)
558 return;
559 if (op->getParentOfType<CertSectionOp>()) {
560 if (!name.empty())
561 sectionReferences.insert(name);
562 return;
563 }
564 auto group = op->getParentOfType<CertAttachToGroupOp>();
565 uint32_t groupId = group ? group.getGroupId() : 0;
566 if (patch) {
567 size_t block = blockFor(groupId);
568 if (failed(emitGlobalData(op, patch.getSymbol(), data[block],
569 emittedGlobals[groupId])))
570 result = failure();
571 return;
572 }
573 if (!deviceOp.lookupSymbol<CertUcDmaChainOp>(name)) {
574 op->emitError("uC DMA chain symbol not found: ") << name;
575 result = failure();
576 return;
577 }
578 chainUsers[name.str()].insert(groupId);
579 });
580
581 // Sections are hoisted ahead of the executable stream: they are labeled
582 // `.include` blocks, not pages, and the code below them may reference them
583 // (e.g. `LOAD_PDI 1, @config_device`), so their labels must already be
584 // defined. `emitAttachToGroupOp` hoists a group's sections the same way.
585 for (auto section : deviceOp.getBody()->getOps<CertSectionOp>()) {
586 if (failed(emitSection(section, text[0], outputPath)))
587 result = failure();
588 }
589
590 // Preserve encounter order: emitting all bare pages first could put a release
591 // after its WAIT_TCTS consumer on controller 0.
592 for (Operation &op : *deviceOp.getBody()) {
593 if (auto page = dyn_cast<CertPageOp>(&op)) {
594 if (failed(emitPage(page, text[0], data[0])))
595 result = failure();
596 } else if (auto job = dyn_cast<CertJobOp>(&op)) {
597 if (failed(emitJob(job, text[0], data[0])))
598 result = failure();
599 } else if (auto grp = dyn_cast<CertAttachToGroupOp>(&op)) {
600 size_t block = blockFor(grp.getGroupId());
601 if (failed(
602 emitAttachToGroupOp(grp, text[block], data[block], outputPath)))
603 result = failure();
604 } else if (auto chain = dyn_cast<CertUcDmaChainOp>(&op)) {
605 std::set<uint32_t> users = {0};
606 auto it = chainUsers.find(chain.getName().str());
607 if (it != chainUsers.end())
608 users = it->second;
609 else if (sectionReferences.contains(chain.getName()))
610 continue;
611 for (uint32_t groupId : users) {
612 size_t block = blockFor(groupId);
613 if (failed(emitUcDmaChain(chain, chains[block], data[block],
614 emittedGlobals[groupId])))
615 result = failure();
616 }
617 }
618 }
619
620 if (failed(result))
621 return result;
622
623 // Emit blocks in group-id order (block 0 = uC 0 first). Each block is a
624 // self-contained stream: code + EOF, then its chains and data.
625 for (auto const &entry : groupIdToBlock) {
626 size_t block = entry.second;
627 if (!text[block].empty()) {
628 assembly += text[block];
629 // Add final EOF after all code (sections emit their own EOF before .endl)
630 assembly += "EOF\n";
631 }
632 if (!chains[block].empty())
633 assembly += chains[block];
634 if (!data[block].empty())
635 assembly += "\n" + data[block];
636 }
637 return success();
638}
639
640// The registered translation receives a stream, so its output filename is
641// unavailable. An explicit directory lets callers keep section files beside it.
642static llvm::cl::opt<std::string>
643 certSectionDir("cert-section-dir",
644 llvm::cl::desc("Directory for aiex.cert.section .asm files "
645 "(default: current directory)"),
646 llvm::cl::init(""));
647
648LogicalResult xilinx::AIE::AIETranslateToUcDma(ModuleOp module,
649 raw_ostream &output) {
650 std::string assembly;
651
652 llvm::SmallString<128> outputPath;
653 if (!certSectionDir.empty()) {
654 outputPath = StringRef(certSectionDir);
655 llvm::sys::path::append(outputPath, "main.asm");
656 }
657
658 auto r = AIETranslateToUcDma(module, assembly, outputPath);
659 if (failed(r))
660 return r;
661 output << assembly;
662 return success();
663}
mlir::LogicalResult AIETranslateToUcDma(mlir::ModuleOp module, llvm::raw_ostream &output)