.. _program_listing_file_lib_Target_EmitHLSToLaksaApp_EmitHLSToLaksaApp.cpp: Program Listing for File EmitHLSToLaksaApp.cpp ============================================== |exhale_lsh| :ref:`Return to documentation for file ` (``lib/Target/EmitHLSToLaksaApp/EmitHLSToLaksaApp.cpp``) .. |exhale_lsh| unicode:: U+021B0 .. UPWARDS ARROW WITH TIP LEFTWARDS .. code-block:: cpp // Implementation of translation EmitHLSToLaksaApp // // @file // @author Jiahong Bi (jiahong.bi@tu-dresden.de) #include "laksa-mlir/Target/EmitHLSToLaksaApp/LaksaAppEmitter.h" #include "laksa-mlir/Target/LaksaCommon/BufferInfo.h" #include "llvm/ADT/STLExtras.h" #include "llvm/Support/CommandLine.h" using namespace mlir; using namespace mlir::emithls; namespace { llvm::cl::opt clLaksaAppDevice( "laksa-app-device", llvm::cl::desc( "Device node the generated application opens, as created by the " "laksa-hls-kria-driver"), llvm::cl::init("/dev/laksa")); llvm::cl::opt clLaksaAppHeader( "laksa-app-header", llvm::cl::desc( "Header the generated application takes its buffer sizes and register " "offsets from, as produced by the emithls-to-laksa-header translation"), llvm::cl::init("app.h")); // The identifiers and macros the generated application uses for one argument. struct ArgNames { std::string hostBuffer; // malloc'ed staging buffer std::string deviceAlloc; // struct hls_alloc handle std::string sizeMacro; // these three come from the generated header std::string lowMacro; std::string highMacro; std::string inFile; // both empty unless the kernel reads the argument std::string inFileMacro; std::string outFile; // both empty unless the kernel writes the argument std::string outFileMacro; }; } // namespace //===----------------------------------------------------------------------===// // Translation entry point //===----------------------------------------------------------------------===// LogicalResult emithls::translateEmitHLSToLaksaApp(ModuleOp op, raw_ostream &os) { FailureOr topFunc = findLaksaTopFunc(op); if (failed(topFunc)) return failure(); SmallVector buffers; if (failed(collectLaksaBufferInfos(*topFunc, buffers))) return failure(); StringRef designName = topFunc->getSymName(); std::string prefix = getLaksaMacroPrefix(designName); // Inputs and outputs are numbered independently, so a design reading two // buffers and writing one consumes input0.bin and input1.bin and produces // output0.bin. An argument the kernel both reads and writes gets a file on // either side. SmallVector names(buffers.size()); unsigned numInputs = 0; unsigned numOutputs = 0; for (auto [idx, info] : llvm::enumerate(buffers)) { std::string arg = "arg" + std::to_string(idx); std::string argMacro = prefix + "_ARG" + std::to_string(idx); ArgNames &name = names[idx]; name.hostBuffer = arg + "_host"; name.deviceAlloc = arg + "_buf"; name.sizeMacro = argMacro + "_SIZE"; name.lowMacro = argMacro + "_LOW_OFFSET"; name.highMacro = argMacro + "_HIGH_OFFSET"; if (info.isReadByKernel) { name.inFile = "input" + std::to_string(numInputs++) + ".bin"; name.inFileMacro = "ARG" + std::to_string(idx) + "_IN_FILE"; } if (info.isWrittenByKernel) { name.outFile = "output" + std::to_string(numOutputs++) + ".bin"; name.outFileMacro = "ARG" + std::to_string(idx) + "_OUT_FILE"; } } os << "// Generated by ladle -t emithls-to-laksa-app.\n" "// Userspace driver for '" << designName << "', running the design through the\n" "// laksa-hls-kria-driver's " << clLaksaAppDevice << ". Buffer sizes and AXI-Lite register\n" "// offsets come from \"" << clLaksaAppHeader << "\", generated by\n" "// ladle .mlir -t emithls-to-laksa-header -o " << clLaksaAppHeader << "\n" "//\n" "// Build (after installing laksa.h to /usr/local/include), assuming " "this file\n" "// was written to app.c:\n" "// gcc -O2 -I. -o app app.c\n" "//\n" "// Run as root, the device node requires elevated privileges:\n" "// sudo ./app\n" "//\n" "// The input and output files are raw dumps of the DMA buffers " "without any\n" "// header, sized by the macros in \"" << clLaksaAppHeader << "\".\n" "\n" "#include \n" "#include \n" "#include \n" "#include \n" "#include \n" "#include \n" "#include \n" "\n" "#include \n" "\n" "#include \"" << clLaksaAppHeader << "\"\n" "\n" "#define DEVICE \"" << clLaksaAppDevice << "\"\n\n"; for (const ArgNames &name : names) { if (!name.inFile.empty()) os << "#define " << name.inFileMacro << " \"" << name.inFile << "\"\n"; if (!name.outFile.empty()) os << "#define " << name.outFileMacro << " \"" << name.outFile << "\"\n"; } os << "\n_Static_assert(\n" " " << prefix << "_NUM_ARGS <= HLS_MAX_ARGS,\n" " \"the design has more m_axi arguments than the driver " "supports\");\n" "\n" "/*\n" " * A failing ioctl means the design's configuration and the " "driver's state\n" " * disagree, which retrying does not fix, so every one of them is " "fatal.\n" " */\n" "#define CHECK_IOCTL(fd, request, arg) \\\n" " do { \\\n" " if (ioctl((fd), (request), (arg)) < 0) { \\\n" " perror(#request); \\\n" " return EXIT_FAILURE; \\\n" " } \\\n" " } while (0)\n" "\n" "static void* xmalloc(size_t size)\n" "{\n" " void* data = malloc(size);\n" " if (!data) {\n" " fprintf(stderr, \"out of memory\\n\");\n" " exit(EXIT_FAILURE);\n" " }\n" " return data;\n" "}\n" "\n" "static double elapsed_ms(struct timespec begin, struct timespec " "end)\n" "{\n" " return (end.tv_sec - begin.tv_sec) * 1e3\n" " + (end.tv_nsec - begin.tv_nsec) / 1e6;\n" "}\n\n"; if (numInputs > 0) os << "static int read_file(const char* path, void* data, size_t " "size)\n" "{\n" " FILE* file = fopen(path, \"rb\");\n" " if (!file) {\n" " perror(path);\n" " return -1;\n" " }\n" " size_t got = fread(data, 1, size, file);\n" " fclose(file);\n" " if (got != size) {\n" " fprintf(stderr, \"%s: expected %zu bytes, got %zu\\n\", " "path, size, got);\n" " return -1;\n" " }\n" " return 0;\n" "}\n\n"; if (numOutputs > 0) os << "static int write_file(const char* path, const void* data, " "size_t size)\n" "{\n" " FILE* file = fopen(path, \"wb\");\n" " if (!file) {\n" " perror(path);\n" " return -1;\n" " }\n" " size_t put = fwrite(data, 1, size, file);\n" " if (fclose(file) != 0 || put != size) {\n" " fprintf(stderr, \"%s: short write, %zu of %zu " "bytes\\n\", path, put, size);\n" " return -1;\n" " }\n" " return 0;\n" "}\n\n"; os << "int main(void)\n" "{\n" " int fd = open(DEVICE, O_RDWR);\n" " if (fd < 0) {\n" " perror(\"open \" DEVICE);\n" " return EXIT_FAILURE;\n" " }\n\n"; for (const ArgNames &name : names) os << " uint8_t* " << name.hostBuffer << " = xmalloc(" << name.sizeMacro << ");\n" << " struct hls_alloc " << name.deviceAlloc << " = {.size = " << name.sizeMacro << "};\n" << " CHECK_IOCTL(fd, IOCTL_ALLOC_BUFFER, &" << name.deviceAlloc << ");\n"; os << "\n"; for (const ArgNames &name : names) { if (name.inFile.empty()) continue; os << " if (read_file(" << name.inFileMacro << ", " << name.hostBuffer << ", " << name.sizeMacro << ") != 0)\n" << " return EXIT_FAILURE;\n"; os << " struct hls_transfer " << name.hostBuffer << "_write = {\n" " .idx = " << name.deviceAlloc << ".idx,\n" " .data = (uint64_t)(uintptr_t)" << name.hostBuffer << ",\n" " .size = " << name.sizeMacro << ",\n" " };\n" << " CHECK_IOCTL(fd, IOCTL_WRITE_BUFFER, &" << name.hostBuffer << "_write);\n\n"; } os << " struct hls_start start = {\n" " .ctrl_offset = " << prefix << "_CTRL_OFFSET,\n" " .num_args = " << prefix << "_NUM_ARGS,\n" " .args =\n" " {\n"; for (const ArgNames &name : names) os << " {.buf_idx = " << name.deviceAlloc << ".idx,\n" << " .low_offset = " << name.lowMacro << ",\n" << " .high_offset = " << name.highMacro << "},\n"; os << " },\n" " };\n" "\n" " /*\n" " * Only the kernel is timed, from starting it to seeing it " "done. Moving\n" " * the buffers to and from the device is left out.\n" " */\n" " struct timespec begin, end;\n" " clock_gettime(CLOCK_MONOTONIC_RAW, &begin);\n" " CHECK_IOCTL(fd, IOCTL_START_KERNEL, &start);\n" "\n" " int done = 0;\n" " while (!done) CHECK_IOCTL(fd, IOCTL_POLL_DONE, &done);\n" " clock_gettime(CLOCK_MONOTONIC_RAW, &end);\n" " printf(\"" << designName << ": ran on the FPGA in %.6f ms\\n\",\n" " elapsed_ms(begin, end));\n\n"; for (const ArgNames &name : names) { if (name.outFile.empty()) continue; os << " struct hls_transfer " << name.hostBuffer << "_read = {\n" " .idx = " << name.deviceAlloc << ".idx,\n" " .data = (uint64_t)(uintptr_t)" << name.hostBuffer << ",\n" " .size = " << name.sizeMacro << ",\n" " };\n" << " CHECK_IOCTL(fd, IOCTL_READ_BUFFER, &" << name.hostBuffer << "_read);\n" << " if (write_file(" << name.outFileMacro << ", " << name.hostBuffer << ", " << name.sizeMacro << ") != 0)\n" << " return EXIT_FAILURE;\n" << " printf(\"wrote %zu bytes to %s\\n\", (size_t)" << name.sizeMacro << ", " << name.outFileMacro << ");\n\n"; } for (const ArgNames &name : names) os << " CHECK_IOCTL(fd, IOCTL_FREE_BUFFER, &" << name.deviceAlloc << ".idx);\n"; for (const ArgNames &name : names) os << " free(" << name.hostBuffer << ");\n"; os << " close(fd);\n" " return EXIT_SUCCESS;\n" "}\n"; return success(); }