diff --git a/reference_designs/utils/test_utils.h b/reference_designs/utils/test_utils.h new file mode 100644 index 0000000000..4dd29a9a54 --- /dev/null +++ b/reference_designs/utils/test_utils.h @@ -0,0 +1,207 @@ +//===- test_utils.h ----------------------------000---*- C++ -*-===// +// +// This file is licensed under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +// Copyright (C) 2024, Advanced Micro Devices, Inc. +// +//===----------------------------------------------------------------------===// + +// This file contains common helper functions for the matrix multiplication +// host code, such as verifying and printing matrices. + +#ifndef TEST_UTILS_H +#define TEST_UTILS_H + +#include +#include + +namespace test_utils { + +namespace po = boost::program_options; + +// -------------------------------------------------------------------------- +// Command Line Argument Handling +// -------------------------------------------------------------------------- + +void check_arg_file_exists(po::variables_map &vm_in, std::string name) { + if (!vm_in.count(name)) { + throw std::runtime_error("Error: no " + name + " file was provided\n"); + } else { + std::ifstream test(vm_in[name].as()); + if (!test) { + throw std::runtime_error("The " + name + " file " + + vm_in[name].as() + + " does not exist.\n"); + } + } +} + +void add_default_options(po::options_description &desc) { + desc.add_options()("help,h", "produce help message")( + "xclbin,x", po::value()->required(), + "the input xclbin path")( + "kernel,k", po::value()->required(), + "the kernel name in the XCLBIN (for instance PP_PRE_FD)")( + "verbosity,v", po::value()->default_value(0), + "the verbosity of the output")( + "instr,i", po::value()->required(), + "path of file containing userspace instructions sent to the NPU")( + "verify", po::value()->default_value(true), + "whether to verify the AIE computed output")( + "iters", po::value()->default_value(1))( + "warmup", po::value()->default_value(0))( + "trace_sz,t", po::value()->default_value(0))( + "trace_file", po::value()->default_value("trace.txt"), + "where to store trace output"); +} + +void parse_options(int argc, const char *argv[], po::options_description &desc, + po::variables_map &vm) { + try { + po::store(po::parse_command_line(argc, argv, desc), vm); + po::notify(vm); + + if (vm.count("help")) { + std::cout << desc << "\n"; + std::exit(1); + } + } catch (const std::exception &ex) { + std::cerr << ex.what() << "\n\n"; + std::cerr << "Usage:\n" << desc << "\n"; + std::exit(1); + } + + check_arg_file_exists(vm, "xclbin"); + check_arg_file_exists(vm, "instr"); +} + +// -------------------------------------------------------------------------- +// AIE Specifics +// -------------------------------------------------------------------------- + +std::vector load_instr_sequence(std::string instr_path) { + std::ifstream instr_file(instr_path); + std::string line; + std::vector instr_v; + while (std::getline(instr_file, line)) { + std::istringstream iss(line); + uint32_t a; + if (!(iss >> std::hex >> a)) { + throw std::runtime_error("Unable to parse instruction file\n"); + } + instr_v.push_back(a); + } + return instr_v; +} + +// -------------------------------------------------------------------------- +// Matrix / Float / Math +// -------------------------------------------------------------------------- + +static inline std::int16_t random_int16_t() { + return (std::int16_t)rand() % 0x10000; +} + +// static inline std::bfloat16_t random_bfloat16_t() { +// // Random numbers should NOT be uniformly between 0 and 1, because that +// // would make the matrix product AB always close to 1. +// return std::bfloat16_t(4.0 * (float)rand() / (float)(RAND_MAX)); +// } + +// nearly_equal function adapted from Stack Overflow, License CC BY-SA 4.0 +// Original author: P-Gn +// Source: https://stackoverflow.com/a/32334103 +bool nearly_equal(float a, float b, float epsilon = 128 * FLT_EPSILON, + float abs_th = FLT_MIN) +// those defaults are arbitrary and could be removed +{ + assert(std::numeric_limits::epsilon() <= epsilon); + assert(epsilon < 1.f); + + if (a == b) + return true; + + auto diff = std::abs(a - b); + auto norm = + std::min((std::abs(a) + std::abs(b)), std::numeric_limits::max()); + // or even faster: std::min(std::abs(a + b), + // std::numeric_limits::max()); keeping this commented out until I + // update figures below + return diff < std::max(abs_th, epsilon * norm); +} + +template +void print_matrix(const std::vector matrix, int n_cols, + int n_printable_rows = 10, int n_printable_cols = 10, + std::ostream &ostream = std::cout, + const char col_sep[] = " ", const char elide_sym[] = " ... ", + int w = -1) { + assert(matrix.size() % n_cols == 0); + + auto maxima = std::minmax_element(matrix.begin(), matrix.end()); + T max_val = std::max(*maxima.first, std::abs(*maxima.second)); + size_t n_digits = log10(max_val); + if (w == -1) { + w = n_digits; + } + int n_rows = matrix.size() / n_cols; + + n_printable_rows = std::min(n_rows, n_printable_rows); + n_printable_cols = std::min(n_cols, n_printable_cols); + + const bool elide_rows = n_printable_rows < n_rows; + const bool elide_cols = n_printable_cols < n_cols; + + if (elide_rows || elide_cols) { + w = std::max((int)w, (int)strlen(elide_sym)); + } + + w += 3; // for decimal point and two decimal digits + ostream << std::fixed << std::setprecision(2); + +#define print_row(what) \ + for (int col = 0; col < n_printable_cols / 2; col++) { \ + ostream << std::right << std::setw(w) << (what); \ + ostream << std::setw(0) << col_sep; \ + } \ + if (elide_cols) { \ + ostream << std::setw(0) << elide_sym; \ + } \ + for (int col = n_printable_cols / 2 + 1; col < n_printable_cols; col++) { \ + ostream << std::right << std::setw(w) << (what); \ + ostream << std::setw(0) << col_sep; \ + } + + for (int row = 0; row < n_printable_rows / 2; row++) { + print_row(matrix[row * n_rows + col]); + ostream << std::endl; + } + if (elide_rows) { + print_row(elide_sym); + ostream << std::endl; + } + for (int row = n_printable_rows / 2 + 1; row < n_printable_rows; row++) { + print_row(matrix[row * n_rows + col]); + ostream << std::endl; + } + +#undef print_row +} + +// -------------------------------------------------------------------------- +// Tracing +// -------------------------------------------------------------------------- +void write_out_trace(char *traceOutPtr, size_t trace_size, std::string path) { + std::ofstream fout(path); + uint32_t *traceOut = (uint32_t *)traceOutPtr; + for (int i = 0; i < trace_size / sizeof(traceOut[0]); i++) { + fout << std::setfill('0') << std::setw(8) << std::hex << (int)traceOut[i]; + fout << std::endl; + } +} + +} // namespace test_utils + +#endif \ No newline at end of file