diff --git a/apps/microtvm/arduino/template_project/microtvm_api_server.py b/apps/microtvm/arduino/template_project/microtvm_api_server.py index 05c17ee194b2..f121238a678d 100644 --- a/apps/microtvm/arduino/template_project/microtvm_api_server.py +++ b/apps/microtvm/arduino/template_project/microtvm_api_server.py @@ -197,8 +197,8 @@ def _disassemble_mlf(self, mlf_tar_path, source_dir): metadata = json.load(f) return metadata - def _template_model_header(self, source_dir, metadata): - with open(source_dir / "model.h", "r") as f: + def _template_model(self, source_dir, metadata): + with open(source_dir / "platform.c", "r") as f: model_h_template = Template(f.read()) all_module_names = [] @@ -218,7 +218,7 @@ def _template_model_header(self, source_dir, metadata): "workspace_size_bytes": workspace_size_bytes, } - with open(source_dir / "model.h", "w") as f: + with open(source_dir / "platform.c", "w") as f: f.write(model_h_template.substitute(template_values)) # Arduino ONLY recognizes .ino, .ccp, .c, .h @@ -415,9 +415,9 @@ def generate_project(self, model_library_format_path, standalone_crt_dir, projec metadata = self._disassemble_mlf(model_library_format_path, source_dir) shutil.copy2(model_library_format_path, project_dir / MODEL_LIBRARY_FORMAT_RELPATH) - # For AOT, template model.h with metadata to minimize space usage + # For AOT, template platform.c with metadata to minimize space usage if project_type == "example_project": - self._template_model_header(source_dir, metadata) + self._template_model(source_dir, metadata) self._change_cpp_file_extensions(source_dir) diff --git a/apps/microtvm/arduino/template_project/src/example_project/model.c b/apps/microtvm/arduino/template_project/src/example_project/platform.c similarity index 80% rename from apps/microtvm/arduino/template_project/src/example_project/model.c rename to apps/microtvm/arduino/template_project/src/example_project/platform.c index 46f43752ef2a..973b8aa18cc4 100644 --- a/apps/microtvm/arduino/template_project/src/example_project/model.c +++ b/apps/microtvm/arduino/template_project/src/example_project/platform.c @@ -17,17 +17,22 @@ * under the License. */ -#include "model.h" +/*! + * \brief Implementation of TVMPlatform functions in tvm/runtime/crt/platform.h + */ #include "Arduino.h" #include "standalone_crt/include/dlpack/dlpack.h" #include "standalone_crt/include/tvm/runtime/crt/stack_allocator.h" +#define TVM_WORKSPACE_SIZE_BYTES $workspace_size_bytes + // AOT memory array, stack allocator wants it aligned -static uint8_t g_aot_memory[WORKSPACE_SIZE] +static uint8_t g_aot_memory[TVM_WORKSPACE_SIZE_BYTES] __attribute__((aligned(TVM_RUNTIME_ALLOC_ALIGNMENT_BYTES))); tvm_workspace_t app_workspace; +// Called when an internal error occurs and execution cannot continue. // Blink code for debugging purposes void TVMPlatformAbort(tvm_crt_error_t error) { TVMLogf("TVMPlatformAbort: 0x%08x\n", error); @@ -45,19 +50,23 @@ void TVMPlatformAbort(tvm_crt_error_t error) { } } -void TVMLogf(const char* msg, ...) {} - +// Allocate memory for use by TVM. tvm_crt_error_t TVMPlatformMemoryAllocate(size_t num_bytes, DLDevice dev, void** out_ptr) { return StackMemoryManager_Allocate(&app_workspace, num_bytes, out_ptr); } +// Free memory used by TVM. tvm_crt_error_t TVMPlatformMemoryFree(void* ptr, DLDevice dev) { return StackMemoryManager_Free(&app_workspace, ptr); } +// Internal logging API call implementation. +void TVMLogf(const char* msg, ...) {} + unsigned long g_utvm_start_time_micros; int g_utvm_timer_running = 0; +// Start a device timer. tvm_crt_error_t TVMPlatformTimerStart() { if (g_utvm_timer_running) { return kTvmErrorPlatformTimerBadState; @@ -67,6 +76,7 @@ tvm_crt_error_t TVMPlatformTimerStart() { return kTvmErrorNoError; } +// Stop the running device timer and get the elapsed time (in microseconds). tvm_crt_error_t TVMPlatformTimerStop(double* elapsed_time_seconds) { if (!g_utvm_timer_running) { return kTvmErrorPlatformTimerBadState; @@ -77,6 +87,7 @@ tvm_crt_error_t TVMPlatformTimerStop(double* elapsed_time_seconds) { return kTvmErrorNoError; } +// Fill a buffer with random data. tvm_crt_error_t TVMPlatformGenerateRandom(uint8_t* buffer, size_t num_bytes) { for (size_t i = 0; i < num_bytes; i++) { buffer[i] = rand(); @@ -84,7 +95,11 @@ tvm_crt_error_t TVMPlatformGenerateRandom(uint8_t* buffer, size_t num_bytes) { return kTvmErrorNoError; } -void TVMInitialize() { StackMemoryManager_Init(&app_workspace, g_aot_memory, WORKSPACE_SIZE); } +// Initialize TVM inference. +tvm_crt_error_t TVMPlatformInitialize() { + StackMemoryManager_Init(&app_workspace, g_aot_memory, sizeof(g_aot_memory)); + return kTvmErrorNoError; +} void TVMExecute(void* input_data, void* output_data) { int ret_val = tvmgen_default___tvm_main__(input_data, output_data); diff --git a/apps/microtvm/arduino/template_project/src/example_project/model.h b/apps/microtvm/arduino/template_project/src/example_project/platform.h similarity index 94% rename from apps/microtvm/arduino/template_project/src/example_project/model.h rename to apps/microtvm/arduino/template_project/src/example_project/platform.h index 7381c97e9b3f..d6f10e13e96e 100644 --- a/apps/microtvm/arduino/template_project/src/example_project/model.h +++ b/apps/microtvm/arduino/template_project/src/example_project/platform.h @@ -17,14 +17,10 @@ * under the License. */ -#define WORKSPACE_SIZE $workspace_size_bytes - #ifdef __cplusplus extern "C" { #endif -void TVMInitialize(); - /* TODO template this function signature with the input and output * data types and sizes. For example: * diff --git a/apps/microtvm/arduino/template_project/src/example_project/project.ino b/apps/microtvm/arduino/template_project/src/example_project/project.ino index 5f5683161e0a..666396b407ae 100644 --- a/apps/microtvm/arduino/template_project/src/example_project/project.ino +++ b/apps/microtvm/arduino/template_project/src/example_project/project.ino @@ -17,10 +17,10 @@ * under the License. */ -#include "src/model.h" +#include "src/standalone_crt/include/tvm/runtime/crt/platform.h" void setup() { - TVMInitialize(); + TVMPlatformInitialize(); // If desired, initialize the RNG with random noise // randomSeed(analogRead(0)); } diff --git a/apps/microtvm/arduino/template_project/src/host_driven/model_support.c b/apps/microtvm/arduino/template_project/src/host_driven/platform.c similarity index 85% rename from apps/microtvm/arduino/template_project/src/host_driven/model_support.c rename to apps/microtvm/arduino/template_project/src/host_driven/platform.c index bcc9a109cace..0a276134d419 100644 --- a/apps/microtvm/arduino/template_project/src/host_driven/model_support.c +++ b/apps/microtvm/arduino/template_project/src/host_driven/platform.c @@ -17,22 +17,28 @@ * under the License. */ +/*! + * \brief Implementation of TVMPlatform functions in tvm/runtime/crt/platform.h + */ + #include "standalone_crt/include/dlpack/dlpack.h" #include "standalone_crt/include/tvm/runtime/crt/error_codes.h" #include "stdarg.h" -// Blink code for debugging purposes +// Called when an internal error occurs and execution cannot continue. void TVMPlatformAbort(tvm_crt_error_t error) { TVMLogf("TVMPlatformAbort: 0x%08x\n", error); for (;;) ; } +// Called by the microTVM RPC server to implement TVMLogf. size_t TVMPlatformFormatMessage(char* out_buf, size_t out_buf_size_bytes, const char* fmt, va_list args) { return vsnprintf(out_buf, out_buf_size_bytes, fmt, args); } +// Allocate memory for use by TVM. tvm_crt_error_t TVMPlatformMemoryAllocate(size_t num_bytes, DLDevice dev, void** out_ptr) { if (num_bytes == 0) { num_bytes = sizeof(int); @@ -41,6 +47,7 @@ tvm_crt_error_t TVMPlatformMemoryAllocate(size_t num_bytes, DLDevice dev, void** return (*out_ptr == NULL) ? kTvmErrorPlatformNoMemory : kTvmErrorNoError; } +// Free memory used by TVM. tvm_crt_error_t TVMPlatformMemoryFree(void* ptr, DLDevice dev) { free(ptr); return kTvmErrorNoError; @@ -49,6 +56,7 @@ tvm_crt_error_t TVMPlatformMemoryFree(void* ptr, DLDevice dev) { unsigned long g_utvm_start_time_micros; int g_utvm_timer_running = 0; +// Start a device timer. tvm_crt_error_t TVMPlatformTimerStart() { if (g_utvm_timer_running) { return kTvmErrorPlatformTimerBadState; @@ -58,6 +66,7 @@ tvm_crt_error_t TVMPlatformTimerStart() { return kTvmErrorNoError; } +// Stop the running device timer and get the elapsed time (in microseconds). tvm_crt_error_t TVMPlatformTimerStop(double* elapsed_time_seconds) { if (!g_utvm_timer_running) { return kTvmErrorPlatformTimerBadState; @@ -68,6 +77,7 @@ tvm_crt_error_t TVMPlatformTimerStop(double* elapsed_time_seconds) { return kTvmErrorNoError; } +// Fill a buffer with random data. tvm_crt_error_t TVMPlatformGenerateRandom(uint8_t* buffer, size_t num_bytes) { for (size_t i = 0; i < num_bytes; i++) { buffer[i] = rand(); diff --git a/apps/microtvm/zephyr/template_project/CMakeLists.txt.template b/apps/microtvm/zephyr/template_project/CMakeLists.txt.template index 1aff9ece6bfa..cec29e724882 100644 --- a/apps/microtvm/zephyr/template_project/CMakeLists.txt.template +++ b/apps/microtvm/zephyr/template_project/CMakeLists.txt.template @@ -83,4 +83,4 @@ endif() file(GLOB_RECURSE app_srcs src/**.c src/**.cc) target_sources(app PRIVATE ${app_srcs} ${cmsis_lib_srcs}) -target_include_directories(app PRIVATE crt_config ${CMAKE_SOURCE_DIR}/include crt/include ${cmsis_includes}) +target_include_directories(app PRIVATE crt_config include ${CMAKE_SOURCE_DIR}/include crt/include ${cmsis_includes}) diff --git a/apps/microtvm/zephyr/template_project/microtvm_api_server.py b/apps/microtvm/zephyr/template_project/microtvm_api_server.py index 17dd991229b8..227b0389445a 100644 --- a/apps/microtvm/zephyr/template_project/microtvm_api_server.py +++ b/apps/microtvm/zephyr/template_project/microtvm_api_server.py @@ -210,14 +210,14 @@ def _get_board_mem_size_bytes(zephyr_base: str, board: str): return None -DEFAULT_HEAP_SIZE_BYTES = 216 * 1024 +DEFAULT_WORKSPACE_SIZE_BYTES = 216 * 1024 def _get_recommended_heap_size_bytes(board: str): prop = BOARD_PROPERTIES[board] if "recommended_heap_size_bytes" in prop: return prop["recommended_heap_size_bytes"] - return DEFAULT_HEAP_SIZE_BYTES + return DEFAULT_WORKSPACE_SIZE_BYTES def generic_find_serial_port(serial_number: str = None): @@ -358,11 +358,11 @@ def _get_nrf_device_args(serial_number: str = None) -> list: help="Run on the FVP emulator instead of hardware.", ), server.ProjectOption( - "heap_size_bytes", + "workspace_size_bytes", optional=["generate_project"], type="int", default=None, - help="Sets the value for HEAP_SIZE_BYTES passed to K_HEAP_DEFINE() to service TVM memory allocation requests.", + help="Sets the value for TVM_WORKSPACE_SIZE_BYTES passed to K_HEAP_DEFINE() to service TVM memory allocation requests.", ), ] @@ -403,7 +403,13 @@ def server_info_query(self, tvm_version): } def _create_prj_conf( - self, project_dir: pathlib.Path, board: str, project_type: str, config_main_stack_size + self, + project_dir: pathlib.Path, + board: str, + project_type: str, + config_main_stack_size: int, + config_led: bool, + use_fvp: bool, ): with open(project_dir / "prj.conf", "w") as f: f.write( @@ -413,6 +419,13 @@ def _create_prj_conf( "CONFIG_UART_INTERRUPT_DRIVEN=y\n" "\n" ) + if ( + config_led + and not self._is_qemu(board, use_fvp) + and not self._is_fvp(board, use_fvp) + ): + f.write("# For debugging.\n" "CONFIG_LED=y\n" "\n") + f.write("# For TVMPlatformAbort().\n" "CONFIG_REBOOT=y\n" "\n") if project_type == "host_driven": @@ -522,6 +535,18 @@ def _generate_cmake_args( return cmake_args + def _copy_src_and_header_files(self, src_dir: pathlib.Path, dst_dir: pathlib.Path): + """Copy content of src_dir from template project to dst_dir in separate + source and header sub-directories. + """ + for file in os.listdir(src_dir): + file = src_dir / file + if file.is_file(): + if file.suffix in [".cc", ".c"]: + shutil.copy2(file, dst_dir / "src") + elif file.suffix in [".h"]: + shutil.copy2(file, dst_dir / "include" / "tvm") + def generate_project(self, model_library_format_path, standalone_crt_dir, project_dir, options): zephyr_board = options["board"] project_type = options["project_type"] @@ -533,7 +558,7 @@ def generate_project(self, model_library_format_path, standalone_crt_dir, projec verbose = options.get("verbose") recommended_heap_size = _get_recommended_heap_size_bytes(zephyr_board) - heap_size_bytes = options.get("heap_size_bytes") or recommended_heap_size + workspace_size_bytes = options.get("workspace_size_bytes") or recommended_heap_size board_mem_size = _get_board_mem_size_bytes(zephyr_base, zephyr_board) compile_definitions = options.get("compile_definitions") @@ -602,7 +627,7 @@ def generate_project(self, model_library_format_path, standalone_crt_dir, projec else: shutil.copy2(src_path, dst_path) - # Populate Makefile. + # Populate CMakeLists. with open(project_dir / CMAKELIST_FILENAME, "w") as cmake_f: with open(API_SERVER_DIR / f"{CMAKELIST_FILENAME}.template", "r") as cmake_template_f: for line in cmake_template_f: @@ -629,10 +654,10 @@ def generate_project(self, model_library_format_path, standalone_crt_dir, projec if board_mem_size is not None: assert ( - heap_size_bytes < board_mem_size - ), f"Heap size {heap_size_bytes} is larger than memory size {board_mem_size} on this board." + workspace_size_bytes < board_mem_size + ), f"Workspace size {workspace_size_bytes} is larger than memory size {board_mem_size} on this board." cmake_f.write( - f"target_compile_definitions(app PUBLIC -DHEAP_SIZE_BYTES={heap_size_bytes})\n" + f"target_compile_definitions(app PUBLIC -DTVM_WORKSPACE_SIZE_BYTES={workspace_size_bytes})\n" ) if compile_definitions: @@ -649,7 +674,9 @@ def generate_project(self, model_library_format_path, standalone_crt_dir, projec if self._is_fvp(zephyr_board, use_fvp): cmake_f.write(f"target_compile_definitions(app PUBLIC -DFVP=1)\n") - self._create_prj_conf(project_dir, zephyr_board, project_type, config_main_stack_size) + self._create_prj_conf( + project_dir, zephyr_board, project_type, config_main_stack_size, verbose, use_fvp + ) # Populate crt-config.h crt_config_dir = project_dir / "crt_config" @@ -658,13 +685,19 @@ def generate_project(self, model_library_format_path, standalone_crt_dir, projec API_SERVER_DIR / "crt_config" / "crt_config.h", crt_config_dir / "crt_config.h" ) - # Populate src/ + # Populate `src` and `include` src_dir = project_dir / "src" - if project_type != "host_driven" or self._is_fvp(zephyr_board, use_fvp): - shutil.copytree(API_SERVER_DIR / "src" / project_type, src_dir) - else: - src_dir.mkdir() - shutil.copy2(API_SERVER_DIR / "src" / project_type / "main.c", src_dir) + src_dir.mkdir() + include_dir = project_dir / "include" / "tvm" + include_dir.mkdir(parents=True) + src_project_type_dir = API_SERVER_DIR / "src" / project_type + self._copy_src_and_header_files(src_project_type_dir, project_dir) + + if self._is_fvp(zephyr_board, use_fvp): + self._copy_src_and_header_files(src_project_type_dir / "fvp", project_dir) + + if project_type == "mlperftiny": + shutil.copytree(src_project_type_dir / "api", src_dir / "api") # Populate extra_files if extra_files_tar: diff --git a/apps/microtvm/zephyr/template_project/src/aot_standalone_demo/main.c b/apps/microtvm/zephyr/template_project/src/aot_standalone_demo/main.c index 9ba521ae171d..fff8f5787597 100644 --- a/apps/microtvm/zephyr/template_project/src/aot_standalone_demo/main.c +++ b/apps/microtvm/zephyr/template_project/src/aot_standalone_demo/main.c @@ -25,23 +25,18 @@ #include #include #include +#include #include -#include +#include -#include "input_data.h" -#include "output_data.h" +#include "tvm/input_data.h" +#include "tvm/output_data.h" #include "tvmgen_default.h" -#include "zephyr_uart.h" #ifdef CONFIG_ARCH_POSIX #include "posix_board_if.h" #endif -// WORKSPACE_SIZE defined in Project API Makefile - -static uint8_t g_aot_memory[WORKSPACE_SIZE]; -tvm_workspace_t app_workspace; - // Transport Commands. // Commands on host end with `\n` // Commands on microTVM device end with `%` @@ -53,126 +48,84 @@ const unsigned char CMD_INFER[] = "infer"; #define CMD_SIZE 80u #define CMD_TERMINATOR '%' -size_t TVMPlatformFormatMessage(char* out_buf, size_t out_buf_size_bytes, const char* fmt, - va_list args) { - return vsnprintk(out_buf, out_buf_size_bytes, fmt, args); -} +static uint8_t main_rx_buf[128]; +static uint8_t g_cmd_buf[128]; +static size_t g_cmd_buf_ind; -void TVMLogf(const char* msg, ...) { - char buffer[256]; - int size; - va_list args; - va_start(args, msg); - size = vsprintf(buffer, msg, args); - va_end(args); - TVMPlatformWriteSerial(buffer, (uint32_t)size); -} +static const struct device* g_microtvm_uart; +#define RING_BUF_SIZE_BYTES (TVM_CRT_MAX_PACKET_SIZE_BYTES + 100) -void TVMPlatformAbort(tvm_crt_error_t error) { - TVMLogf("TVMPlatformAbort: %08x\n", error); - sys_reboot(SYS_REBOOT_COLD); - for (;;) - ; -} +// Ring buffer used to store data read from the UART on rx interrupt. +RING_BUF_DECLARE(uart_rx_rbuf, RING_BUF_SIZE_BYTES); -tvm_crt_error_t TVMPlatformMemoryAllocate(size_t num_bytes, DLDevice dev, void** out_ptr) { - return StackMemoryManager_Allocate(&app_workspace, num_bytes, out_ptr); +uint32_t UartTxWrite(const char* data, uint32_t size) { + for (uint32_t i = 0; i < size; i++) { + uart_poll_out(g_microtvm_uart, data[i]); + } + return size; } -tvm_crt_error_t TVMPlatformMemoryFree(void* ptr, DLDevice dev) { - return StackMemoryManager_Free(&app_workspace, ptr); +uint32_t UartRxRead(uint8_t* data, uint32_t data_size_bytes) { + unsigned int key = irq_lock(); + uint32_t bytes_read = ring_buf_get(&uart_rx_rbuf, data, data_size_bytes); + irq_unlock(key); + return bytes_read; } -void timer_expiry_function(struct k_timer* timer_id) { return; } - -#define MILLIS_TIL_EXPIRY 200 -#define TIME_TIL_EXPIRY (K_MSEC(MILLIS_TIL_EXPIRY)) -struct k_timer g_microtvm_timer; -uint32_t g_microtvm_start_time; -int g_microtvm_timer_running = 0; - -// Called to start system timer. -tvm_crt_error_t TVMPlatformTimerStart() { - if (g_microtvm_timer_running) { - TVMLogf("timer already running"); - return kTvmErrorPlatformTimerBadState; - } - - k_timer_start(&g_microtvm_timer, TIME_TIL_EXPIRY, TIME_TIL_EXPIRY); - g_microtvm_start_time = k_cycle_get_32(); - g_microtvm_timer_running = 1; - return kTvmErrorNoError; +// Initialize UART +void UartInit() { + // Claim console device. + g_microtvm_uart = DEVICE_DT_GET(DT_CHOSEN(zephyr_console)); + const struct uart_config config = {.baudrate = 115200, + .parity = UART_CFG_PARITY_NONE, + .stop_bits = UART_CFG_STOP_BITS_1, + .data_bits = UART_CFG_DATA_BITS_8, + .flow_ctrl = UART_CFG_FLOW_CTRL_NONE}; + uart_configure(g_microtvm_uart, &config); + uart_rx_init(&uart_rx_rbuf, g_microtvm_uart); } -// Called to stop system timer. -tvm_crt_error_t TVMPlatformTimerStop(double* elapsed_time_seconds) { - if (!g_microtvm_timer_running) { - TVMLogf("timer not running"); - return kTvmErrorSystemErrorMask | 2; - } - - uint32_t stop_time = k_cycle_get_32(); - - // compute how long the work took - uint32_t cycles_spent = stop_time - g_microtvm_start_time; - if (stop_time < g_microtvm_start_time) { - // we rolled over *at least* once, so correct the rollover it was *only* - // once, because we might still use this result - cycles_spent = ~((uint32_t)0) - (g_microtvm_start_time - stop_time); - } - - uint32_t ns_spent = (uint32_t)k_cyc_to_ns_floor64(cycles_spent); - double hw_clock_res_us = ns_spent / 1000.0; - - // need to grab time remaining *before* stopping. when stopped, this function - // always returns 0. - int32_t time_remaining_ms = k_timer_remaining_get(&g_microtvm_timer); - k_timer_stop(&g_microtvm_timer); - // check *after* stopping to prevent extra expiries on the happy path - if (time_remaining_ms < 0) { - return kTvmErrorSystemErrorMask | 3; - } - uint32_t num_expiries = k_timer_status_get(&g_microtvm_timer); - uint32_t timer_res_ms = ((num_expiries * MILLIS_TIL_EXPIRY) + time_remaining_ms); - double approx_num_cycles = - (double)k_ticks_to_cyc_floor32(1) * (double)k_ms_to_ticks_ceil32(timer_res_ms); - // if we approach the limits of the HW clock datatype (uint32_t), use the - // coarse-grained timer result instead - if (approx_num_cycles > (0.5 * (~((uint32_t)0)))) { - *elapsed_time_seconds = timer_res_ms / 1000.0; - } else { - *elapsed_time_seconds = hw_clock_res_us / 1e6; +static uint8_t uart_data[8]; +// UART interrupt callback. +void uart_irq_cb(const struct device* dev, void* user_data) { + while (uart_irq_update(dev) && uart_irq_is_pending(dev)) { + struct ring_buf* rbuf = (struct ring_buf*)user_data; + if (uart_irq_rx_ready(dev) != 0) { + for (;;) { + // Read a small chunk of data from the UART. + int bytes_read = uart_fifo_read(dev, uart_data, sizeof(uart_data)); + if (bytes_read < 0) { + TVMPlatformAbort((tvm_crt_error_t)(0xbeef1)); + } else if (bytes_read == 0) { + break; + } + // Write it into the ring buffer. + int bytes_written = ring_buf_put(rbuf, uart_data, bytes_read); + if (bytes_read != bytes_written) { + TVMPlatformAbort((tvm_crt_error_t)(0xbeef2)); + } + } + } } - - g_microtvm_timer_running = 0; - return kTvmErrorNoError; } -void* TVMBackendAllocWorkspace(int device_type, int device_id, uint64_t nbytes, int dtype_code_hint, - int dtype_bits_hint) { - tvm_crt_error_t err = kTvmErrorNoError; - void* ptr = 0; - DLDevice dev = {device_type, device_id}; - assert(nbytes > 0); - err = TVMPlatformMemoryAllocate(nbytes, dev, &ptr); - CHECK_EQ(err, kTvmErrorNoError, - "TVMBackendAllocWorkspace(%d, %d, %" PRIu64 ", %d, %d) -> %" PRId32, device_type, - device_id, nbytes, dtype_code_hint, dtype_bits_hint, err); - return ptr; +// Used to initialize the UART receiver. +void uart_rx_init(struct ring_buf* rbuf, const struct device* dev) { + uart_irq_callback_user_data_set(dev, uart_irq_cb, (void*)rbuf); + uart_irq_rx_enable(dev); } -int TVMBackendFreeWorkspace(int device_type, int device_id, void* ptr) { - tvm_crt_error_t err = kTvmErrorNoError; - DLDevice dev = {device_type, device_id}; - err = TVMPlatformMemoryFree(ptr, dev); - return err; +void TVMLogf(const char* msg, ...) { + char buffer[256]; + int size; + va_list args; + va_start(args, msg); + size = vsprintf(buffer, msg, args); + va_end(args); + UartTxWrite(buffer, (uint32_t)size); } -static uint8_t main_rx_buf[128]; -static uint8_t g_cmd_buf[128]; -static size_t g_cmd_buf_ind; - -void TVMInfer() { +void Infer() { struct tvmgen_default_inputs inputs = { .input_1 = input_data, }; @@ -180,8 +133,6 @@ void TVMInfer() { .Identity = output_data, }; - StackMemoryManager_Init(&app_workspace, g_aot_memory, WORKSPACE_SIZE); - double elapsed_time = 0; TVMPlatformTimerStart(); int ret_val = tvmgen_default_run(&inputs, &outputs); @@ -206,11 +157,11 @@ void TVMInfer() { // Execute functions based on received command void command_ready(char* command) { if (strncmp(command, CMD_INIT, CMD_SIZE) == 0) { - TVMPlatformWriteSerial(CMD_WAKEUP, sizeof(CMD_WAKEUP)); + UartTxWrite(CMD_WAKEUP, sizeof(CMD_WAKEUP)); } else if (strncmp(command, CMD_INFER, CMD_SIZE) == 0) { - TVMInfer(); + Infer(); } else { - TVMPlatformWriteSerial(CMD_READY, sizeof(CMD_READY)); + UartTxWrite(CMD_READY, sizeof(CMD_READY)); } } @@ -229,13 +180,13 @@ void serial_callback(char* message, int len_bytes) { } void main(void) { + TVMPlatformInitialize(); + UartInit(); g_cmd_buf_ind = 0; memset((char*)g_cmd_buf, 0, sizeof(g_cmd_buf)); - TVMPlatformUARTInit(); - k_timer_init(&g_microtvm_timer, NULL, NULL); while (true) { - int bytes_read = TVMPlatformUartRxRead(main_rx_buf, sizeof(main_rx_buf)); + int bytes_read = UartRxRead(main_rx_buf, sizeof(main_rx_buf)); if (bytes_read > 0) { serial_callback(main_rx_buf, bytes_read); } diff --git a/apps/microtvm/zephyr/template_project/src/aot_standalone_demo/platform.c b/apps/microtvm/zephyr/template_project/src/aot_standalone_demo/platform.c new file mode 100644 index 000000000000..c66dad571155 --- /dev/null +++ b/apps/microtvm/zephyr/template_project/src/aot_standalone_demo/platform.c @@ -0,0 +1,126 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +/*! + * \brief Implementation of TVMPlatform functions in tvm/runtime/crt/platform.h + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "crt_config.h" +#include "dlpack/dlpack.h" +#include "tvmgen_default.h" + +// TVM_WORKSPACE_SIZE_BYTES defined in Project API Makefile +static uint8_t g_aot_memory[TVM_WORKSPACE_SIZE_BYTES]; +tvm_workspace_t app_workspace; + +#define MILLIS_TIL_EXPIRY 200 +#define TIME_TIL_EXPIRY (K_MSEC(MILLIS_TIL_EXPIRY)) +struct k_timer g_microtvm_timer; +uint32_t g_microtvm_start_time; +int g_microtvm_timer_running = 0; + +size_t TVMPlatformFormatMessage(char* out_buf, size_t out_buf_size_bytes, const char* fmt, + va_list args) { + return vsnprintk(out_buf, out_buf_size_bytes, fmt, args); +} + +void TVMPlatformAbort(tvm_crt_error_t error) { + TVMLogf("TVMPlatformAbort: %08x\n", error); + sys_reboot(SYS_REBOOT_COLD); + for (;;) + ; +} + +tvm_crt_error_t TVMPlatformMemoryAllocate(size_t num_bytes, DLDevice dev, void** out_ptr) { + return StackMemoryManager_Allocate(&app_workspace, num_bytes, out_ptr); +} + +tvm_crt_error_t TVMPlatformMemoryFree(void* ptr, DLDevice dev) { + return StackMemoryManager_Free(&app_workspace, ptr); +} + +tvm_crt_error_t TVMPlatformInitialize() { + k_timer_init(&g_microtvm_timer, NULL, NULL); + StackMemoryManager_Init(&app_workspace, g_aot_memory, sizeof(g_aot_memory)); + return kTvmErrorNoError; +} + +tvm_crt_error_t TVMPlatformTimerStart() { + if (g_microtvm_timer_running) { + TVMLogf("timer already running"); + return kTvmErrorPlatformTimerBadState; + } + + k_timer_start(&g_microtvm_timer, TIME_TIL_EXPIRY, TIME_TIL_EXPIRY); + g_microtvm_start_time = k_cycle_get_32(); + g_microtvm_timer_running = 1; + return kTvmErrorNoError; +} + +tvm_crt_error_t TVMPlatformTimerStop(double* elapsed_time_seconds) { + if (!g_microtvm_timer_running) { + TVMLogf("timer not running"); + return kTvmErrorSystemErrorMask | 2; + } + + uint32_t stop_time = k_cycle_get_32(); + + // compute how long the work took + uint32_t cycles_spent = stop_time - g_microtvm_start_time; + if (stop_time < g_microtvm_start_time) { + // we rolled over *at least* once, so correct the rollover it was *only* + // once, because we might still use this result + cycles_spent = ~((uint32_t)0) - (g_microtvm_start_time - stop_time); + } + + uint32_t ns_spent = (uint32_t)k_cyc_to_ns_floor64(cycles_spent); + double hw_clock_res_us = ns_spent / 1000.0; + + // need to grab time remaining *before* stopping. when stopped, this function + // always returns 0. + int32_t time_remaining_ms = k_timer_remaining_get(&g_microtvm_timer); + k_timer_stop(&g_microtvm_timer); + // check *after* stopping to prevent extra expiries on the happy path + if (time_remaining_ms < 0) { + return kTvmErrorSystemErrorMask | 3; + } + uint32_t num_expiries = k_timer_status_get(&g_microtvm_timer); + uint32_t timer_res_ms = ((num_expiries * MILLIS_TIL_EXPIRY) + time_remaining_ms); + double approx_num_cycles = + (double)k_ticks_to_cyc_floor32(1) * (double)k_ms_to_ticks_ceil32(timer_res_ms); + // if we approach the limits of the HW clock datatype (uint32_t), use the + // coarse-grained timer result instead + if (approx_num_cycles > (0.5 * (~((uint32_t)0)))) { + *elapsed_time_seconds = timer_res_ms / 1000.0; + } else { + *elapsed_time_seconds = hw_clock_res_us / 1e6; + } + + g_microtvm_timer_running = 0; + return kTvmErrorNoError; +} diff --git a/apps/microtvm/zephyr/template_project/src/aot_standalone_demo/zephyr_uart.c b/apps/microtvm/zephyr/template_project/src/aot_standalone_demo/zephyr_uart.c deleted file mode 100644 index 8d5f91208166..000000000000 --- a/apps/microtvm/zephyr/template_project/src/aot_standalone_demo/zephyr_uart.c +++ /dev/null @@ -1,87 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance - * with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ -#include "zephyr_uart.h" - -#include -#include - -#include "crt_config.h" - -static const struct device* g_microtvm_uart; -#define RING_BUF_SIZE_BYTES (TVM_CRT_MAX_PACKET_SIZE_BYTES + 100) - -// Ring buffer used to store data read from the UART on rx interrupt. -RING_BUF_DECLARE(uart_rx_rbuf, RING_BUF_SIZE_BYTES); - -static uint8_t uart_data[8]; -// UART interrupt callback. -void uart_irq_cb(const struct device* dev, void* user_data) { - while (uart_irq_update(dev) && uart_irq_is_pending(dev)) { - struct ring_buf* rbuf = (struct ring_buf*)user_data; - if (uart_irq_rx_ready(dev) != 0) { - for (;;) { - // Read a small chunk of data from the UART. - int bytes_read = uart_fifo_read(dev, uart_data, sizeof(uart_data)); - if (bytes_read < 0) { - TVMPlatformAbort((tvm_crt_error_t)(0xbeef1)); - } else if (bytes_read == 0) { - break; - } - // Write it into the ring buffer. - int bytes_written = ring_buf_put(rbuf, uart_data, bytes_read); - if (bytes_read != bytes_written) { - TVMPlatformAbort((tvm_crt_error_t)(0xbeef2)); - } - } - } - } -} - -// Used to initialize the UART receiver. -void uart_rx_init(struct ring_buf* rbuf, const struct device* dev) { - uart_irq_callback_user_data_set(dev, uart_irq_cb, (void*)rbuf); - uart_irq_rx_enable(dev); -} - -uint32_t TVMPlatformUartRxRead(uint8_t* data, uint32_t data_size_bytes) { - unsigned int key = irq_lock(); - uint32_t bytes_read = ring_buf_get(&uart_rx_rbuf, data, data_size_bytes); - irq_unlock(key); - return bytes_read; -} - -uint32_t TVMPlatformWriteSerial(const char* data, uint32_t size) { - for (uint32_t i = 0; i < size; i++) { - uart_poll_out(g_microtvm_uart, data[i]); - } - return size; -} - -// Initialize UART -void TVMPlatformUARTInit() { - // Claim console device. - g_microtvm_uart = DEVICE_DT_GET(DT_CHOSEN(zephyr_console)); - const struct uart_config config = {.baudrate = 115200, - .parity = UART_CFG_PARITY_NONE, - .stop_bits = UART_CFG_STOP_BITS_1, - .data_bits = UART_CFG_DATA_BITS_8, - .flow_ctrl = UART_CFG_FLOW_CTRL_NONE}; - uart_configure(g_microtvm_uart, &config); - uart_rx_init(&uart_rx_rbuf, g_microtvm_uart); -} diff --git a/apps/microtvm/zephyr/template_project/src/aot_standalone_demo/zephyr_uart.h b/apps/microtvm/zephyr/template_project/src/aot_standalone_demo/zephyr_uart.h deleted file mode 100644 index 771cb490d0d6..000000000000 --- a/apps/microtvm/zephyr/template_project/src/aot_standalone_demo/zephyr_uart.h +++ /dev/null @@ -1,50 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance - * with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -#ifndef TVM_APPS_MICROTVM_ZEPHYR_AOT_STANDALONE_DEMO_ZEPHYR_UART_H_ -#define TVM_APPS_MICROTVM_ZEPHYR_AOT_STANDALONE_DEMO_ZEPHYR_UART_H_ - -#include - -// Used to read data from the UART. - -/*! - * \brief Read Uart Rx buffer. - * \param data Pointer to read data. - * \param data_size_bytes Read request size in bytes. - * - * \return Number of data read in bytes. - */ -uint32_t TVMPlatformUartRxRead(uint8_t* data, uint32_t data_size_bytes); - -/*! - * \brief Write data in serial. - * \param data Pointer to data to write. - * \param size Size of data in bytes. - * - * \return Number of write in bytes. - */ -uint32_t TVMPlatformWriteSerial(const char* data, uint32_t size); - -/*! - * \brief Initialize Uart. - */ -void TVMPlatformUARTInit(); - -#endif /* TVM_APPS_MICROTVM_ZEPHYR_AOT_STANDALONE_DEMO_ZEPHYR_UART_H_ */ diff --git a/apps/microtvm/zephyr/template_project/src/host_driven/fvp/semihost.c b/apps/microtvm/zephyr/template_project/src/host_driven/fvp/semihost.c index 2e03df096307..f51aa47c9f71 100644 --- a/apps/microtvm/zephyr/template_project/src/host_driven/fvp/semihost.c +++ b/apps/microtvm/zephyr/template_project/src/host_driven/fvp/semihost.c @@ -22,7 +22,7 @@ * SPDX-License-Identifier: Apache-2.0 */ -#include "semihost.h" +#include "tvm/semihost.h" int32_t stdout_fd; int32_t stdin_fd; diff --git a/apps/microtvm/zephyr/template_project/src/host_driven/main.c b/apps/microtvm/zephyr/template_project/src/host_driven/main.c index c01daab6d0d6..1c63474817de 100644 --- a/apps/microtvm/zephyr/template_project/src/host_driven/main.c +++ b/apps/microtvm/zephyr/template_project/src/host_driven/main.c @@ -29,7 +29,6 @@ * this logic into your own application. */ #include -#include #include #include #include @@ -37,15 +36,7 @@ #include #include #include -#include -#include -#include #include -#include - -#ifdef FVP -#include "fvp/semihost.h" -#endif #ifdef CONFIG_ARCH_POSIX #include "posix_board_if.h" @@ -53,15 +44,11 @@ #include "crt_config.h" -static const struct device* tvm_uart; +#ifdef FVP +#include "tvm/semihost.h" +#endif -#ifdef CONFIG_LED -#define LED0_NODE DT_ALIAS(led0) -#define LED0 DT_GPIO_LABEL(LED0_NODE, gpios) -#define LED0_PIN DT_GPIO_PIN(LED0_NODE, gpios) -#define LED0_FLAGS DT_GPIO_FLAGS(LED0_NODE, gpios) -static const struct device* led0_pin; -#endif // CONFIG_LED +static const struct device* tvm_uart; static size_t g_num_bytes_requested = 0; static size_t g_num_bytes_written = 0; @@ -69,20 +56,11 @@ static size_t g_num_bytes_in_rx_buffer = 0; // Called by TVM to write serial data to the UART. ssize_t uart_write(void* unused_context, const uint8_t* data, size_t size) { -#ifdef CONFIG_LED - gpio_pin_set(led0_pin, LED0_PIN, 1); -#endif g_num_bytes_requested += size; - for (size_t i = 0; i < size; i++) { uart_poll_out(tvm_uart, data[i]); g_num_bytes_written++; } - -#ifdef CONFIG_LED - gpio_pin_set(led0_pin, LED0_PIN, 0); -#endif - return size; } @@ -94,105 +72,6 @@ ssize_t serial_write(void* unused_context, const uint8_t* data, size_t size) { #endif } -// This is invoked by Zephyr from an exception handler, which will be invoked -// if the device crashes. Here, we turn on the LED and spin. -void k_sys_fatal_error_handler(unsigned int reason, const z_arch_esf_t* esf) { -#ifdef CONFIG_LED - gpio_pin_set(led0_pin, LED0_PIN, 1); -#endif - for (;;) - ; -} - -// Called by TVM when a message needs to be formatted. -size_t TVMPlatformFormatMessage(char* out_buf, size_t out_buf_size_bytes, const char* fmt, - va_list args) { - return vsnprintk(out_buf, out_buf_size_bytes, fmt, args); -} - -// Called by TVM when an internal invariant is violated, and execution cannot continue. -void TVMPlatformAbort(tvm_crt_error_t error) { - TVMLogf("TVMError: 0x%x", error); - sys_reboot(SYS_REBOOT_COLD); -#ifdef CONFIG_LED - gpio_pin_set(led0_pin, LED0_PIN, 1); -#endif - for (;;) - ; -} - -// Called by TVM to generate random data. -tvm_crt_error_t TVMPlatformGenerateRandom(uint8_t* buffer, size_t num_bytes) { - uint32_t random; // one unit of random data. - - // Fill parts of `buffer` which are as large as `random`. - size_t num_full_blocks = num_bytes / sizeof(random); - for (int i = 0; i < num_full_blocks; ++i) { - random = sys_rand32_get(); - memcpy(&buffer[i * sizeof(random)], &random, sizeof(random)); - } - - // Fill any leftover tail which is smaller than `random`. - size_t num_tail_bytes = num_bytes % sizeof(random); - if (num_tail_bytes > 0) { - random = sys_rand32_get(); - memcpy(&buffer[num_bytes - num_tail_bytes], &random, num_tail_bytes); - } - return kTvmErrorNoError; -} - -// Heap for use by TVMPlatformMemoryAllocate. -K_HEAP_DEFINE(tvm_heap, HEAP_SIZE_BYTES); - -// Called by TVM to allocate memory. -tvm_crt_error_t TVMPlatformMemoryAllocate(size_t num_bytes, DLDevice dev, void** out_ptr) { - *out_ptr = k_heap_alloc(&tvm_heap, num_bytes, K_NO_WAIT); - return (*out_ptr == NULL) ? kTvmErrorPlatformNoMemory : kTvmErrorNoError; -} - -// Called by TVM to deallocate memory. -tvm_crt_error_t TVMPlatformMemoryFree(void* ptr, DLDevice dev) { - k_heap_free(&tvm_heap, ptr); - return kTvmErrorNoError; -} - -volatile timing_t g_microtvm_start_time, g_microtvm_end_time; -int g_microtvm_timer_running = 0; - -// Called to start system timer. -tvm_crt_error_t TVMPlatformTimerStart() { - if (g_microtvm_timer_running) { - TVMLogf("timer already running"); - return kTvmErrorPlatformTimerBadState; - } - -#ifdef CONFIG_LED - gpio_pin_set(led0_pin, LED0_PIN, 1); -#endif - g_microtvm_start_time = timing_counter_get(); - g_microtvm_timer_running = 1; - return kTvmErrorNoError; -} - -// Called to stop system timer. -tvm_crt_error_t TVMPlatformTimerStop(double* elapsed_time_seconds) { - if (!g_microtvm_timer_running) { - TVMLogf("timer not running"); - return kTvmErrorSystemErrorMask | 2; - } - -#ifdef CONFIG_LED - gpio_pin_set(led0_pin, LED0_PIN, 0); -#endif - - g_microtvm_end_time = timing_counter_get(); - uint64_t cycles = timing_cycles_get(&g_microtvm_start_time, &g_microtvm_end_time); - uint64_t ns_spent = timing_cycles_to_ns(cycles); - *elapsed_time_seconds = ns_spent / (double)1e9; - g_microtvm_timer_running = 0; - return kTvmErrorNoError; -} - // Ring buffer used to store data read from the UART on rx interrupt. // This ring buffer size is only required for testing with QEMU and not for physical hardware. #define RING_BUF_SIZE_BYTES (TVM_CRT_MAX_PACKET_SIZE_BYTES + 100) @@ -223,8 +102,6 @@ void uart_irq_cb(const struct device* dev, void* user_data) { if (err != 0) { TVMPlatformAbort((tvm_crt_error_t)0xbeef2); } - // CHECK_EQ(bytes_read, bytes_written, "bytes_read: %d; bytes_written: %d", bytes_read, - // bytes_written); } } } @@ -238,29 +115,12 @@ void uart_rx_init(struct ring_buf* rbuf, const struct device* dev) { // The main function of this application. extern void __stdout_hook_install(int (*hook)(int)); void main(void) { -#ifdef CONFIG_LED - int ret; - led0_pin = device_get_binding(LED0); - if (led0_pin == NULL) { - for (;;) - ; - } - ret = gpio_pin_configure(led0_pin, LED0_PIN, GPIO_OUTPUT_ACTIVE | LED0_FLAGS); - if (ret < 0) { - TVMPlatformAbort((tvm_crt_error_t)0xbeef4); - } - gpio_pin_set(led0_pin, LED0_PIN, 1); -#endif + TVMPlatformInitialize(); // Claim console device. tvm_uart = DEVICE_DT_GET(DT_CHOSEN(zephyr_console)); uart_rx_init(&uart_rx_rbuf, tvm_uart); - // Initialize system timing. We could stop and start it every time, but we'll - // be using it enough we should just keep it enabled. - timing_init(); - timing_start(); - #ifdef FVP init_semihosting(); // send some dummy log to speed up the initialization @@ -274,10 +134,6 @@ void main(void) { microtvm_rpc_server_t server = MicroTVMRpcServerInit(serial_write, NULL); TVMLogf("microTVM Zephyr runtime - running"); -#ifdef CONFIG_LED - gpio_pin_set(led0_pin, LED0_PIN, 0); -#endif - // The main application loop. We continuously read commands from the UART // and dispatch them to MicroTVMRpcServerLoop(). while (true) { diff --git a/apps/microtvm/zephyr/template_project/src/host_driven/platform.c b/apps/microtvm/zephyr/template_project/src/host_driven/platform.c new file mode 100644 index 000000000000..8aa9abf235c7 --- /dev/null +++ b/apps/microtvm/zephyr/template_project/src/host_driven/platform.c @@ -0,0 +1,155 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +/*! + * \brief Implementation of TVMPlatform functions in tvm/runtime/crt/platform.h + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +K_HEAP_DEFINE(tvm_heap, TVM_WORKSPACE_SIZE_BYTES); + +volatile timing_t g_microtvm_start_time, g_microtvm_end_time; +int g_microtvm_timer_running = 0; + +#ifdef CONFIG_LED +#define LED0_NODE DT_ALIAS(led0) +static const struct gpio_dt_spec led0 = GPIO_DT_SPEC_GET(LED0_NODE, gpios); +#endif // CONFIG_LED + +// This is invoked by Zephyr from an exception handler, which will be invoked +// if the device crashes. Here, we turn on the LED and spin. +void k_sys_fatal_error_handler(unsigned int reason, const z_arch_esf_t* esf) { +#ifdef CONFIG_LED + gpio_pin_set_dt(&led0, 1); +#endif + for (;;) + ; +} + +void TVMPlatformAbort(tvm_crt_error_t error) { + TVMLogf("TVMError: 0x%x", error); + sys_reboot(SYS_REBOOT_COLD); +#ifdef CONFIG_LED + gpio_pin_set_dt(&led0, 1); +#endif + for (;;) + ; +} + +size_t TVMPlatformFormatMessage(char* out_buf, size_t out_buf_size_bytes, const char* fmt, + va_list args) { + return vsnprintk(out_buf, out_buf_size_bytes, fmt, args); +} + +tvm_crt_error_t TVMPlatformMemoryAllocate(size_t num_bytes, DLDevice dev, void** out_ptr) { + *out_ptr = k_heap_alloc(&tvm_heap, num_bytes, K_NO_WAIT); + return (*out_ptr == NULL) ? kTvmErrorPlatformNoMemory : kTvmErrorNoError; +} + +tvm_crt_error_t TVMPlatformMemoryFree(void* ptr, DLDevice dev) { + k_heap_free(&tvm_heap, ptr); + return kTvmErrorNoError; +} + +// Called to start system timer. +tvm_crt_error_t TVMPlatformTimerStart() { + if (g_microtvm_timer_running) { + TVMLogf("timer already running"); + return kTvmErrorPlatformTimerBadState; + } + +#ifdef CONFIG_LED + gpio_pin_set_dt(&led0, 1); +#endif + g_microtvm_start_time = timing_counter_get(); + g_microtvm_timer_running = 1; + return kTvmErrorNoError; +} + +// Called to stop system timer. +tvm_crt_error_t TVMPlatformTimerStop(double* elapsed_time_seconds) { + if (!g_microtvm_timer_running) { + TVMLogf("timer not running"); + return kTvmErrorSystemErrorMask | 2; + } + +#ifdef CONFIG_LED + gpio_pin_set_dt(&led0, 0); +#endif + + g_microtvm_end_time = timing_counter_get(); + uint64_t cycles = timing_cycles_get(&g_microtvm_start_time, &g_microtvm_end_time); + uint64_t ns_spent = timing_cycles_to_ns(cycles); + *elapsed_time_seconds = ns_spent / (double)1e9; + g_microtvm_timer_running = 0; + return kTvmErrorNoError; +} + +tvm_crt_error_t TVMPlatformGenerateRandom(uint8_t* buffer, size_t num_bytes) { + uint32_t random; // one unit of random data. + + // Fill parts of `buffer` which are as large as `random`. + size_t num_full_blocks = num_bytes / sizeof(random); + for (int i = 0; i < num_full_blocks; ++i) { + random = sys_rand32_get(); + memcpy(&buffer[i * sizeof(random)], &random, sizeof(random)); + } + + // Fill any leftover tail which is smaller than `random`. + size_t num_tail_bytes = num_bytes % sizeof(random); + if (num_tail_bytes > 0) { + random = sys_rand32_get(); + memcpy(&buffer[num_bytes - num_tail_bytes], &random, num_tail_bytes); + } + return kTvmErrorNoError; +} + +tvm_crt_error_t TVMPlatformInitialize() { +#ifdef CONFIG_LED + if (!device_is_ready(led0.port)) { + for (;;) + ; + } + int ret = gpio_pin_configure_dt(&led0, GPIO_OUTPUT_ACTIVE); + if (ret < 0) { + TVMPlatformAbort((tvm_crt_error_t)0xbeef4); + } + gpio_pin_set_dt(&led0, 0); +#endif + + // Initialize system timing. We could stop and start it every time, but we'll + // be using it enough we should just keep it enabled. + timing_init(); + timing_start(); + + return kTvmErrorNoError; +} diff --git a/apps/microtvm/zephyr/template_project/src/mlperftiny/platform.cc b/apps/microtvm/zephyr/template_project/src/mlperftiny/platform.cc new file mode 100644 index 000000000000..9dc4516271df --- /dev/null +++ b/apps/microtvm/zephyr/template_project/src/mlperftiny/platform.cc @@ -0,0 +1,68 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +/*! + * \brief Implementation of TVMPlatform functions in tvm/runtime/crt/platform.h + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "crt_config.h" + +// TVM_WORKSPACE_SIZE_BYTES is defined in python +static uint8_t g_aot_memory[TVM_WORKSPACE_SIZE_BYTES]; +tvm_workspace_t app_workspace; + +size_t TVMPlatformFormatMessage(char* out_buf, size_t out_buf_size_bytes, const char* fmt, + va_list args) { + return vsnprintk(out_buf, out_buf_size_bytes, fmt, args); +} + +void TVMPlatformAbort(tvm_crt_error_t error) { + TVMLogf("TVMPlatformAbort: %08x\n", error); + sys_reboot(SYS_REBOOT_COLD); + for (;;) + ; +} + +tvm_crt_error_t TVMPlatformMemoryAllocate(size_t num_bytes, DLDevice dev, void** out_ptr) { + return StackMemoryManager_Allocate(&app_workspace, num_bytes, out_ptr); +} + +tvm_crt_error_t TVMPlatformMemoryFree(void* ptr, DLDevice dev) { + return StackMemoryManager_Free(&app_workspace, ptr); +} + +tvm_crt_error_t TVMPlatformInitialize() { + StackMemoryManager_Init(&app_workspace, g_aot_memory, sizeof(g_aot_memory)); + return kTvmErrorNoError; +} diff --git a/apps/microtvm/zephyr/template_project/src/mlperftiny/submitter_implemented.cc b/apps/microtvm/zephyr/template_project/src/mlperftiny/submitter_implemented.cc index 91ae0c025c6e..72b679c6408a 100644 --- a/apps/microtvm/zephyr/template_project/src/mlperftiny/submitter_implemented.cc +++ b/apps/microtvm/zephyr/template_project/src/mlperftiny/submitter_implemented.cc @@ -19,20 +19,46 @@ #include "api/submitter_implemented.h" +#include +#include #include #include #include +#include #include +#include #include "api/internally_implemented.h" -#include "tvmruntime.h" -#include "zephyr_uart.h" +#include "crt_config.h" +#include "tvm/output_data.h" +#include "tvmgen_default.h" + +// ############################################################### +// Model +// ############################################################### +#define MODEL_KWS 1 +#define MODEL_VWW 2 +#define MODEL_AD 3 +#define MODEL_IC 4 static void* g_input_data; -#if TARGET_MODEL == 3 // AD +#if TARGET_MODEL == MODEL_AD static uint8_t __aligned(4) g_input_data_buffer_aligned[MAX_DB_INPUT_SIZE]; #endif +// OUT_QUANT_SCALE and OUT_QUANT_ZERO are set in python. +#if TARGET_MODEL == MODEL_AD +float* g_output_data = output_data; +#else +int8_t* g_output_data = output_data; +float g_quant_scale = OUT_QUANT_SCALE; +int8_t g_quant_zero = OUT_QUANT_ZERO; +#endif +size_t g_output_data_len = output_data_len; + +// ############################################################### +// GPIO +// ############################################################### #if EE_CFG_ENERGY_MODE == 1 && NRF_BOARD != 1 // use GPIO PC6 which is on connector CN7 pin 1 on the nucleo_l4r5zi static const char* g_gpio_device_name = "GPIOC"; @@ -40,23 +66,141 @@ static const struct device* g_gpio_dev; static const gpio_pin_t g_gpio_pin = 6; #endif +// ############################################################### +// UART +// ############################################################### +#define TVM_UART_DEFAULT_BAUDRATE 115200 +static const struct device* g_microtvm_uart; + +void UartInit(uint32_t baudrate = TVM_UART_DEFAULT_BAUDRATE) { + // Claim console device. + g_microtvm_uart = DEVICE_DT_GET(DT_CHOSEN(zephyr_console)); + const struct uart_config config = {.baudrate = baudrate, + .parity = UART_CFG_PARITY_NONE, + .stop_bits = UART_CFG_STOP_BITS_1, + .data_bits = UART_CFG_DATA_BITS_8, + .flow_ctrl = UART_CFG_FLOW_CTRL_NONE}; + uart_configure(g_microtvm_uart, &config); +} + +char UartRxRead() { + unsigned char c; + int ret = -1; + while (ret != 0) { + ret = uart_poll_in(g_microtvm_uart, &c); + } + return (char)c; +} + +uint32_t UartTxWrite(const char* data, uint32_t size) { + for (uint32_t i = 0; i < size; i++) { + uart_poll_out(g_microtvm_uart, data[i]); + } + return size; +} + +// ############################################################### +// TVM +// ############################################################### +#ifdef __cplusplus +extern "C" { +#endif +// TODO(mehrdadh): remove and reuse the CRT +// implementation in src/runtime/crt/common/crt_backend_api.c +void* TVMBackendAllocWorkspace(int device_type, int device_id, uint64_t nbytes, int dtype_code_hint, + int dtype_bits_hint) { + tvm_crt_error_t err = kTvmErrorNoError; + void* ptr = 0; + DLDevice dev = {(DLDeviceType)device_type, device_id}; + assert(nbytes > 0); + err = TVMPlatformMemoryAllocate(nbytes, dev, &ptr); + CHECK_EQ(err, kTvmErrorNoError, + "TVMBackendAllocWorkspace(%d, %d, %" PRIu64 ", %d, %d) -> %" PRId32, device_type, + device_id, nbytes, dtype_code_hint, dtype_bits_hint, err); + return ptr; +} + +// TODO(mehrdadh): remove and reuse the CRT +// implementation in src/runtime/crt/common/crt_backend_api.c +int TVMBackendFreeWorkspace(int device_type, int device_id, void* ptr) { + tvm_crt_error_t err = kTvmErrorNoError; + DLDevice dev = {(DLDeviceType)device_type, device_id}; + err = TVMPlatformMemoryFree(ptr, dev); + CHECK_EQ(err, kTvmErrorNoError, "TVMBackendFreeWorkspace(%d, %d)", device_type, device_id); + return err; +} + +void TVMLogf(const char* msg, ...) { + char buffer[128]; + int size; + va_list args; + va_start(args, msg); + size = TVMPlatformFormatMessage(buffer, 128, msg, args); + va_end(args); + UartTxWrite(buffer, (size_t)size); +} + +#ifdef __cplusplus +} // extern "C" +#endif + +void Infer(void* input_ptr) { + struct tvmgen_default_inputs inputs = { +#if TARGET_MODEL == MODEL_KWS + .input_1 = input_ptr, +#elif TARGET_MODEL == MODEL_IC + .input_1_int8 = input_ptr, +#elif TARGET_MODEL == MODEL_VWW + .input_1_int8 = input_ptr, +#elif TARGET_MODEL == MODEL_AD + .input_1 = input_ptr, +#elif +#error Wrong model. +#endif + }; + + struct tvmgen_default_outputs outputs = { +#if TARGET_MODEL == MODEL_KWS +#if COMPILE_WITH_CMSISNN + .Identity = output_data, +#else + .output = output_data, +#endif +#elif TARGET_MODEL == MODEL_IC + .Identity_int8 = output_data, +#elif TARGET_MODEL == MODEL_VWW + .Identity_int8 = output_data, +#elif TARGET_MODEL == MODEL_AD + .Identity = output_data, +#endif + }; + + int ret_val = tvmgen_default_run(&inputs, &outputs); + if (ret_val != 0) { + th_printf("Error: %d\n", ret_val); + } +} + +// ############################################################### +// MLPerftiny APIs +// ############################################################### // Implement this method to prepare for inference and preprocess inputs. // Modified from source void th_load_tensor() { -#if TARGET_MODEL == 1 // KWS +#if TARGET_MODEL == MODEL_KWS g_input_data = static_cast(ee_get_buffer_pointer()); -#elif TARGET_MODEL == 2 // VWW +#elif TARGET_MODEL == MODEL_VWW // Converting uint8 to int8 int8_t* temp_int = reinterpret_cast(ee_get_buffer_pointer()); for (size_t i = 0; i < MAX_DB_INPUT_SIZE; i++) { temp_int[i] -= 128; } g_input_data = static_cast(temp_int); -#elif TARGET_MODEL == 3 // AD +#elif TARGET_MODEL == MODEL_AD uint8_t* buffer = ee_get_buffer_pointer(); memcpy(g_input_data_buffer_aligned, buffer, sizeof(g_input_data_buffer_aligned)); g_input_data = g_input_data_buffer_aligned; -#elif TARGET_MODEL == 4 // IC +#elif TARGET_MODEL == MODEL_IC uint8_t* temp_uint = reinterpret_cast(ee_get_buffer_pointer()); int8_t* temp_int = reinterpret_cast(ee_get_buffer_pointer()); for (size_t i = 0; i < MAX_DB_INPUT_SIZE; i++) { @@ -71,7 +215,7 @@ void th_load_tensor() { #endif } -#if TARGET_MODEL == 3 // model AD +#if TARGET_MODEL == MODEL_AD // calculate |output - input| for AD model static float calculate_result() { size_t feature_size = g_output_data_len; @@ -95,7 +239,7 @@ void th_results() { * The results need to be printed back in exactly this format; if easier * to just modify this loop than copy to results[] above, do that. */ -#if TARGET_MODEL == 3 // model AD +#if TARGET_MODEL == MODEL_AD th_printf("m-results-[%0.3f]\r\n", calculate_result()); #else size_t kCategoryCount = g_output_data_len; @@ -114,11 +258,11 @@ void th_results() { // Implement this method with the logic to perform one inference cycle. // Modified from source -void th_infer() { TVMInfer(g_input_data); } +void th_infer() { Infer(g_input_data); } /// \brief optional API. // Modified from source -void th_final_initialize(void) { TVMRuntimeInit(); } +void th_final_initialize(void) { TVMPlatformInitialize(); } void th_pre() {} void th_post() {} @@ -156,18 +300,18 @@ void th_printf(const char* p_fmt, ...) { va_start(args, p_fmt); size = TVMPlatformFormatMessage(buffer, 128, p_fmt, args); va_end(args); - TVMPlatformWriteSerial(buffer, (size_t)size); + UartTxWrite(buffer, (size_t)size); } // Modified from source -char th_getchar() { return TVMPlatformUartRxRead(); } +char th_getchar() { return UartRxRead(); } // Modified from source void th_serialport_initialize(void) { #if EE_CFG_ENERGY_MODE == 1 && NRF_BOARD != 1 - TVMPlatformUARTInit(9600); + UartInit(9600); #else - TVMPlatformUARTInit(); + UartInit(); #endif } diff --git a/apps/microtvm/zephyr/template_project/src/mlperftiny/tvmruntime.cc b/apps/microtvm/zephyr/template_project/src/mlperftiny/tvmruntime.cc deleted file mode 100644 index 3fb7ccf8eb39..000000000000 --- a/apps/microtvm/zephyr/template_project/src/mlperftiny/tvmruntime.cc +++ /dev/null @@ -1,164 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance - * with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -#include "tvmruntime.h" - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "output_data.h" -#include "tvmgen_default.h" -#include "zephyr_uart.h" - -#ifdef CONFIG_ARCH_POSIX -#include "posix_board_if.h" -#endif - -// OUT_QUANT_SCALE and OUT_QUANT_ZERO are set in python. -#if TARGET_MODEL == 3 -float* g_output_data = output_data; -#else -int8_t* g_output_data = output_data; -float g_quant_scale = OUT_QUANT_SCALE; -int8_t g_quant_zero = OUT_QUANT_ZERO; -#endif -size_t g_output_data_len = output_data_len; - -// WORKSPACE_SIZE is defined in python -static uint8_t g_aot_memory[WORKSPACE_SIZE]; -tvm_workspace_t app_workspace; - -size_t TVMPlatformFormatMessage(char* out_buf, size_t out_buf_size_bytes, const char* fmt, - va_list args) { - return vsnprintk(out_buf, out_buf_size_bytes, fmt, args); -} - -void TVMLogf(const char* msg, ...) { - char buffer[128]; - int size; - va_list args; - va_start(args, msg); - size = TVMPlatformFormatMessage(buffer, 128, msg, args); - va_end(args); - TVMPlatformWriteSerial(buffer, (size_t)size); -} - -void __attribute__((noreturn)) TVMPlatformAbort(tvm_crt_error_t error) { - TVMLogf("TVMPlatformAbort: %08x\n", error); - sys_reboot(SYS_REBOOT_COLD); - for (;;) - ; -} - -tvm_crt_error_t TVMPlatformMemoryAllocate(size_t num_bytes, DLDevice dev, void** out_ptr) { - return StackMemoryManager_Allocate(&app_workspace, num_bytes, out_ptr); -} - -tvm_crt_error_t TVMPlatformMemoryFree(void* ptr, DLDevice dev) { - return StackMemoryManager_Free(&app_workspace, ptr); -} - -void timer_expiry_function(struct k_timer* timer_id) { return; } - -#ifdef __cplusplus -extern "C" { -#endif -void* TVMBackendAllocWorkspace(int device_type, int device_id, uint64_t nbytes, int dtype_code_hint, - int dtype_bits_hint) { - tvm_crt_error_t err = kTvmErrorNoError; - void* ptr = 0; - DLDevice dev = {(DLDeviceType)device_type, device_id}; - assert(nbytes > 0); - err = TVMPlatformMemoryAllocate(nbytes, dev, &ptr); - CHECK_EQ(err, kTvmErrorNoError, - "TVMBackendAllocWorkspace(%d, %d, %" PRIu64 ", %d, %d) -> %" PRId32, device_type, - device_id, nbytes, dtype_code_hint, dtype_bits_hint, err); - return ptr; -} - -int TVMBackendFreeWorkspace(int device_type, int device_id, void* ptr) { - tvm_crt_error_t err = kTvmErrorNoError; - DLDevice dev = {(DLDeviceType)device_type, device_id}; - err = TVMPlatformMemoryFree(ptr, dev); - CHECK_EQ(err, kTvmErrorNoError, "TVMBackendFreeWorkspace(%d, %d)", device_type, device_id); - return err; -} - -#ifdef __cplusplus -} // extern "C" -#endif - -void TVMRuntimeInit() { StackMemoryManager_Init(&app_workspace, g_aot_memory, WORKSPACE_SIZE); } - -void TVMInfer(void* input_ptr) { - struct tvmgen_default_inputs inputs = { -#if TARGET_MODEL == MODEL_KWS - .input_1 = input_ptr, -#elif TARGET_MODEL == MODEL_IC - .input_1_int8 = input_ptr, -#elif TARGET_MODEL == MODEL_VWW - .input_1_int8 = input_ptr, -#elif TARGET_MODEL == MODEL_AD - .input_1 = input_ptr, -#elif -#error Wrong model. -#endif - }; - - struct tvmgen_default_outputs outputs = { -#if TARGET_MODEL == MODEL_KWS -#if COMPILE_WITH_CMSISNN - .Identity = output_data, -#else - .output = output_data, -#endif -#elif TARGET_MODEL == MODEL_IC - .Identity_int8 = output_data, -#elif TARGET_MODEL == MODEL_VWW - .Identity_int8 = output_data, -#elif TARGET_MODEL == MODEL_AD - .Identity = output_data, -#endif - }; - - int ret_val = tvmgen_default_run(&inputs, &outputs); - if (ret_val != 0) { - TVMLogf("Error: %d\n", ret_val); - } -} - -int8_t QuantizeFloatToInt8(float value, float scale, int zero_point) { - int32_t result = round(value / scale) + zero_point; - if (result < INT8_MIN) { - result = INT8_MIN; - } - if (result > INT8_MAX) { - result = INT8_MAX; - } - return (int8_t)(result); -} diff --git a/apps/microtvm/zephyr/template_project/src/mlperftiny/tvmruntime.h b/apps/microtvm/zephyr/template_project/src/mlperftiny/tvmruntime.h deleted file mode 100644 index 940d64634d59..000000000000 --- a/apps/microtvm/zephyr/template_project/src/mlperftiny/tvmruntime.h +++ /dev/null @@ -1,62 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance - * with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -#ifndef APPS_MICROTVM_ZEPHYR_TEMPLATE_PROJECT_SRC_MLPERFTINY_TVMRUNTIME_H_ -#define APPS_MICROTVM_ZEPHYR_TEMPLATE_PROJECT_SRC_MLPERFTINY_TVMRUNTIME_H_ - -#include -#include -#include - -#define MODEL_KWS 1 -#define MODEL_VWW 2 -#define MODEL_AD 3 -#define MODEL_IC 4 - -extern const unsigned char g_wakeup_sequence[]; -extern size_t g_output_data_len; - -#if TARGET_MODEL == 3 -extern float* g_output_data; -#else -extern int8_t* g_output_data; -#endif - -extern float g_quant_scale; -extern int8_t g_quant_zero; - -/*! - * \brief Initialize TVM runtime. - */ -void TVMRuntimeInit(); - -/*! - * \brief Run TVM inference. - */ -void TVMInfer(void* input_ptr); - -/*! - * \brief Quantize float to int8. - * \param value Input data in float. - * \param scale Quantization scale factor. - * \param zero_point Quantization zero point. - */ -int8_t QuantizeFloatToInt8(float value, float scale, int zero_point); - -#endif /* APPS_MICROTVM_ZEPHYR_TEMPLATE_PROJECT_SRC_MLPERFTINY_TVMRUNTIME_H_ */ diff --git a/apps/microtvm/zephyr/template_project/src/mlperftiny/zephyr_uart.cc b/apps/microtvm/zephyr/template_project/src/mlperftiny/zephyr_uart.cc deleted file mode 100644 index 0922c3213363..000000000000 --- a/apps/microtvm/zephyr/template_project/src/mlperftiny/zephyr_uart.cc +++ /dev/null @@ -1,89 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance - * with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -#include "zephyr_uart.h" - -#include -#include -#include - -#include "crt_config.h" - -static const struct device* g_microtvm_uart; - -static uint8_t uart_data[8]; - -// UART interrupt callback. -void uart_irq_cb(const struct device* dev, void* user_data) { - while (uart_irq_update(dev) && uart_irq_is_pending(dev)) { - struct ring_buf* rbuf = (struct ring_buf*)user_data; - if (uart_irq_rx_ready(dev) != 0) { - for (;;) { - // Read a small chunk of data from the UART. - int bytes_read = uart_fifo_read(dev, uart_data, sizeof(uart_data)); - if (bytes_read < 0) { - TVMPlatformAbort((tvm_crt_error_t)(0xbeef1)); - } else if (bytes_read == 0) { - break; - } - // Write it into the ring buffer. - int bytes_written = ring_buf_put(rbuf, uart_data, bytes_read); - if (bytes_read != bytes_written) { - TVMPlatformAbort((tvm_crt_error_t)(0xbeef2)); - } - } - } - } -} - -// Initialize the UART receiver. -void uart_rx_init(struct ring_buf* rbuf, const struct device* dev) { - uart_irq_callback_user_data_set(dev, uart_irq_cb, (void*)rbuf); - uart_irq_rx_enable(dev); -} - -// UART read. -char TVMPlatformUartRxRead() { - unsigned char c; - int ret = -1; - while (ret != 0) { - ret = uart_poll_in(g_microtvm_uart, &c); - } - return (char)c; -} - -// UART write. -uint32_t TVMPlatformWriteSerial(const char* data, uint32_t size) { - for (uint32_t i = 0; i < size; i++) { - uart_poll_out(g_microtvm_uart, data[i]); - } - return size; -} - -// Initialize UART. -void TVMPlatformUARTInit(uint32_t baudrate /* = TVM_UART_DEFAULT_BAUDRATE */) { - // Claim console device. - g_microtvm_uart = DEVICE_DT_GET(DT_CHOSEN(zephyr_console)); - const struct uart_config config = {.baudrate = baudrate, - .parity = UART_CFG_PARITY_NONE, - .stop_bits = UART_CFG_STOP_BITS_1, - .data_bits = UART_CFG_DATA_BITS_8, - .flow_ctrl = UART_CFG_FLOW_CTRL_NONE}; - uart_configure(g_microtvm_uart, &config); -} diff --git a/apps/microtvm/zephyr/template_project/src/mlperftiny/zephyr_uart.h b/apps/microtvm/zephyr/template_project/src/mlperftiny/zephyr_uart.h deleted file mode 100644 index f10cf0262224..000000000000 --- a/apps/microtvm/zephyr/template_project/src/mlperftiny/zephyr_uart.h +++ /dev/null @@ -1,51 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance - * with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -#ifndef APPS_MICROTVM_ZEPHYR_TEMPLATE_PROJECT_SRC_MLPERFTINY_ZEPHYR_UART_H_ -#define APPS_MICROTVM_ZEPHYR_TEMPLATE_PROJECT_SRC_MLPERFTINY_ZEPHYR_UART_H_ - -#include - -#define TVM_UART_DEFAULT_BAUDRATE 115200 - -/*! - * \brief Read Uart Rx buffer. - * \param data Pointer to read data. - * \param data_size_bytes Read request size in bytes. - * - * \return Number of data read in bytes. - */ -char TVMPlatformUartRxRead(); - -/*! - * \brief Write data in serial. - * \param data Pointer to data to write. - * \param size Size of data in bytes. - * - * \return Number of write in bytes. - */ -uint32_t TVMPlatformWriteSerial(const char* data, uint32_t size); - -/*! - * \brief Initialize Uart. - * \param baudrate Desired UART baudrate. - */ -void TVMPlatformUARTInit(uint32_t baudrate = TVM_UART_DEFAULT_BAUDRATE); - -#endif /* APPS_MICROTVM_ZEPHYR_TEMPLATE_PROJECT_SRC_MLPERFTINY_ZEPHYR_UART_H_ */ diff --git a/cmake/modules/CRT.cmake b/cmake/modules/CRT.cmake index 518a613dc102..3ddbb5298f84 100644 --- a/cmake/modules/CRT.cmake +++ b/cmake/modules/CRT.cmake @@ -22,8 +22,9 @@ if(USE_MICRO) CRT_TEMPLATE_FILE_COPY_JOBS "src/runtime/crt/host microtvm_api_server.py -> crt" "src/runtime/crt/host Makefile.template -> crt" - "src/runtime/crt crt_config-template.h -> crt" "src/runtime/crt/host main.cc -> crt/src" + "src/runtime/crt/host platform.cc -> crt/src" + "src/runtime/crt crt_config-template.h -> crt/crt_config" ) foreach(job_spec IN LISTS CRT_TEMPLATE_FILE_COPY_JOBS) @@ -66,6 +67,10 @@ if(USE_MICRO) endforeach() endforeach() + # Add template files for Model Library Format + configure_file("src/runtime/crt/crt_config-template.h" "${MICROTVM_TEMPLATE_PROJECTS}/crt/templates/crt_config.h.template" COPYONLY) + configure_file("src/runtime/crt/platform-template.c" "${MICROTVM_TEMPLATE_PROJECTS}/crt/templates/platform.c.template" COPYONLY) + add_custom_target(crt DEPENDS ${crt_template_deps}) endfunction() diff --git a/cmake/modules/Zephyr.cmake b/cmake/modules/Zephyr.cmake index a13aef33195f..38551f1dd44d 100644 --- a/cmake/modules/Zephyr.cmake +++ b/cmake/modules/Zephyr.cmake @@ -26,11 +26,9 @@ if(USE_MICRO) "apps/microtvm/zephyr/template_project boards.json -> zephyr" "apps/microtvm/zephyr/template_project CMakeLists.txt.template -> zephyr" "apps/microtvm/zephyr/template_project/src/aot_standalone_demo *.c -> zephyr/src/aot_standalone_demo" - "apps/microtvm/zephyr/template_project/src/aot_standalone_demo *.h -> zephyr/src/aot_standalone_demo" "apps/microtvm/zephyr/template_project/src/host_driven *.c -> zephyr/src/host_driven" "apps/microtvm/zephyr/template_project/src/host_driven *.h -> zephyr/src/host_driven" "apps/microtvm/zephyr/template_project/src/mlperftiny *.cc -> zephyr/src/mlperftiny" - "apps/microtvm/zephyr/template_project/src/mlperftiny *.h -> zephyr/src/mlperftiny" "3rdparty/mlperftiny/api * -> zephyr/src/mlperftiny/api" "apps/microtvm/zephyr/template_project/fvp-hack * -> zephyr/fvp-hack" "apps/microtvm/zephyr/template_project/qemu-hack * -> zephyr/qemu-hack" diff --git a/gallery/how_to/work_with_microtvm/micro_mlperftiny.py b/gallery/how_to/work_with_microtvm/micro_mlperftiny.py index e8c6a253ad2b..bb7abddddce6 100644 --- a/gallery/how_to/work_with_microtvm/micro_mlperftiny.py +++ b/gallery/how_to/work_with_microtvm/micro_mlperftiny.py @@ -226,7 +226,7 @@ shape=output_shape, dtype=output_dtype, ), - "include", + "include/tvm", tf, ) diff --git a/gallery/how_to/work_with_microtvm/micro_pytorch.py b/gallery/how_to/work_with_microtvm/micro_pytorch.py index a7f5f1028047..a0f4ebddee48 100644 --- a/gallery/how_to/work_with_microtvm/micro_pytorch.py +++ b/gallery/how_to/work_with_microtvm/micro_pytorch.py @@ -131,7 +131,7 @@ # template_project_path = pathlib.Path(tvm.micro.get_microtvm_template_projects("crt")) -project_options = {"verbose": False, "memory_size_bytes": 6 * 1024 * 1024} +project_options = {"verbose": False, "workspace_size_bytes": 6 * 1024 * 1024} temp_dir = tvm.contrib.utils.tempdir() / "project" project = tvm.micro.generate_project( diff --git a/include/tvm/runtime/crt/platform.h b/include/tvm/runtime/crt/platform.h index 1bc610e6cc53..85121fd0f520 100644 --- a/include/tvm/runtime/crt/platform.h +++ b/include/tvm/runtime/crt/platform.h @@ -139,6 +139,15 @@ tvm_crt_error_t TVMPlatformAfterMeasurement(); */ tvm_crt_error_t TVMPlatformGenerateRandom(uint8_t* buffer, size_t num_bytes); +/*! \brief Initialize TVM inference. + * + * Placeholder function for TVM inference initializations on a specific platform. + * A common use of this function is setting up workspace memory for TVM inference. + * + * \return kTvmErrorNoError if successful. + */ +tvm_crt_error_t TVMPlatformInitialize(); + #ifdef __cplusplus } // extern "C" #endif diff --git a/python/tvm/micro/model_library_format.py b/python/tvm/micro/model_library_format.py index fc32fe34d6c9..b16877b25645 100644 --- a/python/tvm/micro/model_library_format.py +++ b/python/tvm/micro/model_library_format.py @@ -26,7 +26,7 @@ import typing import tvm -from tvm.micro import get_standalone_crt_dir +from tvm.micro import get_standalone_crt_dir, get_microtvm_template_projects from .._ffi import get_global_func from ..contrib import utils @@ -39,6 +39,7 @@ # This should be kept identical to runtime::symbol::tvm_module_main MAIN_FUNC_NAME_STR = "__tvm_main__" STANDALONE_CRT_URL = "./runtime" +CRT_TEMPLATE_FILES_URL = "./templates" METADATA_FILE = "metadata.json" @@ -373,7 +374,18 @@ def reset(tarinfo): for mod in modules: is_aot = isinstance(mod, executor_factory.AOTExecutorFactoryModule) if is_aot and str(mod.runtime) == "crt": + crt_template_path = pathlib.Path(get_microtvm_template_projects("crt")) tar_f.add(get_standalone_crt_dir(), arcname=STANDALONE_CRT_URL) + + # Add template files from CRT template project + for file in [ + "templates/crt_config.h.template", + "templates/platform.c.template", + ]: + tar_f.add( + crt_template_path / pathlib.Path(file), + arcname=f"{CRT_TEMPLATE_FILES_URL}/{pathlib.Path(file).name}", + ) break diff --git a/python/tvm/testing/aot.py b/python/tvm/testing/aot.py index 5ddbdcabacc9..a975eb95bcf0 100644 --- a/python/tvm/testing/aot.py +++ b/python/tvm/testing/aot.py @@ -725,8 +725,8 @@ def run_and_check_body(base_path): os.mkdir(include_path) crt_root = tvm.micro.get_microtvm_template_projects("crt") shutil.copy2( - os.path.join(crt_root, "crt_config-template.h"), - os.path.join(include_path, "crt_config.h"), + pathlib.Path(crt_root) / "crt_config" / "crt_config-template.h", + pathlib.Path(include_path) / "crt_config.h", ) workspace_bytes = 0 diff --git a/src/runtime/crt/host/Makefile.template b/src/runtime/crt/host/Makefile.template index 2caf7ba0bc23..526b17deb73f 100644 --- a/src/runtime/crt/host/Makefile.template +++ b/src/runtime/crt/host/Makefile.template @@ -16,9 +16,9 @@ # under the License. INCLUDES ?= -isystem crt/include -Icrt_config -MEMORY_SIZE_BYTES := +TVM_WORKSPACE_SIZE_BYTES := CFLAGS ?= -Werror -Wall -CXXFLAGS ?= -Werror -Wall -std=c++11 -DTVM_HOST_USE_GRAPH_EXECUTOR_MODULE -DMEMORY_SIZE_BYTES=$(MEMORY_SIZE_BYTES) +CXXFLAGS ?= -Werror -Wall -std=c++11 -DTVM_HOST_USE_GRAPH_EXECUTOR_MODULE -DTVM_WORKSPACE_SIZE_BYTES=$(TVM_WORKSPACE_SIZE_BYTES) LDFLAGS ?= -Werror -Wall # Codegen produces spurious lines like: int32_t arg2_code = ((int32_t*)arg_type_ids)[(2)]; diff --git a/src/runtime/crt/host/main.cc b/src/runtime/crt/host/main.cc index e9f6813f9b3c..0607d4b28719 100644 --- a/src/runtime/crt/host/main.cc +++ b/src/runtime/crt/host/main.cc @@ -22,14 +22,12 @@ * \brief main entry point for host subprocess-based CRT */ #include -#include #include +#include #include #include -#include #include -#include #include #include "crt_config.h" @@ -38,10 +36,6 @@ #include #endif -#include - -using namespace std::chrono; - extern "C" { ssize_t MicroTVMWriteFunc(void* context, const uint8_t* data, size_t num_bytes) { @@ -50,70 +44,8 @@ ssize_t MicroTVMWriteFunc(void* context, const uint8_t* data, size_t num_bytes) fsync(STDOUT_FILENO); return to_return; } - -size_t TVMPlatformFormatMessage(char* out_buf, size_t out_buf_size_bytes, const char* fmt, - va_list args) { - return vsnprintf(out_buf, out_buf_size_bytes, fmt, args); -} - -void TVMPlatformAbort(tvm_crt_error_t error_code) { - std::cerr << "TVMPlatformAbort: " << error_code << std::endl; - throw "Aborted"; } -MemoryManagerInterface* memory_manager; - -tvm_crt_error_t TVMPlatformMemoryAllocate(size_t num_bytes, DLDevice dev, void** out_ptr) { - return memory_manager->Allocate(memory_manager, num_bytes, dev, out_ptr); -} - -tvm_crt_error_t TVMPlatformMemoryFree(void* ptr, DLDevice dev) { - return memory_manager->Free(memory_manager, ptr, dev); -} - -steady_clock::time_point g_microtvm_start_time; -int g_microtvm_timer_running = 0; - -tvm_crt_error_t TVMPlatformTimerStart() { - if (g_microtvm_timer_running) { - std::cerr << "timer already running" << std::endl; - return kTvmErrorPlatformTimerBadState; - } - g_microtvm_start_time = std::chrono::steady_clock::now(); - g_microtvm_timer_running = 1; - return kTvmErrorNoError; -} - -tvm_crt_error_t TVMPlatformTimerStop(double* elapsed_time_seconds) { - if (!g_microtvm_timer_running) { - std::cerr << "timer not running" << std::endl; - return kTvmErrorPlatformTimerBadState; - } - auto microtvm_stop_time = std::chrono::steady_clock::now(); - std::chrono::microseconds time_span = std::chrono::duration_cast( - microtvm_stop_time - g_microtvm_start_time); - *elapsed_time_seconds = static_cast(time_span.count()) / 1e6; - g_microtvm_timer_running = 0; - return kTvmErrorNoError; -} - -static_assert(RAND_MAX >= (1 << 8), "RAND_MAX is smaller than acceptable"); -unsigned int random_seed = 0; -tvm_crt_error_t TVMPlatformGenerateRandom(uint8_t* buffer, size_t num_bytes) { - if (random_seed == 0) { - random_seed = (unsigned int)time(NULL); - } - for (size_t i = 0; i < num_bytes; ++i) { - int random = rand_r(&random_seed); - buffer[i] = (uint8_t)random; - } - - return kTvmErrorNoError; -} -} - -uint8_t memory[MEMORY_SIZE_BYTES]; - static char** g_argv = NULL; int testonly_reset_server(TVMValue* args, int* type_codes, int num_args, TVMValue* out_ret_value, @@ -125,13 +57,7 @@ int testonly_reset_server(TVMValue* args, int* type_codes, int num_args, TVMValu int main(int argc, char** argv) { g_argv = argv; - int status = - PageMemoryManagerCreate(&memory_manager, memory, sizeof(memory), 8 /* page_size_log2 */); - if (status != 0) { - fprintf(stderr, "error initiailizing memory manager\n"); - return 2; - } - + TVMPlatformInitialize(); microtvm_rpc_server_t rpc_server = MicroTVMRpcServerInit(&MicroTVMWriteFunc, nullptr); #ifdef TVM_HOST_USE_GRAPH_EXECUTOR_MODULE diff --git a/src/runtime/crt/host/microtvm_api_server.py b/src/runtime/crt/host/microtvm_api_server.py index e5b82f96b0ff..57b7506b879f 100644 --- a/src/runtime/crt/host/microtvm_api_server.py +++ b/src/runtime/crt/host/microtvm_api_server.py @@ -38,7 +38,7 @@ IS_TEMPLATE = not os.path.exists(os.path.join(PROJECT_DIR, MODEL_LIBRARY_FORMAT_RELPATH)) # Used this size to pass most CRT tests in TVM. -MEMORY_SIZE_BYTES = 2 * 1024 * 1024 +WORKSPACE_SIZE_BYTES = 2 * 1024 * 1024 MAKEFILE_FILENAME = "Makefile" @@ -67,11 +67,11 @@ def server_info_query(self, tvm_version): help="Run make with verbose output", ), server.ProjectOption( - "memory_size_bytes", + "workspace_size_bytes", optional=["generate_project"], type="int", - default=MEMORY_SIZE_BYTES, - help="Sets the value of MEMORY_SIZE_BYTES.", + default=WORKSPACE_SIZE_BYTES, + help="Sets the value of TVM_WORKSPACE_SIZE_BYTES.", ), ], ) @@ -90,7 +90,7 @@ def _populate_makefile( ): """Generate Makefile from template.""" flags = { - "MEMORY_SIZE_BYTES": str(memory_size), + "TVM_WORKSPACE_SIZE_BYTES": str(memory_size), } regex = re.compile(r"([A-Z_]+) := (<[A-Z_]+>)") @@ -106,6 +106,7 @@ def _populate_makefile( def generate_project(self, model_library_format_path, standalone_crt_dir, project_dir, options): # Make project directory. project_dir.mkdir(parents=True) + current_dir = pathlib.Path(__file__).parent.absolute() # Copy ourselves to the generated project. TVM may perform further build steps on the generated project # by launching the copy. @@ -135,25 +136,29 @@ def generate_project(self, model_library_format_path, standalone_crt_dir, projec # Populate Makefile self._populate_makefile( - pathlib.Path(__file__).parent / f"{MAKEFILE_FILENAME}.template", + current_dir / f"{MAKEFILE_FILENAME}.template", project_dir / MAKEFILE_FILENAME, - options.get("memory_size_bytes", MEMORY_SIZE_BYTES), + options.get("workspace_size_bytes", WORKSPACE_SIZE_BYTES), ) # Populate crt-config.h crt_config_dir = project_dir / "crt_config" crt_config_dir.mkdir() shutil.copy2( - os.path.join(os.path.dirname(__file__), "crt_config-template.h"), - os.path.join(crt_config_dir, "crt_config.h"), + current_dir / "crt_config" / "crt_config-template.h", + crt_config_dir / "crt_config.h", ) # Populate src/ - src_dir = os.path.join(project_dir, "src") - os.mkdir(src_dir) + src_dir = project_dir / "src" + src_dir.mkdir() shutil.copy2( - os.path.join(os.path.dirname(__file__), "src", "main.cc"), - os.path.join(src_dir, "main.cc"), + current_dir / "src" / "main.cc", + src_dir / "main.cc", + ) + shutil.copy2( + current_dir / "src" / "platform.cc", + src_dir / "platform.cc", ) def build(self, options): diff --git a/src/runtime/crt/host/platform.cc b/src/runtime/crt/host/platform.cc new file mode 100644 index 000000000000..f5af08a9be88 --- /dev/null +++ b/src/runtime/crt/host/platform.cc @@ -0,0 +1,126 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +/*! + * \brief Implementation of TVMPlatform functions in tvm/runtime/crt/platform.h + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +using namespace std::chrono; + +extern "C" { + +uint8_t memory[TVM_WORKSPACE_SIZE_BYTES]; +MemoryManagerInterface* memory_manager; + +steady_clock::time_point g_microtvm_start_time; +int g_microtvm_timer_running = 0; + +// Called when an internal error occurs and execution cannot continue. +void TVMPlatformAbort(tvm_crt_error_t error_code) { + std::cerr << "TVMPlatformAbort: " << error_code << std::endl; + throw "Aborted"; +} + +// Called by the microTVM RPC server to implement TVMLogf. +size_t TVMPlatformFormatMessage(char* out_buf, size_t out_buf_size_bytes, const char* fmt, + va_list args) { + return vsprintf(out_buf, fmt, args); +} + +// Allocate memory for use by TVM. +tvm_crt_error_t TVMPlatformMemoryAllocate(size_t num_bytes, DLDevice dev, void** out_ptr) { + return memory_manager->Allocate(memory_manager, num_bytes, dev, out_ptr); +} + +// Free memory used by TVM. +tvm_crt_error_t TVMPlatformMemoryFree(void* ptr, DLDevice dev) { + return memory_manager->Free(memory_manager, ptr, dev); +} + +// Start a device timer. +tvm_crt_error_t TVMPlatformTimerStart() { + if (g_microtvm_timer_running) { + std::cerr << "timer already running" << std::endl; + return kTvmErrorPlatformTimerBadState; + } + g_microtvm_start_time = std::chrono::steady_clock::now(); + g_microtvm_timer_running = 1; + return kTvmErrorNoError; +} + +// Stop the running device timer and get the elapsed time (in microseconds). +tvm_crt_error_t TVMPlatformTimerStop(double* elapsed_time_seconds) { + if (!g_microtvm_timer_running) { + std::cerr << "timer not running" << std::endl; + return kTvmErrorPlatformTimerBadState; + } + auto microtvm_stop_time = std::chrono::steady_clock::now(); + std::chrono::microseconds time_span = std::chrono::duration_cast( + microtvm_stop_time - g_microtvm_start_time); + *elapsed_time_seconds = static_cast(time_span.count()) / 1e6; + g_microtvm_timer_running = 0; + return kTvmErrorNoError; +} + +// Platform-specific before measurement call. +tvm_crt_error_t TVMPlatformBeforeMeasurement() { return kTvmErrorNoError; } + +// Platform-specific after measurement call. +tvm_crt_error_t TVMPlatformAfterMeasurement() { return kTvmErrorNoError; } + +static_assert(RAND_MAX >= (1 << 8), "RAND_MAX is smaller than acceptable"); +unsigned int random_seed = 0; +// Fill a buffer with random data. +tvm_crt_error_t TVMPlatformGenerateRandom(uint8_t* buffer, size_t num_bytes) { + if (random_seed == 0) { + random_seed = (unsigned int)time(NULL); + } + for (size_t i = 0; i < num_bytes; ++i) { + int random = rand_r(&random_seed); + buffer[i] = (uint8_t)random; + } + return kTvmErrorNoError; +} + +// Initialize TVM inference. +tvm_crt_error_t TVMPlatformInitialize() { + int status = + PageMemoryManagerCreate(&memory_manager, memory, sizeof(memory), 8 /* page_size_log2 */); + if (status != 0) { + fprintf(stderr, "error initiailizing memory manager\n"); + return kTvmErrorPlatformMemoryManagerInitialized; + } + return kTvmErrorNoError; +} + +} // extern C diff --git a/src/runtime/crt/platform-template.c b/src/runtime/crt/platform-template.c new file mode 100644 index 000000000000..b93fd1459be6 --- /dev/null +++ b/src/runtime/crt/platform-template.c @@ -0,0 +1,80 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +/*! + * \brief Implementation of TVMPlatform functions in tvm/runtime/crt/platform.h + */ + +#include +#include +#include +#include +#include +#include +#include + +uint8_t memory[TVM_WORKSPACE_SIZE_BYTES]; +MemoryManagerInterface* memory_manager; + +// Called when an internal error occurs and execution cannot continue. +void TVMPlatformAbort(tvm_crt_error_t error_code) { exit(1); } + +// Called by the microTVM RPC server to implement TVMLogf. +size_t TVMPlatformFormatMessage(char* out_buf, size_t out_buf_size_bytes, const char* fmt, + va_list args) { + return vsprintf(out_buf, fmt, args); +} + +// Allocate memory for use by TVM. +tvm_crt_error_t TVMPlatformMemoryAllocate(size_t num_bytes, DLDevice dev, void** out_ptr) { + return memory_manager->Allocate(memory_manager, num_bytes, dev, out_ptr); +} + +// Free memory used by TVM. +tvm_crt_error_t TVMPlatformMemoryFree(void* ptr, DLDevice dev) { + return memory_manager->Free(memory_manager, ptr, dev); +} + +// Start a device timer. +tvm_crt_error_t TVMPlatformTimerStart() { return kTvmErrorNoError; } + +// Stop the running device timer and get the elapsed time (in microseconds). +tvm_crt_error_t TVMPlatformTimerStop(double* elapsed_time_seconds) { return kTvmErrorNoError; } + +// Platform-specific before measurement call. +tvm_crt_error_t TVMPlatformBeforeMeasurement() { return kTvmErrorNoError; } + +// Platform-specific after measurement call. +tvm_crt_error_t TVMPlatformAfterMeasurement() { return kTvmErrorNoError; } + +// Fill a buffer with random data. +tvm_crt_error_t TVMPlatformGenerateRandom(uint8_t* buffer, size_t num_bytes) { + return kTvmErrorNoError; +} + +// Initialize TVM inference. +tvm_crt_error_t TVMPlatformInitialize() { + int status = + PageMemoryManagerCreate(&memory_manager, memory, sizeof(memory), 8 /* page_size_log2 */); + if (status != 0) { + fprintf(stderr, "error initiailizing memory manager\n"); + return kTvmErrorPlatformMemoryManagerInitialized; + } + return kTvmErrorNoError; +} diff --git a/tests/micro/arduino/test_arduino_workflow.py b/tests/micro/arduino/test_arduino_workflow.py index 42874ad6c349..73cdd9b85d28 100644 --- a/tests/micro/arduino/test_arduino_workflow.py +++ b/tests/micro/arduino/test_arduino_workflow.py @@ -71,7 +71,7 @@ def test_project_folder_structure(project_dir, project): source_dir = project_dir / "src" assert _get_directory_elements(source_dir) == set( - ["model", "standalone_crt", "model.c", "model.h"] + ["model", "standalone_crt", "platform.c", "platform.h"] ) @@ -82,15 +82,15 @@ def test_project_model_integrity(project_dir, project): ) -def test_model_header_templating(project_dir, project): - # Ensure model.h was templated with correct WORKSPACE_SIZE - with (project_dir / "src" / "model.h").open() as f: - model_h = f.read() - workspace_size_defs = re.findall(r"\#define WORKSPACE_SIZE ([0-9]*)", model_h) +def test_model_platform_templating(project_dir, project): + # Ensure platform.c was templated with correct TVM_WORKSPACE_SIZE_BYTES + with (project_dir / "src" / "platform.c").open() as f: + platform_c = f.read() + workspace_size_defs = re.findall(r"\#define TVM_WORKSPACE_SIZE_BYTES ([0-9]*)", platform_c) assert workspace_size_defs assert len(workspace_size_defs) == 1 - # Make sure the WORKSPACE_SIZE we define is a reasonable size. We don't want + # Make sure the TVM_WORKSPACE_SIZE_BYTES we define is a reasonable size. We don't want # to set an exact value, as this test shouldn't break if an improvement to # TVM causes the amount of memory needed to decrease. workspace_size = int(workspace_size_defs[0]) diff --git a/tests/micro/arduino/testdata/project.ino b/tests/micro/arduino/testdata/project.ino index ebd1c5e0e650..d7ef155b33f6 100644 --- a/tests/micro/arduino/testdata/project.ino +++ b/tests/micro/arduino/testdata/project.ino @@ -17,11 +17,12 @@ * under the License. */ -#include "src/model.h" +#include "src/platform.h" #include "src/data/yes.c" #include "src/data/no.c" #include "src/data/unknown.c" #include "src/data/silence.c" +#include "src/standalone_crt/include/tvm/runtime/crt/platform.h" void performInference(int8_t input_data[1960], char *data_name) { int8_t output_data[4]; @@ -41,7 +42,7 @@ void performInference(int8_t input_data[1960], char *data_name) { } void setup() { - TVMInitialize(); + TVMPlatformInitialize(); Serial.begin(115200); } diff --git a/tests/micro/zephyr/test_zephyr.py b/tests/micro/zephyr/test_zephyr.py index 59c4cab88147..79e4f46e0f93 100644 --- a/tests/micro/zephyr/test_zephyr.py +++ b/tests/micro/zephyr/test_zephyr.py @@ -608,5 +608,41 @@ def test_schedule_build_with_cmsis_dependency(workspace_dir, board, microtvm_deb assert "CMSIS-NN/Include" in cmake_content +@tvm.testing.requires_micro +def test_debugging_enabled(workspace_dir): + """Test debugging enabled for LED. `verbose=True` in project option enables + debugging. For this test a physical board(nucleo_l4r5zi) is used instead of + QEMU since LED config is not available on QEMU. + """ + board = "nucleo_l4r5zi" + project_options = { + "project_type": "host_driven", + "board": board, + "verbose": True, + } + shape = (10,) + dtype = "int8" + x = relay.var("x", relay.TensorType(shape=shape, dtype=dtype)) + xx = relay.multiply(x, x) + z = relay.add(xx, relay.const(np.ones(shape=shape, dtype=dtype))) + func = relay.Function([x], z) + ir_mod = tvm.IRModule.from_expr(func) + + runtime = Runtime("crt", {"system-lib": True}) + executor = Executor("aot") + target = tvm.micro.testing.get_target("zephyr", board) + + with tvm.transform.PassContext(opt_level=3, config={"tir.disable_vectorize": True}): + mod = tvm.relay.build(ir_mod, target=target, runtime=runtime, executor=executor) + + project = tvm.micro.generate_project( + str(utils.TEMPLATE_PROJECT_DIR), + mod, + workspace_dir / "project", + project_options, + ) + project.build() + + if __name__ == "__main__": tvm.testing.main() diff --git a/tests/micro/zephyr/test_zephyr_aot_exec_standalone.py b/tests/micro/zephyr/test_zephyr_aot_exec_standalone.py index 16c1f9e30814..8c6bc272f0f0 100644 --- a/tests/micro/zephyr/test_zephyr_aot_exec_standalone.py +++ b/tests/micro/zephyr/test_zephyr_aot_exec_standalone.py @@ -63,7 +63,9 @@ def test_tflite(workspace_dir, board, microtvm_debug, serial_number): "aot", {"unpacked-api": True, "interface-api": "c", "workspace-byte-alignment": 4} ) runtime = Runtime("crt") - with tvm.transform.PassContext(opt_level=3, config={"tir.disable_vectorize": True}): + with tvm.transform.PassContext( + opt_level=3, config={"tir.disable_vectorize": True, "tir.usmp.enable": True} + ): lowered = relay.build(relay_mod, target, params=params, runtime=runtime, executor=executor) sample_url = "https://github.com/tlc-pack/web-data/raw/967fc387dadb272c5a7f8c3461d34c060100dbf1/testdata/microTVM/data/keyword_spotting_int8_6.pyc.npy" diff --git a/tests/micro/zephyr/utils.py b/tests/micro/zephyr/utils.py index 26f9d6a10e2d..fdd873c8e8c3 100644 --- a/tests/micro/zephyr/utils.py +++ b/tests/micro/zephyr/utils.py @@ -153,8 +153,8 @@ def generate_project( with tempfile.NamedTemporaryFile() as tar_temp_file: with tarfile.open(tar_temp_file.name, "w:gz") as tf: with tempfile.TemporaryDirectory() as tar_temp_dir: - model_files_path = os.path.join(tar_temp_dir, "include") - os.mkdir(model_files_path) + model_files_path = pathlib.Path(tar_temp_dir) / "include" + model_files_path.mkdir(parents=True) if load_cmsis: loadCMSIS(model_files_path) tf.add( @@ -174,9 +174,9 @@ def generate_project( ) tf.add(header_path, arcname=os.path.relpath(header_path, tar_temp_dir)) - create_header_file("input_data", sample, "include", tf) + create_header_file("input_data", sample, "include/tvm", tf) create_header_file( - "output_data", np.zeros(shape=output_shape, dtype=output_type), "include", tf + "output_data", np.zeros(shape=output_shape, dtype=output_type), "include/tvm", tf ) project, project_dir = build_project( diff --git a/tests/python/unittest/test_micro_model_library_format.py b/tests/python/unittest/test_micro_model_library_format.py index 734404fb3450..6f79723de456 100644 --- a/tests/python/unittest/test_micro_model_library_format.py +++ b/tests/python/unittest/test_micro_model_library_format.py @@ -715,5 +715,30 @@ def test_output_names_many(): } +@tvm.testing.requires_micro +def test_template_files(): + """Check template files in generated model library format.""" + mod = get_conv2d_relay_module() + + executor = Executor("aot", {"unpacked-api": True, "interface-api": "c"}) + runtime = Runtime("crt") + target = tvm.target.target.micro("host") + + with tvm.transform.PassContext(opt_level=3, config={"tir.disable_vectorize": True}): + factory = tvm.relay.build(mod, target, runtime=runtime, executor=executor, mod_name="mod") + + temp_dir = utils.tempdir() + mlf_tar_path = temp_dir / "lib.tar" + micro.export_model_library_format(factory, mlf_tar_path) + + tf = tarfile.open(mlf_tar_path) + extract_dir = temp_dir / "extract" + os.mkdir(extract_dir) + tf.extractall(extract_dir) + + assert (extract_dir / "templates" / "crt_config.h.template").is_file() + assert (extract_dir / "templates" / "platform.c.template").is_file() + + if __name__ == "__main__": tvm.testing.main()