30#ifndef HRX_TEST_WRAPPER_H
31#define HRX_TEST_WRAPPER_H
36#include "hrx_amdxdna.h"
37#include "hrx_runtime.h"
59inline void hrx_check(hrx_status_t status,
const char *what) {
60 if (hrx_status_is_ok(status))
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(
"?");
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);
80 static Context &
get() {
81 static Context instance;
90 hrx_check(hrx_gpu_initialize(0),
"hrx_gpu_initialize");
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,
111 "hrx_buffer_allocate");
114 hrx_check(hrx_buffer_map_with_mode(handle_, HRX_MAPPING_MODE_PERSISTENT,
115 HRX_MAP_READ | HRX_MAP_WRITE, 0, alloc,
117 "hrx_buffer_map_with_mode");
122 hrx_buffer_release(handle_);
130 hrx_check(hrx_buffer_flush_range(handle_, 0, nbytes_),
131 "hrx_buffer_flush_range");
135 hrx_check(hrx_buffer_invalidate_range(handle_, 0, nbytes_),
136 "hrx_buffer_invalidate_range");
139 hrx_buffer_t
handle()
const {
return handle_; }
141 size_t nbytes()
const {
return nbytes_; }
144 hrx_buffer_t handle_ =
nullptr;
145 void *host_ptr_ =
nullptr;
153inline std::vector<uint8_t>
155 auto rd32 = [&](
size_t off) -> uint32_t {
157 std::memcpy(&v, d.data() + off, 4);
160 auto rd16 = [&](
size_t off) -> uint16_t {
162 std::memcpy(&v, d.data() + off, 2);
166 if (d.size() < 52 || d[0] != 0x7f || d[1] !=
'E' || d[2] !=
'L' ||
168 throw std::runtime_error(
"control ELF is not a valid ELF32 file");
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);
175 auto sh = [&](uint32_t i, uint32_t f) ->
size_t {
176 return e_shoff +
static_cast<size_t>(i) * e_shentsize + f;
178 uint32_t shstr_off = rd32(sh(e_shstrndx, 0x10));
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)
185 return std::string(
reinterpret_cast<const char *
>(d.data() + off),
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);
196 throw std::runtime_error(
"control ELF has no .ctrltext section");
206 std::ifstream f(insts_path, std::ios::binary);
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>());
212 if (data.size() >= 4 && data[0] == 0x7f && data[1] ==
'E' && data[2] ==
'L' &&
222 hrx_executable_t
exe =
nullptr;
227 const std::string &insts_path,
228 const std::string &kernel_name) {
230 std::ifstream xf(xclbin_path, std::ios::binary);
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>());
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;
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;
266 hrx_const_byte_span_t xclbin_span{};
267 xclbin_span.data = xclbin.data();
268 xclbin_span.data_length = xclbin.size();
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;
275 params.xclbins = &xclbin_span;
276 params.xclbin_count = 1;
277 params.entry_points = &entry;
278 params.entry_point_count = 1;
283 "hrx_amdxdna_executable_create");
284 hrx_check(hrx_executable_lookup_export_by_name(lk.
exe, kernel_name.c_str(),
286 "hrx_executable_lookup_export_by_name");
294 const std::vector<Buffer *> &bindings) {
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();
300 refs[i].length = bindings[i]->nbytes();
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;
307 auto start = std::chrono::high_resolution_clock::now();
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)
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;
341 auto start = std::chrono::high_resolution_clock::now();
342 for (
const auto &run : runs) {
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();
349 refs[i].length = run.bindings[i]->nbytes();
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");
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)
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 "
382 std::cout << std::endl
383 <<
"Avg NPU time: " << (n_iters ? total / n_iters : 0.0) <<
"us."
385 std::cout << std::endl <<
"Min NPU time: " << mn <<
"us." << std::endl;
386 std::cout << std::endl <<
"Max NPU time: " << mx <<
"us." << std::endl;
407 cxxopts::Options options(
"HRX Test Wrapper");
408 cxxopts::ParseResult vm;
413 myargs.
verbosity = vm[
"verbosity"].as<
int>();
414 myargs.
do_verify = vm[
"verify"].as<
bool>();
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>();
472template <
typename Verify,
typename TOut,
typename... TIns>
475 bool enable_ctrl_pkts =
false) {
489 constexpr int num_inputs =
sizeof...(TIns);
493 std::vector<std::unique_ptr<Buffer>> in_bufs;
494 in_bufs.reserve(num_inputs);
495 std::tuple<TIns *...> in_ptrs;
498 int first_in_volume = 0;
500 auto alloc_inputs = [&](
auto &...inb) {
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);
511 first_in_volume = desc.volume;
512 in_bufs.push_back(std::move(buf));
515 std::apply([&](
auto &...slots) { (one(inb, slots), ...); }, in_ptrs);
517 std::apply(alloc_inputs, inputs);
520 Buffer bo_out(ctx.
stream, (
size_t)output.
volume *
sizeof(TOut));
521 TOut *bufOut =
reinterpret_cast<TOut *
>(bo_out.host_ptr());
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);
533 double npu_time_total = 0, npu_time_min = 1e30, npu_time_max = 0;
536 for (
unsigned iter = 0; iter < num_iter; iter++) {
545 errors += std::apply(
546 [&](
auto *...in_ptr) {
547 return verify_results(in_ptr..., bufOut, first_in_volume,
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;
561 std::cout <<
"\nPASS!\n\n";
564 std::cout <<
"\nError count: " << errors <<
"\n\n";
565 std::cout <<
"\nFailed.\n\n";
567 }
catch (
const std::exception &e) {
568 std::cerr <<
"\nHRX error: " << e.what() <<
"\n\nFailed.\n\n";
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)>
582 struct args myargs,
bool enable_ctrl_pkts =
false) {
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));
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);
608 std::vector<Buffer *> bindings = {&bo_in1, &bo_in2, &bo_out};
611 double npu_time_total = 0, npu_time_min = 1e30, npu_time_max = 0;
614 for (
unsigned iter = 0; iter < num_iter; iter++) {
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()),
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;
636 std::cout <<
"\nPASS!\n\n";
639 std::cout <<
"\nError count: " << errors <<
"\n\n";
640 std::cout <<
"\nFailed.\n\n";
642 }
catch (
const std::exception &e) {
643 std::cerr <<
"\nHRX error: " << e.what() <<
"\n\nFailed.\n\n";
653template <
typename T1,
typename T3, void (*init_bufIn1)(T1 *, int),
654 void (*init_bufOut)(T3 *, int),
655 int (*verify_results)(T1 *, T3 *, int, int)>
657 bool enable_ctrl_pkts =
false) {
671 Buffer bo_in1(ctx.
stream, (
size_t)IN1_VOLUME *
sizeof(T1));
672 Buffer bo_out(ctx.
stream, (
size_t)OUT_VOLUME *
sizeof(T3));
674 init_bufIn1(
reinterpret_cast<T1 *
>(bo_in1.host_ptr()), IN1_VOLUME);
675 init_bufOut(
reinterpret_cast<T3 *
>(bo_out.host_ptr()), OUT_VOLUME);
680 std::vector<Buffer *> bindings = {&bo_in1, &bo_out};
683 double npu_time_total = 0, npu_time_min = 1e30, npu_time_max = 0;
686 for (
unsigned iter = 0; iter < num_iter; iter++) {
694 errors += verify_results(
reinterpret_cast<T1 *
>(bo_in1.host_ptr()),
695 reinterpret_cast<T3 *
>(bo_out.host_ptr()),
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;
707 std::cout <<
"\nPASS!\n\n";
710 std::cout <<
"\nError count: " << errors <<
"\n\n";
711 std::cout <<
"\nFailed.\n\n";
713 }
catch (
const std::exception &e) {
714 std::cerr <<
"\nHRX error: " << e.what() <<
"\n\nFailed.\n\n";
Buffer(hrx_stream_t stream, size_t nbytes)
Buffer(const Buffer &)=delete
hrx_buffer_t handle() const
Buffer & operator=(const Buffer &)=delete
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)
std::vector< Buffer * > bindings