MLIR-AIE 1.4.5.dev
hrx_test_wrapper.h
Go to the documentation of this file.
1//===- hrx_test_wrapper.h ---------------------------------------*- C++ -*-===//
2//
3// Copyright (C) 2026 Advanced Micro Devices, Inc.
4// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
5//
6//===----------------------------------------------------------------------===//
7//
8// HRX host backend for the example `test.cpp` files.
9//
10// It exposes the *same* `args`, `parse_args()`, and templated
11// `setup_and_run_aie<...>()` surface the example `test.cpp` files already use,
12// and dispatches through HRX (libhrx + the amdxdna HAL). A test.cpp keeps
13// `#include "xrt_test_wrapper.h"`; defining `TEST_UTILS_USE_HRX` makes that
14// header pull this one in instead, so no example source changes are needed.
15//
16// It consumes the `aiecc` artifacts (`final.xclbin` + `insts.bin`): the raw
17// insts.bin TXN stream is handed straight to libhrx via
18// `hrx_amdxdna_executable_create`, which builds the amdxdna XADX package and
19// derives the (offset, arg_idx, addend) patch table from the transaction's
20// BLOCKWRITE/DDR_PATCH ops internally -- there is no separate XADX helper or
21// host-side patch-table extraction. An ELF input (aiecc --aie-generate-elf) is
22// reduced to its .ctrltext (the TXN verbatim) so libhrx still sees those ops.
23//
24// The bindings list is [in1, in2, out] (2-input form) or [in1, out] (1-input
25// form), so each buffer lands in the binding slot the transaction's DDR-patch
26// ops expect.
27//
28//===----------------------------------------------------------------------===//
29
30#ifndef HRX_TEST_WRAPPER_H
31#define HRX_TEST_WRAPPER_H
32
33#include "cxxopts.hpp"
34#include "test_utils.h"
35
36#include "hrx_amdxdna.h"
37#include "hrx_runtime.h"
38
39#include <chrono>
40#include <cstdint>
41#include <cstdio>
42#include <cstdlib>
43#include <cstring>
44#include <fstream>
45#include <iostream>
46#include <memory>
47#include <stdexcept>
48#include <string>
49#include <tuple>
50#include <type_traits>
51#include <utility>
52#include <vector>
53
54namespace hrx_test {
55
56// ---------------------------------------------------------------------------
57// Status check
58// ---------------------------------------------------------------------------
59inline void hrx_check(hrx_status_t status, const char *what) {
60 if (hrx_status_is_ok(status))
61 return;
62 char *msg = nullptr;
63 size_t len = 0;
64 hrx_status_t s2 = hrx_status_to_string(status, &msg, &len);
65 int code = hrx_status_code(status);
66 std::string text = msg ? std::string(msg, len) : std::string("?");
67 if (msg)
68 hrx_status_free_message(msg);
69 hrx_status_ignore(s2);
70 hrx_status_ignore(status);
71 throw std::runtime_error(std::string(what) + " failed (hrx status code " +
72 std::to_string(code) + "): " + text);
73}
74
75// ---------------------------------------------------------------------------
76// Process-wide HRX context (device + dispatch stream), created once.
77// ---------------------------------------------------------------------------
78class Context {
79public:
80 static Context &get() {
81 static Context instance;
82 return instance;
83 }
84
85 hrx_device_t device = nullptr;
86 hrx_stream_t stream = nullptr;
87
88private:
89 Context() {
90 hrx_check(hrx_gpu_initialize(0), "hrx_gpu_initialize");
91 hrx_check(hrx_gpu_device_get(0, &device), "hrx_gpu_device_get");
92 hrx_check(hrx_stream_create(device, 0, &stream), "hrx_stream_create");
93 }
94};
95
96// ---------------------------------------------------------------------------
97// A persistent, host-mapped, device-visible HRX buffer.
98// Coherence is maintained explicitly via flush_range / invalidate_range.
99// ---------------------------------------------------------------------------
100class Buffer {
101public:
102 Buffer(hrx_stream_t stream, size_t nbytes) : nbytes_(nbytes) {
103 size_t alloc = nbytes ? nbytes : 1; // HRX rejects 0-size allocations
104 hrx_check(hrx_buffer_allocate(stream, alloc,
105 HRX_MEMORY_TYPE_HOST_VISIBLE |
106 HRX_MEMORY_TYPE_HOST_CACHED |
107 HRX_MEMORY_TYPE_DEVICE_VISIBLE,
108 HRX_BUFFER_USAGE_DEFAULT |
109 HRX_BUFFER_USAGE_MAPPING_PERSISTENT,
110 &handle_),
111 "hrx_buffer_allocate");
112 // PERSISTENT keeps host_ptr_ valid across dispatches (paired with
113 // HRX_BUFFER_USAGE_MAPPING_PERSISTENT above).
114 hrx_check(hrx_buffer_map_with_mode(handle_, HRX_MAPPING_MODE_PERSISTENT,
115 HRX_MAP_READ | HRX_MAP_WRITE, 0, alloc,
116 &host_ptr_),
117 "hrx_buffer_map_with_mode");
118 }
119
121 if (handle_)
122 hrx_buffer_release(handle_);
123 }
124
125 Buffer(const Buffer &) = delete;
126 Buffer &operator=(const Buffer &) = delete;
127
128 void flush() {
129 if (nbytes_)
130 hrx_check(hrx_buffer_flush_range(handle_, 0, nbytes_),
131 "hrx_buffer_flush_range");
132 }
133 void invalidate() {
134 if (nbytes_)
135 hrx_check(hrx_buffer_invalidate_range(handle_, 0, nbytes_),
136 "hrx_buffer_invalidate_range");
137 }
138
139 hrx_buffer_t handle() const { return handle_; }
140 void *host_ptr() const { return host_ptr_; }
141 size_t nbytes() const { return nbytes_; }
142
143private:
144 hrx_buffer_t handle_ = nullptr;
145 void *host_ptr_ = nullptr;
146 size_t nbytes_ = 0;
147};
148
149// ---------------------------------------------------------------------------
150// Extract the XAie transaction words (.ctrltext) from a control ELF (ELF32,
151// little-endian). Used only for an ELF input; .ctrltext is the TXN verbatim.
152// ---------------------------------------------------------------------------
153inline std::vector<uint8_t>
154control_code_from_elf(const std::vector<uint8_t> &d) {
155 auto rd32 = [&](size_t off) -> uint32_t {
156 uint32_t v;
157 std::memcpy(&v, d.data() + off, 4);
158 return v;
159 };
160 auto rd16 = [&](size_t off) -> uint16_t {
161 uint16_t v;
162 std::memcpy(&v, d.data() + off, 2);
163 return v;
164 };
165
166 if (d.size() < 52 || d[0] != 0x7f || d[1] != 'E' || d[2] != 'L' ||
167 d[3] != 'F')
168 throw std::runtime_error("control ELF is not a valid ELF32 file");
169
170 uint32_t e_shoff = rd32(0x20);
171 uint16_t e_shentsize = rd16(0x2E);
172 uint16_t e_shnum = rd16(0x30);
173 uint16_t e_shstrndx = rd16(0x32);
174
175 auto sh = [&](uint32_t i, uint32_t f) -> size_t {
176 return e_shoff + static_cast<size_t>(i) * e_shentsize + f;
177 };
178 uint32_t shstr_off = rd32(sh(e_shstrndx, 0x10));
179
180 auto sname = [&](uint32_t i) -> std::string {
181 uint32_t nm = rd32(sh(i, 0));
182 size_t off = shstr_off + nm, end = off;
183 while (end < d.size() && d[end] != 0)
184 ++end;
185 return std::string(reinterpret_cast<const char *>(d.data() + off),
186 end - off);
187 };
188
189 for (uint32_t i = 0; i < e_shnum; ++i) {
190 if (sname(i) == ".ctrltext") {
191 uint32_t coff = rd32(sh(i, 0x10));
192 uint32_t csize = rd32(sh(i, 0x14));
193 return std::vector<uint8_t>(d.begin() + coff, d.begin() + coff + csize);
194 }
195 }
196 throw std::runtime_error("control ELF has no .ctrltext section");
197}
198
199// ---------------------------------------------------------------------------
200// Read the XAie transaction libhrx patches from. For a raw insts.bin the
201// transaction is the file verbatim; for an ELF input (aiecc --aie-generate-elf)
202// it is the .ctrltext section (also the TXN verbatim). libhrx derives the patch
203// table from the transaction, so no host-side patch-table extraction is needed.
204// ---------------------------------------------------------------------------
205inline std::vector<uint8_t> read_transaction(const std::string &insts_path) {
206 std::ifstream f(insts_path, std::ios::binary);
207 if (!f)
208 throw std::runtime_error("could not read insts: " + insts_path);
209 std::vector<uint8_t> data((std::istreambuf_iterator<char>(f)),
210 std::istreambuf_iterator<char>());
211
212 if (data.size() >= 4 && data[0] == 0x7f && data[1] == 'E' && data[2] == 'L' &&
213 data[3] == 'F')
214 return control_code_from_elf(data);
215 return data;
216}
217
218// ---------------------------------------------------------------------------
219// Build + load an HRX executable from the artifacts, returning (exe, ordinal).
220// ---------------------------------------------------------------------------
222 hrx_executable_t exe = nullptr;
223 uint32_t ordinal = 0;
224};
225
226inline LoadedKernel load_kernel(const std::string &xclbin_path,
227 const std::string &insts_path,
228 const std::string &kernel_name) {
229 // Read xclbin bytes.
230 std::ifstream xf(xclbin_path, std::ios::binary);
231 if (!xf)
232 throw std::runtime_error("could not read xclbin: " + xclbin_path);
233 std::vector<uint8_t> xclbin((std::istreambuf_iterator<char>(xf)),
234 std::istreambuf_iterator<char>());
235
236 // Resolve the XAie transaction. For a raw insts.bin this is the TXN verbatim;
237 // for an ELF input it is the .ctrltext (also the TXN verbatim). libhrx
238 // derives the patch table from the transaction internally.
239 std::vector<uint8_t> transaction = read_transaction(insts_path);
240
241 // Describe the executable to libhrx: one xclbin + one CREATE entry point with
242 // a single run carrying the transaction. libhrx builds the amdxdna XADX
243 // package and derives the patch table internally.
244 hrx_amdxdna_executable_run_t run{};
245 run.record_length = sizeof(run);
246 run.abi_version = HRX_AMDXDNA_EXECUTABLE_RUN_ABI_VERSION_0;
247 run.transaction.data = transaction.data();
248 run.transaction.data_length = transaction.size();
249 run.data_payload.data = nullptr;
250 run.data_payload.data_length = 0;
251
252 hrx_amdxdna_executable_entry_point_t entry{};
253 entry.record_length = sizeof(entry);
254 entry.abi_version = HRX_AMDXDNA_EXECUTABLE_ENTRY_POINT_ABI_VERSION_0;
255 entry.name.data = kernel_name.c_str();
256 entry.name.size = kernel_name.size();
257 entry.context_mode = HRX_AMDXDNA_CONTEXT_MODE_CREATE;
258 entry.xclbin_ordinal = 0;
259 entry.pdi_ordinal = 0;
260 entry.source_line = 0;
261 entry.source_file.data = nullptr;
262 entry.source_file.size = 0;
263 entry.runs = &run;
264 entry.run_count = 1;
265
266 hrx_const_byte_span_t xclbin_span{};
267 xclbin_span.data = xclbin.data();
268 xclbin_span.data_length = xclbin.size();
269
270 hrx_amdxdna_executable_create_params_t params{};
271 params.record_length = sizeof(params);
272 params.abi_version = HRX_AMDXDNA_EXECUTABLE_CREATE_PARAMS_ABI_VERSION_0;
273 params.flags = 0;
274 params.reserved = 0;
275 params.xclbins = &xclbin_span;
276 params.xclbin_count = 1;
277 params.entry_points = &entry;
278 params.entry_point_count = 1;
279
280 Context &ctx = Context::get();
281 LoadedKernel lk;
282 hrx_check(hrx_amdxdna_executable_create(ctx.device, &params, &lk.exe),
283 "hrx_amdxdna_executable_create");
284 hrx_check(hrx_executable_lookup_export_by_name(lk.exe, kernel_name.c_str(),
285 &lk.ordinal),
286 "hrx_executable_lookup_export_by_name");
287 return lk;
288}
289
290// ---------------------------------------------------------------------------
291// One dispatch + synchronize of `lk` with `bindings`. Returns elapsed us.
292// ---------------------------------------------------------------------------
293inline double dispatch_once(const LoadedKernel &lk,
294 const std::vector<Buffer *> &bindings) {
295 Context &ctx = Context::get();
296 std::vector<hrx_buffer_ref_t> refs(bindings.size());
297 for (size_t i = 0; i < bindings.size(); ++i) {
298 refs[i].buffer = bindings[i]->handle();
299 refs[i].offset = 0;
300 refs[i].length = bindings[i]->nbytes();
301 }
302 hrx_dispatch_config_t cfg{};
303 cfg.workgroup_count[0] = cfg.workgroup_count[1] = cfg.workgroup_count[2] = 1;
304 cfg.workgroup_size[0] = cfg.workgroup_size[1] = cfg.workgroup_size[2] = 1;
305 cfg.subgroup_size = 0;
306
307 auto start = std::chrono::high_resolution_clock::now();
308 hrx_check(hrx_stream_dispatch(ctx.stream, lk.exe, lk.ordinal, &cfg, nullptr,
309 0, refs.data(), refs.size(),
310 HRX_DISPATCH_FLAG_NONE),
311 "hrx_stream_dispatch");
312 hrx_check(hrx_stream_synchronize(ctx.stream), "hrx_stream_synchronize");
313 auto stop = std::chrono::high_resolution_clock::now();
314 return std::chrono::duration_cast<std::chrono::microseconds>(stop - start)
315 .count();
316}
317
318// ---------------------------------------------------------------------------
319// Multi-dispatch chain. Each entry is one kernel dispatch with its own
320// bindings; all are recorded, in order, into a single HRX command buffer.
321// HRX inserts an execution + memory barrier after every dispatch, so a later
322// run observes an earlier run's device writes (producer -> consumer chains
323// work, e.g. one run's output buffer is the next run's input). A single
324// `synchronize` then submits the whole batch as one execution -- the amdxdna
325// HAL lowers a multi-dispatch command buffer into one `ERT_CMD_CHAIN`. Entries
326// may share one LoadedKernel (re-dispatch the same kernel) or use different
327// ones (a true multi-kernel pipeline). Returns elapsed us for the whole chain.
328// ---------------------------------------------------------------------------
329struct ChainRun {
331 std::vector<Buffer *> bindings;
332};
333
334inline double dispatch_chain(const std::vector<ChainRun> &runs) {
335 Context &ctx = Context::get();
336 hrx_dispatch_config_t cfg{};
337 cfg.workgroup_count[0] = cfg.workgroup_count[1] = cfg.workgroup_count[2] = 1;
338 cfg.workgroup_size[0] = cfg.workgroup_size[1] = cfg.workgroup_size[2] = 1;
339 cfg.subgroup_size = 0;
340
341 auto start = std::chrono::high_resolution_clock::now();
342 for (const auto &run : runs) {
343 // hrx_stream_dispatch copies the binding refs into the recorded command, so
344 // a per-iteration local is safe; the batch stays pending until synchronize.
345 std::vector<hrx_buffer_ref_t> refs(run.bindings.size());
346 for (size_t i = 0; i < run.bindings.size(); ++i) {
347 refs[i].buffer = run.bindings[i]->handle();
348 refs[i].offset = 0;
349 refs[i].length = run.bindings[i]->nbytes();
350 }
351 hrx_check(hrx_stream_dispatch(ctx.stream, run.lk->exe, run.lk->ordinal,
352 &cfg, nullptr, 0, refs.data(), refs.size(),
353 HRX_DISPATCH_FLAG_NONE),
354 "hrx_stream_dispatch");
355 }
356 hrx_check(hrx_stream_synchronize(ctx.stream), "hrx_stream_synchronize");
357 auto stop = std::chrono::high_resolution_clock::now();
358 return std::chrono::duration_cast<std::chrono::microseconds>(stop - start)
359 .count();
360}
361
362// ---------------------------------------------------------------------------
363// Reject features the HRX backend does not implement (trace capture, control
364// packets). Throwing here -- rather than warning and continuing -- means a
365// caller that asked for trace/ctrl-pkts gets a hard error instead of a
366// misleading PASS. Trace/control-packet designs are not supported on HRX.
367// ---------------------------------------------------------------------------
368inline void reject_unsupported_features(int trace_size, bool enable_ctrl_pkts) {
369 if (trace_size > 0)
370 throw std::runtime_error(
371 "trace capture is not supported on the HRX backend "
372 "(--trace_sz/trace_size > 0). Re-run without trace, or use the XRT "
373 "backend for trace-enabled designs.");
374 if (enable_ctrl_pkts)
375 throw std::runtime_error(
376 "control packets are not supported on the HRX backend "
377 "(enable_ctrl_pkts=true). Use the XRT backend for control-packet "
378 "designs.");
379}
380
381inline void report_timing(double total, double mn, double mx, int n_iters) {
382 std::cout << std::endl
383 << "Avg NPU time: " << (n_iters ? total / n_iters : 0.0) << "us."
384 << std::endl;
385 std::cout << std::endl << "Min NPU time: " << mn << "us." << std::endl;
386 std::cout << std::endl << "Max NPU time: " << mx << "us." << std::endl;
387}
388
389} // namespace hrx_test
390
391// ---------------------------------------------------------------------------
392// Public surface: identical to xrt_test_wrapper.h
393// ---------------------------------------------------------------------------
394struct args {
400 std::string instr;
401 std::string xclbin;
402 std::string kernel;
403 std::string trace_file;
404};
405
406inline struct args parse_args(int argc, const char *argv[]) {
407 cxxopts::Options options("HRX Test Wrapper");
408 cxxopts::ParseResult vm;
410
411 struct args myargs;
412 test_utils::parse_options(argc, argv, options, vm);
413 myargs.verbosity = vm["verbosity"].as<int>();
414 myargs.do_verify = vm["verify"].as<bool>();
415 myargs.n_iterations = vm["iters"].as<int>();
416 myargs.n_warmup_iterations = vm["warmup"].as<int>();
417 myargs.trace_size = vm["trace_sz"].as<int>();
418 myargs.instr = vm["instr"].as<std::string>();
419 myargs.xclbin = vm["xclbin"].as<std::string>();
420 myargs.kernel = vm["kernel"].as<std::string>();
421 myargs.trace_file = vm["trace_file"].as<std::string>();
422 return myargs;
423}
424
425/*
426 ******************************************************************************
427 * Typed buffer descriptors for the variadic HRX test wrapper
428 ******************************************************************************
429 *
430 * Typed buffer descriptors so example test.cpp files that use the variadic form
431 * setup_and_run_aie(verify, std::make_tuple(make_in<T>(...), ...),
432 * make_out<T>(...), myargs)
433 * build unchanged against the HRX backend. Buffer binding order: data buffers
434 * in order, then the output buffer.
435 */
436
437// A typed input buffer: element type, element count, and initializer.
438template <typename T>
439struct InBuf {
440 using elem_type = T;
442 void (*init)(T *, int);
443};
444
445template <typename T>
446InBuf<T> make_in(int volume, void (*init)(T *, int)) {
447 return InBuf<T>{volume, init};
448}
449
450// A typed output buffer.
451template <typename T>
452struct OutBuf {
453 using elem_type = T;
455 void (*init)(T *, int);
456};
457
458template <typename T>
459OutBuf<T> make_out(int volume, void (*init)(T *, int)) {
460 return OutBuf<T>{volume, init};
461}
462
463/*
464 ******************************************************************************
465 * HRX based test wrapper for N inputs and 1 output (variadic)
466 ******************************************************************************
467 *
468 * Inputs are passed as InBuf<T> descriptors (any number, any types). The
469 * verifier is called as verify(in0*, in1*, ..., out*, first_input_volume,
470 * verbosity) and returns an error count -- matching the XRT wrapper contract.
471 */
472template <typename Verify, typename TOut, typename... TIns>
473int setup_and_run_aie(Verify verify_results, std::tuple<InBuf<TIns>...> inputs,
474 OutBuf<TOut> output, struct args myargs,
475 bool enable_ctrl_pkts = false) {
476 using namespace hrx_test;
477 srand(time(NULL));
478
479 try {
480 // The HRX backend does not (yet) implement trace capture or control-packet
481 // configuration. Fail loudly rather than silently ignoring the request:
482 // running without them would emit a misleading "PASS" with no trace / an
483 // unconfigured control path. Trace/control-packet designs must use XRT.
484 reject_unsupported_features(myargs.trace_size, enable_ctrl_pkts);
485
486 Context &ctx = Context::get();
487 LoadedKernel lk = load_kernel(myargs.xclbin, myargs.instr, myargs.kernel);
488
489 constexpr int num_inputs = sizeof...(TIns);
490
491 // Allocate + initialize the input buffers, in order. Buffer is non-movable
492 // (owns an HRX allocation), so hold them via unique_ptr.
493 std::vector<std::unique_ptr<Buffer>> in_bufs;
494 in_bufs.reserve(num_inputs);
495 std::tuple<TIns *...> in_ptrs;
496 // The verifier's SIZE argument is the first input's element count, matching
497 // the historical wrapper contract (verify iterates the primary input).
498 int first_in_volume = 0;
499
500 auto alloc_inputs = [&](auto &...inb) {
501 int idx = 0;
502 auto one = [&](auto &desc, auto &ptr_slot) {
503 using T = typename std::remove_reference_t<decltype(desc)>::elem_type;
504 auto buf = std::make_unique<Buffer>(ctx.stream,
505 (size_t)desc.volume * sizeof(T));
506 T *mapped = reinterpret_cast<T *>(buf->host_ptr());
507 desc.init(mapped, desc.volume);
508 buf->flush();
509 ptr_slot = mapped;
510 if (idx == 0)
511 first_in_volume = desc.volume;
512 in_bufs.push_back(std::move(buf));
513 idx++;
514 };
515 std::apply([&](auto &...slots) { (one(inb, slots), ...); }, in_ptrs);
516 };
517 std::apply(alloc_inputs, inputs);
518
519 // Output buffer follows the inputs.
520 Buffer bo_out(ctx.stream, (size_t)output.volume * sizeof(TOut));
521 TOut *bufOut = reinterpret_cast<TOut *>(bo_out.host_ptr());
522 output.init(bufOut, output.volume);
523 bo_out.flush();
524
525 // Binding order: data buffers in order, then out.
526 std::vector<Buffer *> bindings;
527 bindings.reserve(num_inputs + 1);
528 for (auto &b : in_bufs)
529 bindings.push_back(b.get());
530 bindings.push_back(&bo_out);
531
532 unsigned num_iter = myargs.n_iterations + myargs.n_warmup_iterations;
533 double npu_time_total = 0, npu_time_min = 1e30, npu_time_max = 0;
534 int errors = 0;
535
536 for (unsigned iter = 0; iter < num_iter; iter++) {
537 double us = dispatch_once(lk, bindings);
538 bo_out.invalidate();
539
540 if (iter < (unsigned)myargs.n_warmup_iterations)
541 continue;
542
543 if (myargs.do_verify) {
544 // verify(in0*, in1*, ..., out*, first_input_volume, verbosity)
545 errors += std::apply(
546 [&](auto *...in_ptr) {
547 return verify_results(in_ptr..., bufOut, first_in_volume,
548 myargs.verbosity);
549 },
550 in_ptrs);
551 }
552 npu_time_total += us;
553 npu_time_min = us < npu_time_min ? us : npu_time_min;
554 npu_time_max = us > npu_time_max ? us : npu_time_max;
555 }
556
557 report_timing(npu_time_total, npu_time_min, npu_time_max,
558 myargs.n_iterations);
559
560 if (!errors) {
561 std::cout << "\nPASS!\n\n";
562 return 0;
563 }
564 std::cout << "\nError count: " << errors << "\n\n";
565 std::cout << "\nFailed.\n\n";
566 return 1;
567 } catch (const std::exception &e) {
568 std::cerr << "\nHRX error: " << e.what() << "\n\nFailed.\n\n";
569 return 1;
570 }
571}
572
573/*
574 ******************************************************************************
575 * HRX based test wrapper for 2 inputs and 1 output (legacy fixed-arity)
576 ******************************************************************************
577 */
578template <typename T1, typename T2, typename T3, void (*init_bufIn1)(T1 *, int),
579 void (*init_bufIn2)(T2 *, int), void (*init_bufOut)(T3 *, int),
580 int (*verify_results)(T1 *, T2 *, T3 *, int, int)>
581int setup_and_run_aie(int IN1_VOLUME, int IN2_VOLUME, int OUT_VOLUME,
582 struct args myargs, bool enable_ctrl_pkts = false) {
583 using namespace hrx_test;
584 srand(time(NULL));
585
586 try {
587 // The HRX backend does not (yet) implement trace capture or control-packet
588 // configuration. Fail loudly rather than silently ignoring the request:
589 // running without them would emit a misleading "PASS" with no trace / an
590 // unconfigured control path. Trace/control-packet designs must use XRT.
591 reject_unsupported_features(myargs.trace_size, enable_ctrl_pkts);
592
593 Context &ctx = Context::get();
594 LoadedKernel lk = load_kernel(myargs.xclbin, myargs.instr, myargs.kernel);
595
596 Buffer bo_in1(ctx.stream, (size_t)IN1_VOLUME * sizeof(T1));
597 Buffer bo_in2(ctx.stream, (size_t)IN2_VOLUME * sizeof(T2));
598 Buffer bo_out(ctx.stream, (size_t)OUT_VOLUME * sizeof(T3));
599
600 init_bufIn1(reinterpret_cast<T1 *>(bo_in1.host_ptr()), IN1_VOLUME);
601 init_bufIn2(reinterpret_cast<T2 *>(bo_in2.host_ptr()), IN2_VOLUME);
602 init_bufOut(reinterpret_cast<T3 *>(bo_out.host_ptr()), OUT_VOLUME);
603 bo_in1.flush();
604 bo_in2.flush();
605 bo_out.flush();
606
607 // Binding order: [in1, in2, out].
608 std::vector<Buffer *> bindings = {&bo_in1, &bo_in2, &bo_out};
609
610 unsigned num_iter = myargs.n_iterations + myargs.n_warmup_iterations;
611 double npu_time_total = 0, npu_time_min = 1e30, npu_time_max = 0;
612 int errors = 0;
613
614 for (unsigned iter = 0; iter < num_iter; iter++) {
615 double us = dispatch_once(lk, bindings);
616 bo_out.invalidate();
617
618 if (iter < (unsigned)myargs.n_warmup_iterations)
619 continue;
620
621 if (myargs.do_verify) {
622 errors += verify_results(reinterpret_cast<T1 *>(bo_in1.host_ptr()),
623 reinterpret_cast<T2 *>(bo_in2.host_ptr()),
624 reinterpret_cast<T3 *>(bo_out.host_ptr()),
625 IN1_VOLUME, myargs.verbosity);
626 }
627 npu_time_total += us;
628 npu_time_min = us < npu_time_min ? us : npu_time_min;
629 npu_time_max = us > npu_time_max ? us : npu_time_max;
630 }
631
632 report_timing(npu_time_total, npu_time_min, npu_time_max,
633 myargs.n_iterations);
634
635 if (!errors) {
636 std::cout << "\nPASS!\n\n";
637 return 0;
638 }
639 std::cout << "\nError count: " << errors << "\n\n";
640 std::cout << "\nFailed.\n\n";
641 return 1;
642 } catch (const std::exception &e) {
643 std::cerr << "\nHRX error: " << e.what() << "\n\nFailed.\n\n";
644 return 1;
645 }
646}
647
648/*
649 ******************************************************************************
650 * HRX based test wrapper for 1 input and 1 output
651 ******************************************************************************
652 */
653template <typename T1, typename T3, void (*init_bufIn1)(T1 *, int),
654 void (*init_bufOut)(T3 *, int),
655 int (*verify_results)(T1 *, T3 *, int, int)>
656int setup_and_run_aie(int IN1_VOLUME, int OUT_VOLUME, struct args myargs,
657 bool enable_ctrl_pkts = false) {
658 using namespace hrx_test;
659 srand(time(NULL));
660
661 try {
662 // The HRX backend does not (yet) implement trace capture or control-packet
663 // configuration. Fail loudly rather than silently ignoring the request:
664 // running without them would emit a misleading "PASS" with no trace / an
665 // unconfigured control path. Trace/control-packet designs must use XRT.
666 reject_unsupported_features(myargs.trace_size, enable_ctrl_pkts);
667
668 Context &ctx = Context::get();
669 LoadedKernel lk = load_kernel(myargs.xclbin, myargs.instr, myargs.kernel);
670
671 Buffer bo_in1(ctx.stream, (size_t)IN1_VOLUME * sizeof(T1));
672 Buffer bo_out(ctx.stream, (size_t)OUT_VOLUME * sizeof(T3));
673
674 init_bufIn1(reinterpret_cast<T1 *>(bo_in1.host_ptr()), IN1_VOLUME);
675 init_bufOut(reinterpret_cast<T3 *>(bo_out.host_ptr()), OUT_VOLUME);
676 bo_in1.flush();
677 bo_out.flush();
678
679 // Binding order: [in1, out].
680 std::vector<Buffer *> bindings = {&bo_in1, &bo_out};
681
682 unsigned num_iter = myargs.n_iterations + myargs.n_warmup_iterations;
683 double npu_time_total = 0, npu_time_min = 1e30, npu_time_max = 0;
684 int errors = 0;
685
686 for (unsigned iter = 0; iter < num_iter; iter++) {
687 double us = dispatch_once(lk, bindings);
688 bo_out.invalidate();
689
690 if (iter < (unsigned)myargs.n_warmup_iterations)
691 continue;
692
693 if (myargs.do_verify) {
694 errors += verify_results(reinterpret_cast<T1 *>(bo_in1.host_ptr()),
695 reinterpret_cast<T3 *>(bo_out.host_ptr()),
696 IN1_VOLUME, myargs.verbosity);
697 }
698 npu_time_total += us;
699 npu_time_min = us < npu_time_min ? us : npu_time_min;
700 npu_time_max = us > npu_time_max ? us : npu_time_max;
701 }
702
703 report_timing(npu_time_total, npu_time_min, npu_time_max,
704 myargs.n_iterations);
705
706 if (!errors) {
707 std::cout << "\nPASS!\n\n";
708 return 0;
709 }
710 std::cout << "\nError count: " << errors << "\n\n";
711 std::cout << "\nFailed.\n\n";
712 return 1;
713 } catch (const std::exception &e) {
714 std::cerr << "\nHRX error: " << e.what() << "\n\nFailed.\n\n";
715 return 1;
716 }
717}
718
719#endif // HRX_TEST_WRAPPER_H
Buffer(hrx_stream_t stream, size_t nbytes)
Buffer(const Buffer &)=delete
hrx_buffer_t handle() const
void * host_ptr() const
Buffer & operator=(const Buffer &)=delete
size_t nbytes() const
static Context & get()
struct args parse_args(int argc, const char *argv[])
InBuf< T > make_in(int volume, void(*init)(T *, int))
OutBuf< T > make_out(int volume, void(*init)(T *, int))
int setup_and_run_aie(Verify verify_results, std::tuple< InBuf< TIns >... > inputs, OutBuf< TOut > output, struct args myargs, bool enable_ctrl_pkts=false)
std::vector< uint8_t > read_transaction(const std::string &insts_path)
double dispatch_chain(const std::vector< ChainRun > &runs)
LoadedKernel load_kernel(const std::string &xclbin_path, const std::string &insts_path, const std::string &kernel_name)
double dispatch_once(const LoadedKernel &lk, const std::vector< Buffer * > &bindings)
void reject_unsupported_features(int trace_size, bool enable_ctrl_pkts)
void hrx_check(hrx_status_t status, const char *what)
std::vector< uint8_t > control_code_from_elf(const std::vector< uint8_t > &d)
void report_timing(double total, double mn, double mx, int n_iters)
void parse_options(int argc, const char *argv[], cxxopts::Options &options, cxxopts::ParseResult &result)
void add_default_options(cxxopts::Options &options)
void(* init)(T *, int)
void(* init)(T *, int)
int n_warmup_iterations
std::string trace_file
std::string xclbin
std::string kernel
int n_iterations
std::string instr
const LoadedKernel * lk
std::vector< Buffer * > bindings